diff --git a/0.73_My_PuTTY/proxy.c b/0.73_My_PuTTY/proxy.c deleted file mode 100644 index bcecec3..0000000 --- a/0.73_My_PuTTY/proxy.c +++ /dev/null @@ -1,1517 +0,0 @@ -/* - * Network proxy abstraction in PuTTY - * - * A proxy layer, if necessary, wedges itself between the network - * code and the higher level backend. - */ - -#include -#include -#include - -#include "putty.h" -#include "network.h" -#include "proxy.h" - -#define do_proxy_dns(conf) \ - (conf_get_int(conf, CONF_proxy_dns) == FORCE_ON || \ - (conf_get_int(conf, CONF_proxy_dns) == AUTO && \ - conf_get_int(conf, CONF_proxy_type) != PROXY_SOCKS4)) - -/* - * Call this when proxy negotiation is complete, so that this - * socket can begin working normally. - */ -void proxy_activate (ProxySocket *p) -{ - size_t output_before, output_after; - - p->state = PROXY_STATE_ACTIVE; - - /* we want to ignore new receive events until we have sent - * all of our buffered receive data. - */ - sk_set_frozen(p->sub_socket, true); - - /* how many bytes of output have we buffered? */ - output_before = bufchain_size(&p->pending_oob_output_data) + - bufchain_size(&p->pending_output_data); - /* and keep track of how many bytes do not get sent. */ - output_after = 0; - - /* send buffered OOB writes */ - while (bufchain_size(&p->pending_oob_output_data) > 0) { - ptrlen data = bufchain_prefix(&p->pending_oob_output_data); - output_after += sk_write_oob(p->sub_socket, data.ptr, data.len); - bufchain_consume(&p->pending_oob_output_data, data.len); - } - - /* send buffered normal writes */ - while (bufchain_size(&p->pending_output_data) > 0) { - ptrlen data = bufchain_prefix(&p->pending_output_data); - output_after += sk_write(p->sub_socket, data.ptr, data.len); - bufchain_consume(&p->pending_output_data, data.len); - } - - /* if we managed to send any data, let the higher levels know. */ - if (output_after < output_before) - plug_sent(p->plug, output_after); - - /* if we have a pending EOF to send, send it */ - if (p->pending_eof) sk_write_eof(p->sub_socket); - - /* if the backend wanted the socket unfrozen, try to unfreeze. - * our set_frozen handler will flush buffered receive data before - * unfreezing the actual underlying socket. - */ - if (!p->freeze) - sk_set_frozen(&p->sock, 0); -} - -/* basic proxy socket functions */ - -static Plug *sk_proxy_plug (Socket *s, Plug *p) -{ - ProxySocket *ps = container_of(s, ProxySocket, sock); - Plug *ret = ps->plug; - if (p) - ps->plug = p; - return ret; -} - -static void sk_proxy_close (Socket *s) -{ - ProxySocket *ps = container_of(s, ProxySocket, sock); - - sk_close(ps->sub_socket); - sk_addr_free(ps->remote_addr); - sfree(ps); -} - -static size_t sk_proxy_write (Socket *s, const void *data, size_t len) -{ - ProxySocket *ps = container_of(s, ProxySocket, sock); - - if (ps->state != PROXY_STATE_ACTIVE) { - bufchain_add(&ps->pending_output_data, data, len); - return bufchain_size(&ps->pending_output_data); - } - return sk_write(ps->sub_socket, data, len); -} - -static size_t sk_proxy_write_oob (Socket *s, const void *data, size_t len) -{ - ProxySocket *ps = container_of(s, ProxySocket, sock); - - if (ps->state != PROXY_STATE_ACTIVE) { - bufchain_clear(&ps->pending_output_data); - bufchain_clear(&ps->pending_oob_output_data); - bufchain_add(&ps->pending_oob_output_data, data, len); - return len; - } - return sk_write_oob(ps->sub_socket, data, len); -} - -static void sk_proxy_write_eof (Socket *s) -{ - ProxySocket *ps = container_of(s, ProxySocket, sock); - - if (ps->state != PROXY_STATE_ACTIVE) { - ps->pending_eof = true; - return; - } - sk_write_eof(ps->sub_socket); -} - -static void sk_proxy_set_frozen (Socket *s, bool is_frozen) -{ - ProxySocket *ps = container_of(s, ProxySocket, sock); - - if (ps->state != PROXY_STATE_ACTIVE) { - ps->freeze = is_frozen; - return; - } - - /* handle any remaining buffered recv data first */ - if (bufchain_size(&ps->pending_input_data) > 0) { - ps->freeze = is_frozen; - - /* loop while we still have buffered data, and while we are - * unfrozen. the plug_receive call in the loop could result - * in a call back into this function refreezing the socket, - * so we have to check each time. - */ - while (!ps->freeze && bufchain_size(&ps->pending_input_data) > 0) { - char databuf[512]; - ptrlen data = bufchain_prefix(&ps->pending_input_data); - if (data.len > lenof(databuf)) - data.len = lenof(databuf); - memcpy(databuf, data.ptr, data.len); - bufchain_consume(&ps->pending_input_data, data.len); - plug_receive(ps->plug, 0, databuf, data.len); - } - - /* if we're still frozen, we'll have to wait for another - * call from the backend to finish unbuffering the data. - */ - if (ps->freeze) return; - } - - sk_set_frozen(ps->sub_socket, is_frozen); -} - -static const char * sk_proxy_socket_error (Socket *s) -{ - ProxySocket *ps = container_of(s, ProxySocket, sock); - if (ps->error != NULL || ps->sub_socket == NULL) { - return ps->error; - } - return sk_socket_error(ps->sub_socket); -} - -/* basic proxy plug functions */ - -static void plug_proxy_log(Plug *plug, int type, SockAddr *addr, int port, - const char *error_msg, int error_code) -{ - ProxySocket *ps = container_of(plug, ProxySocket, plugimpl); - - plug_log(ps->plug, type, addr, port, error_msg, error_code); -} - -static void plug_proxy_closing (Plug *p, const char *error_msg, - int error_code, bool calling_back) -{ - ProxySocket *ps = container_of(p, ProxySocket, plugimpl); - - if (ps->state != PROXY_STATE_ACTIVE) { - ps->closing_error_msg = error_msg; - ps->closing_error_code = error_code; - ps->closing_calling_back = calling_back; - ps->negotiate(ps, PROXY_CHANGE_CLOSING); - } else { - plug_closing(ps->plug, error_msg, error_code, calling_back); - } -} - -static void plug_proxy_receive( - Plug *p, int urgent, const char *data, size_t len) -{ - ProxySocket *ps = container_of(p, ProxySocket, plugimpl); - - if (ps->state != PROXY_STATE_ACTIVE) { - /* we will lose the urgentness of this data, but since most, - * if not all, of this data will be consumed by the negotiation - * process, hopefully it won't affect the protocol above us - */ - bufchain_add(&ps->pending_input_data, data, len); - ps->receive_urgent = (urgent != 0); - ps->receive_data = data; - ps->receive_len = len; - ps->negotiate(ps, PROXY_CHANGE_RECEIVE); - } else { - plug_receive(ps->plug, urgent, data, len); - } -} - -static void plug_proxy_sent (Plug *p, size_t bufsize) -{ - ProxySocket *ps = container_of(p, ProxySocket, plugimpl); - - if (ps->state != PROXY_STATE_ACTIVE) { - ps->negotiate(ps, PROXY_CHANGE_SENT); - return; - } - plug_sent(ps->plug, bufsize); -} - -static int plug_proxy_accepting(Plug *p, - accept_fn_t constructor, accept_ctx_t ctx) -{ - ProxySocket *ps = container_of(p, ProxySocket, plugimpl); - - if (ps->state != PROXY_STATE_ACTIVE) { - ps->accepting_constructor = constructor; - ps->accepting_ctx = ctx; - return ps->negotiate(ps, PROXY_CHANGE_ACCEPTING); - } - return plug_accepting(ps->plug, constructor, ctx); -} - -/* - * This function can accept a NULL pointer as `addr', in which case - * it will only check the host name. - */ -static bool proxy_for_destination(SockAddr *addr, const char *hostname, - int port, Conf *conf) -{ - int s = 0, e = 0; - char hostip[64]; - int hostip_len, hostname_len; - const char *exclude_list; - - /* - * Special local connections such as Unix-domain sockets - * unconditionally cannot be proxied, even in proxy-localhost - * mode. There just isn't any way to ask any known proxy type for - * them. - */ - if (addr && sk_address_is_special_local(addr)) - return false; /* do not proxy */ - - /* - * Check the host name and IP against the hard-coded - * representations of `localhost'. - */ - if (!conf_get_bool(conf, CONF_even_proxy_localhost) && - (sk_hostname_is_local(hostname) || - (addr && sk_address_is_local(addr)))) - return false; /* do not proxy */ - - /* we want a string representation of the IP address for comparisons */ - if (addr) { - sk_getaddr(addr, hostip, 64); - hostip_len = strlen(hostip); - } else - hostip_len = 0; /* placate gcc; shouldn't be required */ - - hostname_len = strlen(hostname); - - exclude_list = conf_get_str(conf, CONF_proxy_exclude_list); - - /* now parse the exclude list, and see if either our IP - * or hostname matches anything in it. - */ - - while (exclude_list[s]) { - while (exclude_list[s] && - (isspace((unsigned char)exclude_list[s]) || - exclude_list[s] == ',')) s++; - - if (!exclude_list[s]) break; - - e = s; - - while (exclude_list[e] && - (isalnum((unsigned char)exclude_list[e]) || - exclude_list[e] == '-' || - exclude_list[e] == '.' || - exclude_list[e] == '*')) e++; - - if (exclude_list[s] == '*') { - /* wildcard at beginning of entry */ - - if ((addr && strnicmp(hostip + hostip_len - (e - s - 1), - exclude_list + s + 1, e - s - 1) == 0) || - strnicmp(hostname + hostname_len - (e - s - 1), - exclude_list + s + 1, e - s - 1) == 0) { - /* IP/hostname range excluded. do not use proxy. */ - return false; - } - } else if (exclude_list[e-1] == '*') { - /* wildcard at end of entry */ - - if ((addr && strnicmp(hostip, exclude_list + s, e - s - 1) == 0) || - strnicmp(hostname, exclude_list + s, e - s - 1) == 0) { - /* IP/hostname range excluded. do not use proxy. */ - return false; - } - } else { - /* no wildcard at either end, so let's try an absolute - * match (ie. a specific IP) - */ - - if (addr && strnicmp(hostip, exclude_list + s, e - s) == 0) - return false; /* IP/hostname excluded. do not use proxy. */ - if (strnicmp(hostname, exclude_list + s, e - s) == 0) - return false; /* IP/hostname excluded. do not use proxy. */ - } - - s = e; - - /* Make sure we really have reached the next comma or end-of-string */ - while (exclude_list[s] && - !isspace((unsigned char)exclude_list[s]) && - exclude_list[s] != ',') s++; - } - - /* no matches in the exclude list, so use the proxy */ - return true; -} - -static char *dns_log_msg(const char *host, int addressfamily, - const char *reason) -{ - return dupprintf("Looking up host \"%s\"%s for %s", host, - (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" : - addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : - ""), reason); -} - -SockAddr *name_lookup(const char *host, int port, char **canonicalname, - Conf *conf, int addressfamily, LogContext *logctx, - const char *reason) -{ - if (conf_get_int(conf, CONF_proxy_type) != PROXY_NONE && - do_proxy_dns(conf) && - proxy_for_destination(NULL, host, port, conf)) { - - if (logctx) - logeventf(logctx, "Leaving host lookup to proxy of \"%s\"" - " (for %s)", host, reason); - - *canonicalname = dupstr(host); - return sk_nonamelookup(host); - } else { - if (logctx) - logevent_and_free( - logctx, dns_log_msg(host, addressfamily, reason)); - - return sk_namelookup(host, canonicalname, addressfamily); - } -} - -static const struct SocketVtable ProxySocket_sockvt = { - sk_proxy_plug, - sk_proxy_close, - sk_proxy_write, - sk_proxy_write_oob, - sk_proxy_write_eof, - sk_proxy_set_frozen, - sk_proxy_socket_error, - NULL, /* peer_info */ -}; - -static const struct PlugVtable ProxySocket_plugvt = { - plug_proxy_log, - plug_proxy_closing, - plug_proxy_receive, - plug_proxy_sent, - plug_proxy_accepting -}; - -Socket *new_connection(SockAddr *addr, const char *hostname, - int port, bool privport, - bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf) -{ - if (conf_get_int(conf, CONF_proxy_type) != PROXY_NONE && - proxy_for_destination(addr, hostname, port, conf)) - { - ProxySocket *ret; - SockAddr *proxy_addr; - char *proxy_canonical_name; - const char *proxy_type; - Socket *sret; - int type; - - if ((sret = platform_new_connection(addr, hostname, port, privport, - oobinline, nodelay, keepalive, - plug, conf)) != - NULL) - return sret; - - ret = snew(ProxySocket); - ret->sock.vt = &ProxySocket_sockvt; - ret->plugimpl.vt = &ProxySocket_plugvt; - ret->conf = conf_copy(conf); - ret->plug = plug; - ret->remote_addr = addr; /* will need to be freed on close */ - ret->remote_port = port; - - ret->error = NULL; - ret->pending_eof = false; - ret->freeze = false; - - bufchain_init(&ret->pending_input_data); - bufchain_init(&ret->pending_output_data); - bufchain_init(&ret->pending_oob_output_data); - - ret->sub_socket = NULL; - ret->state = PROXY_STATE_NEW; - ret->negotiate = NULL; - - type = conf_get_int(conf, CONF_proxy_type); - if (type == PROXY_HTTP) { - ret->negotiate = proxy_http_negotiate; - proxy_type = "HTTP"; - } else if (type == PROXY_SOCKS4) { - ret->negotiate = proxy_socks4_negotiate; - proxy_type = "SOCKS 4"; - } else if (type == PROXY_SOCKS5) { - ret->negotiate = proxy_socks5_negotiate; - proxy_type = "SOCKS 5"; - } else if (type == PROXY_TELNET) { - ret->negotiate = proxy_telnet_negotiate; - proxy_type = "Telnet"; - } else { - ret->error = "Proxy error: Unknown proxy method"; - return &ret->sock; - } - - { - char *logmsg = dupprintf("Will use %s proxy at %s:%d to connect" - " to %s:%d", proxy_type, - conf_get_str(conf, CONF_proxy_host), - conf_get_int(conf, CONF_proxy_port), - hostname, port); - plug_log(plug, 2, NULL, 0, logmsg, 0); - sfree(logmsg); - } - - { - char *logmsg = dns_log_msg(conf_get_str(conf, CONF_proxy_host), - conf_get_int(conf, CONF_addressfamily), - "proxy"); - plug_log(plug, 2, NULL, 0, logmsg, 0); - sfree(logmsg); - } - - /* look-up proxy */ - proxy_addr = sk_namelookup(conf_get_str(conf, CONF_proxy_host), - &proxy_canonical_name, - conf_get_int(conf, CONF_addressfamily)); - if (sk_addr_error(proxy_addr) != NULL) { - ret->error = "Proxy error: Unable to resolve proxy host name"; - sk_addr_free(proxy_addr); - return &ret->sock; - } - sfree(proxy_canonical_name); - - { - char addrbuf[256], *logmsg; - sk_getaddr(proxy_addr, addrbuf, lenof(addrbuf)); - logmsg = dupprintf("Connecting to %s proxy at %s port %d", - proxy_type, addrbuf, - conf_get_int(conf, CONF_proxy_port)); - plug_log(plug, 2, NULL, 0, logmsg, 0); - sfree(logmsg); - } - - /* create the actual socket we will be using, - * connected to our proxy server and port. - */ - ret->sub_socket = sk_new(proxy_addr, - conf_get_int(conf, CONF_proxy_port), - privport, oobinline, - nodelay, keepalive, &ret->plugimpl); - if (sk_socket_error(ret->sub_socket) != NULL) - return &ret->sock; - - /* start the proxy negotiation process... */ - sk_set_frozen(ret->sub_socket, 0); - ret->negotiate(ret, PROXY_CHANGE_NEW); - - return &ret->sock; - } - - /* no proxy, so just return the direct socket */ - return sk_new(addr, port, privport, oobinline, nodelay, keepalive, plug); -} - -Socket *new_listener(const char *srcaddr, int port, Plug *plug, - bool local_host_only, Conf *conf, int addressfamily) -{ - /* TODO: SOCKS (and potentially others) support inbound - * TODO: connections via the proxy. support them. - */ - - return sk_newlistener(srcaddr, port, plug, local_host_only, addressfamily); -} - -/* ---------------------------------------------------------------------- - * HTTP CONNECT proxy type. - */ - -static bool get_line_end(char *data, size_t len, size_t *out) -{ - size_t off = 0; - - while (off < len) - { - if (data[off] == '\n') { - /* we have a newline */ - off++; - - /* is that the only thing on this line? */ - if (off <= 2) { - *out = off; - return true; - } - - /* if not, then there is the possibility that this header - * continues onto the next line, if it starts with a space - * or a tab. - */ - - if (off + 1 < len && data[off+1] != ' ' && data[off+1] != '\t') { - *out = off; - return true; - } - - /* the line does continue, so we have to keep going - * until we see an the header's "real" end of line. - */ - off++; - } - - off++; - } - - return false; -} - -int proxy_http_negotiate (ProxySocket *p, int change) -{ - if (p->state == PROXY_STATE_NEW) { - /* we are just beginning the proxy negotiate process, - * so we'll send off the initial bits of the request. - * for this proxy method, it's just a simple HTTP - * request - */ - char *buf, dest[512]; - char *username, *password; - - sk_getaddr(p->remote_addr, dest, lenof(dest)); - - buf = dupprintf("CONNECT %s:%i HTTP/1.1\r\nHost: %s:%i\r\n", - dest, p->remote_port, dest, p->remote_port); - sk_write(p->sub_socket, buf, strlen(buf)); - sfree(buf); - - username = conf_get_str(p->conf, CONF_proxy_username); - password = conf_get_str(p->conf, CONF_proxy_password); - if (username[0] || password[0]) { - char *buf, *buf2; - int i, j, len; - buf = dupprintf("%s:%s", username, password); - len = strlen(buf); - buf2 = snewn(len * 4 / 3 + 100, char); - sprintf(buf2, "Proxy-Authorization: Basic "); - for (i = 0, j = strlen(buf2); i < len; i += 3, j += 4) - base64_encode_atom((unsigned char *)(buf+i), - (len-i > 3 ? 3 : len-i), buf2+j); - strcpy(buf2+j, "\r\n"); - sk_write(p->sub_socket, buf2, strlen(buf2)); - sfree(buf); - sfree(buf2); - } - - sk_write(p->sub_socket, "\r\n", 2); - - p->state = 1; - return 0; - } - - if (change == PROXY_CHANGE_CLOSING) { - /* if our proxy negotiation process involves closing and opening - * new sockets, then we would want to intercept this closing - * callback when we were expecting it. if we aren't anticipating - * a socket close, then some error must have occurred. we'll - * just pass those errors up to the backend. - */ - plug_closing(p->plug, p->closing_error_msg, p->closing_error_code, - p->closing_calling_back); - return 0; /* ignored */ - } - - if (change == PROXY_CHANGE_SENT) { - /* some (or all) of what we wrote to the proxy was sent. - * we don't do anything new, however, until we receive the - * proxy's response. we might want to set a timer so we can - * timeout the proxy negotiation after a while... - */ - return 0; - } - - if (change == PROXY_CHANGE_ACCEPTING) { - /* we should _never_ see this, as we are using our socket to - * connect to a proxy, not accepting inbound connections. - * what should we do? close the socket with an appropriate - * error message? - */ - return plug_accepting(p->plug, - p->accepting_constructor, p->accepting_ctx); - } - - if (change == PROXY_CHANGE_RECEIVE) { - /* we have received data from the underlying socket, which - * we'll need to parse, process, and respond to appropriately. - */ - - char *data, *datap; - size_t len, eol; - - if (p->state == 1) { - - int min_ver, maj_ver, status; - - /* get the status line */ - len = bufchain_size(&p->pending_input_data); - assert(len > 0); /* or we wouldn't be here */ - data = snewn(len+1, char); - bufchain_fetch(&p->pending_input_data, data, len); - /* - * We must NUL-terminate this data, because Windows - * sscanf appears to require a NUL at the end of the - * string because it strlens it _first_. Sigh. - */ - data[len] = '\0'; - - if (!get_line_end(data, len, &eol)) { - sfree(data); - return 1; - } - - status = -1; - /* We can't rely on whether the %n incremented the sscanf return */ - if (sscanf((char *)data, "HTTP/%i.%i %n", - &maj_ver, &min_ver, &status) < 2 || status == -1) { - plug_closing(p->plug, "Proxy error: HTTP response was absent", - PROXY_ERROR_GENERAL, 0); - sfree(data); - return 1; - } - - /* remove the status line from the input buffer. */ - bufchain_consume(&p->pending_input_data, eol); - if (data[status] != '2') { - /* error */ - char *buf; - data[eol] = '\0'; - while (eol > status && - (data[eol-1] == '\r' || data[eol-1] == '\n')) - data[--eol] = '\0'; - buf = dupprintf("Proxy error: %s", data+status); - plug_closing(p->plug, buf, PROXY_ERROR_GENERAL, 0); - sfree(buf); - sfree(data); - return 1; - } - - sfree(data); - - p->state = 2; - } - - if (p->state == 2) { - - /* get headers. we're done when we get a - * header of length 2, (ie. just "\r\n") - */ - - len = bufchain_size(&p->pending_input_data); - assert(len > 0); /* or we wouldn't be here */ - data = snewn(len, char); - datap = data; - bufchain_fetch(&p->pending_input_data, data, len); - - if (!get_line_end(datap, len, &eol)) { - sfree(data); - return 1; - } - while (eol > 2) { - bufchain_consume(&p->pending_input_data, eol); - datap += eol; - len -= eol; - if (!get_line_end(datap, len, &eol)) - eol = 0; /* terminate the loop */ - } - - if (eol == 2) { - /* we're done */ - bufchain_consume(&p->pending_input_data, 2); - proxy_activate(p); - /* proxy activate will have dealt with - * whatever is left of the buffer */ - sfree(data); - return 1; - } - - sfree(data); - return 1; - } - } - - plug_closing(p->plug, "Proxy error: unexpected proxy error", - PROXY_ERROR_UNEXPECTED, 0); - return 1; -} - -/* ---------------------------------------------------------------------- - * SOCKS proxy type. - */ - -/* SOCKS version 4 */ -int proxy_socks4_negotiate (ProxySocket *p, int change) -{ - if (p->state == PROXY_CHANGE_NEW) { - - /* request format: - * version number (1 byte) = 4 - * command code (1 byte) - * 1 = CONNECT - * 2 = BIND - * dest. port (2 bytes) [network order] - * dest. address (4 bytes) - * user ID (variable length, null terminated string) - */ - - strbuf *command = strbuf_new(); - char hostname[512]; - bool write_hostname = false; - - put_byte(command, 4); /* SOCKS version 4 */ - put_byte(command, 1); /* CONNECT command */ - put_uint16(command, p->remote_port); - - switch (sk_addrtype(p->remote_addr)) { - case ADDRTYPE_IPV4: - { - char addr[4]; - sk_addrcopy(p->remote_addr, addr); - put_data(command, addr, 4); - break; - } - case ADDRTYPE_NAME: - sk_getaddr(p->remote_addr, hostname, lenof(hostname)); - put_uint32(command, 1); - write_hostname = true; - break; - case ADDRTYPE_IPV6: - p->error = "Proxy error: SOCKS version 4 does not support IPv6"; - strbuf_free(command); - return 1; - } - - put_asciz(command, conf_get_str(p->conf, CONF_proxy_username)); - if (write_hostname) - put_asciz(command, hostname); - sk_write(p->sub_socket, command->s, command->len); - strbuf_free(command); - - p->state = 1; - return 0; - } - - if (change == PROXY_CHANGE_CLOSING) { - /* if our proxy negotiation process involves closing and opening - * new sockets, then we would want to intercept this closing - * callback when we were expecting it. if we aren't anticipating - * a socket close, then some error must have occurred. we'll - * just pass those errors up to the backend. - */ - plug_closing(p->plug, p->closing_error_msg, p->closing_error_code, - p->closing_calling_back); - return 0; /* ignored */ - } - - if (change == PROXY_CHANGE_SENT) { - /* some (or all) of what we wrote to the proxy was sent. - * we don't do anything new, however, until we receive the - * proxy's response. we might want to set a timer so we can - * timeout the proxy negotiation after a while... - */ - return 0; - } - - if (change == PROXY_CHANGE_ACCEPTING) { - /* we should _never_ see this, as we are using our socket to - * connect to a proxy, not accepting inbound connections. - * what should we do? close the socket with an appropriate - * error message? - */ - return plug_accepting(p->plug, - p->accepting_constructor, p->accepting_ctx); - } - - if (change == PROXY_CHANGE_RECEIVE) { - /* we have received data from the underlying socket, which - * we'll need to parse, process, and respond to appropriately. - */ - - if (p->state == 1) { - /* response format: - * version number (1 byte) = 4 - * reply code (1 byte) - * 90 = request granted - * 91 = request rejected or failed - * 92 = request rejected due to lack of IDENTD on client - * 93 = request rejected due to difference in user ID - * (what we sent vs. what IDENTD said) - * dest. port (2 bytes) - * dest. address (4 bytes) - */ - - char data[8]; - - if (bufchain_size(&p->pending_input_data) < 8) - return 1; /* not got anything yet */ - - /* get the response */ - bufchain_fetch(&p->pending_input_data, data, 8); - - if (data[0] != 0) { - plug_closing(p->plug, "Proxy error: SOCKS proxy responded with " - "unexpected reply code version", - PROXY_ERROR_GENERAL, 0); - return 1; - } - - if (data[1] != 90) { - - switch (data[1]) { - case 92: - plug_closing(p->plug, "Proxy error: SOCKS server wanted IDENTD on client", - PROXY_ERROR_GENERAL, 0); - break; - case 93: - plug_closing(p->plug, "Proxy error: Username and IDENTD on client don't agree", - PROXY_ERROR_GENERAL, 0); - break; - case 91: - default: - plug_closing(p->plug, "Proxy error: Error while communicating with proxy", - PROXY_ERROR_GENERAL, 0); - break; - } - - return 1; - } - bufchain_consume(&p->pending_input_data, 8); - - /* we're done */ - proxy_activate(p); - /* proxy activate will have dealt with - * whatever is left of the buffer */ - return 1; - } - } - - plug_closing(p->plug, "Proxy error: unexpected proxy error", - PROXY_ERROR_UNEXPECTED, 0); - return 1; -} - -/* SOCKS version 5 */ -int proxy_socks5_negotiate (ProxySocket *p, int change) -{ - if (p->state == PROXY_CHANGE_NEW) { - - /* initial command: - * version number (1 byte) = 5 - * number of available authentication methods (1 byte) - * available authentication methods (1 byte * previous value) - * authentication methods: - * 0x00 = no authentication - * 0x01 = GSSAPI - * 0x02 = username/password - * 0x03 = CHAP - */ - - strbuf *command; - char *username, *password; - int method_count_offset, methods_start; - - command = strbuf_new(); - put_byte(command, 5); /* SOCKS version 5 */ - username = conf_get_str(p->conf, CONF_proxy_username); - password = conf_get_str(p->conf, CONF_proxy_password); - - method_count_offset = command->len; - put_byte(command, 0); - methods_start = command->len; - - put_byte(command, 0x00); /* no authentication */ - - if (username[0] || password[0]) { - proxy_socks5_offerencryptedauth(BinarySink_UPCAST(command)); - put_byte(command, 0x02); /* username/password */ - } - - command->u[method_count_offset] = command->len - methods_start; - - sk_write(p->sub_socket, command->s, command->len); - strbuf_free(command); - - p->state = 1; - return 0; - } - - if (change == PROXY_CHANGE_CLOSING) { - /* if our proxy negotiation process involves closing and opening - * new sockets, then we would want to intercept this closing - * callback when we were expecting it. if we aren't anticipating - * a socket close, then some error must have occurred. we'll - * just pass those errors up to the backend. - */ - plug_closing(p->plug, p->closing_error_msg, p->closing_error_code, - p->closing_calling_back); - return 0; /* ignored */ - } - - if (change == PROXY_CHANGE_SENT) { - /* some (or all) of what we wrote to the proxy was sent. - * we don't do anything new, however, until we receive the - * proxy's response. we might want to set a timer so we can - * timeout the proxy negotiation after a while... - */ - return 0; - } - - if (change == PROXY_CHANGE_ACCEPTING) { - /* we should _never_ see this, as we are using our socket to - * connect to a proxy, not accepting inbound connections. - * what should we do? close the socket with an appropriate - * error message? - */ - return plug_accepting(p->plug, - p->accepting_constructor, p->accepting_ctx); - } - - if (change == PROXY_CHANGE_RECEIVE) { - /* we have received data from the underlying socket, which - * we'll need to parse, process, and respond to appropriately. - */ - - if (p->state == 1) { - - /* initial response: - * version number (1 byte) = 5 - * authentication method (1 byte) - * authentication methods: - * 0x00 = no authentication - * 0x01 = GSSAPI - * 0x02 = username/password - * 0x03 = CHAP - * 0xff = no acceptable methods - */ - char data[2]; - - if (bufchain_size(&p->pending_input_data) < 2) - return 1; /* not got anything yet */ - - /* get the response */ - bufchain_fetch(&p->pending_input_data, data, 2); - - if (data[0] != 5) { - plug_closing(p->plug, "Proxy error: SOCKS proxy returned unexpected version", - PROXY_ERROR_GENERAL, 0); - return 1; - } - - if (data[1] == 0x00) p->state = 2; /* no authentication needed */ - else if (data[1] == 0x01) p->state = 4; /* GSSAPI authentication */ - else if (data[1] == 0x02) p->state = 5; /* username/password authentication */ - else if (data[1] == 0x03) p->state = 6; /* CHAP authentication */ - else { - plug_closing(p->plug, "Proxy error: SOCKS proxy did not accept our authentication", - PROXY_ERROR_GENERAL, 0); - return 1; - } - bufchain_consume(&p->pending_input_data, 2); - } - - if (p->state == 7) { - - /* password authentication reply format: - * version number (1 bytes) = 1 - * reply code (1 byte) - * 0 = succeeded - * >0 = failed - */ - char data[2]; - - if (bufchain_size(&p->pending_input_data) < 2) - return 1; /* not got anything yet */ - - /* get the response */ - bufchain_fetch(&p->pending_input_data, data, 2); - - if (data[0] != 1) { - plug_closing(p->plug, "Proxy error: SOCKS password " - "subnegotiation contained wrong version number", - PROXY_ERROR_GENERAL, 0); - return 1; - } - - if (data[1] != 0) { - - plug_closing(p->plug, "Proxy error: SOCKS proxy refused" - " password authentication", - PROXY_ERROR_GENERAL, 0); - return 1; - } - - bufchain_consume(&p->pending_input_data, 2); - p->state = 2; /* now proceed as authenticated */ - } - - if (p->state == 8) { - int ret; - ret = proxy_socks5_handlechap(p); - if (ret) return ret; - } - - if (p->state == 2) { - - /* request format: - * version number (1 byte) = 5 - * command code (1 byte) - * 1 = CONNECT - * 2 = BIND - * 3 = UDP ASSOCIATE - * reserved (1 byte) = 0x00 - * address type (1 byte) - * 1 = IPv4 - * 3 = domainname (first byte has length, no terminating null) - * 4 = IPv6 - * dest. address (variable) - * dest. port (2 bytes) [network order] - */ - - strbuf *command = strbuf_new(); - put_byte(command, 5); /* SOCKS version 5 */ - put_byte(command, 1); /* CONNECT command */ - put_byte(command, 0x00); /* reserved byte */ - - switch (sk_addrtype(p->remote_addr)) { - case ADDRTYPE_IPV4: - put_byte(command, 1); /* IPv4 */ - sk_addrcopy(p->remote_addr, strbuf_append(command, 4)); - break; - case ADDRTYPE_IPV6: - put_byte(command, 4); /* IPv6 */ - sk_addrcopy(p->remote_addr, strbuf_append(command, 16)); - break; - case ADDRTYPE_NAME: - { - char hostname[512]; - put_byte(command, 3); /* domain name */ - sk_getaddr(p->remote_addr, hostname, lenof(hostname)); - if (!put_pstring(command, hostname)) { - p->error = "Proxy error: SOCKS 5 cannot " - "support host names longer than 255 chars"; - strbuf_free(command); - return 1; - } - } - break; - } - - put_uint16(command, p->remote_port); - - sk_write(p->sub_socket, command->s, command->len); - - strbuf_free(command); - - p->state = 3; - return 1; - } - - if (p->state == 3) { - - /* reply format: - * version number (1 bytes) = 5 - * reply code (1 byte) - * 0 = succeeded - * 1 = general SOCKS server failure - * 2 = connection not allowed by ruleset - * 3 = network unreachable - * 4 = host unreachable - * 5 = connection refused - * 6 = TTL expired - * 7 = command not supported - * 8 = address type not supported - * reserved (1 byte) = x00 - * address type (1 byte) - * 1 = IPv4 - * 3 = domainname (first byte has length, no terminating null) - * 4 = IPv6 - * server bound address (variable) - * server bound port (2 bytes) [network order] - */ - char data[5]; - int len; - - /* First 5 bytes of packet are enough to tell its length. */ - if (bufchain_size(&p->pending_input_data) < 5) - return 1; /* not got anything yet */ - - /* get the response */ - bufchain_fetch(&p->pending_input_data, data, 5); - - if (data[0] != 5) { - plug_closing(p->plug, "Proxy error: SOCKS proxy returned wrong version number", - PROXY_ERROR_GENERAL, 0); - return 1; - } - - if (data[1] != 0) { - char buf[256]; - - strcpy(buf, "Proxy error: "); - - switch (data[1]) { - case 1: strcat(buf, "General SOCKS server failure"); break; - case 2: strcat(buf, "Connection not allowed by ruleset"); break; - case 3: strcat(buf, "Network unreachable"); break; - case 4: strcat(buf, "Host unreachable"); break; - case 5: strcat(buf, "Connection refused"); break; - case 6: strcat(buf, "TTL expired"); break; - case 7: strcat(buf, "Command not supported"); break; - case 8: strcat(buf, "Address type not supported"); break; - default: sprintf(buf+strlen(buf), - "Unrecognised SOCKS error code %d", - data[1]); - break; - } - plug_closing(p->plug, buf, PROXY_ERROR_GENERAL, 0); - - return 1; - } - - /* - * Eat the rest of the reply packet. - */ - len = 6; /* first 4 bytes, last 2 */ - switch (data[3]) { - case 1: len += 4; break; /* IPv4 address */ - case 4: len += 16; break;/* IPv6 address */ - case 3: len += (unsigned char)data[4]; break; /* domain name */ - default: - plug_closing(p->plug, "Proxy error: SOCKS proxy returned " - "unrecognised address format", - PROXY_ERROR_GENERAL, 0); - return 1; - } - if (bufchain_size(&p->pending_input_data) < len) - return 1; /* not got whole reply yet */ - bufchain_consume(&p->pending_input_data, len); - - /* we're done */ - proxy_activate(p); - return 1; - } - - if (p->state == 4) { - /* TODO: Handle GSSAPI authentication */ - plug_closing(p->plug, "Proxy error: We don't support GSSAPI authentication", - PROXY_ERROR_GENERAL, 0); - return 1; - } - - if (p->state == 5) { - const char *username = conf_get_str(p->conf, CONF_proxy_username); - const char *password = conf_get_str(p->conf, CONF_proxy_password); - if (username[0] || password[0]) { - strbuf *auth = strbuf_new_nm(); - put_byte(auth, 1); /* version number of subnegotiation */ - if (!put_pstring(auth, username)) { - p->error = "Proxy error: SOCKS 5 authentication cannot " - "support usernames longer than 255 chars"; - strbuf_free(auth); - return 1; - } - if (!put_pstring(auth, password)) { - p->error = "Proxy error: SOCKS 5 authentication cannot " - "support passwords longer than 255 chars"; - strbuf_free(auth); - return 1; - } - sk_write(p->sub_socket, auth->s, auth->len); - strbuf_free(auth); - p->state = 7; - } else - plug_closing(p->plug, "Proxy error: Server chose " - "username/password authentication but we " - "didn't offer it!", - PROXY_ERROR_GENERAL, 0); - return 1; - } - - if (p->state == 6) { - int ret; - ret = proxy_socks5_selectchap(p); - if (ret) return ret; - } - - } - - plug_closing(p->plug, "Proxy error: Unexpected proxy error", - PROXY_ERROR_UNEXPECTED, 0); - return 1; -} - -/* ---------------------------------------------------------------------- - * `Telnet' proxy type. - * - * (This is for ad-hoc proxies where you connect to the proxy's - * telnet port and send a command such as `connect host port'. The - * command is configurable, since this proxy type is typically not - * standardised or at all well-defined.) - */ - -char *format_telnet_command(SockAddr *addr, int port, Conf *conf) -{ - char *fmt = conf_get_str(conf, CONF_proxy_telnet_command); - int so = 0, eo = 0; - strbuf *buf = strbuf_new(); - - /* we need to escape \\, \%, \r, \n, \t, \x??, \0???, - * %%, %host, %port, %user, and %pass - */ - - while (fmt[eo] != 0) { - - /* scan forward until we hit end-of-line, - * or an escape character (\ or %) */ - while (fmt[eo] != 0 && fmt[eo] != '%' && fmt[eo] != '\\') - eo++; - - /* if we hit eol, break out of our escaping loop */ - if (fmt[eo] == 0) break; - - /* if there was any unescaped text before the escape - * character, send that now */ - if (eo != so) - put_data(buf, fmt + so, eo - so); - - so = eo++; - - /* if the escape character was the last character of - * the line, we'll just stop and send it. */ - if (fmt[eo] == 0) break; - - if (fmt[so] == '\\') { - - /* we recognize \\, \%, \r, \n, \t, \x??. - * anything else, we just send unescaped (including the \). - */ - - switch (fmt[eo]) { - - case '\\': - put_byte(buf, '\\'); - eo++; - break; - - case '%': - put_byte(buf, '%'); - eo++; - break; - - case 'r': - put_byte(buf, '\r'); - eo++; - break; - - case 'n': - put_byte(buf, '\n'); - eo++; - break; - - case 't': - put_byte(buf, '\t'); - eo++; - break; - - case 'x': - case 'X': - { - /* escaped hexadecimal value (ie. \xff) */ - unsigned char v = 0; - int i = 0; - - for (;;) { - eo++; - if (fmt[eo] >= '0' && fmt[eo] <= '9') - v += fmt[eo] - '0'; - else if (fmt[eo] >= 'a' && fmt[eo] <= 'f') - v += fmt[eo] - 'a' + 10; - else if (fmt[eo] >= 'A' && fmt[eo] <= 'F') - v += fmt[eo] - 'A' + 10; - else { - /* non hex character, so we abort and just - * send the whole thing unescaped (including \x) - */ - put_byte(buf, '\\'); - eo = so + 1; - break; - } - - /* we only extract two hex characters */ - if (i == 1) { - put_byte(buf, v); - eo++; - break; - } - - i++; - v <<= 4; - } - } - break; - - default: - put_data(buf, fmt + so, 2); - eo++; - break; - } - } else { - - /* % escape. we recognize %%, %host, %port, %user, %pass. - * %proxyhost, %proxyport. Anything else we just send - * unescaped (including the %). - */ - - if (fmt[eo] == '%') { - put_byte(buf, '%'); - eo++; - } - else if (strnicmp(fmt + eo, "host", 4) == 0) { - char dest[512]; - sk_getaddr(addr, dest, lenof(dest)); - put_data(buf, dest, strlen(dest)); - eo += 4; - } - else if (strnicmp(fmt + eo, "port", 4) == 0) { - strbuf_catf(buf, "%d", port); - eo += 4; - } - else if (strnicmp(fmt + eo, "user", 4) == 0) { - const char *username = conf_get_str(conf, CONF_proxy_username); - put_data(buf, username, strlen(username)); - eo += 4; - } - else if (strnicmp(fmt + eo, "pass", 4) == 0) { - const char *password = conf_get_str(conf, CONF_proxy_password); - put_data(buf, password, strlen(password)); - eo += 4; - } - else if (strnicmp(fmt + eo, "proxyhost", 9) == 0) { - const char *host = conf_get_str(conf, CONF_proxy_host); - put_data(buf, host, strlen(host)); - eo += 9; - } - else if (strnicmp(fmt + eo, "proxyport", 9) == 0) { - int port = conf_get_int(conf, CONF_proxy_port); - strbuf_catf(buf, "%d", port); - eo += 9; - } - else { - /* we don't escape this, so send the % now, and - * don't advance eo, so that we'll consider the - * text immediately following the % as unescaped. - */ - put_byte(buf, '%'); - } - } - - /* resume scanning for additional escapes after this one. */ - so = eo; - } - - /* if there is any unescaped text at the end of the line, send it */ - if (eo != so) { - put_data(buf, fmt + so, eo - so); - } - - return strbuf_to_str(buf); -} - -int proxy_telnet_negotiate (ProxySocket *p, int change) -{ - if (p->state == PROXY_CHANGE_NEW) { - char *formatted_cmd; - - formatted_cmd = format_telnet_command(p->remote_addr, p->remote_port, - p->conf); - - { - /* - * Re-escape control chars in the command, for logging. - */ - char *reescaped = snewn(4*strlen(formatted_cmd) + 1, char); - const char *in; - char *out; - char *logmsg; - - for (in = formatted_cmd, out = reescaped; *in; in++) { - if (*in == '\n') { - *out++ = '\\'; *out++ = 'n'; - } else if (*in == '\r') { - *out++ = '\\'; *out++ = 'r'; - } else if (*in == '\t') { - *out++ = '\\'; *out++ = 't'; - } else if (*in == '\\') { - *out++ = '\\'; *out++ = '\\'; - } else if ((unsigned)(((unsigned char)*in) - 0x20) < - (0x7F-0x20)) { - *out++ = *in; - } else { - out += sprintf(out, "\\x%02X", (unsigned)*in & 0xFF); - } - } - *out = '\0'; - - logmsg = dupprintf("Sending Telnet proxy command: %s", reescaped); - plug_log(p->plug, 2, NULL, 0, logmsg, 0); - sfree(logmsg); - sfree(reescaped); - } - - sk_write(p->sub_socket, formatted_cmd, strlen(formatted_cmd)); - sfree(formatted_cmd); - - p->state = 1; - return 0; - } - - if (change == PROXY_CHANGE_CLOSING) { - /* if our proxy negotiation process involves closing and opening - * new sockets, then we would want to intercept this closing - * callback when we were expecting it. if we aren't anticipating - * a socket close, then some error must have occurred. we'll - * just pass those errors up to the backend. - */ - plug_closing(p->plug, p->closing_error_msg, p->closing_error_code, - p->closing_calling_back); - return 0; /* ignored */ - } - - if (change == PROXY_CHANGE_SENT) { - /* some (or all) of what we wrote to the proxy was sent. - * we don't do anything new, however, until we receive the - * proxy's response. we might want to set a timer so we can - * timeout the proxy negotiation after a while... - */ - return 0; - } - - if (change == PROXY_CHANGE_ACCEPTING) { - /* we should _never_ see this, as we are using our socket to - * connect to a proxy, not accepting inbound connections. - * what should we do? close the socket with an appropriate - * error message? - */ - return plug_accepting(p->plug, - p->accepting_constructor, p->accepting_ctx); - } - - if (change == PROXY_CHANGE_RECEIVE) { - /* we have received data from the underlying socket, which - * we'll need to parse, process, and respond to appropriately. - */ - - /* we're done */ - proxy_activate(p); - /* proxy activate will have dealt with - * whatever is left of the buffer */ - return 1; - } - - plug_closing(p->plug, "Proxy error: Unexpected proxy error", - PROXY_ERROR_UNEXPECTED, 0); - return 1; -} diff --git a/0.73_My_PuTTY/version.h b/0.73_My_PuTTY/version.h deleted file mode 100644 index 8ee7e06..0000000 --- a/0.73_My_PuTTY/version.h +++ /dev/null @@ -1,5 +0,0 @@ -#define RELEASE 0.73 -#define TEXTVER "Release 0.73" -#define SSHVER "-Release-0.73" -#define BINARY_VERSION 0,73,2,18 -#define SOURCE_COMMIT "unavailable" diff --git a/0.73_My_PuTTY/windows/version_major.txt b/0.73_My_PuTTY/windows/version_major.txt deleted file mode 100644 index b2b5ae1..0000000 --- a/0.73_My_PuTTY/windows/version_major.txt +++ /dev/null @@ -1 +0,0 @@ -"0.73.2" diff --git a/0.73_My_PuTTY/windows/version_minor.txt b/0.73_My_PuTTY/windows/version_minor.txt deleted file mode 100644 index 3c03207..0000000 --- a/0.73_My_PuTTY/windows/version_minor.txt +++ /dev/null @@ -1 +0,0 @@ -18 diff --git a/0.73_My_PuTTY/windows/winser.c b/0.73_My_PuTTY/windows/winser.c deleted file mode 100644 index fd412e3..0000000 --- a/0.73_My_PuTTY/windows/winser.c +++ /dev/null @@ -1,450 +0,0 @@ -/* - * Serial back end (Windows-specific). - */ - -#include -#include -#include - -#include "putty.h" - -#define SERIAL_MAX_BACKLOG 4096 - -typedef struct Serial Serial; -struct Serial { - HANDLE port; - struct handle *out, *in; - Seat *seat; - LogContext *logctx; - int bufsize; - long clearbreak_time; - bool break_in_progress; - Backend backend; -}; - -static void serial_terminate(Serial *serial) -{ - if (serial->out) { - handle_free(serial->out); - serial->out = NULL; - } - if (serial->in) { - handle_free(serial->in); - serial->in = NULL; - } - if (serial->port != INVALID_HANDLE_VALUE) { - if (serial->break_in_progress) - ClearCommBreak(serial->port); - CloseHandle(serial->port); - serial->port = INVALID_HANDLE_VALUE; - } -} - -static size_t serial_gotdata( - struct handle *h, const void *data, size_t len, int err) -{ - Serial *serial = (Serial *)handle_get_privdata(h); - if (err || len == 0) { - const char *error_msg; - - /* - * Currently, len==0 should never happen because we're - * ignoring EOFs. However, it seems not totally impossible - * that this same back end might be usable to talk to named - * pipes or some other non-serial device, in which case EOF - * may become meaningful here. - */ - if (!err) - error_msg = "End of file reading from serial device"; - else - error_msg = "Error reading from serial device"; - - serial_terminate(serial); - - seat_notify_remote_exit(serial->seat); - - logevent(serial->logctx, error_msg); - - seat_connection_fatal(serial->seat, "%s", error_msg); - - return 0; - } else { - return seat_stdout(serial->seat, data, len); - } -} - -static void serial_sentdata(struct handle *h, size_t new_backlog, int err) -{ - Serial *serial = (Serial *)handle_get_privdata(h); - if (err) { - const char *error_msg = "Error writing to serial device"; - - serial_terminate(serial); - - seat_notify_remote_exit(serial->seat); - - logevent(serial->logctx, error_msg); - - seat_connection_fatal(serial->seat, "%s", error_msg); - } else { - serial->bufsize = new_backlog; - } -} - -static const char *serial_configure(Serial *serial, HANDLE serport, Conf *conf) -{ - DCB dcb; - COMMTIMEOUTS timeouts; - - /* - * Set up the serial port parameters. If we can't even - * GetCommState, we ignore the problem on the grounds that the - * user might have pointed us at some other type of two-way - * device instead of a serial port. - */ - if (GetCommState(serport, &dcb)) { - const char *str; - - /* - * Boilerplate. - */ - dcb.fBinary = true; - dcb.fDtrControl = DTR_CONTROL_ENABLE; - dcb.fDsrSensitivity = false; - dcb.fTXContinueOnXoff = false; - dcb.fOutX = false; - dcb.fInX = false; - dcb.fErrorChar = false; - dcb.fNull = false; - dcb.fRtsControl = RTS_CONTROL_ENABLE; - dcb.fAbortOnError = false; - dcb.fOutxCtsFlow = false; - dcb.fOutxDsrFlow = false; - - /* - * Configurable parameters. - */ - dcb.BaudRate = conf_get_int(conf, CONF_serspeed); - logeventf(serial->logctx, "Configuring baud rate %lu", dcb.BaudRate); - - dcb.ByteSize = conf_get_int(conf, CONF_serdatabits); - logeventf(serial->logctx, "Configuring %u data bits", dcb.ByteSize); - - switch (conf_get_int(conf, CONF_serstopbits)) { - case 2: dcb.StopBits = ONESTOPBIT; str = "1"; break; - case 3: dcb.StopBits = ONE5STOPBITS; str = "1.5"; break; - case 4: dcb.StopBits = TWOSTOPBITS; str = "2"; break; - default: return "Invalid number of stop bits (need 1, 1.5 or 2)"; - } - logeventf(serial->logctx, "Configuring %s data bits", str); - - switch (conf_get_int(conf, CONF_serparity)) { - case SER_PAR_NONE: dcb.Parity = NOPARITY; str = "no"; break; - case SER_PAR_ODD: dcb.Parity = ODDPARITY; str = "odd"; break; - case SER_PAR_EVEN: dcb.Parity = EVENPARITY; str = "even"; break; - case SER_PAR_MARK: dcb.Parity = MARKPARITY; str = "mark"; break; - case SER_PAR_SPACE: dcb.Parity = SPACEPARITY; str = "space"; break; - } - logeventf(serial->logctx, "Configuring %s parity", str); - - switch (conf_get_int(conf, CONF_serflow)) { - case SER_FLOW_NONE: - str = "no"; - break; - case SER_FLOW_XONXOFF: - dcb.fOutX = dcb.fInX = true; - str = "XON/XOFF"; - break; - case SER_FLOW_RTSCTS: - dcb.fRtsControl = RTS_CONTROL_HANDSHAKE; - dcb.fOutxCtsFlow = true; - str = "RTS/CTS"; - break; - case SER_FLOW_DSRDTR: - dcb.fDtrControl = DTR_CONTROL_HANDSHAKE; - dcb.fOutxDsrFlow = true; - str = "DSR/DTR"; - break; - } - logeventf(serial->logctx, "Configuring %s flow control", str); - - if (!SetCommState(serport, &dcb)) - return "Unable to configure serial port"; - - timeouts.ReadIntervalTimeout = 1; - timeouts.ReadTotalTimeoutMultiplier = 0; - timeouts.ReadTotalTimeoutConstant = 0; - timeouts.WriteTotalTimeoutMultiplier = 0; - timeouts.WriteTotalTimeoutConstant = 0; - if (!SetCommTimeouts(serport, &timeouts)) - return "Unable to configure serial timeouts"; - } - - return NULL; -} - -/* - * Called to set up the serial connection. - * - * Returns an error message, or NULL on success. - * - * Also places the canonical host name into `realhost'. It must be - * freed by the caller. - */ -static const char *serial_init(Seat *seat, Backend **backend_handle, - LogContext *logctx, Conf *conf, - const char *host, int port, - char **realhost, bool nodelay, bool keepalive) -{ - Serial *serial; - HANDLE serport; - const char *err; - char *serline; - - /* No local authentication phase in this protocol */ - seat_set_trust_status(seat, false); - - serial = snew(Serial); - serial->port = INVALID_HANDLE_VALUE; - serial->out = serial->in = NULL; - serial->bufsize = 0; - serial->break_in_progress = false; - serial->backend.vt = &serial_backend; - *backend_handle = &serial->backend; - - serial->seat = seat; - serial->logctx = logctx; - - serline = conf_get_str(conf, CONF_serline); - logeventf(serial->logctx, "Opening serial device %s", serline); - - { - /* - * Munge the string supplied by the user into a Windows filename. - * - * Windows supports opening a few "legacy" devices (including - * COM1-9) by specifying their names verbatim as a filename to - * open. (Thus, no files can ever have these names. See - * - * ("Naming a File") for the complete list of reserved names.) - * - * However, this doesn't let you get at devices COM10 and above. - * For that, you need to specify a filename like "\\.\COM10". - * This is also necessary for special serial and serial-like - * devices such as \\.\WCEUSBSH001. It also works for the "legacy" - * names, so you can do \\.\COM1 (verified as far back as Win95). - * See - * (CreateFile() docs). - * - * So, we believe that prepending "\\.\" should always be the - * Right Thing. However, just in case someone finds something to - * talk to that doesn't exist under there, if the serial line - * contains a backslash, we use it verbatim. (This also lets - * existing configurations using \\.\ continue working.) - */ - char *serfilename = - dupprintf("%s%s", strchr(serline, '\\') ? "" : "\\\\.\\", serline); - serport = CreateFile(serfilename, GENERIC_READ | GENERIC_WRITE, 0, NULL, - OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); - sfree(serfilename); - } - - if (serport == INVALID_HANDLE_VALUE) - return "Unable to open serial port"; - - err = serial_configure(serial, serport, conf); - if (err) - return err; - - serial->port = serport; - serial->out = handle_output_new(serport, serial_sentdata, serial, - HANDLE_FLAG_OVERLAPPED); - serial->in = handle_input_new(serport, serial_gotdata, serial, - HANDLE_FLAG_OVERLAPPED | - HANDLE_FLAG_IGNOREEOF | - HANDLE_FLAG_UNITBUFFER); - - *realhost = dupstr(serline); - - /* - * Specials are always available. - */ - seat_update_specials_menu(serial->seat); - - return NULL; -} - -static void serial_free(Backend *be) -{ - Serial *serial = container_of(be, Serial, backend); - - serial_terminate(serial); - expire_timer_context(serial); - sfree(serial); -} - -static void serial_reconfig(Backend *be, Conf *conf) -{ - Serial *serial = container_of(be, Serial, backend); - - serial_configure(serial, serial->port, conf); - - /* - * FIXME: what should we do if that call returned a non-NULL error - * message? - */ -} - -/* - * Called to send data down the serial connection. - */ -static size_t serial_send(Backend *be, const char *buf, size_t len) -{ - Serial *serial = container_of(be, Serial, backend); - - if (serial->out == NULL) - return 0; - - serial->bufsize = handle_write(serial->out, buf, len); - return serial->bufsize; -} - -/* - * Called to query the current sendability status. - */ -static size_t serial_sendbuffer(Backend *be) -{ - Serial *serial = container_of(be, Serial, backend); - return serial->bufsize; -} - -/* - * Called to set the size of the window - */ -static void serial_size(Backend *be, int width, int height) -{ - /* Do nothing! */ - return; -} - -static void serbreak_timer(void *ctx, unsigned long now) -{ - Serial *serial = (Serial *)ctx; - - if (now == serial->clearbreak_time && serial->port) { - ClearCommBreak(serial->port); - serial->break_in_progress = false; - logevent(serial->logctx, "Finished serial break"); - } -} - -/* - * Send serial special codes. - */ -static void serial_special(Backend *be, SessionSpecialCode code, int arg) -{ - Serial *serial = container_of(be, Serial, backend); - - if (serial->port && code == SS_BRK) { - logevent(serial->logctx, "Starting serial break at user request"); - SetCommBreak(serial->port); - /* - * To send a serial break on Windows, we call SetCommBreak - * to begin the break, then wait a bit, and then call - * ClearCommBreak to finish it. Hence, I must use timing.c - * to arrange a callback when it's time to do the latter. - * - * SUS says that a default break length must be between 1/4 - * and 1/2 second. FreeBSD apparently goes with 2/5 second, - * and so will I. - */ - serial->clearbreak_time = - schedule_timer(TICKSPERSEC * 2 / 5, serbreak_timer, serial); - serial->break_in_progress = true; - } - - return; -} - -/* - * Return a list of the special codes that make sense in this - * protocol. - */ -static const SessionSpecial *serial_get_specials(Backend *be) -{ - static const SessionSpecial specials[] = { - {"Break", SS_BRK}, - {NULL, SS_EXITMENU} - }; - return specials; -} - -static bool serial_connected(Backend *be) -{ - return true; /* always connected */ -} - -static bool serial_sendok(Backend *be) -{ - return true; -} - -static void serial_unthrottle(Backend *be, size_t backlog) -{ - Serial *serial = container_of(be, Serial, backend); - if (serial->in) - handle_unthrottle(serial->in, backlog); -} - -static bool serial_ldisc(Backend *be, int option) -{ - /* - * Local editing and local echo are off by default. - */ - return false; -} - -static void serial_provide_ldisc(Backend *be, Ldisc *ldisc) -{ - /* This is a stub. */ -} - -static int serial_exitcode(Backend *be) -{ - Serial *serial = container_of(be, Serial, backend); - if (serial->port != INVALID_HANDLE_VALUE) - return -1; /* still connected */ - else - /* Exit codes are a meaningless concept with serial ports */ - return INT_MAX; -} - -/* - * cfg_info for Serial does nothing at all. - */ -static int serial_cfg_info(Backend *be) -{ - return 0; -} - -const struct BackendVtable serial_backend = { - serial_init, - serial_free, - serial_reconfig, - serial_send, - serial_sendbuffer, - serial_size, - serial_special, - serial_get_specials, - serial_connected, - serial_exitcode, - serial_sendok, - serial_ldisc, - serial_provide_ldisc, - serial_unthrottle, - serial_cfg_info, - NULL /* test_for_upstream */, - "serial", - PROT_SERIAL, - 0 -}; diff --git a/0.73_My_PuTTY/agentf.c b/0.74_My_PuTTY/agentf.c similarity index 100% rename from 0.73_My_PuTTY/agentf.c rename to 0.74_My_PuTTY/agentf.c diff --git a/0.73_My_PuTTY/aqsync.c b/0.74_My_PuTTY/aqsync.c similarity index 100% rename from 0.73_My_PuTTY/aqsync.c rename to 0.74_My_PuTTY/aqsync.c diff --git a/0.73_My_PuTTY/be_all.c b/0.74_My_PuTTY/be_all.c similarity index 100% rename from 0.73_My_PuTTY/be_all.c rename to 0.74_My_PuTTY/be_all.c diff --git a/0.73_My_PuTTY/be_all_s.c b/0.74_My_PuTTY/be_all_s.c similarity index 100% rename from 0.73_My_PuTTY/be_all_s.c rename to 0.74_My_PuTTY/be_all_s.c diff --git a/0.73_My_PuTTY/be_misc.c b/0.74_My_PuTTY/be_misc.c similarity index 100% rename from 0.73_My_PuTTY/be_misc.c rename to 0.74_My_PuTTY/be_misc.c diff --git a/0.73_My_PuTTY/be_none.c b/0.74_My_PuTTY/be_none.c similarity index 100% rename from 0.73_My_PuTTY/be_none.c rename to 0.74_My_PuTTY/be_none.c diff --git a/0.73_My_PuTTY/be_nos_s.c b/0.74_My_PuTTY/be_nos_s.c similarity index 100% rename from 0.73_My_PuTTY/be_nos_s.c rename to 0.74_My_PuTTY/be_nos_s.c diff --git a/0.73_My_PuTTY/be_nossh.c b/0.74_My_PuTTY/be_nossh.c similarity index 100% rename from 0.73_My_PuTTY/be_nossh.c rename to 0.74_My_PuTTY/be_nossh.c diff --git a/0.73_My_PuTTY/be_ssh.c b/0.74_My_PuTTY/be_ssh.c similarity index 100% rename from 0.73_My_PuTTY/be_ssh.c rename to 0.74_My_PuTTY/be_ssh.c diff --git a/0.73_My_PuTTY/callback.c b/0.74_My_PuTTY/callback.c similarity index 100% rename from 0.73_My_PuTTY/callback.c rename to 0.74_My_PuTTY/callback.c diff --git a/0.73_My_PuTTY/cgtest.c b/0.74_My_PuTTY/cgtest.c similarity index 100% rename from 0.73_My_PuTTY/cgtest.c rename to 0.74_My_PuTTY/cgtest.c diff --git a/0.73_My_PuTTY/charset/README b/0.74_My_PuTTY/charset/README similarity index 100% rename from 0.73_My_PuTTY/charset/README rename to 0.74_My_PuTTY/charset/README diff --git a/0.73_My_PuTTY/charset/charset.h b/0.74_My_PuTTY/charset/charset.h similarity index 100% rename from 0.73_My_PuTTY/charset/charset.h rename to 0.74_My_PuTTY/charset/charset.h diff --git a/0.73_My_PuTTY/charset/enum.c b/0.74_My_PuTTY/charset/enum.c similarity index 100% rename from 0.73_My_PuTTY/charset/enum.c rename to 0.74_My_PuTTY/charset/enum.c diff --git a/0.73_My_PuTTY/charset/fromucs.c b/0.74_My_PuTTY/charset/fromucs.c similarity index 100% rename from 0.73_My_PuTTY/charset/fromucs.c rename to 0.74_My_PuTTY/charset/fromucs.c diff --git a/0.73_My_PuTTY/charset/internal.h b/0.74_My_PuTTY/charset/internal.h similarity index 100% rename from 0.73_My_PuTTY/charset/internal.h rename to 0.74_My_PuTTY/charset/internal.h diff --git a/0.73_My_PuTTY/charset/localenc.c b/0.74_My_PuTTY/charset/localenc.c similarity index 100% rename from 0.73_My_PuTTY/charset/localenc.c rename to 0.74_My_PuTTY/charset/localenc.c diff --git a/0.73_My_PuTTY/charset/macenc.c b/0.74_My_PuTTY/charset/macenc.c similarity index 100% rename from 0.73_My_PuTTY/charset/macenc.c rename to 0.74_My_PuTTY/charset/macenc.c diff --git a/0.73_My_PuTTY/charset/mimeenc.c b/0.74_My_PuTTY/charset/mimeenc.c similarity index 100% rename from 0.73_My_PuTTY/charset/mimeenc.c rename to 0.74_My_PuTTY/charset/mimeenc.c diff --git a/0.73_My_PuTTY/charset/sbcs.c b/0.74_My_PuTTY/charset/sbcs.c similarity index 100% rename from 0.73_My_PuTTY/charset/sbcs.c rename to 0.74_My_PuTTY/charset/sbcs.c diff --git a/0.73_My_PuTTY/charset/sbcs.dat b/0.74_My_PuTTY/charset/sbcs.dat similarity index 100% rename from 0.73_My_PuTTY/charset/sbcs.dat rename to 0.74_My_PuTTY/charset/sbcs.dat diff --git a/0.73_My_PuTTY/charset/sbcsdat.c b/0.74_My_PuTTY/charset/sbcsdat.c similarity index 100% rename from 0.73_My_PuTTY/charset/sbcsdat.c rename to 0.74_My_PuTTY/charset/sbcsdat.c diff --git a/0.73_My_PuTTY/charset/sbcsgen.pl b/0.74_My_PuTTY/charset/sbcsgen.pl similarity index 100% rename from 0.73_My_PuTTY/charset/sbcsgen.pl rename to 0.74_My_PuTTY/charset/sbcsgen.pl diff --git a/0.73_My_PuTTY/charset/slookup.c b/0.74_My_PuTTY/charset/slookup.c similarity index 100% rename from 0.73_My_PuTTY/charset/slookup.c rename to 0.74_My_PuTTY/charset/slookup.c diff --git a/0.73_My_PuTTY/charset/toucs.c b/0.74_My_PuTTY/charset/toucs.c similarity index 100% rename from 0.73_My_PuTTY/charset/toucs.c rename to 0.74_My_PuTTY/charset/toucs.c diff --git a/0.73_My_PuTTY/charset/utf8.c b/0.74_My_PuTTY/charset/utf8.c similarity index 100% rename from 0.73_My_PuTTY/charset/utf8.c rename to 0.74_My_PuTTY/charset/utf8.c diff --git a/0.73_My_PuTTY/charset/xenc.c b/0.74_My_PuTTY/charset/xenc.c similarity index 100% rename from 0.73_My_PuTTY/charset/xenc.c rename to 0.74_My_PuTTY/charset/xenc.c diff --git a/0.73_My_PuTTY/cmdgen.c b/0.74_My_PuTTY/cmdgen.c similarity index 94% rename from 0.73_My_PuTTY/cmdgen.c rename to 0.74_My_PuTTY/cmdgen.c index 563abc8..9faf29b 100644 --- a/0.73_My_PuTTY/cmdgen.c +++ b/0.74_My_PuTTY/cmdgen.c @@ -53,10 +53,10 @@ int console_get_userpass_input(prompts_t *p) int ret = 1; for (i = 0; i < p->n_prompts; i++) { if (promptsgot < nprompts) { - p->prompts[i]->result = dupstr(prompts[promptsgot++]); + prompt_set_result(p->prompts[i], prompts[promptsgot++]); if (cgtest_verbose) printf(" prompt \"%s\": response \"%s\"\n", - p->prompts[i]->prompt, p->prompts[i]->result); + p->prompts[i]->prompt, p->prompts[i]->result->s); } else { promptsgot++; /* track number of requests anyway */ ret = 0; @@ -486,7 +486,7 @@ int main(int argc, char **argv) bits = 384; break; case ED25519: - bits = 256; + bits = 255; break; default: bits = DEFAULT_RSADSA_BITS; @@ -499,8 +499,8 @@ int main(int argc, char **argv) errs = true; } - if (keytype == ED25519 && (bits != 256)) { - fprintf(stderr, "puttygen: invalid bits for ED25519, choose 256\n"); + if (keytype == ED25519 && (bits != 255) && (bits != 256)) { + fprintf(stderr, "puttygen: invalid bits for ED25519, choose 255\n"); errs = true; } @@ -619,7 +619,7 @@ int main(int argc, char **argv) (intype == SSH_KEYTYPE_SSHCOM && outtype == SSHCOM)) { if (!outfile) { outfile = infile; - outfiletmp = dupcat(outfile, ".tmp", NULL); + outfiletmp = dupcat(outfile, ".tmp"); } if (!change_passphrase && !comment) { @@ -774,7 +774,7 @@ int main(int argc, char **argv) perror("puttygen: unable to read passphrase"); return 1; } else { - old_passphrase = dupstr(p->prompts[0]->result); + old_passphrase = prompt_get_result(p->prompts[0]); free_prompts(p); } } @@ -903,7 +903,7 @@ int main(int argc, char **argv) * we have just generated a key. */ if (!new_passphrase && (change_passphrase || keytype != NOKEYGEN)) { - prompts_t *p = new_prompts(NULL); + prompts_t *p = new_prompts(); int ret; p->to_server = false; @@ -918,12 +918,13 @@ int main(int argc, char **argv) perror("puttygen: unable to read new passphrase"); return 1; } else { - if (strcmp(p->prompts[0]->result, p->prompts[1]->result)) { + if (strcmp(prompt_get_result_ref(p->prompts[0]), + prompt_get_result_ref(p->prompts[1]))) { free_prompts(p); fprintf(stderr, "puttygen: passphrases do not match\n"); return 1; } - new_passphrase = dupstr(p->prompts[0]->result); + new_passphrase = prompt_get_result(p->prompts[0]); free_prompts(p); } } @@ -1194,7 +1195,7 @@ void test(int retval, ...) sfree(argv); } -void filecmp(char *file1, char *file2, char *fmt, ...) +PRINTF_LIKE(3, 4) void filecmp(char *file1, char *file2, char *fmt, ...) { /* * Ideally I should do file comparison myself, to maximise the @@ -1259,7 +1260,7 @@ char *get_fp(char *filename) return cleanup_fp(buf); } -void check_fp(char *filename, char *fp, char *fmt, ...) +PRINTF_LIKE(3, 4) void check_fp(char *filename, char *fp, char *fmt, ...) { char *newfp; @@ -1335,7 +1336,8 @@ int main(int argc, char **argv) pubfilename, tmpfilename1); if (system(cmdbuf) || (fp = get_fp(tmpfilename1)) == NULL) { - printf("UNABLE to test fingerprint matching against OpenSSH"); + printf("UNABLE to test fingerprint matching against " + "OpenSSH\n"); } sfree(cmdbuf); } @@ -1679,7 +1681,7 @@ int main(int argc, char **argv) test(1, "puttygen", "-C", "spurious-new-comment", pubfilename, NULL); } printf("%d passes, %d fails\n", passes, fails); - return 0; + return fails == 0 ? 0 : 1; } #endif diff --git a/0.73_My_PuTTY/cmdline.c b/0.74_My_PuTTY/cmdline.c similarity index 100% rename from 0.73_My_PuTTY/cmdline.c rename to 0.74_My_PuTTY/cmdline.c diff --git a/0.73_My_PuTTY/conf.c b/0.74_My_PuTTY/conf.c similarity index 100% rename from 0.73_My_PuTTY/conf.c rename to 0.74_My_PuTTY/conf.c diff --git a/0.73_My_PuTTY/config.c b/0.74_My_PuTTY/config.c similarity index 99% rename from 0.73_My_PuTTY/config.c rename to 0.74_My_PuTTY/config.c index b4d010e..ac1b5d5 100644 --- a/0.73_My_PuTTY/config.c +++ b/0.74_My_PuTTY/config.c @@ -1857,7 +1857,7 @@ static void environ_handler(union control *ctrl, dlgparam *dlg, return; } conf_set_str_str(conf, CONF_environmt, key, val); - str = dupcat(key, "\t", val, NULL); + str = dupcat(key, "\t", val); dlg_editbox_set(ed->varbox, dlg, ""); dlg_editbox_set(ed->valbox, dlg, ""); sfree(str); @@ -1988,7 +1988,7 @@ static void portfwd_handler(union control *ctrl, dlgparam *dlg, val = dupstr("D"); /* special case */ } - key = dupcat(family, type, src, NULL); + key = dupcat(family, type, src); sfree(src); if (conf_get_str_str_opt(conf, CONF_portfwd, key)) { @@ -2155,7 +2155,7 @@ static void clipboard_selector_handler(union control *ctrl, dlgparam *dlg, if (!strcmp(sval, options[i].name)) break; /* needs escaping */ if (i < lenof(options) || sval[0] == '=') { - char *escaped = dupcat("=", sval, (const char *)NULL); + char *escaped = dupcat("=", sval); dlg_editbox_set(ctrl, dlg, escaped); sfree(escaped); } else { @@ -2182,7 +2182,7 @@ static void clipboard_selector_handler(union control *ctrl, dlgparam *dlg, #endif ) { #ifdef NAMED_CLIPBOARDS - const char *sval = dlg_editbox_get(ctrl, dlg); + char *sval = dlg_editbox_get(ctrl, dlg); int i; for (i = 0; i < lenof(options); i++) @@ -2197,6 +2197,7 @@ static void clipboard_selector_handler(union control *ctrl, dlgparam *dlg, sval++; conf_set_str(conf, strsetting, sval); } + sfree(sval); #else int index = dlg_listbox_index(ctrl, dlg); if (index >= 0) { @@ -3656,6 +3657,10 @@ if( !GetPuttyFlag() ) { HELPCTX(ssh_hklist), hklist_handler, P(NULL)); c->listbox.height = 5; + + ctrl_checkbox(s, "Prefer algorithms for which a host key is known", + 'p', HELPCTX(ssh_hk_known), conf_checkbox_handler, + I(CONF_ssh_prefer_known_hostkeys)); } /* diff --git a/0.73_My_PuTTY/cproxy.c b/0.74_My_PuTTY/cproxy.c similarity index 100% rename from 0.73_My_PuTTY/cproxy.c rename to 0.74_My_PuTTY/cproxy.c diff --git a/0.73_My_PuTTY/defs.h b/0.74_My_PuTTY/defs.h similarity index 85% rename from 0.73_My_PuTTY/defs.h rename to 0.74_My_PuTTY/defs.h index afeb506..b957703 100644 --- a/0.73_My_PuTTY/defs.h +++ b/0.74_My_PuTTY/defs.h @@ -22,11 +22,38 @@ #define PRIdMAX "I64d" #define PRIXMAX "I64X" #define SCNu64 "I64u" +#define SIZEx "Ix" +#define SIZEu "Iu" uintmax_t strtoumax(const char *nptr, char **endptr, int base); #else #include +/* Because we still support older MSVC libraries which don't recognise the + * standard C "z" modifier for size_t-sized integers, we must use an + * inttypes.h-style macro for those */ +#define SIZEx "zx" +#define SIZEu "zu" #endif +#if defined __GNUC__ || defined __clang__ +/* + * On MinGW, the correct compiler format checking for vsnprintf() etc + * can depend on compile-time flags; these control whether you get + * ISO C or Microsoft's non-standard format strings. + * We sometimes use __attribute__ ((format)) for our own printf-like + * functions, which are ultimately interpreted by the toolchain-chosen + * printf, so we need to take that into account to get correct warnings. + */ +#ifdef __MINGW_PRINTF_FORMAT +#define PRINTF_LIKE(fmt_index, ellipsis_index) \ + __attribute__ ((format (__MINGW_PRINTF_FORMAT, fmt_index, ellipsis_index))) +#else +#define PRINTF_LIKE(fmt_index, ellipsis_index) \ + __attribute__ ((format (printf, fmt_index, ellipsis_index))) +#endif +#else /* __GNUC__ */ +#define PRINTF_LIKE(fmt_index, ellipsis_index) +#endif /* __GNUC__ */ + typedef struct conf_tag Conf; typedef struct terminal_tag Terminal; typedef struct term_utf8_decode term_utf8_decode; diff --git a/0.73_My_PuTTY/dialog.c b/0.74_My_PuTTY/dialog.c similarity index 100% rename from 0.73_My_PuTTY/dialog.c rename to 0.74_My_PuTTY/dialog.c diff --git a/0.73_My_PuTTY/dialog.h b/0.74_My_PuTTY/dialog.h similarity index 100% rename from 0.73_My_PuTTY/dialog.h rename to 0.74_My_PuTTY/dialog.h diff --git a/0.73_My_PuTTY/ecc.c b/0.74_My_PuTTY/ecc.c similarity index 100% rename from 0.73_My_PuTTY/ecc.c rename to 0.74_My_PuTTY/ecc.c diff --git a/0.73_My_PuTTY/ecc.h b/0.74_My_PuTTY/ecc.h similarity index 100% rename from 0.73_My_PuTTY/ecc.h rename to 0.74_My_PuTTY/ecc.h diff --git a/0.73_My_PuTTY/empty.h b/0.74_My_PuTTY/empty.h similarity index 100% rename from 0.73_My_PuTTY/empty.h rename to 0.74_My_PuTTY/empty.h diff --git a/0.73_My_PuTTY/errsock.c b/0.74_My_PuTTY/errsock.c similarity index 100% rename from 0.73_My_PuTTY/errsock.c rename to 0.74_My_PuTTY/errsock.c diff --git a/0.73_My_PuTTY/fuzzterm.c b/0.74_My_PuTTY/fuzzterm.c similarity index 57% rename from 0.73_My_PuTTY/fuzzterm.c rename to 0.74_My_PuTTY/fuzzterm.c index a54f6fc..61bf2e1 100644 --- a/0.73_My_PuTTY/fuzzterm.c +++ b/0.74_My_PuTTY/fuzzterm.c @@ -1,223 +1,228 @@ -#include -#include -#include - -#define PUTTY_DO_GLOBALS -#include "putty.h" -#include "terminal.h" - -/* For Unix in particular, but harmless if this main() is reused elsewhere */ -const bool buildinfo_gtk_relevant = false; - -static const TermWinVtable fuzz_termwin_vt; - -int main(int argc, char **argv) -{ - char blk[512]; - size_t len; - Terminal *term; - Conf *conf; - struct unicode_data ucsdata; - TermWin termwin; - - termwin.vt = &fuzz_termwin_vt; - - conf = conf_new(); - do_defaults(NULL, conf); - init_ucs(&ucsdata, conf_get_str(conf, CONF_line_codepage), - conf_get_bool(conf, CONF_utf8_override), - CS_NONE, conf_get_int(conf, CONF_vtmode)); - - term = term_init(conf, &ucsdata, &termwin); - term_size(term, 24, 80, 10000); - term->ldisc = NULL; - /* Tell american fuzzy lop that this is a good place to fork. */ -#ifdef __AFL_HAVE_MANUAL_CONTROL - __AFL_INIT(); -#endif - while (!feof(stdin)) { - len = fread(blk, 1, sizeof(blk), stdin); - term_data(term, false, blk, len); - } - term_update(term); - return 0; -} - -/* functions required by terminal.c */ -static bool fuzz_setup_draw_ctx(TermWin *tw) { return true; } -static void fuzz_draw_text( - TermWin *tw, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr, truecolour tc) -{ - int i; - - printf("TEXT[attr=%08lx,lattr=%02x]@(%d,%d):", attr, lattr, x, y); - for (i = 0; i < len; i++) { - printf(" %x", (unsigned)text[i]); - } - printf("\n"); -} -static void fuzz_draw_cursor( - TermWin *tw, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr, truecolour tc) -{ - int i; - - printf("CURS[attr=%08lx,lattr=%02x]@(%d,%d):", attr, lattr, x, y); - for (i = 0; i < len; i++) { - printf(" %x", (unsigned)text[i]); - } - printf("\n"); -} -static void fuzz_draw_trust_sigil(TermWin *tw, int x, int y) -{ - printf("TRUST@(%d,%d)\n", x, y); -} -static int fuzz_char_width(TermWin *tw, int uc) { return 1; } -static void fuzz_free_draw_ctx(TermWin *tw) {} -static void fuzz_set_cursor_pos(TermWin *tw, int x, int y) {} -static void fuzz_set_raw_mouse_mode(TermWin *tw, bool enable) {} -static void fuzz_set_scrollbar(TermWin *tw, int total, int start, int page) {} -static void fuzz_bell(TermWin *tw, int mode) {} -static void fuzz_clip_write( - TermWin *tw, int clipboard, wchar_t *text, int *attrs, - truecolour *colours, int len, bool must_deselect) {} -static void fuzz_clip_request_paste(TermWin *tw, int clipboard) {} -static void fuzz_refresh(TermWin *tw) {} -static void fuzz_request_resize(TermWin *tw, int w, int h) {} -static void fuzz_set_title(TermWin *tw, const char *title) {} -static void fuzz_set_icon_title(TermWin *tw, const char *icontitle) {} -static void fuzz_set_minimised(TermWin *tw, bool minimised) {} -static bool fuzz_is_minimised(TermWin *tw) { return false; } -static void fuzz_set_maximised(TermWin *tw, bool maximised) {} -static void fuzz_move(TermWin *tw, int x, int y) {} -static void fuzz_set_zorder(TermWin *tw, bool top) {} -static bool fuzz_palette_get(TermWin *tw, int n, int *r, int *g, int *b) -{ return false; } -static void fuzz_palette_set(TermWin *tw, int n, int r, int g, int b) {} -static void fuzz_palette_reset(TermWin *tw) {} -static void fuzz_get_pos(TermWin *tw, int *x, int *y) { *x = *y = 0; } -static void fuzz_get_pixels(TermWin *tw, int *x, int *y) { *x = *y = 0; } -static const char *fuzz_get_title(TermWin *tw, bool icon) { return "moo"; } -static bool fuzz_is_utf8(TermWin *tw) { return true; } - -static const TermWinVtable fuzz_termwin_vt = { - fuzz_setup_draw_ctx, - fuzz_draw_text, - fuzz_draw_cursor, - fuzz_draw_trust_sigil, - fuzz_char_width, - fuzz_free_draw_ctx, - fuzz_set_cursor_pos, - fuzz_set_raw_mouse_mode, - fuzz_set_scrollbar, - fuzz_bell, - fuzz_clip_write, - fuzz_clip_request_paste, - fuzz_refresh, - fuzz_request_resize, - fuzz_set_title, - fuzz_set_icon_title, - fuzz_set_minimised, - fuzz_is_minimised, - fuzz_set_maximised, - fuzz_move, - fuzz_set_zorder, - fuzz_palette_get, - fuzz_palette_set, - fuzz_palette_reset, - fuzz_get_pos, - fuzz_get_pixels, - fuzz_get_title, - fuzz_is_utf8, -}; - -void ldisc_send(Ldisc *ldisc, const void *buf, int len, bool interactive) {} -void ldisc_echoedit_update(Ldisc *ldisc) {} -void modalfatalbox(const char *fmt, ...) { exit(0); } -void nonfatal(const char *fmt, ...) { } - -/* needed by timing.c */ -void timer_change_notify(unsigned long next) { } - -/* needed by config.c and sercfg.c */ - -void dlg_radiobutton_set(union control *ctrl, void *dlg, int whichbutton) { } -int dlg_radiobutton_get(union control *ctrl, void *dlg) { return 0; } -void dlg_checkbox_set(union control *ctrl, void *dlg, int checked) { } -int dlg_checkbox_get(union control *ctrl, void *dlg) { return 0; } -void dlg_editbox_set(union control *ctrl, void *dlg, char const *text) { } -char *dlg_editbox_get(union control *ctrl, void *dlg) { return dupstr("moo"); } -void dlg_listbox_clear(union control *ctrl, void *dlg) { } -void dlg_listbox_del(union control *ctrl, void *dlg, int index) { } -void dlg_listbox_add(union control *ctrl, void *dlg, char const *text) { } -void dlg_listbox_addwithid(union control *ctrl, void *dlg, - char const *text, int id) { } -int dlg_listbox_getid(union control *ctrl, void *dlg, int index) { return 0; } -int dlg_listbox_index(union control *ctrl, void *dlg) { return -1; } -int dlg_listbox_issel(union control *ctrl, void *dlg, int index) { return 0; } -void dlg_listbox_select(union control *ctrl, void *dlg, int index) { } -void dlg_text_set(union control *ctrl, void *dlg, char const *text) { } -void dlg_filesel_set(union control *ctrl, void *dlg, Filename *fn) { } -Filename *dlg_filesel_get(union control *ctrl, void *dlg) { return NULL; } -void dlg_fontsel_set(union control *ctrl, void *dlg, FontSpec *fn) { } -FontSpec *dlg_fontsel_get(union control *ctrl, void *dlg) { return NULL; } -void dlg_update_start(union control *ctrl, void *dlg) { } -void dlg_update_done(union control *ctrl, void *dlg) { } -void dlg_set_focus(union control *ctrl, void *dlg) { } -void dlg_label_change(union control *ctrl, void *dlg, char const *text) { } -union control *dlg_last_focused(union control *ctrl, void *dlg) { return NULL; } -void dlg_beep(void *dlg) { } -void dlg_error_msg(void *dlg, const char *msg) { } -void dlg_end(void *dlg, int value) { } -void dlg_coloursel_start(union control *ctrl, void *dlg, - int r, int g, int b) { } -bool dlg_coloursel_results(union control *ctrl, void *dlg, - int *r, int *g, int *b) { return false; } -void dlg_refresh(union control *ctrl, void *dlg) { } -bool dlg_is_visible(union control *ctrl, dlgparam *dp) { return false; } - -const char *const appname = "FuZZterm"; -const int ngsslibs = 0; -const char *const gsslibnames[0] = { }; -const struct keyvalwhere gsslibkeywords[0] = { }; - -/* - * Default settings that are specific to Unix plink. - */ -char *platform_default_s(const char *name) -{ - if (!strcmp(name, "TermType")) - return dupstr(getenv("TERM")); - if (!strcmp(name, "SerialLine")) - return dupstr("/dev/ttyS0"); - return NULL; -} - -bool platform_default_b(const char *name, bool def) -{ - return def; -} - -int platform_default_i(const char *name, int def) -{ - return def; -} - -FontSpec *platform_default_fontspec(const char *name) -{ - return fontspec_new(""); -} - -Filename *platform_default_filename(const char *name) -{ - if (!strcmp(name, "LogFileName")) - return filename_from_str("putty.log"); - else - return filename_from_str(""); -} - -char *x_get_default(const char *key) -{ - return NULL; /* this is a stub */ -} +#include +#include +#include + +#define PUTTY_DO_GLOBALS +#include "putty.h" +#include "dialog.h" +#include "terminal.h" + +/* For Unix in particular, but harmless if this main() is reused elsewhere */ +const bool buildinfo_gtk_relevant = false; + +static const TermWinVtable fuzz_termwin_vt; + +int main(int argc, char **argv) +{ + char blk[512]; + size_t len; + Terminal *term; + Conf *conf; + struct unicode_data ucsdata; + TermWin termwin; + + termwin.vt = &fuzz_termwin_vt; + + conf = conf_new(); + do_defaults(NULL, conf); + init_ucs(&ucsdata, conf_get_str(conf, CONF_line_codepage), + conf_get_bool(conf, CONF_utf8_override), + CS_NONE, conf_get_int(conf, CONF_vtmode)); + + term = term_init(conf, &ucsdata, &termwin); + term_size(term, 24, 80, 10000); + term->ldisc = NULL; + /* Tell american fuzzy lop that this is a good place to fork. */ +#ifdef __AFL_HAVE_MANUAL_CONTROL + __AFL_INIT(); +#endif + while (!feof(stdin)) { + len = fread(blk, 1, sizeof(blk), stdin); + term_data(term, false, blk, len); + } + term_update(term); + return 0; +} + +/* functions required by terminal.c */ +static bool fuzz_setup_draw_ctx(TermWin *tw) { return true; } +static void fuzz_draw_text( + TermWin *tw, int x, int y, wchar_t *text, int len, + unsigned long attr, int lattr, truecolour tc) +{ + int i; + + printf("TEXT[attr=%08lx,lattr=%02x]@(%d,%d):", attr, lattr, x, y); + for (i = 0; i < len; i++) { + printf(" %x", (unsigned)text[i]); + } + printf("\n"); +} +static void fuzz_draw_cursor( + TermWin *tw, int x, int y, wchar_t *text, int len, + unsigned long attr, int lattr, truecolour tc) +{ + int i; + + printf("CURS[attr=%08lx,lattr=%02x]@(%d,%d):", attr, lattr, x, y); + for (i = 0; i < len; i++) { + printf(" %x", (unsigned)text[i]); + } + printf("\n"); +} +static void fuzz_draw_trust_sigil(TermWin *tw, int x, int y) +{ + printf("TRUST@(%d,%d)\n", x, y); +} +static int fuzz_char_width(TermWin *tw, int uc) { return 1; } +static void fuzz_free_draw_ctx(TermWin *tw) {} +static void fuzz_set_cursor_pos(TermWin *tw, int x, int y) {} +static void fuzz_set_raw_mouse_mode(TermWin *tw, bool enable) {} +static void fuzz_set_scrollbar(TermWin *tw, int total, int start, int page) {} +static void fuzz_bell(TermWin *tw, int mode) {} +static void fuzz_clip_write( + TermWin *tw, int clipboard, wchar_t *text, int *attrs, + truecolour *colours, int len, bool must_deselect) {} +static void fuzz_clip_request_paste(TermWin *tw, int clipboard) {} +static void fuzz_refresh(TermWin *tw) {} +static void fuzz_request_resize(TermWin *tw, int w, int h) {} +static void fuzz_set_title(TermWin *tw, const char *title) {} +static void fuzz_set_icon_title(TermWin *tw, const char *icontitle) {} +static void fuzz_set_minimised(TermWin *tw, bool minimised) {} +static bool fuzz_is_minimised(TermWin *tw) { return false; } +static void fuzz_set_maximised(TermWin *tw, bool maximised) {} +static void fuzz_move(TermWin *tw, int x, int y) {} +static void fuzz_set_zorder(TermWin *tw, bool top) {} +static bool fuzz_palette_get(TermWin *tw, int n, int *r, int *g, int *b) +{ return false; } +static void fuzz_palette_set(TermWin *tw, int n, int r, int g, int b) {} +static void fuzz_palette_reset(TermWin *tw) {} +static void fuzz_get_pos(TermWin *tw, int *x, int *y) { *x = *y = 0; } +static void fuzz_get_pixels(TermWin *tw, int *x, int *y) { *x = *y = 0; } +static const char *fuzz_get_title(TermWin *tw, bool icon) { return "moo"; } +static bool fuzz_is_utf8(TermWin *tw) { return true; } + +static const TermWinVtable fuzz_termwin_vt = { + fuzz_setup_draw_ctx, + fuzz_draw_text, + fuzz_draw_cursor, + fuzz_draw_trust_sigil, + fuzz_char_width, + fuzz_free_draw_ctx, + fuzz_set_cursor_pos, + fuzz_set_raw_mouse_mode, + fuzz_set_scrollbar, + fuzz_bell, + fuzz_clip_write, + fuzz_clip_request_paste, + fuzz_refresh, + fuzz_request_resize, + fuzz_set_title, + fuzz_set_icon_title, + fuzz_set_minimised, + fuzz_is_minimised, + fuzz_set_maximised, + fuzz_move, + fuzz_set_zorder, + fuzz_palette_get, + fuzz_palette_set, + fuzz_palette_reset, + fuzz_get_pos, + fuzz_get_pixels, + fuzz_get_title, + fuzz_is_utf8, +}; + +void ldisc_send(Ldisc *ldisc, const void *buf, int len, bool interactive) {} +void ldisc_echoedit_update(Ldisc *ldisc) {} +void modalfatalbox(const char *fmt, ...) { exit(0); } +void nonfatal(const char *fmt, ...) { } + +/* needed by timing.c */ +void timer_change_notify(unsigned long next) { } + +/* needed by config.c and sercfg.c */ + +void dlg_radiobutton_set(union control *ctrl, dlgparam *dp, int whichbutton) { } +int dlg_radiobutton_get(union control *ctrl, dlgparam *dp) { return 0; } +void dlg_checkbox_set(union control *ctrl, dlgparam *dp, bool checked) { } +bool dlg_checkbox_get(union control *ctrl, dlgparam *dp) { return false; } +void dlg_editbox_set(union control *ctrl, dlgparam *dp, char const *text) { } +char *dlg_editbox_get(union control *ctrl, dlgparam *dp) +{ return dupstr("moo"); } +void dlg_listbox_clear(union control *ctrl, dlgparam *dp) { } +void dlg_listbox_del(union control *ctrl, dlgparam *dp, int index) { } +void dlg_listbox_add(union control *ctrl, dlgparam *dp, char const *text) { } +void dlg_listbox_addwithid(union control *ctrl, dlgparam *dp, + char const *text, int id) { } +int dlg_listbox_getid(union control *ctrl, dlgparam *dp, int index) +{ return 0; } +int dlg_listbox_index(union control *ctrl, dlgparam *dp) { return -1; } +bool dlg_listbox_issel(union control *ctrl, dlgparam *dp, int index) +{ return false; } +void dlg_listbox_select(union control *ctrl, dlgparam *dp, int index) { } +void dlg_text_set(union control *ctrl, dlgparam *dp, char const *text) { } +void dlg_filesel_set(union control *ctrl, dlgparam *dp, Filename *fn) { } +Filename *dlg_filesel_get(union control *ctrl, dlgparam *dp) { return NULL; } +void dlg_fontsel_set(union control *ctrl, dlgparam *dp, FontSpec *fn) { } +FontSpec *dlg_fontsel_get(union control *ctrl, dlgparam *dp) { return NULL; } +void dlg_update_start(union control *ctrl, dlgparam *dp) { } +void dlg_update_done(union control *ctrl, dlgparam *dp) { } +void dlg_set_focus(union control *ctrl, dlgparam *dp) { } +void dlg_label_change(union control *ctrl, dlgparam *dp, char const *text) { } +union control *dlg_last_focused(union control *ctrl, dlgparam *dp) +{ return NULL; } +void dlg_beep(dlgparam *dp) { } +void dlg_error_msg(dlgparam *dp, const char *msg) { } +void dlg_end(dlgparam *dp, int value) { } +void dlg_coloursel_start(union control *ctrl, dlgparam *dp, + int r, int g, int b) { } +bool dlg_coloursel_results(union control *ctrl, dlgparam *dp, + int *r, int *g, int *b) { return false; } +void dlg_refresh(union control *ctrl, dlgparam *dp) { } +bool dlg_is_visible(union control *ctrl, dlgparam *dp) { return false; } + +const char *const appname = "FuZZterm"; +const int ngsslibs = 0; +const char *const gsslibnames[0] = { }; +const struct keyvalwhere gsslibkeywords[0] = { }; + +/* + * Default settings that are specific to Unix plink. + */ +char *platform_default_s(const char *name) +{ + if (!strcmp(name, "TermType")) + return dupstr(getenv("TERM")); + if (!strcmp(name, "SerialLine")) + return dupstr("/dev/ttyS0"); + return NULL; +} + +bool platform_default_b(const char *name, bool def) +{ + return def; +} + +int platform_default_i(const char *name, int def) +{ + return def; +} + +FontSpec *platform_default_fontspec(const char *name) +{ + return fontspec_new(""); +} + +Filename *platform_default_filename(const char *name) +{ + if (!strcmp(name, "LogFileName")) + return filename_from_str("putty.log"); + else + return filename_from_str(""); +} + +char *x_get_default(const char *key) +{ + return NULL; /* this is a stub */ +} diff --git a/0.73_My_PuTTY/import.c b/0.74_My_PuTTY/import.c similarity index 83% rename from 0.73_My_PuTTY/import.c rename to 0.74_My_PuTTY/import.c index 61f1437..2d63334 100644 --- a/0.73_My_PuTTY/import.c +++ b/0.74_My_PuTTY/import.c @@ -1,2317 +1,2319 @@ -/* - * Code for PuTTY to import and export private key files in other - * SSH clients' formats. - */ - -#include -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "mpint.h" -#include "misc.h" - -static bool openssh_pem_encrypted(const Filename *file); -static bool openssh_new_encrypted(const Filename *file); -static ssh2_userkey *openssh_pem_read( - const Filename *file, const char *passphrase, const char **errmsg_p); -static ssh2_userkey *openssh_new_read( - const Filename *file, const char *passphrase, const char **errmsg_p); -static bool openssh_auto_write( - const Filename *file, ssh2_userkey *key, const char *passphrase); -static bool openssh_pem_write( - const Filename *file, ssh2_userkey *key, const char *passphrase); -static bool openssh_new_write( - const Filename *file, ssh2_userkey *key, const char *passphrase); - -static bool sshcom_encrypted(const Filename *file, char **comment); -static ssh2_userkey *sshcom_read( - const Filename *file, const char *passphrase, const char **errmsg_p); -static bool sshcom_write( - const Filename *file, ssh2_userkey *key, const char *passphrase); - -/* - * Given a key type, determine whether we know how to import it. - */ -bool import_possible(int type) -{ - if (type == SSH_KEYTYPE_OPENSSH_PEM) - return true; - if (type == SSH_KEYTYPE_OPENSSH_NEW) - return true; - if (type == SSH_KEYTYPE_SSHCOM) - return true; - return false; -} - -/* - * Given a key type, determine what native key type - * (SSH_KEYTYPE_SSH1 or SSH_KEYTYPE_SSH2) it will come out as once - * we've imported it. - */ -int import_target_type(int type) -{ - /* - * There are no known foreign SSH-1 key formats. - */ - return SSH_KEYTYPE_SSH2; -} - -/* - * Determine whether a foreign key is encrypted. - */ -bool import_encrypted(const Filename *filename, int type, char **comment) -{ - if (type == SSH_KEYTYPE_OPENSSH_PEM) { - /* OpenSSH PEM format doesn't contain a key comment at all */ - *comment = dupstr(filename_to_str(filename)); - return openssh_pem_encrypted(filename); - } else if (type == SSH_KEYTYPE_OPENSSH_NEW) { - /* OpenSSH new format does, but it's inside the encrypted - * section for some reason */ - *comment = dupstr(filename_to_str(filename)); - return openssh_new_encrypted(filename); - } else if (type == SSH_KEYTYPE_SSHCOM) { - return sshcom_encrypted(filename, comment); - } - return false; -} - -/* - * Import an SSH-1 key. - */ -int import_ssh1(const Filename *filename, int type, - RSAKey *key, char *passphrase, const char **errmsg_p) -{ - return 0; -} - -/* - * Import an SSH-2 key. - */ -ssh2_userkey *import_ssh2(const Filename *filename, int type, - char *passphrase, const char **errmsg_p) -{ - if (type == SSH_KEYTYPE_OPENSSH_PEM) - return openssh_pem_read(filename, passphrase, errmsg_p); - else if (type == SSH_KEYTYPE_OPENSSH_NEW) - return openssh_new_read(filename, passphrase, errmsg_p); - if (type == SSH_KEYTYPE_SSHCOM) - return sshcom_read(filename, passphrase, errmsg_p); - return NULL; -} - -/* - * Export an SSH-1 key. - */ -bool export_ssh1(const Filename *filename, int type, RSAKey *key, - char *passphrase) -{ - return false; -} - -/* - * Export an SSH-2 key. - */ -bool export_ssh2(const Filename *filename, int type, - ssh2_userkey *key, char *passphrase) -{ - if (type == SSH_KEYTYPE_OPENSSH_AUTO) - return openssh_auto_write(filename, key, passphrase); - if (type == SSH_KEYTYPE_OPENSSH_NEW) - return openssh_new_write(filename, key, passphrase); - if (type == SSH_KEYTYPE_SSHCOM) - return sshcom_write(filename, key, passphrase); - return false; -} - -/* - * Strip trailing CRs and LFs at the end of a line of text. - */ -void strip_crlf(char *str) -{ - char *p = str + strlen(str); - - while (p > str && (p[-1] == '\r' || p[-1] == '\n')) - *--p = '\0'; -} - -/* ---------------------------------------------------------------------- - * Helper routines. (The base64 ones are defined in sshpubk.c.) - */ - -#define isbase64(c) ( ((c) >= 'A' && (c) <= 'Z') || \ - ((c) >= 'a' && (c) <= 'z') || \ - ((c) >= '0' && (c) <= '9') || \ - (c) == '+' || (c) == '/' || (c) == '=' \ - ) - -/* - * Read an ASN.1/BER identifier and length pair. - * - * Flags are a combination of the #defines listed below. - * - * Returns -1 if unsuccessful; otherwise returns the number of - * bytes used out of the source data. - */ - -/* ASN.1 tag classes. */ -#define ASN1_CLASS_UNIVERSAL (0 << 6) -#define ASN1_CLASS_APPLICATION (1 << 6) -#define ASN1_CLASS_CONTEXT_SPECIFIC (2 << 6) -#define ASN1_CLASS_PRIVATE (3 << 6) -#define ASN1_CLASS_MASK (3 << 6) - -/* Primitive versus constructed bit. */ -#define ASN1_CONSTRUCTED (1 << 5) - -/* - * Write an ASN.1/BER identifier and length pair. Returns the - * number of bytes consumed. Assumes dest contains enough space. - * Will avoid writing anything if dest is NULL, but still return - * amount of space required. - */ -static void BinarySink_put_ber_id_len(BinarySink *bs, - int id, int length, int flags) -{ - if (id <= 30) { - /* - * Identifier is one byte. - */ - put_byte(bs, id | flags); - } else { - int n; - /* - * Identifier is multiple bytes: the first byte is 11111 - * plus the flags, and subsequent bytes encode the value of - * the identifier, 7 bits at a time, with the top bit of - * each byte 1 except the last one which is 0. - */ - put_byte(bs, 0x1F | flags); - for (n = 1; (id >> (7*n)) > 0; n++) - continue; /* count the bytes */ - while (n--) - put_byte(bs, (n ? 0x80 : 0) | ((id >> (7*n)) & 0x7F)); - } - - if (length < 128) { - /* - * Length is one byte. - */ - put_byte(bs, length); - } else { - int n; - /* - * Length is multiple bytes. The first is 0x80 plus the - * number of subsequent bytes, and the subsequent bytes - * encode the actual length. - */ - for (n = 1; (length >> (8*n)) > 0; n++) - continue; /* count the bytes */ - put_byte(bs, 0x80 | n); - while (n--) - put_byte(bs, (length >> (8*n)) & 0xFF); - } -} - -#define put_ber_id_len(bs, id, len, flags) \ - BinarySink_put_ber_id_len(BinarySink_UPCAST(bs), id, len, flags) - -typedef struct ber_item { - int id; - int flags; - ptrlen data; -} ber_item; - -static ber_item BinarySource_get_ber(BinarySource *src) -{ - ber_item toret; - unsigned char leadbyte, lenbyte; - size_t length; - - leadbyte = get_byte(src); - toret.flags = (leadbyte & 0xE0); - if ((leadbyte & 0x1F) == 0x1F) { - unsigned char idbyte; - - toret.id = 0; - do { - idbyte = get_byte(src); - toret.id = (toret.id << 7) | (idbyte & 0x7F); - } while (idbyte & 0x80); - } else { - toret.id = leadbyte & 0x1F; - } - - lenbyte = get_byte(src); - if (lenbyte & 0x80) { - int nbytes = lenbyte & 0x7F; - length = 0; - while (nbytes-- > 0) - length = (length << 8) | get_byte(src); - } else { - length = lenbyte; - } - - toret.data = get_data(src, length); - return toret; -} - -#define get_ber(bs) BinarySource_get_ber(BinarySource_UPCAST(bs)) - -/* ---------------------------------------------------------------------- - * Code to read and write OpenSSH private keys, in the old-style PEM - * format. - */ - -typedef enum { - OP_DSA, OP_RSA, OP_ECDSA -} openssh_pem_keytype; -typedef enum { - OP_E_3DES, OP_E_AES -} openssh_pem_enc; - -struct openssh_pem_key { - openssh_pem_keytype keytype; - bool encrypted; - openssh_pem_enc encryption; - char iv[32]; - strbuf *keyblob; -}; - -void BinarySink_put_mp_ssh2_from_string(BinarySink *bs, ptrlen str) -{ - const unsigned char *bytes = (const unsigned char *)str.ptr; - size_t nbytes = str.len; - while (nbytes > 0 && bytes[0] == 0) { - nbytes--; - bytes++; - } - if (nbytes > 0 && bytes[0] & 0x80) { - put_uint32(bs, nbytes + 1); - put_byte(bs, 0); - } else { - put_uint32(bs, nbytes); - } - put_data(bs, bytes, nbytes); -} -#define put_mp_ssh2_from_string(bs, str) \ - BinarySink_put_mp_ssh2_from_string(BinarySink_UPCAST(bs), str) - -static struct openssh_pem_key *load_openssh_pem_key(const Filename *filename, - const char **errmsg_p) -{ - struct openssh_pem_key *ret; - FILE *fp = NULL; - char *line = NULL; - const char *errmsg; - char *p; - bool headers_done; - char base64_bit[4]; - int base64_chars = 0; - - ret = snew(struct openssh_pem_key); - ret->keyblob = strbuf_new_nm(); - - fp = f_open(filename, "r", false); - if (!fp) { - errmsg = "unable to open key file"; - goto error; - } - - if (!(line = fgetline(fp))) { - errmsg = "unexpected end of file"; - goto error; - } - strip_crlf(line); - if (!strstartswith(line, "-----BEGIN ") || - !strendswith(line, "PRIVATE KEY-----")) { - errmsg = "file does not begin with OpenSSH key header"; - goto error; - } - /* - * Parse the BEGIN line. For old-format keys, this tells us the - * type of the key; for new-format keys, all it tells us is the - * format, and we'll find out the key type once we parse the - * base64. - */ - if (!strcmp(line, "-----BEGIN RSA PRIVATE KEY-----")) { - ret->keytype = OP_RSA; - } else if (!strcmp(line, "-----BEGIN DSA PRIVATE KEY-----")) { - ret->keytype = OP_DSA; - } else if (!strcmp(line, "-----BEGIN EC PRIVATE KEY-----")) { - ret->keytype = OP_ECDSA; - } else if (!strcmp(line, "-----BEGIN OPENSSH PRIVATE KEY-----")) { - errmsg = "this is a new-style OpenSSH key"; - goto error; - } else { - errmsg = "unrecognised key type"; - goto error; - } - smemclr(line, strlen(line)); - sfree(line); - line = NULL; - - ret->encrypted = false; - memset(ret->iv, 0, sizeof(ret->iv)); - - headers_done = false; - while (1) { - if (!(line = fgetline(fp))) { - errmsg = "unexpected end of file"; - goto error; - } - strip_crlf(line); - if (strstartswith(line, "-----END ") && - strendswith(line, "PRIVATE KEY-----")) { - sfree(line); - line = NULL; - break; /* done */ - } - if ((p = strchr(line, ':')) != NULL) { - if (headers_done) { - errmsg = "header found in body of key data"; - goto error; - } - *p++ = '\0'; - while (*p && isspace((unsigned char)*p)) p++; - if (!strcmp(line, "Proc-Type")) { - if (p[0] != '4' || p[1] != ',') { - errmsg = "Proc-Type is not 4 (only 4 is supported)"; - goto error; - } - p += 2; - if (!strcmp(p, "ENCRYPTED")) - ret->encrypted = true; - } else if (!strcmp(line, "DEK-Info")) { - int i, ivlen; - - if (!strncmp(p, "DES-EDE3-CBC,", 13)) { - ret->encryption = OP_E_3DES; - ivlen = 8; - } else if (!strncmp(p, "AES-128-CBC,", 12)) { - ret->encryption = OP_E_AES; - ivlen = 16; - } else { - errmsg = "unsupported cipher"; - goto error; - } - p = strchr(p, ',') + 1;/* always non-NULL, by above checks */ - for (i = 0; i < ivlen; i++) { - unsigned j; - if (1 != sscanf(p, "%2x", &j)) { - errmsg = "expected more iv data in DEK-Info"; - goto error; - } - ret->iv[i] = j; - p += 2; - } - if (*p) { - errmsg = "more iv data than expected in DEK-Info"; - goto error; - } - } - } else { - headers_done = true; - - p = line; - while (isbase64(*p)) { - base64_bit[base64_chars++] = *p; - if (base64_chars == 4) { - unsigned char out[3]; - int len; - - base64_chars = 0; - - len = base64_decode_atom(base64_bit, out); - - if (len <= 0) { - errmsg = "invalid base64 encoding"; - goto error; - } - - put_data(ret->keyblob, out, len); - - smemclr(out, sizeof(out)); - } - - p++; - } - } - smemclr(line, strlen(line)); - sfree(line); - line = NULL; - } - - fclose(fp); - fp = NULL; - - if (!ret->keyblob || ret->keyblob->len == 0) { - errmsg = "key body not present"; - goto error; - } - - if (ret->encrypted && ret->keyblob->len % 8 != 0) { - errmsg = "encrypted key blob is not a multiple of " - "cipher block size"; - goto error; - } - - smemclr(base64_bit, sizeof(base64_bit)); - if (errmsg_p) *errmsg_p = NULL; - return ret; - - error: - if (line) { - smemclr(line, strlen(line)); - sfree(line); - line = NULL; - } - smemclr(base64_bit, sizeof(base64_bit)); - if (ret) { - if (ret->keyblob) - strbuf_free(ret->keyblob); - smemclr(ret, sizeof(*ret)); - sfree(ret); - } - if (errmsg_p) *errmsg_p = errmsg; - if (fp) fclose(fp); - return NULL; -} - -static bool openssh_pem_encrypted(const Filename *filename) -{ - struct openssh_pem_key *key = load_openssh_pem_key(filename, NULL); - bool ret; - - if (!key) - return false; - ret = key->encrypted; - strbuf_free(key->keyblob); - smemclr(key, sizeof(*key)); - sfree(key); - return ret; -} - -static void openssh_pem_derivekey( - ptrlen passphrase, const void *iv, uint8_t *keybuf) -{ - /* - * Derive the encryption key for a PEM key file from the - * passphrase and iv/salt: - * - * - let block A equal MD5(passphrase || iv) - * - let block B equal MD5(A || passphrase || iv) - * - block C would be MD5(B || passphrase || iv) and so on - * - encryption key is the first N bytes of A || B - * - * (Note that only 8 bytes of the iv are used for key - * derivation, even when the key is encrypted with AES and - * hence there are 16 bytes available.) - */ - ssh_hash *h; - - h = ssh_hash_new(&ssh_md5); - put_datapl(h, passphrase); - put_data(h, iv, 8); - ssh_hash_final(h, keybuf); - - h = ssh_hash_new(&ssh_md5); - put_data(h, keybuf, 16); - put_datapl(h, passphrase); - put_data(h, iv, 8); - ssh_hash_final(h, keybuf + 16); -} - -static ssh2_userkey *openssh_pem_read( - const Filename *filename, const char *passphrase, const char **errmsg_p) -{ - struct openssh_pem_key *key = load_openssh_pem_key(filename, errmsg_p); - ssh2_userkey *retkey; - const ssh_keyalg *alg; - BinarySource src[1]; - int i, num_integers; - ssh2_userkey *retval = NULL; - const char *errmsg; - strbuf *blob = strbuf_new_nm(); - int privptr = 0, publen; - - if (!key) - return NULL; - - if (key->encrypted) { - unsigned char keybuf[32]; - openssh_pem_derivekey(ptrlen_from_asciz(passphrase), key->iv, keybuf); - - /* - * Decrypt the key blob. - */ - if (key->encryption == OP_E_3DES) - des3_decrypt_pubkey_ossh(keybuf, key->iv, - key->keyblob->u, key->keyblob->len); - else { - ssh_cipher *cipher = ssh_cipher_new(&ssh_aes128_cbc); - ssh_cipher_setkey(cipher, keybuf); - ssh_cipher_setiv(cipher, key->iv); - ssh_cipher_decrypt(cipher, key->keyblob->u, key->keyblob->len); - ssh_cipher_free(cipher); - } - - smemclr(keybuf, sizeof(keybuf)); - } - - /* - * Now we have a decrypted key blob, which contains an ASN.1 - * encoded private key. We must now untangle the ASN.1. - * - * We expect the whole key blob to be formatted as a SEQUENCE - * (0x30 followed by a length code indicating that the rest of - * the blob is part of the sequence). Within that SEQUENCE we - * expect to see a bunch of INTEGERs. What those integers mean - * depends on the key type: - * - * - For RSA, we expect the integers to be 0, n, e, d, p, q, - * dmp1, dmq1, iqmp in that order. (The last three are d mod - * (p-1), d mod (q-1), inverse of q mod p respectively.) - * - * - For DSA, we expect them to be 0, p, q, g, y, x in that - * order. - * - * - In ECDSA the format is totally different: we see the - * SEQUENCE, but beneath is an INTEGER 1, OCTET STRING priv - * EXPLICIT [0] OID curve, EXPLICIT [1] BIT STRING pubPoint - */ - - BinarySource_BARE_INIT(src, key->keyblob->u, key->keyblob->len); - - { - /* Expect the SEQUENCE header. Take its absence as a failure to - * decrypt, if the key was encrypted. */ - ber_item seq = get_ber(src); - if (get_err(src) || seq.id != 16) { - errmsg = "ASN.1 decoding failure"; - retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL; - goto error; - } - - /* Reinitialise our BinarySource to parse just the inside of that - * SEQUENCE. */ - BinarySource_BARE_INIT_PL(src, seq.data); - } - - /* Expect a load of INTEGERs. */ - if (key->keytype == OP_RSA) - num_integers = 9; - else if (key->keytype == OP_DSA) - num_integers = 6; - else - num_integers = 0; /* placate compiler warnings */ - - - if (key->keytype == OP_ECDSA) { - /* And now for something completely different */ - ber_item integer, privkey, sub0, sub1, oid, pubkey; - const ssh_keyalg *alg; - const struct ec_curve *curve; - - /* Parse the outer layer of things inside the containing SEQUENCE */ - integer = get_ber(src); - privkey = get_ber(src); - sub0 = get_ber(src); - sub1 = get_ber(src); - - /* Now look inside sub0 for the curve OID */ - BinarySource_BARE_INIT_PL(src, sub0.data); - oid = get_ber(src); - - /* And inside sub1 for the public-key BIT STRING */ - BinarySource_BARE_INIT_PL(src, sub1.data); - pubkey = get_ber(src); - - if (get_err(src) || - integer.id != 2 || - integer.data.len != 1 || - ((const unsigned char *)integer.data.ptr)[0] != 1 || - privkey.id != 4 || - sub0.id != 0 || - sub1.id != 1 || - oid.id != 6 || - pubkey.id != 3) { - - errmsg = "ASN.1 decoding failure"; - retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL; - goto error; - } - - alg = ec_alg_by_oid(oid.data.len, oid.data.ptr, &curve); - if (!alg) { - errmsg = "Unsupported ECDSA curve."; - retval = NULL; - goto error; - } - if (pubkey.data.len != ((((curve->fieldBits + 7) / 8) * 2) + 2)) { - errmsg = "ASN.1 decoding failure"; - retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL; - goto error; - } - /* Skip 0x00 before point */ - pubkey.data.ptr = (const char *)pubkey.data.ptr + 1; - pubkey.data.len -= 1; - - /* Construct the key */ - retkey = snew(ssh2_userkey); - - put_stringz(blob, alg->ssh_id); - put_stringz(blob, curve->name); - put_stringpl(blob, pubkey.data); - publen = blob->len; - put_mp_ssh2_from_string(blob, privkey.data); - - retkey->key = ssh_key_new_priv( - alg, make_ptrlen(blob->u, publen), - make_ptrlen(blob->u + publen, blob->len - publen)); - - if (!retkey->key) { - sfree(retkey); - errmsg = "unable to create key data structure"; - goto error; - } - - } else if (key->keytype == OP_RSA || key->keytype == OP_DSA) { - - put_stringz(blob, key->keytype == OP_DSA ? "ssh-dss" : "ssh-rsa"); - - ptrlen rsa_modulus = PTRLEN_LITERAL(""); - - for (i = 0; i < num_integers; i++) { - ber_item integer = get_ber(src); - - if (get_err(src) || integer.id != 2) { - errmsg = "ASN.1 decoding failure"; - retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL; - goto error; - } - - if (i == 0) { - /* - * The first integer should be zero always (I think - * this is some sort of version indication). - */ - if (integer.data.len != 1 || - ((const unsigned char *)integer.data.ptr)[0] != 0) { - errmsg = "version number mismatch"; - goto error; - } - } else if (key->keytype == OP_RSA) { - /* - * Integers 1 and 2 go into the public blob but in the - * opposite order; integers 3, 4, 5 and 8 go into the - * private blob. The other two (6 and 7) are ignored. - */ - if (i == 1) { - /* Save the details for after we deal with number 2. */ - rsa_modulus = integer.data; - } else if (i != 6 && i != 7) { - put_mp_ssh2_from_string(blob, integer.data); - if (i == 2) { - put_mp_ssh2_from_string(blob, rsa_modulus); - privptr = blob->len; - } - } - } else if (key->keytype == OP_DSA) { - /* - * Integers 1-4 go into the public blob; integer 5 goes - * into the private blob. - */ - put_mp_ssh2_from_string(blob, integer.data); - if (i == 4) - privptr = blob->len; - } - } - - /* - * Now put together the actual key. Simplest way to do this is - * to assemble our own key blobs and feed them to the createkey - * functions; this is a bit faffy but it does mean we get all - * the sanity checks for free. - */ - assert(privptr > 0); /* should have bombed by now if not */ - retkey = snew(ssh2_userkey); - alg = (key->keytype == OP_RSA ? &ssh_rsa : &ssh_dss); - retkey->key = ssh_key_new_priv( - alg, make_ptrlen(blob->u, privptr), - make_ptrlen(blob->u+privptr, blob->len-privptr)); - - if (!retkey->key) { - sfree(retkey); - errmsg = "unable to create key data structure"; - goto error; - } - - } else { - unreachable("Bad key type from load_openssh_pem_key"); - errmsg = "Bad key type from load_openssh_pem_key"; - goto error; - } - - /* - * The old key format doesn't include a comment in the private - * key file. - */ - retkey->comment = dupstr("imported-openssh-key"); - - errmsg = NULL; /* no error */ - retval = retkey; - - error: - strbuf_free(blob); - strbuf_free(key->keyblob); - smemclr(key, sizeof(*key)); - sfree(key); - if (errmsg_p) *errmsg_p = errmsg; - return retval; -} - -static bool openssh_pem_write( - const Filename *filename, ssh2_userkey *key, const char *passphrase) -{ - strbuf *pubblob, *privblob, *outblob; - unsigned char *spareblob; - int sparelen = 0; - ptrlen numbers[9]; - int nnumbers, i; - const char *header, *footer; - char zero[1]; - unsigned char iv[8]; - bool ret = false; - FILE *fp; - BinarySource src[1]; - - /* - * Fetch the key blobs. - */ - pubblob = strbuf_new(); - ssh_key_public_blob(key->key, BinarySink_UPCAST(pubblob)); - privblob = strbuf_new_nm(); - ssh_key_private_blob(key->key, BinarySink_UPCAST(privblob)); - spareblob = NULL; - - outblob = strbuf_new_nm(); - - /* - * Encode the OpenSSH key blob, and also decide on the header - * line. - */ - if (ssh_key_alg(key->key) == &ssh_rsa || - ssh_key_alg(key->key) == &ssh_dss) { - strbuf *seq; - - /* - * The RSA and DSS handlers share some code because the two - * key types have very similar ASN.1 representations, as a - * plain SEQUENCE of big integers. So we set up a list of - * bignums per key type and then construct the actual blob in - * common code after that. - */ - if (ssh_key_alg(key->key) == &ssh_rsa) { - ptrlen n, e, d, p, q, iqmp, dmp1, dmq1; - mp_int *bd, *bp, *bq, *bdmp1, *bdmq1; - - /* - * These blobs were generated from inside PuTTY, so we needn't - * treat them as untrusted. - */ - BinarySource_BARE_INIT(src, pubblob->u, pubblob->len); - get_string(src); /* skip algorithm name */ - e = get_string(src); - n = get_string(src); - BinarySource_BARE_INIT(src, privblob->u, privblob->len); - d = get_string(src); - p = get_string(src); - q = get_string(src); - iqmp = get_string(src); - - assert(!get_err(src)); /* can't go wrong */ - - /* We also need d mod (p-1) and d mod (q-1). */ - bd = mp_from_bytes_be(d); - bp = mp_from_bytes_be(p); - bq = mp_from_bytes_be(q); - mp_sub_integer_into(bp, bp, 1); - mp_sub_integer_into(bq, bq, 1); - bdmp1 = mp_mod(bd, bp); - bdmq1 = mp_mod(bd, bq); - mp_free(bd); - mp_free(bp); - mp_free(bq); - - dmp1.len = (mp_get_nbits(bdmp1)+8)/8; - dmq1.len = (mp_get_nbits(bdmq1)+8)/8; - sparelen = dmp1.len + dmq1.len; - spareblob = snewn(sparelen, unsigned char); - dmp1.ptr = spareblob; - dmq1.ptr = spareblob + dmp1.len; - for (i = 0; i < dmp1.len; i++) - spareblob[i] = mp_get_byte(bdmp1, dmp1.len-1 - i); - for (i = 0; i < dmq1.len; i++) - spareblob[i+dmp1.len] = mp_get_byte(bdmq1, dmq1.len-1 - i); - mp_free(bdmp1); - mp_free(bdmq1); - - numbers[0] = make_ptrlen(zero, 1); zero[0] = '\0'; - numbers[1] = n; - numbers[2] = e; - numbers[3] = d; - numbers[4] = p; - numbers[5] = q; - numbers[6] = dmp1; - numbers[7] = dmq1; - numbers[8] = iqmp; - - nnumbers = 9; - header = "-----BEGIN RSA PRIVATE KEY-----\n"; - footer = "-----END RSA PRIVATE KEY-----\n"; - } else { /* ssh-dss */ - ptrlen p, q, g, y, x; - - /* - * These blobs were generated from inside PuTTY, so we needn't - * treat them as untrusted. - */ - BinarySource_BARE_INIT(src, pubblob->u, pubblob->len); - get_string(src); /* skip algorithm name */ - p = get_string(src); - q = get_string(src); - g = get_string(src); - y = get_string(src); - BinarySource_BARE_INIT(src, privblob->u, privblob->len); - x = get_string(src); - - assert(!get_err(src)); /* can't go wrong */ - - numbers[0].ptr = zero; numbers[0].len = 1; zero[0] = '\0'; - numbers[1] = p; - numbers[2] = q; - numbers[3] = g; - numbers[4] = y; - numbers[5] = x; - - nnumbers = 6; - header = "-----BEGIN DSA PRIVATE KEY-----\n"; - footer = "-----END DSA PRIVATE KEY-----\n"; - } - - seq = strbuf_new_nm(); - for (i = 0; i < nnumbers; i++) { - put_ber_id_len(seq, 2, numbers[i].len, 0); - put_datapl(seq, numbers[i]); - } - put_ber_id_len(outblob, 16, seq->len, ASN1_CONSTRUCTED); - put_data(outblob, seq->s, seq->len); - strbuf_free(seq); - } else if (ssh_key_alg(key->key) == &ssh_ecdsa_nistp256 || - ssh_key_alg(key->key) == &ssh_ecdsa_nistp384 || - ssh_key_alg(key->key) == &ssh_ecdsa_nistp521) { - const unsigned char *oid; - struct ecdsa_key *ec = container_of(key->key, struct ecdsa_key, sshk); - int oidlen; - int pointlen; - strbuf *seq, *sub; - - /* - * Structure of asn1: - * SEQUENCE - * INTEGER 1 - * OCTET STRING (private key) - * [0] - * OID (curve) - * [1] - * BIT STRING (0x00 public key point) - */ - oid = ec_alg_oid(ssh_key_alg(key->key), &oidlen); - pointlen = (ec->curve->fieldBits + 7) / 8 * 2; - - seq = strbuf_new_nm(); - - /* INTEGER 1 */ - put_ber_id_len(seq, 2, 1, 0); - put_byte(seq, 1); - - /* OCTET STRING private key */ - put_ber_id_len(seq, 4, privblob->len - 4, 0); - put_data(seq, privblob->s + 4, privblob->len - 4); - - /* Subsidiary OID */ - sub = strbuf_new(); - put_ber_id_len(sub, 6, oidlen, 0); - put_data(sub, oid, oidlen); - - /* Append the OID to the sequence */ - put_ber_id_len(seq, 0, sub->len, - ASN1_CLASS_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED); - put_data(seq, sub->s, sub->len); - strbuf_free(sub); - - /* Subsidiary BIT STRING */ - sub = strbuf_new(); - put_ber_id_len(sub, 3, 2 + pointlen, 0); - put_byte(sub, 0); - put_data(sub, pubblob->s+39, 1 + pointlen); - - /* Append the BIT STRING to the sequence */ - put_ber_id_len(seq, 1, sub->len, - ASN1_CLASS_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED); - put_data(seq, sub->s, sub->len); - strbuf_free(sub); - - /* Write the full sequence with header to the output blob. */ - put_ber_id_len(outblob, 16, seq->len, ASN1_CONSTRUCTED); - put_data(outblob, seq->s, seq->len); - strbuf_free(seq); - - header = "-----BEGIN EC PRIVATE KEY-----\n"; - footer = "-----END EC PRIVATE KEY-----\n"; - } else { - unreachable("bad key alg in openssh_pem_write"); - } - - /* - * Encrypt the key. - * - * For the moment, we still encrypt our OpenSSH keys using - * old-style 3DES. - */ - if (passphrase) { - unsigned char keybuf[32]; - int origlen, outlen, pad; - - /* - * Padding on OpenSSH keys is deterministic. The number of - * padding bytes is always more than zero, and always at most - * the cipher block length. The value of each padding byte is - * equal to the number of padding bytes. So a plaintext that's - * an exact multiple of the block size will be padded with 08 - * 08 08 08 08 08 08 08 (assuming a 64-bit block cipher); a - * plaintext one byte less than a multiple of the block size - * will be padded with just 01. - * - * This enables the OpenSSL key decryption function to strip - * off the padding algorithmically and return the unpadded - * plaintext to the next layer: it looks at the final byte, and - * then expects to find that many bytes at the end of the data - * with the same value. Those are all removed and the rest is - * returned. - */ - origlen = outblob->len; - outlen = (origlen + 8) &~ 7; - pad = outlen - origlen; - put_padding(outblob, pad, pad); - - /* - * Invent an iv, and derive the encryption key. - */ - random_read(iv, 8); - - openssh_pem_derivekey(ptrlen_from_asciz(passphrase), iv, keybuf); - - /* - * Now encrypt the key blob. - */ - des3_encrypt_pubkey_ossh(keybuf, iv, - outblob->u, outlen); - - smemclr(keybuf, sizeof(keybuf)); - } - - /* - * And save it. We'll use Unix line endings just in case it's - * subsequently transferred in binary mode. - */ - fp = f_open(filename, "wb", true); /* ensure Unix line endings */ - if (!fp) - goto error; - fputs(header, fp); - if (passphrase) { - fprintf(fp, "Proc-Type: 4,ENCRYPTED\nDEK-Info: DES-EDE3-CBC,"); - for (i = 0; i < 8; i++) - fprintf(fp, "%02X", iv[i]); - fprintf(fp, "\n\n"); - } - base64_encode(fp, outblob->u, outblob->len, 64); - fputs(footer, fp); - fclose(fp); - ret = true; - - error: - if (outblob) - strbuf_free(outblob); - if (spareblob) { - smemclr(spareblob, sparelen); - sfree(spareblob); - } - if (privblob) - strbuf_free(privblob); - if (pubblob) - strbuf_free(pubblob); - return ret; -} - -/* ---------------------------------------------------------------------- - * Code to read and write OpenSSH private keys in the new-style format. - */ - -typedef enum { - ON_E_NONE, ON_E_AES256CBC, ON_E_AES256CTR -} openssh_new_cipher; -typedef enum { - ON_K_NONE, ON_K_BCRYPT -} openssh_new_kdf; - -struct openssh_new_key { - openssh_new_cipher cipher; - openssh_new_kdf kdf; - union { - struct { - int rounds; - /* This points to a position within keyblob, not a - * separately allocated thing */ - ptrlen salt; - } bcrypt; - } kdfopts; - int nkeys, key_wanted; - /* This too points to a position within keyblob */ - ptrlen private; - - strbuf *keyblob; -}; - -static struct openssh_new_key *load_openssh_new_key(const Filename *filename, - const char **errmsg_p) -{ - struct openssh_new_key *ret; - FILE *fp = NULL; - char *line = NULL; - const char *errmsg; - char *p; - char base64_bit[4]; - int base64_chars = 0; - BinarySource src[1]; - ptrlen str; - unsigned key_index; - - ret = snew(struct openssh_new_key); - ret->keyblob = strbuf_new_nm(); - - fp = f_open(filename, "r", false); - if (!fp) { - errmsg = "unable to open key file"; - goto error; - } - - if (!(line = fgetline(fp))) { - errmsg = "unexpected end of file"; - goto error; - } - strip_crlf(line); - if (0 != strcmp(line, "-----BEGIN OPENSSH PRIVATE KEY-----")) { - errmsg = "file does not begin with OpenSSH new-style key header"; - goto error; - } - smemclr(line, strlen(line)); - sfree(line); - line = NULL; - - while (1) { - if (!(line = fgetline(fp))) { - errmsg = "unexpected end of file"; - goto error; - } - strip_crlf(line); - if (0 == strcmp(line, "-----END OPENSSH PRIVATE KEY-----")) { - sfree(line); - line = NULL; - break; /* done */ - } - - p = line; - while (isbase64(*p)) { - base64_bit[base64_chars++] = *p; - if (base64_chars == 4) { - unsigned char out[3]; - int len; - - base64_chars = 0; - - len = base64_decode_atom(base64_bit, out); - - if (len <= 0) { - errmsg = "invalid base64 encoding"; - goto error; - } - - put_data(ret->keyblob, out, len); - - smemclr(out, sizeof(out)); - } - - p++; - } - smemclr(line, strlen(line)); - sfree(line); - line = NULL; - } - - fclose(fp); - fp = NULL; - - if (ret->keyblob->len == 0) { - errmsg = "key body not present"; - goto error; - } - - BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(ret->keyblob)); - - if (strcmp(get_asciz(src), "openssh-key-v1") != 0) { - errmsg = "new-style OpenSSH magic number missing\n"; - goto error; - } - - /* Cipher name */ - str = get_string(src); - if (ptrlen_eq_string(str, "none")) { - ret->cipher = ON_E_NONE; - } else if (ptrlen_eq_string(str, "aes256-cbc")) { - ret->cipher = ON_E_AES256CBC; - } else if (ptrlen_eq_string(str, "aes256-ctr")) { - ret->cipher = ON_E_AES256CTR; - } else { - errmsg = get_err(src) ? "no cipher name found" : - "unrecognised cipher name\n"; - goto error; - } - - /* Key derivation function name */ - str = get_string(src); - if (ptrlen_eq_string(str, "none")) { - ret->kdf = ON_K_NONE; - } else if (ptrlen_eq_string(str, "bcrypt")) { - ret->kdf = ON_K_BCRYPT; - } else { - errmsg = get_err(src) ? "no kdf name found" : - "unrecognised kdf name\n"; - goto error; - } - - /* KDF extra options */ - str = get_string(src); - switch (ret->kdf) { - case ON_K_NONE: - if (str.len != 0) { - errmsg = "expected empty options string for 'none' kdf"; - goto error; - } - break; - case ON_K_BCRYPT: - { - BinarySource opts[1]; - - BinarySource_BARE_INIT_PL(opts, str); - ret->kdfopts.bcrypt.salt = get_string(opts); - ret->kdfopts.bcrypt.rounds = get_uint32(opts); - - if (get_err(opts)) { - errmsg = "failed to parse bcrypt options string"; - goto error; - } - } - break; - } - - /* - * At this point we expect a uint32 saying how many keys are - * stored in this file. OpenSSH new-style key files can - * contain more than one. Currently we don't have any user - * interface to specify which one we're trying to extract, so - * we just bomb out with an error if more than one is found in - * the file. However, I've put in all the mechanism here to - * extract the nth one for a given n, in case we later connect - * up some UI to that mechanism. Just arrange that the - * 'key_wanted' field is set to a value in the range [0, - * nkeys) by some mechanism. - */ - ret->nkeys = toint(get_uint32(src)); - if (ret->nkeys != 1) { - errmsg = get_err(src) ? "no key count found" : - "multiple keys in new-style OpenSSH key file not supported\n"; - goto error; - } - ret->key_wanted = 0; - - /* Read and ignore a string per public key. */ - for (key_index = 0; key_index < ret->nkeys; key_index++) - str = get_string(src); - - /* - * Now we expect a string containing the encrypted part of the - * key file. - */ - ret->private = get_string(src); - if (get_err(src)) { - errmsg = "no private key container string found\n"; - goto error; - } - - /* - * And now we're done, until asked to actually decrypt. - */ - - smemclr(base64_bit, sizeof(base64_bit)); - if (errmsg_p) *errmsg_p = NULL; - return ret; - - error: - if (line) { - smemclr(line, strlen(line)); - sfree(line); - line = NULL; - } - smemclr(base64_bit, sizeof(base64_bit)); - if (ret) { - strbuf_free(ret->keyblob); - smemclr(ret, sizeof(*ret)); - sfree(ret); - } - if (errmsg_p) *errmsg_p = errmsg; - if (fp) fclose(fp); - return NULL; -} - -static bool openssh_new_encrypted(const Filename *filename) -{ - struct openssh_new_key *key = load_openssh_new_key(filename, NULL); - bool ret; - - if (!key) - return false; - ret = (key->cipher != ON_E_NONE); - strbuf_free(key->keyblob); - smemclr(key, sizeof(*key)); - sfree(key); - return ret; -} - -static ssh2_userkey *openssh_new_read( - const Filename *filename, const char *passphrase, const char **errmsg_p) -{ - struct openssh_new_key *key = load_openssh_new_key(filename, errmsg_p); - ssh2_userkey *retkey = NULL; - ssh2_userkey *retval = NULL; - const char *errmsg; - unsigned checkint; - BinarySource src[1]; - int key_index; - const ssh_keyalg *alg = NULL; - - if (!key) - return NULL; - - if (key->cipher != ON_E_NONE) { - unsigned char keybuf[48]; - int keysize; - - /* - * Construct the decryption key, and decrypt the string. - */ - switch (key->cipher) { - case ON_E_NONE: - keysize = 0; - break; - case ON_E_AES256CBC: - case ON_E_AES256CTR: - keysize = 48; /* 32 byte key + 16 byte IV */ - break; - default: - unreachable("Bad cipher enumeration value"); - } - assert(keysize <= sizeof(keybuf)); - switch (key->kdf) { - case ON_K_NONE: - memset(keybuf, 0, keysize); - break; - case ON_K_BCRYPT: - openssh_bcrypt(passphrase, - key->kdfopts.bcrypt.salt.ptr, - key->kdfopts.bcrypt.salt.len, - key->kdfopts.bcrypt.rounds, - keybuf, keysize); - break; - default: - unreachable("Bad kdf enumeration value"); - } - switch (key->cipher) { - case ON_E_NONE: - break; - case ON_E_AES256CBC: - case ON_E_AES256CTR: - if (key->private.len % 16 != 0) { - errmsg = "private key container length is not a" - " multiple of AES block size\n"; - goto error; - } - { - ssh_cipher *cipher = ssh_cipher_new( - key->cipher == ON_E_AES256CBC ? - &ssh_aes256_cbc : &ssh_aes256_sdctr); - ssh_cipher_setkey(cipher, keybuf); - ssh_cipher_setiv(cipher, keybuf + 32); - /* Decrypt the private section in place, casting away - * the const from key->private being a ptrlen */ - ssh_cipher_decrypt(cipher, (char *)key->private.ptr, - key->private.len); - ssh_cipher_free(cipher); - } - break; - default: - unreachable("Bad cipher enumeration value"); - } - } - - /* - * Now parse the entire encrypted section, and extract the key - * identified by key_wanted. - */ - BinarySource_BARE_INIT_PL(src, key->private); - - checkint = get_uint32(src); - if (get_uint32(src) != checkint || get_err(src)) { - errmsg = "decryption check failed"; - goto error; - } - - retkey = snew(ssh2_userkey); - retkey->key = NULL; - retkey->comment = NULL; - - for (key_index = 0; key_index < key->nkeys; key_index++) { - ptrlen comment; - - /* - * Identify the key type. - */ - alg = find_pubkey_alg_len(get_string(src)); - if (!alg) { - errmsg = "private key type not recognised\n"; - goto error; - } - - /* - * Read the key. We have to do this even if it's not the one - * we want, because it's the only way to find out how much - * data to skip past to get to the next key in the file. - */ - retkey->key = ssh_key_new_priv_openssh(alg, src); - if (get_err(src)) { - errmsg = "unable to read entire private key"; - goto error; - } - if (!retkey->key) { - errmsg = "unable to create key data structure"; - goto error; - } - if (key_index != key->key_wanted) { - /* - * If this isn't the key we're looking for, throw it away. - */ - ssh_key_free(retkey->key); - retkey->key = NULL; - } - - /* - * Read the key comment. - */ - comment = get_string(src); - if (get_err(src)) { - errmsg = "unable to read key comment"; - goto error; - } - if (key_index == key->key_wanted) { - assert(retkey); - retkey->comment = mkstr(comment); - } - } - - if (!retkey->key) { - errmsg = "key index out of range"; - goto error; - } - - /* - * Now we expect nothing left but padding. - */ - { - unsigned char expected_pad_byte = 1; - while (get_avail(src) > 0) - if (get_byte(src) != expected_pad_byte++) { - errmsg = "padding at end of private string did not match"; - goto error; - } - } - - errmsg = NULL; /* no error */ - retval = retkey; - retkey = NULL; /* prevent the free */ - - error: - if (retkey) { - sfree(retkey->comment); - if (retkey->key) - ssh_key_free(retkey->key); - sfree(retkey); - } - strbuf_free(key->keyblob); - smemclr(key, sizeof(*key)); - sfree(key); - if (errmsg_p) *errmsg_p = errmsg; - return retval; -} - -static bool openssh_new_write( - const Filename *filename, ssh2_userkey *key, const char *passphrase) -{ - strbuf *pubblob, *privblob, *cblob; - int padvalue; - unsigned checkint; - bool ret = false; - unsigned char bcrypt_salt[16]; - const int bcrypt_rounds = 16; - FILE *fp; - - /* - * Fetch the key blobs and find out the lengths of things. - */ - pubblob = strbuf_new(); - ssh_key_public_blob(key->key, BinarySink_UPCAST(pubblob)); - privblob = strbuf_new_nm(); - ssh_key_openssh_blob(key->key, BinarySink_UPCAST(privblob)); - - /* - * Construct the cleartext version of the blob. - */ - cblob = strbuf_new_nm(); - - /* Magic number. */ - put_asciz(cblob, "openssh-key-v1"); - - /* Cipher and kdf names, and kdf options. */ - if (!passphrase) { - memset(bcrypt_salt, 0, sizeof(bcrypt_salt)); /* prevent warnings */ - put_stringz(cblob, "none"); - put_stringz(cblob, "none"); - put_stringz(cblob, ""); - } else { - strbuf *substr; - - random_read(bcrypt_salt, sizeof(bcrypt_salt)); - put_stringz(cblob, "aes256-ctr"); - put_stringz(cblob, "bcrypt"); - substr = strbuf_new_nm(); - put_string(substr, bcrypt_salt, sizeof(bcrypt_salt)); - put_uint32(substr, bcrypt_rounds); - put_stringsb(cblob, substr); - } - - /* Number of keys. */ - put_uint32(cblob, 1); - - /* Public blob. */ - put_string(cblob, pubblob->s, pubblob->len); - - /* Private section. */ - { - strbuf *cpblob = strbuf_new_nm(); - - /* checkint. */ - uint8_t checkint_buf[4]; - random_read(checkint_buf, 4); - checkint = GET_32BIT_MSB_FIRST(checkint_buf); - put_uint32(cpblob, checkint); - put_uint32(cpblob, checkint); - - /* Private key. The main private blob goes inline, with no string - * wrapper. */ - put_stringz(cpblob, ssh_key_ssh_id(key->key)); - put_data(cpblob, privblob->s, privblob->len); - - /* Comment. */ - put_stringz(cpblob, key->comment); - - /* Pad out the encrypted section. */ - padvalue = 1; - do { - put_byte(cpblob, padvalue++); - } while (cpblob->len & 15); - - if (passphrase) { - /* - * Encrypt the private section. We need 48 bytes of key - * material: 32 bytes AES key + 16 bytes iv. - */ - unsigned char keybuf[48]; - ssh_cipher *cipher; - - openssh_bcrypt(passphrase, - bcrypt_salt, sizeof(bcrypt_salt), bcrypt_rounds, - keybuf, sizeof(keybuf)); - - cipher = ssh_cipher_new(&ssh_aes256_sdctr); - ssh_cipher_setkey(cipher, keybuf); - ssh_cipher_setiv(cipher, keybuf + 32); - ssh_cipher_encrypt(cipher, cpblob->u, cpblob->len); - ssh_cipher_free(cipher); - - smemclr(keybuf, sizeof(keybuf)); - } - - put_stringsb(cblob, cpblob); - } - - /* - * And save it. We'll use Unix line endings just in case it's - * subsequently transferred in binary mode. - */ - fp = f_open(filename, "wb", true); /* ensure Unix line endings */ - if (!fp) - goto error; - fputs("-----BEGIN OPENSSH PRIVATE KEY-----\n", fp); - base64_encode(fp, cblob->u, cblob->len, 64); - fputs("-----END OPENSSH PRIVATE KEY-----\n", fp); - fclose(fp); - ret = true; - - error: - if (cblob) - strbuf_free(cblob); - if (privblob) - strbuf_free(privblob); - if (pubblob) - strbuf_free(pubblob); - return ret; -} - -/* ---------------------------------------------------------------------- - * The switch function openssh_auto_write(), which chooses one of the - * concrete OpenSSH output formats based on the key type. - */ -static bool openssh_auto_write( - const Filename *filename, ssh2_userkey *key, const char *passphrase) -{ - /* - * The old OpenSSH format supports a fixed list of key types. We - * assume that anything not in that fixed list is newer, and hence - * will use the new format. - */ - if (ssh_key_alg(key->key) == &ssh_dss || - ssh_key_alg(key->key) == &ssh_rsa || - ssh_key_alg(key->key) == &ssh_ecdsa_nistp256 || - ssh_key_alg(key->key) == &ssh_ecdsa_nistp384 || - ssh_key_alg(key->key) == &ssh_ecdsa_nistp521) - return openssh_pem_write(filename, key, passphrase); - else - return openssh_new_write(filename, key, passphrase); -} - -/* ---------------------------------------------------------------------- - * Code to read ssh.com private keys. - */ - -/* - * The format of the base64 blob is largely SSH-2-packet-formatted, - * except that mpints are a bit different: they're more like the - * old SSH-1 mpint. You have a 32-bit bit count N, followed by - * (N+7)/8 bytes of data. - * - * So. The blob contains: - * - * - uint32 0x3f6ff9eb (magic number) - * - uint32 size (total blob size) - * - string key-type (see below) - * - string cipher-type (tells you if key is encrypted) - * - string encrypted-blob - * - * (The first size field includes the size field itself and the - * magic number before it. All other size fields are ordinary SSH-2 - * strings, so the size field indicates how much data is to - * _follow_.) - * - * The encrypted blob, once decrypted, contains a single string - * which in turn contains the payload. (This allows padding to be - * added after that string while still making it clear where the - * real payload ends. Also it probably makes for a reasonable - * decryption check.) - * - * The payload blob, for an RSA key, contains: - * - mpint e - * - mpint d - * - mpint n (yes, the public and private stuff is intermixed) - * - mpint u (presumably inverse of p mod q) - * - mpint p (p is the smaller prime) - * - mpint q (q is the larger) - * - * For a DSA key, the payload blob contains: - * - uint32 0 - * - mpint p - * - mpint g - * - mpint q - * - mpint y - * - mpint x - * - * Alternatively, if the parameters are `predefined', that - * (0,p,g,q) sequence can be replaced by a uint32 1 and a string - * containing some predefined parameter specification. *shudder*, - * but I doubt we'll encounter this in real life. - * - * The key type strings are ghastly. The RSA key I looked at had a - * type string of - * - * `if-modn{sign{rsa-pkcs1-sha1},encrypt{rsa-pkcs1v2-oaep}}' - * - * and the DSA key wasn't much better: - * - * `dl-modp{sign{dsa-nist-sha1},dh{plain}}' - * - * It isn't clear that these will always be the same. I think it - * might be wise just to look at the `if-modn{sign{rsa' and - * `dl-modp{sign{dsa' prefixes. - * - * Finally, the encryption. The cipher-type string appears to be - * either `none' or `3des-cbc'. Looks as if this is SSH-2-style - * 3des-cbc (i.e. outer cbc rather than inner). The key is created - * from the passphrase by means of yet another hashing faff: - * - * - first 16 bytes are MD5(passphrase) - * - next 16 bytes are MD5(passphrase || first 16 bytes) - * - if there were more, they'd be MD5(passphrase || first 32), - * and so on. - */ - -#define SSHCOM_MAGIC_NUMBER 0x3f6ff9eb - -struct sshcom_key { - char comment[256]; /* allowing any length is overkill */ - strbuf *keyblob; -}; - -static struct sshcom_key *load_sshcom_key(const Filename *filename, - const char **errmsg_p) -{ - struct sshcom_key *ret; - FILE *fp; - char *line = NULL; - int hdrstart, len; - const char *errmsg; - char *p; - bool headers_done; - char base64_bit[4]; - int base64_chars = 0; - - ret = snew(struct sshcom_key); - ret->comment[0] = '\0'; - ret->keyblob = strbuf_new_nm(); - - fp = f_open(filename, "r", false); - if (!fp) { - errmsg = "unable to open key file"; - goto error; - } - if (!(line = fgetline(fp))) { - errmsg = "unexpected end of file"; - goto error; - } - strip_crlf(line); - if (0 != strcmp(line, "---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----")) { - errmsg = "file does not begin with ssh.com key header"; - goto error; - } - smemclr(line, strlen(line)); - sfree(line); - line = NULL; - - headers_done = false; - while (1) { - if (!(line = fgetline(fp))) { - errmsg = "unexpected end of file"; - goto error; - } - strip_crlf(line); - if (!strcmp(line, "---- END SSH2 ENCRYPTED PRIVATE KEY ----")) { - sfree(line); - line = NULL; - break; /* done */ - } - if ((p = strchr(line, ':')) != NULL) { - if (headers_done) { - errmsg = "header found in body of key data"; - goto error; - } - *p++ = '\0'; - while (*p && isspace((unsigned char)*p)) p++; - hdrstart = p - line; - - /* - * Header lines can end in a trailing backslash for - * continuation. - */ - len = hdrstart + strlen(line+hdrstart); - assert(!line[len]); - while (line[len-1] == '\\') { - char *line2; - int line2len; - - line2 = fgetline(fp); - if (!line2) { - errmsg = "unexpected end of file"; - goto error; - } - strip_crlf(line2); - - line2len = strlen(line2); - line = sresize(line, len + line2len + 1, char); - strcpy(line + len - 1, line2); - len += line2len - 1; - assert(!line[len]); - - smemclr(line2, strlen(line2)); - sfree(line2); - line2 = NULL; - } - p = line + hdrstart; - strip_crlf(p); - if (!strcmp(line, "Comment")) { - /* Strip quotes in comment if present. */ - if (p[0] == '"' && p[strlen(p)-1] == '"') { - p++; - p[strlen(p)-1] = '\0'; - } - strncpy(ret->comment, p, sizeof(ret->comment)); - ret->comment[sizeof(ret->comment)-1] = '\0'; - } - } else { - headers_done = true; - - p = line; - while (isbase64(*p)) { - base64_bit[base64_chars++] = *p; - if (base64_chars == 4) { - unsigned char out[3]; - - base64_chars = 0; - - len = base64_decode_atom(base64_bit, out); - - if (len <= 0) { - errmsg = "invalid base64 encoding"; - goto error; - } - - put_data(ret->keyblob, out, len); - } - - p++; - } - } - smemclr(line, strlen(line)); - sfree(line); - line = NULL; - } - - if (ret->keyblob->len == 0) { - errmsg = "key body not present"; - goto error; - } - - fclose(fp); - if (errmsg_p) *errmsg_p = NULL; - return ret; - - error: - if (fp) - fclose(fp); - - if (line) { - smemclr(line, strlen(line)); - sfree(line); - line = NULL; - } - if (ret) { - strbuf_free(ret->keyblob); - smemclr(ret, sizeof(*ret)); - sfree(ret); - } - if (errmsg_p) *errmsg_p = errmsg; - return NULL; -} - -static bool sshcom_encrypted(const Filename *filename, char **comment) -{ - struct sshcom_key *key = load_sshcom_key(filename, NULL); - BinarySource src[1]; - ptrlen str; - bool answer = false; - - *comment = NULL; - if (!key) - goto done; - - BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(key->keyblob)); - - if (get_uint32(src) != SSHCOM_MAGIC_NUMBER) - goto done; /* key is invalid */ - get_uint32(src); /* skip length field */ - get_string(src); /* skip key type */ - str = get_string(src); /* cipher type */ - if (get_err(src)) - goto done; /* key is invalid */ - if (!ptrlen_eq_string(str, "none")) - answer = true; - - done: - if (key) { - *comment = dupstr(key->comment); - strbuf_free(key->keyblob); - smemclr(key, sizeof(*key)); - sfree(key); - } else { - *comment = dupstr(""); - } - return answer; -} - -void BinarySink_put_mp_sshcom_from_string(BinarySink *bs, ptrlen str) -{ - const unsigned char *bytes = (const unsigned char *)str.ptr; - size_t nbytes = str.len; - int bits = nbytes * 8 - 1; - - while (bits > 0) { - if (*bytes & (1 << (bits & 7))) - break; - if (!(bits-- & 7)) - bytes++, nbytes--; - } - - put_uint32(bs, bits+1); - put_data(bs, bytes, nbytes); -} - -#define put_mp_sshcom_from_string(bs, str) \ - BinarySink_put_mp_sshcom_from_string(BinarySink_UPCAST(bs), str) - -static ptrlen BinarySource_get_mp_sshcom_as_string(BinarySource *src) -{ - unsigned bits = get_uint32(src); - return get_data(src, (bits + 7) / 8); -} - -#define get_mp_sshcom_as_string(bs) \ - BinarySource_get_mp_sshcom_as_string(BinarySource_UPCAST(bs)) - -static void sshcom_derivekey(ptrlen passphrase, uint8_t *keybuf) -{ - /* - * Derive the encryption key for an ssh.com key file from the - * passphrase and iv/salt: - * - * - let block A equal MD5(passphrase) - * - let block B equal MD5(passphrase || A) - * - block C would be MD5(passphrase || A || B) and so on - * - encryption key is the first N bytes of A || B - */ - ssh_hash *h; - - h = ssh_hash_new(&ssh_md5); - put_datapl(h, passphrase); - ssh_hash_final(ssh_hash_copy(h), keybuf); - put_data(h, keybuf, 16); - ssh_hash_final(h, keybuf + 16); -} - -static ssh2_userkey *sshcom_read( - const Filename *filename, const char *passphrase, const char **errmsg_p) -{ - struct sshcom_key *key = load_sshcom_key(filename, errmsg_p); - const char *errmsg; - BinarySource src[1]; - ptrlen str, ciphertext; - int publen; - const char prefix_rsa[] = "if-modn{sign{rsa"; - const char prefix_dsa[] = "dl-modp{sign{dsa"; - enum { RSA, DSA } type; - bool encrypted; - ssh2_userkey *ret = NULL, *retkey; - const ssh_keyalg *alg; - strbuf *blob = NULL; - - if (!key) - return NULL; - - BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(key->keyblob)); - - if (get_uint32(src) != SSHCOM_MAGIC_NUMBER) { - errmsg = "key does not begin with magic number"; - goto error; - } - get_uint32(src); /* skip length field */ - - /* - * Determine the key type. - */ - str = get_string(src); - if (str.len > sizeof(prefix_rsa) - 1 && - !memcmp(str.ptr, prefix_rsa, sizeof(prefix_rsa) - 1)) { - type = RSA; - } else if (str.len > sizeof(prefix_dsa) - 1 && - !memcmp(str.ptr, prefix_dsa, sizeof(prefix_dsa) - 1)) { - type = DSA; - } else { - errmsg = "key is of unknown type"; - goto error; - } - - /* - * Determine the cipher type. - */ - str = get_string(src); - if (ptrlen_eq_string(str, "none")) - encrypted = false; - else if (ptrlen_eq_string(str, "3des-cbc")) - encrypted = true; - else { - errmsg = "key encryption is of unknown type"; - goto error; - } - - /* - * Get hold of the encrypted part of the key. - */ - ciphertext = get_string(src); - if (ciphertext.len == 0) { - errmsg = "no key data found"; - goto error; - } - - /* - * Decrypt it if necessary. - */ - if (encrypted) { - /* - * Derive encryption key from passphrase and iv/salt: - * - * - let block A equal MD5(passphrase) - * - let block B equal MD5(passphrase || A) - * - block C would be MD5(passphrase || A || B) and so on - * - encryption key is the first N bytes of A || B - */ - unsigned char keybuf[32], iv[8]; - - if (ciphertext.len % 8 != 0) { - errmsg = "encrypted part of key is not a multiple of cipher block" - " size"; - goto error; - } - - sshcom_derivekey(ptrlen_from_asciz(passphrase), keybuf); - - /* - * Now decrypt the key blob in place (casting away const from - * ciphertext being a ptrlen). - */ - memset(iv, 0, sizeof(iv)); - des3_decrypt_pubkey_ossh(keybuf, iv, - (char *)ciphertext.ptr, ciphertext.len); - - smemclr(keybuf, sizeof(keybuf)); - - /* - * Hereafter we return WRONG_PASSPHRASE for any parsing - * error. (But only if we've just tried to decrypt it! - * Returning WRONG_PASSPHRASE for an unencrypted key is - * automatic doom.) - */ - if (encrypted) - ret = SSH2_WRONG_PASSPHRASE; - } - - /* - * Expect the ciphertext to be formatted as a containing string, - * and reinitialise src to start parsing the inside of that string. - */ - BinarySource_BARE_INIT_PL(src, ciphertext); - str = get_string(src); - if (get_err(src)) { - errmsg = "containing string was ill-formed"; - goto error; - } - BinarySource_BARE_INIT_PL(src, str); - - /* - * Now we break down into RSA versus DSA. In either case we'll - * construct public and private blobs in our own format, and - * end up feeding them to ssh_key_new_priv(). - */ - blob = strbuf_new_nm(); - if (type == RSA) { - ptrlen n, e, d, u, p, q; - - e = get_mp_sshcom_as_string(src); - d = get_mp_sshcom_as_string(src); - n = get_mp_sshcom_as_string(src); - u = get_mp_sshcom_as_string(src); - p = get_mp_sshcom_as_string(src); - q = get_mp_sshcom_as_string(src); - if (get_err(src)) { - errmsg = "key data did not contain six integers"; - goto error; - } - - alg = &ssh_rsa; - put_stringz(blob, "ssh-rsa"); - put_mp_ssh2_from_string(blob, e); - put_mp_ssh2_from_string(blob, n); - publen = blob->len; - put_mp_ssh2_from_string(blob, d); - put_mp_ssh2_from_string(blob, q); - put_mp_ssh2_from_string(blob, p); - put_mp_ssh2_from_string(blob, u); - } else { - ptrlen p, q, g, x, y; - - assert(type == DSA); /* the only other option from the if above */ - - if (get_uint32(src) != 0) { - errmsg = "predefined DSA parameters not supported"; - goto error; - } - p = get_mp_sshcom_as_string(src); - g = get_mp_sshcom_as_string(src); - q = get_mp_sshcom_as_string(src); - y = get_mp_sshcom_as_string(src); - x = get_mp_sshcom_as_string(src); - if (get_err(src)) { - errmsg = "key data did not contain five integers"; - goto error; - } - - alg = &ssh_dss; - put_stringz(blob, "ssh-dss"); - put_mp_ssh2_from_string(blob, p); - put_mp_ssh2_from_string(blob, q); - put_mp_ssh2_from_string(blob, g); - put_mp_ssh2_from_string(blob, y); - publen = blob->len; - put_mp_ssh2_from_string(blob, x); - } - - retkey = snew(ssh2_userkey); - retkey->key = ssh_key_new_priv( - alg, make_ptrlen(blob->u, publen), - make_ptrlen(blob->u + publen, blob->len - publen)); - if (!retkey->key) { - sfree(retkey); - errmsg = "unable to create key data structure"; - goto error; - } - retkey->comment = dupstr(key->comment); - - errmsg = NULL; /* no error */ - ret = retkey; - - error: - if (blob) { - strbuf_free(blob); - } - strbuf_free(key->keyblob); - smemclr(key, sizeof(*key)); - sfree(key); - if (errmsg_p) *errmsg_p = errmsg; - return ret; -} - -static bool sshcom_write( - const Filename *filename, ssh2_userkey *key, const char *passphrase) -{ - strbuf *pubblob, *privblob, *outblob; - ptrlen numbers[6]; - int nnumbers, lenpos, i; - bool initial_zero; - BinarySource src[1]; - const char *type; - char *ciphertext; - int cipherlen; - bool ret = false; - FILE *fp; - - /* - * Fetch the key blobs. - */ - pubblob = strbuf_new(); - ssh_key_public_blob(key->key, BinarySink_UPCAST(pubblob)); - privblob = strbuf_new_nm(); - ssh_key_private_blob(key->key, BinarySink_UPCAST(privblob)); - outblob = NULL; - - /* - * Find the sequence of integers to be encoded into the OpenSSH - * key blob, and also decide on the header line. - */ - if (ssh_key_alg(key->key) == &ssh_rsa) { - ptrlen n, e, d, p, q, iqmp; - - /* - * These blobs were generated from inside PuTTY, so we needn't - * treat them as untrusted. - */ - BinarySource_BARE_INIT(src, pubblob->u, pubblob->len); - get_string(src); /* skip algorithm name */ - e = get_string(src); - n = get_string(src); - BinarySource_BARE_INIT(src, privblob->u, privblob->len); - d = get_string(src); - p = get_string(src); - q = get_string(src); - iqmp = get_string(src); - - assert(!get_err(src)); /* can't go wrong */ - - numbers[0] = e; - numbers[1] = d; - numbers[2] = n; - numbers[3] = iqmp; - numbers[4] = q; - numbers[5] = p; - - nnumbers = 6; - initial_zero = false; - type = "if-modn{sign{rsa-pkcs1-sha1},encrypt{rsa-pkcs1v2-oaep}}"; - } else if (ssh_key_alg(key->key) == &ssh_dss) { - ptrlen p, q, g, y, x; - - /* - * These blobs were generated from inside PuTTY, so we needn't - * treat them as untrusted. - */ - BinarySource_BARE_INIT(src, pubblob->u, pubblob->len); - get_string(src); /* skip algorithm name */ - p = get_string(src); - q = get_string(src); - g = get_string(src); - y = get_string(src); - BinarySource_BARE_INIT(src, privblob->u, privblob->len); - x = get_string(src); - - assert(!get_err(src)); /* can't go wrong */ - - numbers[0] = p; - numbers[1] = g; - numbers[2] = q; - numbers[3] = y; - numbers[4] = x; - - nnumbers = 5; - initial_zero = true; - type = "dl-modp{sign{dsa-nist-sha1},dh{plain}}"; - } else { - goto error; /* unsupported key type */ - } - - outblob = strbuf_new_nm(); - - /* - * Create the unencrypted key blob. - */ - put_uint32(outblob, SSHCOM_MAGIC_NUMBER); - put_uint32(outblob, 0); /* length field, fill in later */ - put_stringz(outblob, type); - put_stringz(outblob, passphrase ? "3des-cbc" : "none"); - lenpos = outblob->len; /* remember this position */ - put_uint32(outblob, 0); /* encrypted-blob size */ - put_uint32(outblob, 0); /* encrypted-payload size */ - if (initial_zero) - put_uint32(outblob, 0); - for (i = 0; i < nnumbers; i++) - put_mp_sshcom_from_string(outblob, numbers[i]); - /* Now wrap up the encrypted payload. */ - PUT_32BIT_MSB_FIRST(outblob->s + lenpos + 4, - outblob->len - (lenpos + 8)); - /* Pad encrypted blob to a multiple of cipher block size. */ - if (passphrase) { - int padding = -(outblob->len - (lenpos+4)) & 7; - uint8_t padding_buf[8]; - random_read(padding_buf, padding); - put_data(outblob, padding_buf, padding); - } - ciphertext = outblob->s + lenpos + 4; - cipherlen = outblob->len - (lenpos + 4); - assert(!passphrase || cipherlen % 8 == 0); - /* Wrap up the encrypted blob string. */ - PUT_32BIT_MSB_FIRST(outblob->s + lenpos, cipherlen); - /* And finally fill in the total length field. */ - PUT_32BIT_MSB_FIRST(outblob->s + 4, outblob->len); - - /* - * Encrypt the key. - */ - if (passphrase) { - unsigned char keybuf[32], iv[8]; - - sshcom_derivekey(ptrlen_from_asciz(passphrase), keybuf); - - /* - * Now decrypt the key blob. - */ - memset(iv, 0, sizeof(iv)); - des3_encrypt_pubkey_ossh(keybuf, iv, ciphertext, cipherlen); - - smemclr(keybuf, sizeof(keybuf)); - } - - /* - * And save it. We'll use Unix line endings just in case it's - * subsequently transferred in binary mode. - */ - fp = f_open(filename, "wb", true); /* ensure Unix line endings */ - if (!fp) - goto error; - fputs("---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----\n", fp); - fprintf(fp, "Comment: \""); - /* - * Comment header is broken with backslash-newline if it goes - * over 70 chars. Although it's surrounded by quotes, it - * _doesn't_ escape backslashes or quotes within the string. - * Don't ask me, I didn't design it. - */ - { - int slen = 60; /* starts at 60 due to "Comment: " */ - char *c = key->comment; - while ((int)strlen(c) > slen) { - fprintf(fp, "%.*s\\\n", slen, c); - c += slen; - slen = 70; /* allow 70 chars on subsequent lines */ - } - fprintf(fp, "%s\"\n", c); - } - base64_encode(fp, outblob->u, outblob->len, 70); - fputs("---- END SSH2 ENCRYPTED PRIVATE KEY ----\n", fp); - fclose(fp); - ret = true; - - error: - if (outblob) - strbuf_free(outblob); - if (privblob) - strbuf_free(privblob); - if (pubblob) - strbuf_free(pubblob); - return ret; -} +/* + * Code for PuTTY to import and export private key files in other + * SSH clients' formats. + */ + +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "mpint.h" +#include "misc.h" + +static bool openssh_pem_encrypted(const Filename *file); +static bool openssh_new_encrypted(const Filename *file); +static ssh2_userkey *openssh_pem_read( + const Filename *file, const char *passphrase, const char **errmsg_p); +static ssh2_userkey *openssh_new_read( + const Filename *file, const char *passphrase, const char **errmsg_p); +static bool openssh_auto_write( + const Filename *file, ssh2_userkey *key, const char *passphrase); +static bool openssh_pem_write( + const Filename *file, ssh2_userkey *key, const char *passphrase); +static bool openssh_new_write( + const Filename *file, ssh2_userkey *key, const char *passphrase); + +static bool sshcom_encrypted(const Filename *file, char **comment); +static ssh2_userkey *sshcom_read( + const Filename *file, const char *passphrase, const char **errmsg_p); +static bool sshcom_write( + const Filename *file, ssh2_userkey *key, const char *passphrase); + +/* + * Given a key type, determine whether we know how to import it. + */ +bool import_possible(int type) +{ + if (type == SSH_KEYTYPE_OPENSSH_PEM) + return true; + if (type == SSH_KEYTYPE_OPENSSH_NEW) + return true; + if (type == SSH_KEYTYPE_SSHCOM) + return true; + return false; +} + +/* + * Given a key type, determine what native key type + * (SSH_KEYTYPE_SSH1 or SSH_KEYTYPE_SSH2) it will come out as once + * we've imported it. + */ +int import_target_type(int type) +{ + /* + * There are no known foreign SSH-1 key formats. + */ + return SSH_KEYTYPE_SSH2; +} + +/* + * Determine whether a foreign key is encrypted. + */ +bool import_encrypted(const Filename *filename, int type, char **comment) +{ + if (type == SSH_KEYTYPE_OPENSSH_PEM) { + /* OpenSSH PEM format doesn't contain a key comment at all */ + *comment = dupstr(filename_to_str(filename)); + return openssh_pem_encrypted(filename); + } else if (type == SSH_KEYTYPE_OPENSSH_NEW) { + /* OpenSSH new format does, but it's inside the encrypted + * section for some reason */ + *comment = dupstr(filename_to_str(filename)); + return openssh_new_encrypted(filename); + } else if (type == SSH_KEYTYPE_SSHCOM) { + return sshcom_encrypted(filename, comment); + } + return false; +} + +/* + * Import an SSH-1 key. + */ +int import_ssh1(const Filename *filename, int type, + RSAKey *key, char *passphrase, const char **errmsg_p) +{ + return 0; +} + +/* + * Import an SSH-2 key. + */ +ssh2_userkey *import_ssh2(const Filename *filename, int type, + char *passphrase, const char **errmsg_p) +{ + if (type == SSH_KEYTYPE_OPENSSH_PEM) + return openssh_pem_read(filename, passphrase, errmsg_p); + else if (type == SSH_KEYTYPE_OPENSSH_NEW) + return openssh_new_read(filename, passphrase, errmsg_p); + if (type == SSH_KEYTYPE_SSHCOM) + return sshcom_read(filename, passphrase, errmsg_p); + return NULL; +} + +/* + * Export an SSH-1 key. + */ +bool export_ssh1(const Filename *filename, int type, RSAKey *key, + char *passphrase) +{ + return false; +} + +/* + * Export an SSH-2 key. + */ +bool export_ssh2(const Filename *filename, int type, + ssh2_userkey *key, char *passphrase) +{ + if (type == SSH_KEYTYPE_OPENSSH_AUTO) + return openssh_auto_write(filename, key, passphrase); + if (type == SSH_KEYTYPE_OPENSSH_NEW) + return openssh_new_write(filename, key, passphrase); + if (type == SSH_KEYTYPE_SSHCOM) + return sshcom_write(filename, key, passphrase); + return false; +} + +/* + * Strip trailing CRs and LFs at the end of a line of text. + */ +void strip_crlf(char *str) +{ + char *p = str + strlen(str); + + while (p > str && (p[-1] == '\r' || p[-1] == '\n')) + *--p = '\0'; +} + +/* ---------------------------------------------------------------------- + * Helper routines. (The base64 ones are defined in sshpubk.c.) + */ + +#define isbase64(c) ( ((c) >= 'A' && (c) <= 'Z') || \ + ((c) >= 'a' && (c) <= 'z') || \ + ((c) >= '0' && (c) <= '9') || \ + (c) == '+' || (c) == '/' || (c) == '=' \ + ) + +/* + * Read an ASN.1/BER identifier and length pair. + * + * Flags are a combination of the #defines listed below. + * + * Returns -1 if unsuccessful; otherwise returns the number of + * bytes used out of the source data. + */ + +/* ASN.1 tag classes. */ +#define ASN1_CLASS_UNIVERSAL (0 << 6) +#define ASN1_CLASS_APPLICATION (1 << 6) +#define ASN1_CLASS_CONTEXT_SPECIFIC (2 << 6) +#define ASN1_CLASS_PRIVATE (3 << 6) +#define ASN1_CLASS_MASK (3 << 6) + +/* Primitive versus constructed bit. */ +#define ASN1_CONSTRUCTED (1 << 5) + +/* + * Write an ASN.1/BER identifier and length pair. Returns the + * number of bytes consumed. Assumes dest contains enough space. + * Will avoid writing anything if dest is NULL, but still return + * amount of space required. + */ +static void BinarySink_put_ber_id_len(BinarySink *bs, + int id, int length, int flags) +{ + if (id <= 30) { + /* + * Identifier is one byte. + */ + put_byte(bs, id | flags); + } else { + int n; + /* + * Identifier is multiple bytes: the first byte is 11111 + * plus the flags, and subsequent bytes encode the value of + * the identifier, 7 bits at a time, with the top bit of + * each byte 1 except the last one which is 0. + */ + put_byte(bs, 0x1F | flags); + for (n = 1; (id >> (7*n)) > 0; n++) + continue; /* count the bytes */ + while (n--) + put_byte(bs, (n ? 0x80 : 0) | ((id >> (7*n)) & 0x7F)); + } + + if (length < 128) { + /* + * Length is one byte. + */ + put_byte(bs, length); + } else { + int n; + /* + * Length is multiple bytes. The first is 0x80 plus the + * number of subsequent bytes, and the subsequent bytes + * encode the actual length. + */ + for (n = 1; (length >> (8*n)) > 0; n++) + continue; /* count the bytes */ + put_byte(bs, 0x80 | n); + while (n--) + put_byte(bs, (length >> (8*n)) & 0xFF); + } +} + +#define put_ber_id_len(bs, id, len, flags) \ + BinarySink_put_ber_id_len(BinarySink_UPCAST(bs), id, len, flags) + +typedef struct ber_item { + int id; + int flags; + ptrlen data; +} ber_item; + +static ber_item BinarySource_get_ber(BinarySource *src) +{ + ber_item toret; + unsigned char leadbyte, lenbyte; + size_t length; + + leadbyte = get_byte(src); + toret.flags = (leadbyte & 0xE0); + if ((leadbyte & 0x1F) == 0x1F) { + unsigned char idbyte; + + toret.id = 0; + do { + idbyte = get_byte(src); + toret.id = (toret.id << 7) | (idbyte & 0x7F); + } while (idbyte & 0x80); + } else { + toret.id = leadbyte & 0x1F; + } + + lenbyte = get_byte(src); + if (lenbyte & 0x80) { + int nbytes = lenbyte & 0x7F; + length = 0; + while (nbytes-- > 0) + length = (length << 8) | get_byte(src); + } else { + length = lenbyte; + } + + toret.data = get_data(src, length); + return toret; +} + +#define get_ber(bs) BinarySource_get_ber(BinarySource_UPCAST(bs)) + +/* ---------------------------------------------------------------------- + * Code to read and write OpenSSH private keys, in the old-style PEM + * format. + */ + +typedef enum { + OP_DSA, OP_RSA, OP_ECDSA +} openssh_pem_keytype; +typedef enum { + OP_E_3DES, OP_E_AES +} openssh_pem_enc; + +struct openssh_pem_key { + openssh_pem_keytype keytype; + bool encrypted; + openssh_pem_enc encryption; + char iv[32]; + strbuf *keyblob; +}; + +void BinarySink_put_mp_ssh2_from_string(BinarySink *bs, ptrlen str) +{ + const unsigned char *bytes = (const unsigned char *)str.ptr; + size_t nbytes = str.len; + while (nbytes > 0 && bytes[0] == 0) { + nbytes--; + bytes++; + } + if (nbytes > 0 && bytes[0] & 0x80) { + put_uint32(bs, nbytes + 1); + put_byte(bs, 0); + } else { + put_uint32(bs, nbytes); + } + put_data(bs, bytes, nbytes); +} +#define put_mp_ssh2_from_string(bs, str) \ + BinarySink_put_mp_ssh2_from_string(BinarySink_UPCAST(bs), str) + +static struct openssh_pem_key *load_openssh_pem_key(const Filename *filename, + const char **errmsg_p) +{ + struct openssh_pem_key *ret; + FILE *fp = NULL; + char *line = NULL; + const char *errmsg; + char *p; + bool headers_done; + char base64_bit[4]; + int base64_chars = 0; + + ret = snew(struct openssh_pem_key); + ret->keyblob = strbuf_new_nm(); + + fp = f_open(filename, "r", false); + if (!fp) { + errmsg = "unable to open key file"; + goto error; + } + + if (!(line = fgetline(fp))) { + errmsg = "unexpected end of file"; + goto error; + } + strip_crlf(line); + if (!strstartswith(line, "-----BEGIN ") || + !strendswith(line, "PRIVATE KEY-----")) { + errmsg = "file does not begin with OpenSSH key header"; + goto error; + } + /* + * Parse the BEGIN line. For old-format keys, this tells us the + * type of the key; for new-format keys, all it tells us is the + * format, and we'll find out the key type once we parse the + * base64. + */ + if (!strcmp(line, "-----BEGIN RSA PRIVATE KEY-----")) { + ret->keytype = OP_RSA; + } else if (!strcmp(line, "-----BEGIN DSA PRIVATE KEY-----")) { + ret->keytype = OP_DSA; + } else if (!strcmp(line, "-----BEGIN EC PRIVATE KEY-----")) { + ret->keytype = OP_ECDSA; + } else if (!strcmp(line, "-----BEGIN OPENSSH PRIVATE KEY-----")) { + errmsg = "this is a new-style OpenSSH key"; + goto error; + } else { + errmsg = "unrecognised key type"; + goto error; + } + smemclr(line, strlen(line)); + sfree(line); + line = NULL; + + ret->encrypted = false; + memset(ret->iv, 0, sizeof(ret->iv)); + + headers_done = false; + while (1) { + if (!(line = fgetline(fp))) { + errmsg = "unexpected end of file"; + goto error; + } + strip_crlf(line); + if (strstartswith(line, "-----END ") && + strendswith(line, "PRIVATE KEY-----")) { + sfree(line); + line = NULL; + break; /* done */ + } + if ((p = strchr(line, ':')) != NULL) { + if (headers_done) { + errmsg = "header found in body of key data"; + goto error; + } + *p++ = '\0'; + while (*p && isspace((unsigned char)*p)) p++; + if (!strcmp(line, "Proc-Type")) { + if (p[0] != '4' || p[1] != ',') { + errmsg = "Proc-Type is not 4 (only 4 is supported)"; + goto error; + } + p += 2; + if (!strcmp(p, "ENCRYPTED")) + ret->encrypted = true; + } else if (!strcmp(line, "DEK-Info")) { + int i, ivlen; + + if (!strncmp(p, "DES-EDE3-CBC,", 13)) { + ret->encryption = OP_E_3DES; + ivlen = 8; + } else if (!strncmp(p, "AES-128-CBC,", 12)) { + ret->encryption = OP_E_AES; + ivlen = 16; + } else { + errmsg = "unsupported cipher"; + goto error; + } + p = strchr(p, ',') + 1;/* always non-NULL, by above checks */ + for (i = 0; i < ivlen; i++) { + unsigned j; + if (1 != sscanf(p, "%2x", &j)) { + errmsg = "expected more iv data in DEK-Info"; + goto error; + } + ret->iv[i] = j; + p += 2; + } + if (*p) { + errmsg = "more iv data than expected in DEK-Info"; + goto error; + } + } + } else { + headers_done = true; + + p = line; + while (isbase64(*p)) { + base64_bit[base64_chars++] = *p; + if (base64_chars == 4) { + unsigned char out[3]; + int len; + + base64_chars = 0; + + len = base64_decode_atom(base64_bit, out); + + if (len <= 0) { + errmsg = "invalid base64 encoding"; + goto error; + } + + put_data(ret->keyblob, out, len); + + smemclr(out, sizeof(out)); + } + + p++; + } + } + smemclr(line, strlen(line)); + sfree(line); + line = NULL; + } + + fclose(fp); + fp = NULL; + + if (!ret->keyblob || ret->keyblob->len == 0) { + errmsg = "key body not present"; + goto error; + } + + if (ret->encrypted && ret->keyblob->len % 8 != 0) { + errmsg = "encrypted key blob is not a multiple of " + "cipher block size"; + goto error; + } + + smemclr(base64_bit, sizeof(base64_bit)); + if (errmsg_p) *errmsg_p = NULL; + return ret; + + error: + if (line) { + smemclr(line, strlen(line)); + sfree(line); + line = NULL; + } + smemclr(base64_bit, sizeof(base64_bit)); + if (ret) { + if (ret->keyblob) + strbuf_free(ret->keyblob); + smemclr(ret, sizeof(*ret)); + sfree(ret); + } + if (errmsg_p) *errmsg_p = errmsg; + if (fp) fclose(fp); + return NULL; +} + +static bool openssh_pem_encrypted(const Filename *filename) +{ + struct openssh_pem_key *key = load_openssh_pem_key(filename, NULL); + bool ret; + + if (!key) + return false; + ret = key->encrypted; + strbuf_free(key->keyblob); + smemclr(key, sizeof(*key)); + sfree(key); + return ret; +} + +static void openssh_pem_derivekey( + ptrlen passphrase, const void *iv, uint8_t *keybuf) +{ + /* + * Derive the encryption key for a PEM key file from the + * passphrase and iv/salt: + * + * - let block A equal MD5(passphrase || iv) + * - let block B equal MD5(A || passphrase || iv) + * - block C would be MD5(B || passphrase || iv) and so on + * - encryption key is the first N bytes of A || B + * + * (Note that only 8 bytes of the iv are used for key + * derivation, even when the key is encrypted with AES and + * hence there are 16 bytes available.) + */ + ssh_hash *h; + + h = ssh_hash_new(&ssh_md5); + put_datapl(h, passphrase); + put_data(h, iv, 8); + ssh_hash_final(h, keybuf); + + h = ssh_hash_new(&ssh_md5); + put_data(h, keybuf, 16); + put_datapl(h, passphrase); + put_data(h, iv, 8); + ssh_hash_final(h, keybuf + 16); +} + +static ssh2_userkey *openssh_pem_read( + const Filename *filename, const char *passphrase, const char **errmsg_p) +{ + struct openssh_pem_key *key = load_openssh_pem_key(filename, errmsg_p); + ssh2_userkey *retkey; + const ssh_keyalg *alg; + BinarySource src[1]; + int i, num_integers; + ssh2_userkey *retval = NULL; + const char *errmsg; + strbuf *blob = strbuf_new_nm(); + int privptr = 0, publen; + + if (!key) { + strbuf_free(blob); + return NULL; + } + + if (key->encrypted) { + unsigned char keybuf[32]; + openssh_pem_derivekey(ptrlen_from_asciz(passphrase), key->iv, keybuf); + + /* + * Decrypt the key blob. + */ + if (key->encryption == OP_E_3DES) + des3_decrypt_pubkey_ossh(keybuf, key->iv, + key->keyblob->u, key->keyblob->len); + else { + ssh_cipher *cipher = ssh_cipher_new(&ssh_aes128_cbc); + ssh_cipher_setkey(cipher, keybuf); + ssh_cipher_setiv(cipher, key->iv); + ssh_cipher_decrypt(cipher, key->keyblob->u, key->keyblob->len); + ssh_cipher_free(cipher); + } + + smemclr(keybuf, sizeof(keybuf)); + } + + /* + * Now we have a decrypted key blob, which contains an ASN.1 + * encoded private key. We must now untangle the ASN.1. + * + * We expect the whole key blob to be formatted as a SEQUENCE + * (0x30 followed by a length code indicating that the rest of + * the blob is part of the sequence). Within that SEQUENCE we + * expect to see a bunch of INTEGERs. What those integers mean + * depends on the key type: + * + * - For RSA, we expect the integers to be 0, n, e, d, p, q, + * dmp1, dmq1, iqmp in that order. (The last three are d mod + * (p-1), d mod (q-1), inverse of q mod p respectively.) + * + * - For DSA, we expect them to be 0, p, q, g, y, x in that + * order. + * + * - In ECDSA the format is totally different: we see the + * SEQUENCE, but beneath is an INTEGER 1, OCTET STRING priv + * EXPLICIT [0] OID curve, EXPLICIT [1] BIT STRING pubPoint + */ + + BinarySource_BARE_INIT(src, key->keyblob->u, key->keyblob->len); + + { + /* Expect the SEQUENCE header. Take its absence as a failure to + * decrypt, if the key was encrypted. */ + ber_item seq = get_ber(src); + if (get_err(src) || seq.id != 16) { + errmsg = "ASN.1 decoding failure"; + retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL; + goto error; + } + + /* Reinitialise our BinarySource to parse just the inside of that + * SEQUENCE. */ + BinarySource_BARE_INIT_PL(src, seq.data); + } + + /* Expect a load of INTEGERs. */ + if (key->keytype == OP_RSA) + num_integers = 9; + else if (key->keytype == OP_DSA) + num_integers = 6; + else + num_integers = 0; /* placate compiler warnings */ + + + if (key->keytype == OP_ECDSA) { + /* And now for something completely different */ + ber_item integer, privkey, sub0, sub1, oid, pubkey; + const ssh_keyalg *alg; + const struct ec_curve *curve; + + /* Parse the outer layer of things inside the containing SEQUENCE */ + integer = get_ber(src); + privkey = get_ber(src); + sub0 = get_ber(src); + sub1 = get_ber(src); + + /* Now look inside sub0 for the curve OID */ + BinarySource_BARE_INIT_PL(src, sub0.data); + oid = get_ber(src); + + /* And inside sub1 for the public-key BIT STRING */ + BinarySource_BARE_INIT_PL(src, sub1.data); + pubkey = get_ber(src); + + if (get_err(src) || + integer.id != 2 || + integer.data.len != 1 || + ((const unsigned char *)integer.data.ptr)[0] != 1 || + privkey.id != 4 || + sub0.id != 0 || + sub1.id != 1 || + oid.id != 6 || + pubkey.id != 3) { + + errmsg = "ASN.1 decoding failure"; + retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL; + goto error; + } + + alg = ec_alg_by_oid(oid.data.len, oid.data.ptr, &curve); + if (!alg) { + errmsg = "Unsupported ECDSA curve."; + retval = NULL; + goto error; + } + if (pubkey.data.len != ((((curve->fieldBits + 7) / 8) * 2) + 2)) { + errmsg = "ASN.1 decoding failure"; + retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL; + goto error; + } + /* Skip 0x00 before point */ + pubkey.data.ptr = (const char *)pubkey.data.ptr + 1; + pubkey.data.len -= 1; + + /* Construct the key */ + retkey = snew(ssh2_userkey); + + put_stringz(blob, alg->ssh_id); + put_stringz(blob, curve->name); + put_stringpl(blob, pubkey.data); + publen = blob->len; + put_mp_ssh2_from_string(blob, privkey.data); + + retkey->key = ssh_key_new_priv( + alg, make_ptrlen(blob->u, publen), + make_ptrlen(blob->u + publen, blob->len - publen)); + + if (!retkey->key) { + sfree(retkey); + errmsg = "unable to create key data structure"; + goto error; + } + + } else if (key->keytype == OP_RSA || key->keytype == OP_DSA) { + + put_stringz(blob, key->keytype == OP_DSA ? "ssh-dss" : "ssh-rsa"); + + ptrlen rsa_modulus = PTRLEN_LITERAL(""); + + for (i = 0; i < num_integers; i++) { + ber_item integer = get_ber(src); + + if (get_err(src) || integer.id != 2) { + errmsg = "ASN.1 decoding failure"; + retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL; + goto error; + } + + if (i == 0) { + /* + * The first integer should be zero always (I think + * this is some sort of version indication). + */ + if (integer.data.len != 1 || + ((const unsigned char *)integer.data.ptr)[0] != 0) { + errmsg = "version number mismatch"; + goto error; + } + } else if (key->keytype == OP_RSA) { + /* + * Integers 1 and 2 go into the public blob but in the + * opposite order; integers 3, 4, 5 and 8 go into the + * private blob. The other two (6 and 7) are ignored. + */ + if (i == 1) { + /* Save the details for after we deal with number 2. */ + rsa_modulus = integer.data; + } else if (i != 6 && i != 7) { + put_mp_ssh2_from_string(blob, integer.data); + if (i == 2) { + put_mp_ssh2_from_string(blob, rsa_modulus); + privptr = blob->len; + } + } + } else if (key->keytype == OP_DSA) { + /* + * Integers 1-4 go into the public blob; integer 5 goes + * into the private blob. + */ + put_mp_ssh2_from_string(blob, integer.data); + if (i == 4) + privptr = blob->len; + } + } + + /* + * Now put together the actual key. Simplest way to do this is + * to assemble our own key blobs and feed them to the createkey + * functions; this is a bit faffy but it does mean we get all + * the sanity checks for free. + */ + assert(privptr > 0); /* should have bombed by now if not */ + retkey = snew(ssh2_userkey); + alg = (key->keytype == OP_RSA ? &ssh_rsa : &ssh_dss); + retkey->key = ssh_key_new_priv( + alg, make_ptrlen(blob->u, privptr), + make_ptrlen(blob->u+privptr, blob->len-privptr)); + + if (!retkey->key) { + sfree(retkey); + errmsg = "unable to create key data structure"; + goto error; + } + + } else { + unreachable("Bad key type from load_openssh_pem_key"); + errmsg = "Bad key type from load_openssh_pem_key"; + goto error; + } + + /* + * The old key format doesn't include a comment in the private + * key file. + */ + retkey->comment = dupstr("imported-openssh-key"); + + errmsg = NULL; /* no error */ + retval = retkey; + + error: + strbuf_free(blob); + strbuf_free(key->keyblob); + smemclr(key, sizeof(*key)); + sfree(key); + if (errmsg_p) *errmsg_p = errmsg; + return retval; +} + +static bool openssh_pem_write( + const Filename *filename, ssh2_userkey *key, const char *passphrase) +{ + strbuf *pubblob, *privblob, *outblob; + unsigned char *spareblob; + int sparelen = 0; + ptrlen numbers[9]; + int nnumbers, i; + const char *header, *footer; + char zero[1]; + unsigned char iv[8]; + bool ret = false; + FILE *fp; + BinarySource src[1]; + + /* + * Fetch the key blobs. + */ + pubblob = strbuf_new(); + ssh_key_public_blob(key->key, BinarySink_UPCAST(pubblob)); + privblob = strbuf_new_nm(); + ssh_key_private_blob(key->key, BinarySink_UPCAST(privblob)); + spareblob = NULL; + + outblob = strbuf_new_nm(); + + /* + * Encode the OpenSSH key blob, and also decide on the header + * line. + */ + if (ssh_key_alg(key->key) == &ssh_rsa || + ssh_key_alg(key->key) == &ssh_dss) { + strbuf *seq; + + /* + * The RSA and DSS handlers share some code because the two + * key types have very similar ASN.1 representations, as a + * plain SEQUENCE of big integers. So we set up a list of + * bignums per key type and then construct the actual blob in + * common code after that. + */ + if (ssh_key_alg(key->key) == &ssh_rsa) { + ptrlen n, e, d, p, q, iqmp, dmp1, dmq1; + mp_int *bd, *bp, *bq, *bdmp1, *bdmq1; + + /* + * These blobs were generated from inside PuTTY, so we needn't + * treat them as untrusted. + */ + BinarySource_BARE_INIT(src, pubblob->u, pubblob->len); + get_string(src); /* skip algorithm name */ + e = get_string(src); + n = get_string(src); + BinarySource_BARE_INIT(src, privblob->u, privblob->len); + d = get_string(src); + p = get_string(src); + q = get_string(src); + iqmp = get_string(src); + + assert(!get_err(src)); /* can't go wrong */ + + /* We also need d mod (p-1) and d mod (q-1). */ + bd = mp_from_bytes_be(d); + bp = mp_from_bytes_be(p); + bq = mp_from_bytes_be(q); + mp_sub_integer_into(bp, bp, 1); + mp_sub_integer_into(bq, bq, 1); + bdmp1 = mp_mod(bd, bp); + bdmq1 = mp_mod(bd, bq); + mp_free(bd); + mp_free(bp); + mp_free(bq); + + dmp1.len = (mp_get_nbits(bdmp1)+8)/8; + dmq1.len = (mp_get_nbits(bdmq1)+8)/8; + sparelen = dmp1.len + dmq1.len; + spareblob = snewn(sparelen, unsigned char); + dmp1.ptr = spareblob; + dmq1.ptr = spareblob + dmp1.len; + for (i = 0; i < dmp1.len; i++) + spareblob[i] = mp_get_byte(bdmp1, dmp1.len-1 - i); + for (i = 0; i < dmq1.len; i++) + spareblob[i+dmp1.len] = mp_get_byte(bdmq1, dmq1.len-1 - i); + mp_free(bdmp1); + mp_free(bdmq1); + + numbers[0] = make_ptrlen(zero, 1); zero[0] = '\0'; + numbers[1] = n; + numbers[2] = e; + numbers[3] = d; + numbers[4] = p; + numbers[5] = q; + numbers[6] = dmp1; + numbers[7] = dmq1; + numbers[8] = iqmp; + + nnumbers = 9; + header = "-----BEGIN RSA PRIVATE KEY-----\n"; + footer = "-----END RSA PRIVATE KEY-----\n"; + } else { /* ssh-dss */ + ptrlen p, q, g, y, x; + + /* + * These blobs were generated from inside PuTTY, so we needn't + * treat them as untrusted. + */ + BinarySource_BARE_INIT(src, pubblob->u, pubblob->len); + get_string(src); /* skip algorithm name */ + p = get_string(src); + q = get_string(src); + g = get_string(src); + y = get_string(src); + BinarySource_BARE_INIT(src, privblob->u, privblob->len); + x = get_string(src); + + assert(!get_err(src)); /* can't go wrong */ + + numbers[0].ptr = zero; numbers[0].len = 1; zero[0] = '\0'; + numbers[1] = p; + numbers[2] = q; + numbers[3] = g; + numbers[4] = y; + numbers[5] = x; + + nnumbers = 6; + header = "-----BEGIN DSA PRIVATE KEY-----\n"; + footer = "-----END DSA PRIVATE KEY-----\n"; + } + + seq = strbuf_new_nm(); + for (i = 0; i < nnumbers; i++) { + put_ber_id_len(seq, 2, numbers[i].len, 0); + put_datapl(seq, numbers[i]); + } + put_ber_id_len(outblob, 16, seq->len, ASN1_CONSTRUCTED); + put_data(outblob, seq->s, seq->len); + strbuf_free(seq); + } else if (ssh_key_alg(key->key) == &ssh_ecdsa_nistp256 || + ssh_key_alg(key->key) == &ssh_ecdsa_nistp384 || + ssh_key_alg(key->key) == &ssh_ecdsa_nistp521) { + const unsigned char *oid; + struct ecdsa_key *ec = container_of(key->key, struct ecdsa_key, sshk); + int oidlen; + int pointlen; + strbuf *seq, *sub; + + /* + * Structure of asn1: + * SEQUENCE + * INTEGER 1 + * OCTET STRING (private key) + * [0] + * OID (curve) + * [1] + * BIT STRING (0x00 public key point) + */ + oid = ec_alg_oid(ssh_key_alg(key->key), &oidlen); + pointlen = (ec->curve->fieldBits + 7) / 8 * 2; + + seq = strbuf_new_nm(); + + /* INTEGER 1 */ + put_ber_id_len(seq, 2, 1, 0); + put_byte(seq, 1); + + /* OCTET STRING private key */ + put_ber_id_len(seq, 4, privblob->len - 4, 0); + put_data(seq, privblob->s + 4, privblob->len - 4); + + /* Subsidiary OID */ + sub = strbuf_new(); + put_ber_id_len(sub, 6, oidlen, 0); + put_data(sub, oid, oidlen); + + /* Append the OID to the sequence */ + put_ber_id_len(seq, 0, sub->len, + ASN1_CLASS_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED); + put_data(seq, sub->s, sub->len); + strbuf_free(sub); + + /* Subsidiary BIT STRING */ + sub = strbuf_new(); + put_ber_id_len(sub, 3, 2 + pointlen, 0); + put_byte(sub, 0); + put_data(sub, pubblob->s+39, 1 + pointlen); + + /* Append the BIT STRING to the sequence */ + put_ber_id_len(seq, 1, sub->len, + ASN1_CLASS_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED); + put_data(seq, sub->s, sub->len); + strbuf_free(sub); + + /* Write the full sequence with header to the output blob. */ + put_ber_id_len(outblob, 16, seq->len, ASN1_CONSTRUCTED); + put_data(outblob, seq->s, seq->len); + strbuf_free(seq); + + header = "-----BEGIN EC PRIVATE KEY-----\n"; + footer = "-----END EC PRIVATE KEY-----\n"; + } else { + unreachable("bad key alg in openssh_pem_write"); + } + + /* + * Encrypt the key. + * + * For the moment, we still encrypt our OpenSSH keys using + * old-style 3DES. + */ + if (passphrase) { + unsigned char keybuf[32]; + int origlen, outlen, pad; + + /* + * Padding on OpenSSH keys is deterministic. The number of + * padding bytes is always more than zero, and always at most + * the cipher block length. The value of each padding byte is + * equal to the number of padding bytes. So a plaintext that's + * an exact multiple of the block size will be padded with 08 + * 08 08 08 08 08 08 08 (assuming a 64-bit block cipher); a + * plaintext one byte less than a multiple of the block size + * will be padded with just 01. + * + * This enables the OpenSSL key decryption function to strip + * off the padding algorithmically and return the unpadded + * plaintext to the next layer: it looks at the final byte, and + * then expects to find that many bytes at the end of the data + * with the same value. Those are all removed and the rest is + * returned. + */ + origlen = outblob->len; + outlen = (origlen + 8) &~ 7; + pad = outlen - origlen; + put_padding(outblob, pad, pad); + + /* + * Invent an iv, and derive the encryption key. + */ + random_read(iv, 8); + + openssh_pem_derivekey(ptrlen_from_asciz(passphrase), iv, keybuf); + + /* + * Now encrypt the key blob. + */ + des3_encrypt_pubkey_ossh(keybuf, iv, + outblob->u, outlen); + + smemclr(keybuf, sizeof(keybuf)); + } + + /* + * And save it. We'll use Unix line endings just in case it's + * subsequently transferred in binary mode. + */ + fp = f_open(filename, "wb", true); /* ensure Unix line endings */ + if (!fp) + goto error; + fputs(header, fp); + if (passphrase) { + fprintf(fp, "Proc-Type: 4,ENCRYPTED\nDEK-Info: DES-EDE3-CBC,"); + for (i = 0; i < 8; i++) + fprintf(fp, "%02X", iv[i]); + fprintf(fp, "\n\n"); + } + base64_encode(fp, outblob->u, outblob->len, 64); + fputs(footer, fp); + fclose(fp); + ret = true; + + error: + if (outblob) + strbuf_free(outblob); + if (spareblob) { + smemclr(spareblob, sparelen); + sfree(spareblob); + } + if (privblob) + strbuf_free(privblob); + if (pubblob) + strbuf_free(pubblob); + return ret; +} + +/* ---------------------------------------------------------------------- + * Code to read and write OpenSSH private keys in the new-style format. + */ + +typedef enum { + ON_E_NONE, ON_E_AES256CBC, ON_E_AES256CTR +} openssh_new_cipher; +typedef enum { + ON_K_NONE, ON_K_BCRYPT +} openssh_new_kdf; + +struct openssh_new_key { + openssh_new_cipher cipher; + openssh_new_kdf kdf; + union { + struct { + int rounds; + /* This points to a position within keyblob, not a + * separately allocated thing */ + ptrlen salt; + } bcrypt; + } kdfopts; + int nkeys, key_wanted; + /* This too points to a position within keyblob */ + ptrlen private; + + strbuf *keyblob; +}; + +static struct openssh_new_key *load_openssh_new_key(const Filename *filename, + const char **errmsg_p) +{ + struct openssh_new_key *ret; + FILE *fp = NULL; + char *line = NULL; + const char *errmsg; + char *p; + char base64_bit[4]; + int base64_chars = 0; + BinarySource src[1]; + ptrlen str; + unsigned key_index; + + ret = snew(struct openssh_new_key); + ret->keyblob = strbuf_new_nm(); + + fp = f_open(filename, "r", false); + if (!fp) { + errmsg = "unable to open key file"; + goto error; + } + + if (!(line = fgetline(fp))) { + errmsg = "unexpected end of file"; + goto error; + } + strip_crlf(line); + if (0 != strcmp(line, "-----BEGIN OPENSSH PRIVATE KEY-----")) { + errmsg = "file does not begin with OpenSSH new-style key header"; + goto error; + } + smemclr(line, strlen(line)); + sfree(line); + line = NULL; + + while (1) { + if (!(line = fgetline(fp))) { + errmsg = "unexpected end of file"; + goto error; + } + strip_crlf(line); + if (0 == strcmp(line, "-----END OPENSSH PRIVATE KEY-----")) { + sfree(line); + line = NULL; + break; /* done */ + } + + p = line; + while (isbase64(*p)) { + base64_bit[base64_chars++] = *p; + if (base64_chars == 4) { + unsigned char out[3]; + int len; + + base64_chars = 0; + + len = base64_decode_atom(base64_bit, out); + + if (len <= 0) { + errmsg = "invalid base64 encoding"; + goto error; + } + + put_data(ret->keyblob, out, len); + + smemclr(out, sizeof(out)); + } + + p++; + } + smemclr(line, strlen(line)); + sfree(line); + line = NULL; + } + + fclose(fp); + fp = NULL; + + if (ret->keyblob->len == 0) { + errmsg = "key body not present"; + goto error; + } + + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(ret->keyblob)); + + if (strcmp(get_asciz(src), "openssh-key-v1") != 0) { + errmsg = "new-style OpenSSH magic number missing\n"; + goto error; + } + + /* Cipher name */ + str = get_string(src); + if (ptrlen_eq_string(str, "none")) { + ret->cipher = ON_E_NONE; + } else if (ptrlen_eq_string(str, "aes256-cbc")) { + ret->cipher = ON_E_AES256CBC; + } else if (ptrlen_eq_string(str, "aes256-ctr")) { + ret->cipher = ON_E_AES256CTR; + } else { + errmsg = get_err(src) ? "no cipher name found" : + "unrecognised cipher name\n"; + goto error; + } + + /* Key derivation function name */ + str = get_string(src); + if (ptrlen_eq_string(str, "none")) { + ret->kdf = ON_K_NONE; + } else if (ptrlen_eq_string(str, "bcrypt")) { + ret->kdf = ON_K_BCRYPT; + } else { + errmsg = get_err(src) ? "no kdf name found" : + "unrecognised kdf name\n"; + goto error; + } + + /* KDF extra options */ + str = get_string(src); + switch (ret->kdf) { + case ON_K_NONE: + if (str.len != 0) { + errmsg = "expected empty options string for 'none' kdf"; + goto error; + } + break; + case ON_K_BCRYPT: + { + BinarySource opts[1]; + + BinarySource_BARE_INIT_PL(opts, str); + ret->kdfopts.bcrypt.salt = get_string(opts); + ret->kdfopts.bcrypt.rounds = get_uint32(opts); + + if (get_err(opts)) { + errmsg = "failed to parse bcrypt options string"; + goto error; + } + } + break; + } + + /* + * At this point we expect a uint32 saying how many keys are + * stored in this file. OpenSSH new-style key files can + * contain more than one. Currently we don't have any user + * interface to specify which one we're trying to extract, so + * we just bomb out with an error if more than one is found in + * the file. However, I've put in all the mechanism here to + * extract the nth one for a given n, in case we later connect + * up some UI to that mechanism. Just arrange that the + * 'key_wanted' field is set to a value in the range [0, + * nkeys) by some mechanism. + */ + ret->nkeys = toint(get_uint32(src)); + if (ret->nkeys != 1) { + errmsg = get_err(src) ? "no key count found" : + "multiple keys in new-style OpenSSH key file not supported\n"; + goto error; + } + ret->key_wanted = 0; + + /* Read and ignore a string per public key. */ + for (key_index = 0; key_index < ret->nkeys; key_index++) + str = get_string(src); + + /* + * Now we expect a string containing the encrypted part of the + * key file. + */ + ret->private = get_string(src); + if (get_err(src)) { + errmsg = "no private key container string found\n"; + goto error; + } + + /* + * And now we're done, until asked to actually decrypt. + */ + + smemclr(base64_bit, sizeof(base64_bit)); + if (errmsg_p) *errmsg_p = NULL; + return ret; + + error: + if (line) { + smemclr(line, strlen(line)); + sfree(line); + line = NULL; + } + smemclr(base64_bit, sizeof(base64_bit)); + if (ret) { + strbuf_free(ret->keyblob); + smemclr(ret, sizeof(*ret)); + sfree(ret); + } + if (errmsg_p) *errmsg_p = errmsg; + if (fp) fclose(fp); + return NULL; +} + +static bool openssh_new_encrypted(const Filename *filename) +{ + struct openssh_new_key *key = load_openssh_new_key(filename, NULL); + bool ret; + + if (!key) + return false; + ret = (key->cipher != ON_E_NONE); + strbuf_free(key->keyblob); + smemclr(key, sizeof(*key)); + sfree(key); + return ret; +} + +static ssh2_userkey *openssh_new_read( + const Filename *filename, const char *passphrase, const char **errmsg_p) +{ + struct openssh_new_key *key = load_openssh_new_key(filename, errmsg_p); + ssh2_userkey *retkey = NULL; + ssh2_userkey *retval = NULL; + const char *errmsg; + unsigned checkint; + BinarySource src[1]; + int key_index; + const ssh_keyalg *alg = NULL; + + if (!key) + return NULL; + + if (key->cipher != ON_E_NONE) { + unsigned char keybuf[48]; + int keysize; + + /* + * Construct the decryption key, and decrypt the string. + */ + switch (key->cipher) { + case ON_E_NONE: + keysize = 0; + break; + case ON_E_AES256CBC: + case ON_E_AES256CTR: + keysize = 48; /* 32 byte key + 16 byte IV */ + break; + default: + unreachable("Bad cipher enumeration value"); + } + assert(keysize <= sizeof(keybuf)); + switch (key->kdf) { + case ON_K_NONE: + memset(keybuf, 0, keysize); + break; + case ON_K_BCRYPT: + openssh_bcrypt(passphrase, + key->kdfopts.bcrypt.salt.ptr, + key->kdfopts.bcrypt.salt.len, + key->kdfopts.bcrypt.rounds, + keybuf, keysize); + break; + default: + unreachable("Bad kdf enumeration value"); + } + switch (key->cipher) { + case ON_E_NONE: + break; + case ON_E_AES256CBC: + case ON_E_AES256CTR: + if (key->private.len % 16 != 0) { + errmsg = "private key container length is not a" + " multiple of AES block size\n"; + goto error; + } + { + ssh_cipher *cipher = ssh_cipher_new( + key->cipher == ON_E_AES256CBC ? + &ssh_aes256_cbc : &ssh_aes256_sdctr); + ssh_cipher_setkey(cipher, keybuf); + ssh_cipher_setiv(cipher, keybuf + 32); + /* Decrypt the private section in place, casting away + * the const from key->private being a ptrlen */ + ssh_cipher_decrypt(cipher, (char *)key->private.ptr, + key->private.len); + ssh_cipher_free(cipher); + } + break; + default: + unreachable("Bad cipher enumeration value"); + } + } + + /* + * Now parse the entire encrypted section, and extract the key + * identified by key_wanted. + */ + BinarySource_BARE_INIT_PL(src, key->private); + + checkint = get_uint32(src); + if (get_uint32(src) != checkint || get_err(src)) { + errmsg = "decryption check failed"; + goto error; + } + + retkey = snew(ssh2_userkey); + retkey->key = NULL; + retkey->comment = NULL; + + for (key_index = 0; key_index < key->nkeys; key_index++) { + ptrlen comment; + + /* + * Identify the key type. + */ + alg = find_pubkey_alg_len(get_string(src)); + if (!alg) { + errmsg = "private key type not recognised\n"; + goto error; + } + + /* + * Read the key. We have to do this even if it's not the one + * we want, because it's the only way to find out how much + * data to skip past to get to the next key in the file. + */ + retkey->key = ssh_key_new_priv_openssh(alg, src); + if (get_err(src)) { + errmsg = "unable to read entire private key"; + goto error; + } + if (!retkey->key) { + errmsg = "unable to create key data structure"; + goto error; + } + if (key_index != key->key_wanted) { + /* + * If this isn't the key we're looking for, throw it away. + */ + ssh_key_free(retkey->key); + retkey->key = NULL; + } + + /* + * Read the key comment. + */ + comment = get_string(src); + if (get_err(src)) { + errmsg = "unable to read key comment"; + goto error; + } + if (key_index == key->key_wanted) { + assert(retkey); + retkey->comment = mkstr(comment); + } + } + + if (!retkey->key) { + errmsg = "key index out of range"; + goto error; + } + + /* + * Now we expect nothing left but padding. + */ + { + unsigned char expected_pad_byte = 1; + while (get_avail(src) > 0) + if (get_byte(src) != expected_pad_byte++) { + errmsg = "padding at end of private string did not match"; + goto error; + } + } + + errmsg = NULL; /* no error */ + retval = retkey; + retkey = NULL; /* prevent the free */ + + error: + if (retkey) { + sfree(retkey->comment); + if (retkey->key) + ssh_key_free(retkey->key); + sfree(retkey); + } + strbuf_free(key->keyblob); + smemclr(key, sizeof(*key)); + sfree(key); + if (errmsg_p) *errmsg_p = errmsg; + return retval; +} + +static bool openssh_new_write( + const Filename *filename, ssh2_userkey *key, const char *passphrase) +{ + strbuf *pubblob, *privblob, *cblob; + int padvalue; + unsigned checkint; + bool ret = false; + unsigned char bcrypt_salt[16]; + const int bcrypt_rounds = 16; + FILE *fp; + + /* + * Fetch the key blobs and find out the lengths of things. + */ + pubblob = strbuf_new(); + ssh_key_public_blob(key->key, BinarySink_UPCAST(pubblob)); + privblob = strbuf_new_nm(); + ssh_key_openssh_blob(key->key, BinarySink_UPCAST(privblob)); + + /* + * Construct the cleartext version of the blob. + */ + cblob = strbuf_new_nm(); + + /* Magic number. */ + put_asciz(cblob, "openssh-key-v1"); + + /* Cipher and kdf names, and kdf options. */ + if (!passphrase) { + memset(bcrypt_salt, 0, sizeof(bcrypt_salt)); /* prevent warnings */ + put_stringz(cblob, "none"); + put_stringz(cblob, "none"); + put_stringz(cblob, ""); + } else { + strbuf *substr; + + random_read(bcrypt_salt, sizeof(bcrypt_salt)); + put_stringz(cblob, "aes256-ctr"); + put_stringz(cblob, "bcrypt"); + substr = strbuf_new_nm(); + put_string(substr, bcrypt_salt, sizeof(bcrypt_salt)); + put_uint32(substr, bcrypt_rounds); + put_stringsb(cblob, substr); + } + + /* Number of keys. */ + put_uint32(cblob, 1); + + /* Public blob. */ + put_string(cblob, pubblob->s, pubblob->len); + + /* Private section. */ + { + strbuf *cpblob = strbuf_new_nm(); + + /* checkint. */ + uint8_t checkint_buf[4]; + random_read(checkint_buf, 4); + checkint = GET_32BIT_MSB_FIRST(checkint_buf); + put_uint32(cpblob, checkint); + put_uint32(cpblob, checkint); + + /* Private key. The main private blob goes inline, with no string + * wrapper. */ + put_stringz(cpblob, ssh_key_ssh_id(key->key)); + put_data(cpblob, privblob->s, privblob->len); + + /* Comment. */ + put_stringz(cpblob, key->comment); + + /* Pad out the encrypted section. */ + padvalue = 1; + do { + put_byte(cpblob, padvalue++); + } while (cpblob->len & 15); + + if (passphrase) { + /* + * Encrypt the private section. We need 48 bytes of key + * material: 32 bytes AES key + 16 bytes iv. + */ + unsigned char keybuf[48]; + ssh_cipher *cipher; + + openssh_bcrypt(passphrase, + bcrypt_salt, sizeof(bcrypt_salt), bcrypt_rounds, + keybuf, sizeof(keybuf)); + + cipher = ssh_cipher_new(&ssh_aes256_sdctr); + ssh_cipher_setkey(cipher, keybuf); + ssh_cipher_setiv(cipher, keybuf + 32); + ssh_cipher_encrypt(cipher, cpblob->u, cpblob->len); + ssh_cipher_free(cipher); + + smemclr(keybuf, sizeof(keybuf)); + } + + put_stringsb(cblob, cpblob); + } + + /* + * And save it. We'll use Unix line endings just in case it's + * subsequently transferred in binary mode. + */ + fp = f_open(filename, "wb", true); /* ensure Unix line endings */ + if (!fp) + goto error; + fputs("-----BEGIN OPENSSH PRIVATE KEY-----\n", fp); + base64_encode(fp, cblob->u, cblob->len, 64); + fputs("-----END OPENSSH PRIVATE KEY-----\n", fp); + fclose(fp); + ret = true; + + error: + if (cblob) + strbuf_free(cblob); + if (privblob) + strbuf_free(privblob); + if (pubblob) + strbuf_free(pubblob); + return ret; +} + +/* ---------------------------------------------------------------------- + * The switch function openssh_auto_write(), which chooses one of the + * concrete OpenSSH output formats based on the key type. + */ +static bool openssh_auto_write( + const Filename *filename, ssh2_userkey *key, const char *passphrase) +{ + /* + * The old OpenSSH format supports a fixed list of key types. We + * assume that anything not in that fixed list is newer, and hence + * will use the new format. + */ + if (ssh_key_alg(key->key) == &ssh_dss || + ssh_key_alg(key->key) == &ssh_rsa || + ssh_key_alg(key->key) == &ssh_ecdsa_nistp256 || + ssh_key_alg(key->key) == &ssh_ecdsa_nistp384 || + ssh_key_alg(key->key) == &ssh_ecdsa_nistp521) + return openssh_pem_write(filename, key, passphrase); + else + return openssh_new_write(filename, key, passphrase); +} + +/* ---------------------------------------------------------------------- + * Code to read ssh.com private keys. + */ + +/* + * The format of the base64 blob is largely SSH-2-packet-formatted, + * except that mpints are a bit different: they're more like the + * old SSH-1 mpint. You have a 32-bit bit count N, followed by + * (N+7)/8 bytes of data. + * + * So. The blob contains: + * + * - uint32 0x3f6ff9eb (magic number) + * - uint32 size (total blob size) + * - string key-type (see below) + * - string cipher-type (tells you if key is encrypted) + * - string encrypted-blob + * + * (The first size field includes the size field itself and the + * magic number before it. All other size fields are ordinary SSH-2 + * strings, so the size field indicates how much data is to + * _follow_.) + * + * The encrypted blob, once decrypted, contains a single string + * which in turn contains the payload. (This allows padding to be + * added after that string while still making it clear where the + * real payload ends. Also it probably makes for a reasonable + * decryption check.) + * + * The payload blob, for an RSA key, contains: + * - mpint e + * - mpint d + * - mpint n (yes, the public and private stuff is intermixed) + * - mpint u (presumably inverse of p mod q) + * - mpint p (p is the smaller prime) + * - mpint q (q is the larger) + * + * For a DSA key, the payload blob contains: + * - uint32 0 + * - mpint p + * - mpint g + * - mpint q + * - mpint y + * - mpint x + * + * Alternatively, if the parameters are `predefined', that + * (0,p,g,q) sequence can be replaced by a uint32 1 and a string + * containing some predefined parameter specification. *shudder*, + * but I doubt we'll encounter this in real life. + * + * The key type strings are ghastly. The RSA key I looked at had a + * type string of + * + * `if-modn{sign{rsa-pkcs1-sha1},encrypt{rsa-pkcs1v2-oaep}}' + * + * and the DSA key wasn't much better: + * + * `dl-modp{sign{dsa-nist-sha1},dh{plain}}' + * + * It isn't clear that these will always be the same. I think it + * might be wise just to look at the `if-modn{sign{rsa' and + * `dl-modp{sign{dsa' prefixes. + * + * Finally, the encryption. The cipher-type string appears to be + * either `none' or `3des-cbc'. Looks as if this is SSH-2-style + * 3des-cbc (i.e. outer cbc rather than inner). The key is created + * from the passphrase by means of yet another hashing faff: + * + * - first 16 bytes are MD5(passphrase) + * - next 16 bytes are MD5(passphrase || first 16 bytes) + * - if there were more, they'd be MD5(passphrase || first 32), + * and so on. + */ + +#define SSHCOM_MAGIC_NUMBER 0x3f6ff9eb + +struct sshcom_key { + char comment[256]; /* allowing any length is overkill */ + strbuf *keyblob; +}; + +static struct sshcom_key *load_sshcom_key(const Filename *filename, + const char **errmsg_p) +{ + struct sshcom_key *ret; + FILE *fp; + char *line = NULL; + int hdrstart, len; + const char *errmsg; + char *p; + bool headers_done; + char base64_bit[4]; + int base64_chars = 0; + + ret = snew(struct sshcom_key); + ret->comment[0] = '\0'; + ret->keyblob = strbuf_new_nm(); + + fp = f_open(filename, "r", false); + if (!fp) { + errmsg = "unable to open key file"; + goto error; + } + if (!(line = fgetline(fp))) { + errmsg = "unexpected end of file"; + goto error; + } + strip_crlf(line); + if (0 != strcmp(line, "---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----")) { + errmsg = "file does not begin with ssh.com key header"; + goto error; + } + smemclr(line, strlen(line)); + sfree(line); + line = NULL; + + headers_done = false; + while (1) { + if (!(line = fgetline(fp))) { + errmsg = "unexpected end of file"; + goto error; + } + strip_crlf(line); + if (!strcmp(line, "---- END SSH2 ENCRYPTED PRIVATE KEY ----")) { + sfree(line); + line = NULL; + break; /* done */ + } + if ((p = strchr(line, ':')) != NULL) { + if (headers_done) { + errmsg = "header found in body of key data"; + goto error; + } + *p++ = '\0'; + while (*p && isspace((unsigned char)*p)) p++; + hdrstart = p - line; + + /* + * Header lines can end in a trailing backslash for + * continuation. + */ + len = hdrstart + strlen(line+hdrstart); + assert(!line[len]); + while (line[len-1] == '\\') { + char *line2; + int line2len; + + line2 = fgetline(fp); + if (!line2) { + errmsg = "unexpected end of file"; + goto error; + } + strip_crlf(line2); + + line2len = strlen(line2); + line = sresize(line, len + line2len + 1, char); + strcpy(line + len - 1, line2); + len += line2len - 1; + assert(!line[len]); + + smemclr(line2, strlen(line2)); + sfree(line2); + line2 = NULL; + } + p = line + hdrstart; + strip_crlf(p); + if (!strcmp(line, "Comment")) { + /* Strip quotes in comment if present. */ + if (p[0] == '"' && p[strlen(p)-1] == '"') { + p++; + p[strlen(p)-1] = '\0'; + } + strncpy(ret->comment, p, sizeof(ret->comment)); + ret->comment[sizeof(ret->comment)-1] = '\0'; + } + } else { + headers_done = true; + + p = line; + while (isbase64(*p)) { + base64_bit[base64_chars++] = *p; + if (base64_chars == 4) { + unsigned char out[3]; + + base64_chars = 0; + + len = base64_decode_atom(base64_bit, out); + + if (len <= 0) { + errmsg = "invalid base64 encoding"; + goto error; + } + + put_data(ret->keyblob, out, len); + } + + p++; + } + } + smemclr(line, strlen(line)); + sfree(line); + line = NULL; + } + + if (ret->keyblob->len == 0) { + errmsg = "key body not present"; + goto error; + } + + fclose(fp); + if (errmsg_p) *errmsg_p = NULL; + return ret; + + error: + if (fp) + fclose(fp); + + if (line) { + smemclr(line, strlen(line)); + sfree(line); + line = NULL; + } + if (ret) { + strbuf_free(ret->keyblob); + smemclr(ret, sizeof(*ret)); + sfree(ret); + } + if (errmsg_p) *errmsg_p = errmsg; + return NULL; +} + +static bool sshcom_encrypted(const Filename *filename, char **comment) +{ + struct sshcom_key *key = load_sshcom_key(filename, NULL); + BinarySource src[1]; + ptrlen str; + bool answer = false; + + *comment = NULL; + if (!key) + goto done; + + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(key->keyblob)); + + if (get_uint32(src) != SSHCOM_MAGIC_NUMBER) + goto done; /* key is invalid */ + get_uint32(src); /* skip length field */ + get_string(src); /* skip key type */ + str = get_string(src); /* cipher type */ + if (get_err(src)) + goto done; /* key is invalid */ + if (!ptrlen_eq_string(str, "none")) + answer = true; + + done: + if (key) { + *comment = dupstr(key->comment); + strbuf_free(key->keyblob); + smemclr(key, sizeof(*key)); + sfree(key); + } else { + *comment = dupstr(""); + } + return answer; +} + +void BinarySink_put_mp_sshcom_from_string(BinarySink *bs, ptrlen str) +{ + const unsigned char *bytes = (const unsigned char *)str.ptr; + size_t nbytes = str.len; + int bits = nbytes * 8 - 1; + + while (bits > 0) { + if (*bytes & (1 << (bits & 7))) + break; + if (!(bits-- & 7)) + bytes++, nbytes--; + } + + put_uint32(bs, bits+1); + put_data(bs, bytes, nbytes); +} + +#define put_mp_sshcom_from_string(bs, str) \ + BinarySink_put_mp_sshcom_from_string(BinarySink_UPCAST(bs), str) + +static ptrlen BinarySource_get_mp_sshcom_as_string(BinarySource *src) +{ + unsigned bits = get_uint32(src); + return get_data(src, (bits + 7) / 8); +} + +#define get_mp_sshcom_as_string(bs) \ + BinarySource_get_mp_sshcom_as_string(BinarySource_UPCAST(bs)) + +static void sshcom_derivekey(ptrlen passphrase, uint8_t *keybuf) +{ + /* + * Derive the encryption key for an ssh.com key file from the + * passphrase and iv/salt: + * + * - let block A equal MD5(passphrase) + * - let block B equal MD5(passphrase || A) + * - block C would be MD5(passphrase || A || B) and so on + * - encryption key is the first N bytes of A || B + */ + ssh_hash *h; + + h = ssh_hash_new(&ssh_md5); + put_datapl(h, passphrase); + ssh_hash_final(ssh_hash_copy(h), keybuf); + put_data(h, keybuf, 16); + ssh_hash_final(h, keybuf + 16); +} + +static ssh2_userkey *sshcom_read( + const Filename *filename, const char *passphrase, const char **errmsg_p) +{ + struct sshcom_key *key = load_sshcom_key(filename, errmsg_p); + const char *errmsg; + BinarySource src[1]; + ptrlen str, ciphertext; + int publen; + const char prefix_rsa[] = "if-modn{sign{rsa"; + const char prefix_dsa[] = "dl-modp{sign{dsa"; + enum { RSA, DSA } type; + bool encrypted; + ssh2_userkey *ret = NULL, *retkey; + const ssh_keyalg *alg; + strbuf *blob = NULL; + + if (!key) + return NULL; + + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(key->keyblob)); + + if (get_uint32(src) != SSHCOM_MAGIC_NUMBER) { + errmsg = "key does not begin with magic number"; + goto error; + } + get_uint32(src); /* skip length field */ + + /* + * Determine the key type. + */ + str = get_string(src); + if (str.len > sizeof(prefix_rsa) - 1 && + !memcmp(str.ptr, prefix_rsa, sizeof(prefix_rsa) - 1)) { + type = RSA; + } else if (str.len > sizeof(prefix_dsa) - 1 && + !memcmp(str.ptr, prefix_dsa, sizeof(prefix_dsa) - 1)) { + type = DSA; + } else { + errmsg = "key is of unknown type"; + goto error; + } + + /* + * Determine the cipher type. + */ + str = get_string(src); + if (ptrlen_eq_string(str, "none")) + encrypted = false; + else if (ptrlen_eq_string(str, "3des-cbc")) + encrypted = true; + else { + errmsg = "key encryption is of unknown type"; + goto error; + } + + /* + * Get hold of the encrypted part of the key. + */ + ciphertext = get_string(src); + if (ciphertext.len == 0) { + errmsg = "no key data found"; + goto error; + } + + /* + * Decrypt it if necessary. + */ + if (encrypted) { + /* + * Derive encryption key from passphrase and iv/salt: + * + * - let block A equal MD5(passphrase) + * - let block B equal MD5(passphrase || A) + * - block C would be MD5(passphrase || A || B) and so on + * - encryption key is the first N bytes of A || B + */ + unsigned char keybuf[32], iv[8]; + + if (ciphertext.len % 8 != 0) { + errmsg = "encrypted part of key is not a multiple of cipher block" + " size"; + goto error; + } + + sshcom_derivekey(ptrlen_from_asciz(passphrase), keybuf); + + /* + * Now decrypt the key blob in place (casting away const from + * ciphertext being a ptrlen). + */ + memset(iv, 0, sizeof(iv)); + des3_decrypt_pubkey_ossh(keybuf, iv, + (char *)ciphertext.ptr, ciphertext.len); + + smemclr(keybuf, sizeof(keybuf)); + + /* + * Hereafter we return WRONG_PASSPHRASE for any parsing + * error. (But only if we've just tried to decrypt it! + * Returning WRONG_PASSPHRASE for an unencrypted key is + * automatic doom.) + */ + if (encrypted) + ret = SSH2_WRONG_PASSPHRASE; + } + + /* + * Expect the ciphertext to be formatted as a containing string, + * and reinitialise src to start parsing the inside of that string. + */ + BinarySource_BARE_INIT_PL(src, ciphertext); + str = get_string(src); + if (get_err(src)) { + errmsg = "containing string was ill-formed"; + goto error; + } + BinarySource_BARE_INIT_PL(src, str); + + /* + * Now we break down into RSA versus DSA. In either case we'll + * construct public and private blobs in our own format, and + * end up feeding them to ssh_key_new_priv(). + */ + blob = strbuf_new_nm(); + if (type == RSA) { + ptrlen n, e, d, u, p, q; + + e = get_mp_sshcom_as_string(src); + d = get_mp_sshcom_as_string(src); + n = get_mp_sshcom_as_string(src); + u = get_mp_sshcom_as_string(src); + p = get_mp_sshcom_as_string(src); + q = get_mp_sshcom_as_string(src); + if (get_err(src)) { + errmsg = "key data did not contain six integers"; + goto error; + } + + alg = &ssh_rsa; + put_stringz(blob, "ssh-rsa"); + put_mp_ssh2_from_string(blob, e); + put_mp_ssh2_from_string(blob, n); + publen = blob->len; + put_mp_ssh2_from_string(blob, d); + put_mp_ssh2_from_string(blob, q); + put_mp_ssh2_from_string(blob, p); + put_mp_ssh2_from_string(blob, u); + } else { + ptrlen p, q, g, x, y; + + assert(type == DSA); /* the only other option from the if above */ + + if (get_uint32(src) != 0) { + errmsg = "predefined DSA parameters not supported"; + goto error; + } + p = get_mp_sshcom_as_string(src); + g = get_mp_sshcom_as_string(src); + q = get_mp_sshcom_as_string(src); + y = get_mp_sshcom_as_string(src); + x = get_mp_sshcom_as_string(src); + if (get_err(src)) { + errmsg = "key data did not contain five integers"; + goto error; + } + + alg = &ssh_dss; + put_stringz(blob, "ssh-dss"); + put_mp_ssh2_from_string(blob, p); + put_mp_ssh2_from_string(blob, q); + put_mp_ssh2_from_string(blob, g); + put_mp_ssh2_from_string(blob, y); + publen = blob->len; + put_mp_ssh2_from_string(blob, x); + } + + retkey = snew(ssh2_userkey); + retkey->key = ssh_key_new_priv( + alg, make_ptrlen(blob->u, publen), + make_ptrlen(blob->u + publen, blob->len - publen)); + if (!retkey->key) { + sfree(retkey); + errmsg = "unable to create key data structure"; + goto error; + } + retkey->comment = dupstr(key->comment); + + errmsg = NULL; /* no error */ + ret = retkey; + + error: + if (blob) { + strbuf_free(blob); + } + strbuf_free(key->keyblob); + smemclr(key, sizeof(*key)); + sfree(key); + if (errmsg_p) *errmsg_p = errmsg; + return ret; +} + +static bool sshcom_write( + const Filename *filename, ssh2_userkey *key, const char *passphrase) +{ + strbuf *pubblob, *privblob, *outblob; + ptrlen numbers[6]; + int nnumbers, lenpos, i; + bool initial_zero; + BinarySource src[1]; + const char *type; + char *ciphertext; + int cipherlen; + bool ret = false; + FILE *fp; + + /* + * Fetch the key blobs. + */ + pubblob = strbuf_new(); + ssh_key_public_blob(key->key, BinarySink_UPCAST(pubblob)); + privblob = strbuf_new_nm(); + ssh_key_private_blob(key->key, BinarySink_UPCAST(privblob)); + outblob = NULL; + + /* + * Find the sequence of integers to be encoded into the OpenSSH + * key blob, and also decide on the header line. + */ + if (ssh_key_alg(key->key) == &ssh_rsa) { + ptrlen n, e, d, p, q, iqmp; + + /* + * These blobs were generated from inside PuTTY, so we needn't + * treat them as untrusted. + */ + BinarySource_BARE_INIT(src, pubblob->u, pubblob->len); + get_string(src); /* skip algorithm name */ + e = get_string(src); + n = get_string(src); + BinarySource_BARE_INIT(src, privblob->u, privblob->len); + d = get_string(src); + p = get_string(src); + q = get_string(src); + iqmp = get_string(src); + + assert(!get_err(src)); /* can't go wrong */ + + numbers[0] = e; + numbers[1] = d; + numbers[2] = n; + numbers[3] = iqmp; + numbers[4] = q; + numbers[5] = p; + + nnumbers = 6; + initial_zero = false; + type = "if-modn{sign{rsa-pkcs1-sha1},encrypt{rsa-pkcs1v2-oaep}}"; + } else if (ssh_key_alg(key->key) == &ssh_dss) { + ptrlen p, q, g, y, x; + + /* + * These blobs were generated from inside PuTTY, so we needn't + * treat them as untrusted. + */ + BinarySource_BARE_INIT(src, pubblob->u, pubblob->len); + get_string(src); /* skip algorithm name */ + p = get_string(src); + q = get_string(src); + g = get_string(src); + y = get_string(src); + BinarySource_BARE_INIT(src, privblob->u, privblob->len); + x = get_string(src); + + assert(!get_err(src)); /* can't go wrong */ + + numbers[0] = p; + numbers[1] = g; + numbers[2] = q; + numbers[3] = y; + numbers[4] = x; + + nnumbers = 5; + initial_zero = true; + type = "dl-modp{sign{dsa-nist-sha1},dh{plain}}"; + } else { + goto error; /* unsupported key type */ + } + + outblob = strbuf_new_nm(); + + /* + * Create the unencrypted key blob. + */ + put_uint32(outblob, SSHCOM_MAGIC_NUMBER); + put_uint32(outblob, 0); /* length field, fill in later */ + put_stringz(outblob, type); + put_stringz(outblob, passphrase ? "3des-cbc" : "none"); + lenpos = outblob->len; /* remember this position */ + put_uint32(outblob, 0); /* encrypted-blob size */ + put_uint32(outblob, 0); /* encrypted-payload size */ + if (initial_zero) + put_uint32(outblob, 0); + for (i = 0; i < nnumbers; i++) + put_mp_sshcom_from_string(outblob, numbers[i]); + /* Now wrap up the encrypted payload. */ + PUT_32BIT_MSB_FIRST(outblob->s + lenpos + 4, + outblob->len - (lenpos + 8)); + /* Pad encrypted blob to a multiple of cipher block size. */ + if (passphrase) { + int padding = -(outblob->len - (lenpos+4)) & 7; + uint8_t padding_buf[8]; + random_read(padding_buf, padding); + put_data(outblob, padding_buf, padding); + } + ciphertext = outblob->s + lenpos + 4; + cipherlen = outblob->len - (lenpos + 4); + assert(!passphrase || cipherlen % 8 == 0); + /* Wrap up the encrypted blob string. */ + PUT_32BIT_MSB_FIRST(outblob->s + lenpos, cipherlen); + /* And finally fill in the total length field. */ + PUT_32BIT_MSB_FIRST(outblob->s + 4, outblob->len); + + /* + * Encrypt the key. + */ + if (passphrase) { + unsigned char keybuf[32], iv[8]; + + sshcom_derivekey(ptrlen_from_asciz(passphrase), keybuf); + + /* + * Now decrypt the key blob. + */ + memset(iv, 0, sizeof(iv)); + des3_encrypt_pubkey_ossh(keybuf, iv, ciphertext, cipherlen); + + smemclr(keybuf, sizeof(keybuf)); + } + + /* + * And save it. We'll use Unix line endings just in case it's + * subsequently transferred in binary mode. + */ + fp = f_open(filename, "wb", true); /* ensure Unix line endings */ + if (!fp) + goto error; + fputs("---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----\n", fp); + fprintf(fp, "Comment: \""); + /* + * Comment header is broken with backslash-newline if it goes + * over 70 chars. Although it's surrounded by quotes, it + * _doesn't_ escape backslashes or quotes within the string. + * Don't ask me, I didn't design it. + */ + { + int slen = 60; /* starts at 60 due to "Comment: " */ + char *c = key->comment; + while ((int)strlen(c) > slen) { + fprintf(fp, "%.*s\\\n", slen, c); + c += slen; + slen = 70; /* allow 70 chars on subsequent lines */ + } + fprintf(fp, "%s\"\n", c); + } + base64_encode(fp, outblob->u, outblob->len, 70); + fputs("---- END SSH2 ENCRYPTED PRIVATE KEY ----\n", fp); + fclose(fp); + ret = true; + + error: + if (outblob) + strbuf_free(outblob); + if (privblob) + strbuf_free(privblob); + if (pubblob) + strbuf_free(pubblob); + return ret; +} diff --git a/0.73_My_PuTTY/ldisc.c b/0.74_My_PuTTY/ldisc.c similarity index 99% rename from 0.73_My_PuTTY/ldisc.c rename to 0.74_My_PuTTY/ldisc.c index 1409dd2..4b800ad 100644 --- a/0.73_My_PuTTY/ldisc.c +++ b/0.74_My_PuTTY/ldisc.c @@ -157,9 +157,6 @@ void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive) int keyflag = 0; assert(ldisc->term); -#ifndef MOD_PERSO - assert(len); -#endif /* rutty: */ #ifdef MOD_RUTTY diff --git a/0.73_My_PuTTY/ldisc.h b/0.74_My_PuTTY/ldisc.h similarity index 100% rename from 0.73_My_PuTTY/ldisc.h rename to 0.74_My_PuTTY/ldisc.h diff --git a/0.73_My_PuTTY/licence.h b/0.74_My_PuTTY/licence.h similarity index 100% rename from 0.73_My_PuTTY/licence.h rename to 0.74_My_PuTTY/licence.h diff --git a/0.73_My_PuTTY/logging.c b/0.74_My_PuTTY/logging.c similarity index 98% rename from 0.73_My_PuTTY/logging.c rename to 0.74_My_PuTTY/logging.c index ea0c2ea..1e5ebea 100644 --- a/0.73_My_PuTTY/logging.c +++ b/0.74_My_PuTTY/logging.c @@ -191,7 +191,7 @@ static void logwrite(LogContext *ctx, ptrlen data) * Convenience wrapper on logwrite() which printf-formats the * string. */ -static void logprintf(LogContext *ctx, const char *fmt, ...) +static PRINTF_LIKE(2, 3) void logprintf(LogContext *ctx, const char *fmt, ...) { va_list ap; char *data; @@ -481,7 +481,7 @@ void log_packet(LogContext *ctx, int direction, int type, /* If we're about to stop omitting, it's time to say how * much we omitted. */ if ((blktype != PKTLOG_OMIT) && omitted) { - logprintf(ctx, " (%d byte%s omitted)\r\n", + logprintf(ctx, " (%"SIZEu" byte%s omitted)\r\n", omitted, (omitted==1?"":"s")); omitted = 0; } @@ -489,7 +489,8 @@ void log_packet(LogContext *ctx, int direction, int type, /* (Re-)initialise dumpdata as necessary * (start of row, or if we've just stopped omitting) */ if (!output_pos && !omitted) - sprintf(dumpdata, " %08zx%*s\r\n", p-(p%16), 1+3*16+2+16, ""); + sprintf(dumpdata, " %08"SIZEx"%*s\r\n", + p-(p%16), 1+3*16+2+16, ""); /* Deal with the current byte. */ if (blktype == PKTLOG_OMIT) { @@ -524,7 +525,7 @@ void log_packet(LogContext *ctx, int direction, int type, /* Tidy up */ if (omitted) - logprintf(ctx, " (%d byte%s omitted)\r\n", + logprintf(ctx, " (%"SIZEu" byte%s omitted)\r\n", omitted, (omitted==1?"":"s")); logflush(ctx); } diff --git a/0.73_My_PuTTY/mainchan.c b/0.74_My_PuTTY/mainchan.c similarity index 100% rename from 0.73_My_PuTTY/mainchan.c rename to 0.74_My_PuTTY/mainchan.c diff --git a/0.73_My_PuTTY/marshal.c b/0.74_My_PuTTY/marshal.c similarity index 90% rename from 0.73_My_PuTTY/marshal.c rename to 0.74_My_PuTTY/marshal.c index a9b1074..3bc613c 100644 --- a/0.73_My_PuTTY/marshal.c +++ b/0.74_My_PuTTY/marshal.c @@ -1,260 +1,271 @@ -#include -#include -#include - -#include "marshal.h" -#include "misc.h" - -void BinarySink_put_data(BinarySink *bs, const void *data, size_t len) -{ - bs->write(bs, data, len); -} - -void BinarySink_put_datapl(BinarySink *bs, ptrlen pl) -{ - BinarySink_put_data(bs, pl.ptr, pl.len); -} - -void BinarySink_put_padding(BinarySink *bs, size_t len, unsigned char padbyte) -{ - char buf[16]; - memset(buf, padbyte, sizeof(buf)); - while (len > 0) { - size_t thislen = len < sizeof(buf) ? len : sizeof(buf); - bs->write(bs, buf, thislen); - len -= thislen; - } -} - -void BinarySink_put_byte(BinarySink *bs, unsigned char val) -{ - bs->write(bs, &val, 1); -} - -void BinarySink_put_bool(BinarySink *bs, bool val) -{ - unsigned char cval = val ? 1 : 0; - bs->write(bs, &cval, 1); -} - -void BinarySink_put_uint16(BinarySink *bs, unsigned long val) -{ - unsigned char data[2]; - PUT_16BIT_MSB_FIRST(data, val); - bs->write(bs, data, sizeof(data)); -} - -void BinarySink_put_uint32(BinarySink *bs, unsigned long val) -{ - unsigned char data[4]; - PUT_32BIT_MSB_FIRST(data, val); - bs->write(bs, data, sizeof(data)); -} - -void BinarySink_put_uint64(BinarySink *bs, uint64_t val) -{ - unsigned char data[8]; - PUT_64BIT_MSB_FIRST(data, val); - bs->write(bs, data, sizeof(data)); -} - -void BinarySink_put_string(BinarySink *bs, const void *data, size_t len) -{ - /* Check that the string length fits in a uint32, without doing a - * potentially implementation-defined shift of more than 31 bits */ - assert((len >> 31) < 2); - - BinarySink_put_uint32(bs, len); - bs->write(bs, data, len); -} - -void BinarySink_put_stringpl(BinarySink *bs, ptrlen pl) -{ - BinarySink_put_string(bs, pl.ptr, pl.len); -} - -void BinarySink_put_stringz(BinarySink *bs, const char *str) -{ - BinarySink_put_string(bs, str, strlen(str)); -} - -void BinarySink_put_stringsb(BinarySink *bs, struct strbuf *buf) -{ - BinarySink_put_string(bs, buf->s, buf->len); - strbuf_free(buf); -} - -void BinarySink_put_asciz(BinarySink *bs, const char *str) -{ - bs->write(bs, str, strlen(str) + 1); -} - -bool BinarySink_put_pstring(BinarySink *bs, const char *str) -{ - size_t len = strlen(str); - if (len > 255) - return false; /* can't write a Pascal-style string this long */ - BinarySink_put_byte(bs, len); - bs->write(bs, str, len); - return true; -} - -/* ---------------------------------------------------------------------- */ - -static bool BinarySource_data_avail(BinarySource *src, size_t wanted) -{ - if (src->err) - return false; - - if (wanted <= src->len - src->pos) - return true; - - src->err = BSE_OUT_OF_DATA; - return false; -} - -#define avail(wanted) BinarySource_data_avail(src, wanted) -#define advance(dist) (src->pos += dist) -#define here ((const void *)((const unsigned char *)src->data + src->pos)) -#define consume(dist) \ - ((const void *)((const unsigned char *)src->data + \ - ((src->pos += dist) - dist))) - -ptrlen BinarySource_get_data(BinarySource *src, size_t wanted) -{ - if (!avail(wanted)) - return make_ptrlen("", 0); - - return make_ptrlen(consume(wanted), wanted); -} - -unsigned char BinarySource_get_byte(BinarySource *src) -{ - const unsigned char *ucp; - - if (!avail(1)) - return 0; - - ucp = consume(1); - return *ucp; -} - -bool BinarySource_get_bool(BinarySource *src) -{ - const unsigned char *ucp; - - if (!avail(1)) - return false; - - ucp = consume(1); - return *ucp != 0; -} - -unsigned BinarySource_get_uint16(BinarySource *src) -{ - const unsigned char *ucp; - - if (!avail(2)) - return 0; - - ucp = consume(2); - return GET_16BIT_MSB_FIRST(ucp); -} - -unsigned long BinarySource_get_uint32(BinarySource *src) -{ - const unsigned char *ucp; - - if (!avail(4)) - return 0; - - ucp = consume(4); - return GET_32BIT_MSB_FIRST(ucp); -} - -uint64_t BinarySource_get_uint64(BinarySource *src) -{ - const unsigned char *ucp; - - if (!avail(8)) - return 0; - - ucp = consume(8); - return GET_64BIT_MSB_FIRST(ucp); -} - -ptrlen BinarySource_get_string(BinarySource *src) -{ - const unsigned char *ucp; - size_t len; - - if (!avail(4)) - return make_ptrlen("", 0); - - ucp = consume(4); - len = GET_32BIT_MSB_FIRST(ucp); - - if (!avail(len)) - return make_ptrlen("", 0); - - return make_ptrlen(consume(len), len); -} - -const char *BinarySource_get_asciz(BinarySource *src) -{ - const char *start, *end; - - if (src->err) - return ""; - - start = here; - end = memchr(start, '\0', src->len - src->pos); - if (!end) { - src->err = BSE_OUT_OF_DATA; - return ""; - } - - advance(end + 1 - start); - return start; -} - -ptrlen BinarySource_get_pstring(BinarySource *src) -{ - const unsigned char *ucp; - size_t len; - - if (!avail(1)) - return make_ptrlen("", 0); - - ucp = consume(1); - len = *ucp; - - if (!avail(len)) - return make_ptrlen("", 0); - - return make_ptrlen(consume(len), len); -} - -static void stdio_sink_write(BinarySink *bs, const void *data, size_t len) -{ - stdio_sink *sink = BinarySink_DOWNCAST(bs, stdio_sink); - fwrite(data, 1, len, sink->fp); -} - -void stdio_sink_init(stdio_sink *sink, FILE *fp) -{ - sink->fp = fp; - BinarySink_INIT(sink, stdio_sink_write); -} - -static void bufchain_sink_write(BinarySink *bs, const void *data, size_t len) -{ - bufchain_sink *sink = BinarySink_DOWNCAST(bs, bufchain_sink); - bufchain_add(sink->ch, data, len); -} - -void bufchain_sink_init(bufchain_sink *sink, bufchain *ch) -{ - sink->ch = ch; - BinarySink_INIT(sink, bufchain_sink_write); -} +#include +#include +#include + +#include "marshal.h" +#include "misc.h" + +void BinarySink_put_data(BinarySink *bs, const void *data, size_t len) +{ + bs->write(bs, data, len); +} + +void BinarySink_put_datapl(BinarySink *bs, ptrlen pl) +{ + BinarySink_put_data(bs, pl.ptr, pl.len); +} + +void BinarySink_put_padding(BinarySink *bs, size_t len, unsigned char padbyte) +{ + char buf[16]; + memset(buf, padbyte, sizeof(buf)); + while (len > 0) { + size_t thislen = len < sizeof(buf) ? len : sizeof(buf); + bs->write(bs, buf, thislen); + len -= thislen; + } +} + +void BinarySink_put_byte(BinarySink *bs, unsigned char val) +{ + bs->write(bs, &val, 1); +} + +void BinarySink_put_bool(BinarySink *bs, bool val) +{ + unsigned char cval = val ? 1 : 0; + bs->write(bs, &cval, 1); +} + +void BinarySink_put_uint16(BinarySink *bs, unsigned long val) +{ + unsigned char data[2]; + PUT_16BIT_MSB_FIRST(data, val); + bs->write(bs, data, sizeof(data)); +} + +void BinarySink_put_uint32(BinarySink *bs, unsigned long val) +{ + unsigned char data[4]; + PUT_32BIT_MSB_FIRST(data, val); + bs->write(bs, data, sizeof(data)); +} + +void BinarySink_put_uint64(BinarySink *bs, uint64_t val) +{ + unsigned char data[8]; + PUT_64BIT_MSB_FIRST(data, val); + bs->write(bs, data, sizeof(data)); +} + +void BinarySink_put_string(BinarySink *bs, const void *data, size_t len) +{ + /* Check that the string length fits in a uint32, without doing a + * potentially implementation-defined shift of more than 31 bits */ + assert((len >> 31) < 2); + + BinarySink_put_uint32(bs, len); + bs->write(bs, data, len); +} + +void BinarySink_put_stringpl(BinarySink *bs, ptrlen pl) +{ + BinarySink_put_string(bs, pl.ptr, pl.len); +} + +void BinarySink_put_stringz(BinarySink *bs, const char *str) +{ + BinarySink_put_string(bs, str, strlen(str)); +} + +void BinarySink_put_stringsb(BinarySink *bs, struct strbuf *buf) +{ + BinarySink_put_string(bs, buf->s, buf->len); + strbuf_free(buf); +} + +void BinarySink_put_asciz(BinarySink *bs, const char *str) +{ + bs->write(bs, str, strlen(str) + 1); +} + +bool BinarySink_put_pstring(BinarySink *bs, const char *str) +{ + size_t len = strlen(str); + if (len > 255) + return false; /* can't write a Pascal-style string this long */ + BinarySink_put_byte(bs, len); + bs->write(bs, str, len); + return true; +} + +/* ---------------------------------------------------------------------- */ + +static bool BinarySource_data_avail(BinarySource *src, size_t wanted) +{ + if (src->err) + return false; + + if (wanted <= src->len - src->pos) + return true; + + src->err = BSE_OUT_OF_DATA; + return false; +} + +#define avail(wanted) BinarySource_data_avail(src, wanted) +#define advance(dist) (src->pos += dist) +#define here ((const void *)((const unsigned char *)src->data + src->pos)) +#define consume(dist) \ + ((const void *)((const unsigned char *)src->data + \ + ((src->pos += dist) - dist))) + +ptrlen BinarySource_get_data(BinarySource *src, size_t wanted) +{ + if (!avail(wanted)) + return make_ptrlen("", 0); + + return make_ptrlen(consume(wanted), wanted); +} + +unsigned char BinarySource_get_byte(BinarySource *src) +{ + const unsigned char *ucp; + + if (!avail(1)) + return 0; + + ucp = consume(1); + return *ucp; +} + +bool BinarySource_get_bool(BinarySource *src) +{ + const unsigned char *ucp; + + if (!avail(1)) + return false; + + ucp = consume(1); + return *ucp != 0; +} + +unsigned BinarySource_get_uint16(BinarySource *src) +{ + const unsigned char *ucp; + + if (!avail(2)) + return 0; + + ucp = consume(2); + return GET_16BIT_MSB_FIRST(ucp); +} + +unsigned long BinarySource_get_uint32(BinarySource *src) +{ + const unsigned char *ucp; + + if (!avail(4)) + return 0; + + ucp = consume(4); + return GET_32BIT_MSB_FIRST(ucp); +} + +uint64_t BinarySource_get_uint64(BinarySource *src) +{ + const unsigned char *ucp; + + if (!avail(8)) + return 0; + + ucp = consume(8); + return GET_64BIT_MSB_FIRST(ucp); +} + +ptrlen BinarySource_get_string(BinarySource *src) +{ + const unsigned char *ucp; + size_t len; + + if (!avail(4)) + return make_ptrlen("", 0); + + ucp = consume(4); + len = GET_32BIT_MSB_FIRST(ucp); + + if (!avail(len)) + return make_ptrlen("", 0); + + return make_ptrlen(consume(len), len); +} + +const char *BinarySource_get_asciz(BinarySource *src) +{ + const char *start, *end; + + if (src->err) + return ""; + + start = here; + end = memchr(start, '\0', src->len - src->pos); + if (!end) { + src->err = BSE_OUT_OF_DATA; + return ""; + } + + advance(end + 1 - start); + return start; +} + +ptrlen BinarySource_get_pstring(BinarySource *src) +{ + const unsigned char *ucp; + size_t len; + + if (!avail(1)) + return make_ptrlen("", 0); + + ucp = consume(1); + len = *ucp; + + if (!avail(len)) + return make_ptrlen("", 0); + + return make_ptrlen(consume(len), len); +} + +void BinarySource_REWIND_TO__(BinarySource *src, size_t pos) +{ + if (pos <= src->len) { + src->pos = pos; + src->err = BSE_NO_ERROR; /* clear any existing error */ + } else { + src->pos = src->len; + src->err = BSE_OUT_OF_DATA; /* new error if we rewind out of range */ + } +} + +static void stdio_sink_write(BinarySink *bs, const void *data, size_t len) +{ + stdio_sink *sink = BinarySink_DOWNCAST(bs, stdio_sink); + fwrite(data, 1, len, sink->fp); +} + +void stdio_sink_init(stdio_sink *sink, FILE *fp) +{ + sink->fp = fp; + BinarySink_INIT(sink, stdio_sink_write); +} + +static void bufchain_sink_write(BinarySink *bs, const void *data, size_t len) +{ + bufchain_sink *sink = BinarySink_DOWNCAST(bs, bufchain_sink); + bufchain_add(sink->ch, data, len); +} + +void bufchain_sink_init(bufchain_sink *sink, bufchain *ch) +{ + sink->ch = ch; + BinarySink_INIT(sink, bufchain_sink_write); +} diff --git a/0.73_My_PuTTY/marshal.h b/0.74_My_PuTTY/marshal.h similarity index 95% rename from 0.73_My_PuTTY/marshal.h rename to 0.74_My_PuTTY/marshal.h index 817620e..a76bc8c 100644 --- a/0.73_My_PuTTY/marshal.h +++ b/0.74_My_PuTTY/marshal.h @@ -1,323 +1,331 @@ -#ifndef PUTTY_MARSHAL_H -#define PUTTY_MARSHAL_H - -#include "defs.h" - -#include - -/* - * A sort of 'abstract base class' or 'interface' or 'trait' which is - * the common feature of all types that want to accept data formatted - * using the SSH binary conventions of uint32, string, mpint etc. - */ -struct BinarySink { - void (*write)(BinarySink *sink, const void *data, size_t len); - BinarySink *binarysink_; -}; - -/* - * To define a structure type as a valid target for binary formatted - * data, put 'BinarySink_IMPLEMENTATION' in its declaration, and when - * an instance is set up, use 'BinarySink_INIT' to initialise the - * 'base class' state, providing a function pointer to be the - * implementation of the write() call above. - */ -#define BinarySink_IMPLEMENTATION BinarySink binarysink_[1] -#define BinarySink_INIT(obj, writefn) \ - ((obj)->binarysink_->write = (writefn), \ - (obj)->binarysink_->binarysink_ = (obj)->binarysink_) - -/* - * To define a larger structure type as a valid BinarySink in such a - * way that it will delegate the write method to some other object, - * put 'BinarySink_DELEGATE_IMPLEMENTATION' in its declaration, and - * when an instance is set up, use 'BinarySink_DELEGATE_INIT' to point - * at the object it wants to delegate to. - * - * In such a delegated structure, you might sometimes want to have the - * delegation stop being valid (e.g. it might be delegating to an - * object that only sometimes exists). You can null out the delegate - * pointer using BinarySink_DELEGATE_CLEAR. - */ -#define BinarySink_DELEGATE_IMPLEMENTATION BinarySink *binarysink_ -#define BinarySink_DELEGATE_INIT(obj, othersink) \ - ((obj)->binarysink_ = BinarySink_UPCAST(othersink)) -#define BinarySink_DELEGATE_CLEAR(obj) ((obj)->binarysink_ = NULL) - -/* - * The implementing type's write function will want to downcast its - * 'BinarySink *' parameter back to the more specific type. Also, - * sometimes you'll want to upcast a pointer to a particular - * implementing type into an abstract 'BinarySink *' to pass to - * generic subroutines not defined in this file. These macros do that - * job. - * - * Importantly, BinarySink_UPCAST can also be applied to a BinarySink - * * itself (and leaves it unchanged). That's achieved by a small - * piece of C trickery: implementing structures and the BinarySink - * structure itself both contain a field called binarysink_, but in - * implementing objects it's a BinarySink[1] whereas in the abstract - * type it's a 'BinarySink *' pointing back to the same structure, - * meaning that you can say 'foo->binarysink_' in either case and get - * a pointer type by different methods. - */ -#define BinarySink_DOWNCAST(object, type) \ - TYPECHECK((object) == ((type *)0)->binarysink_, \ - ((type *)(((char *)(object)) - offsetof(type, binarysink_)))) -#define BinarySink_UPCAST(object) \ - TYPECHECK((object)->binarysink_ == (BinarySink *)0, \ - (object)->binarysink_) - -/* - * If you structure-copy an object that's implementing BinarySink, - * then that tricky self-pointer in its trait subobject will point to - * the wrong place. You could call BinarySink_INIT again, but this - * macro is terser and does all that's needed to fix up the copied - * object. - */ -#define BinarySink_COPIED(obj) \ - ((obj)->binarysink_->binarysink_ = (obj)->binarysink_) - -/* - * The put_* macros are the main client to this system. Any structure - * which implements the BinarySink 'trait' is valid for use as the - * first parameter of any of these put_* macros. - */ - -/* Basic big-endian integer types. */ -#define put_byte(bs, val) \ - BinarySink_put_byte(BinarySink_UPCAST(bs), val) -#define put_uint16(bs, val) \ - BinarySink_put_uint16(BinarySink_UPCAST(bs), val) -#define put_uint32(bs, val) \ - BinarySink_put_uint32(BinarySink_UPCAST(bs), val) -#define put_uint64(bs, val) \ - BinarySink_put_uint64(BinarySink_UPCAST(bs), val) - -/* SSH booleans, encoded as a single byte storing either 0 or 1. */ -#define put_bool(bs, val) \ - BinarySink_put_bool(BinarySink_UPCAST(bs), val) - -/* SSH strings, with a leading uint32 length field. 'stringz' is a - * convenience function that takes an ordinary C zero-terminated - * string as input. 'stringsb' takes a strbuf * as input, and - * finalises it as a side effect (handy for multi-level marshalling in - * which you use these same functions to format an inner blob of data - * that then gets wrapped into a string container in an outer one). */ -#define put_string(bs, val, len) \ - BinarySink_put_string(BinarySink_UPCAST(bs),val,len) -#define put_stringpl(bs, ptrlen) \ - BinarySink_put_stringpl(BinarySink_UPCAST(bs),ptrlen) -#define put_stringz(bs, val) \ - BinarySink_put_stringz(BinarySink_UPCAST(bs), val) -#define put_stringsb(bs, val) \ - BinarySink_put_stringsb(BinarySink_UPCAST(bs), val) - -/* Other string outputs: 'asciz' emits the string data directly into - * the output including the terminating \0, and 'pstring' emits the - * string in Pascal style with a leading _one_-byte length field. - * pstring can fail if the string is too long. */ -#define put_asciz(bs, val) \ - BinarySink_put_asciz(BinarySink_UPCAST(bs), val) -#define put_pstring(bs, val) \ - BinarySink_put_pstring(BinarySink_UPCAST(bs), val) - -/* Multiprecision integers, in both the SSH-1 and SSH-2 formats. */ -#define put_mp_ssh1(bs, val) \ - BinarySink_put_mp_ssh1(BinarySink_UPCAST(bs), val) -#define put_mp_ssh2(bs, val) \ - BinarySink_put_mp_ssh2(BinarySink_UPCAST(bs), val) - -/* Padding with a specified byte. */ -#define put_padding(bs, len, padbyte) \ - BinarySink_put_padding(BinarySink_UPCAST(bs), len, padbyte) - -/* Fallback: just emit raw data bytes, using a syntax that matches the - * rest of these macros. */ -#define put_data(bs, val, len) \ - BinarySink_put_data(BinarySink_UPCAST(bs), val, len) -#define put_datapl(bs, pl) \ - BinarySink_put_datapl(BinarySink_UPCAST(bs), pl) - -/* - * The underlying real C functions that implement most of those - * macros. Generally you won't want to call these directly, because - * they have such cumbersome names; you call the wrapper macros above - * instead. - * - * A few functions whose wrapper macros are defined above are actually - * declared in other headers, so as to guarantee that the - * declaration(s) of their other parameter type(s) are in scope. - */ -void BinarySink_put_data(BinarySink *, const void *data, size_t len); -void BinarySink_put_datapl(BinarySink *, ptrlen); -void BinarySink_put_padding(BinarySink *, size_t len, unsigned char padbyte); -void BinarySink_put_byte(BinarySink *, unsigned char); -void BinarySink_put_bool(BinarySink *, bool); -void BinarySink_put_uint16(BinarySink *, unsigned long); -void BinarySink_put_uint32(BinarySink *, unsigned long); -void BinarySink_put_uint64(BinarySink *, uint64_t); -void BinarySink_put_string(BinarySink *, const void *data, size_t len); -void BinarySink_put_stringpl(BinarySink *, ptrlen); -void BinarySink_put_stringz(BinarySink *, const char *str); -struct strbuf; -void BinarySink_put_stringsb(BinarySink *, struct strbuf *); -void BinarySink_put_asciz(BinarySink *, const char *str); -bool BinarySink_put_pstring(BinarySink *, const char *str); -void BinarySink_put_mp_ssh1(BinarySink *bs, mp_int *x); -void BinarySink_put_mp_ssh2(BinarySink *bs, mp_int *x); - -/* ---------------------------------------------------------------------- */ - -/* - * A complementary trait structure for _un_-marshalling. - * - * This structure contains client-visible data fields rather than - * methods, because that seemed more useful than leaving it totally - * opaque. But it's still got the self-pointer system that will allow - * the set of get_* macros to target one of these itself or any other - * type that 'derives' from it. So, for example, an SSH packet - * structure can act as a BinarySource while also having additional - * fields like the packet type. - */ -typedef enum BinarySourceError { - BSE_NO_ERROR, - BSE_OUT_OF_DATA, - BSE_INVALID -} BinarySourceError; -struct BinarySource { - /* - * (data, len) is the data block being decoded. pos is the current - * position within the block. - */ - const void *data; - size_t pos, len; - - /* - * 'err' indicates whether a decoding error has happened at any - * point. Once this has been set to something other than - * BSE_NO_ERROR, it shouldn't be changed by any unmarshalling - * function. So you can safely do a long sequence of get_foo() - * operations and then test err just once at the end, rather than - * having to conditionalise every single get. - * - * The unmarshalling functions should always return some value, - * even if a decoding error occurs. Generally on error they'll - * return zero (if numeric) or the empty string (if string-based), - * or some other appropriate default value for more complicated - * types. - * - * If the usual return value is dynamically allocated (e.g. a - * bignum, or a normal C 'char *' string), then the error value is - * also dynamic in the same way. So you have to free exactly the - * same set of things whether or not there was a decoding error, - * which simplifies exit paths - for example, you could call a big - * pile of get_foo functions, then put the actual handling of the - * results under 'if (!get_err(src))', and then free everything - * outside that if. - */ - BinarySourceError err; - - /* - * Self-pointer for the implicit derivation trick, same as - * BinarySink above. - */ - BinarySource *binarysource_; -}; - -/* - * Implementation macros, similar to BinarySink. - */ -#define BinarySource_IMPLEMENTATION BinarySource binarysource_[1] -static inline void BinarySource_INIT__(BinarySource *src, ptrlen data) -{ - src->data = data.ptr; - src->len = data.len; - src->pos = 0; - src->err = BSE_NO_ERROR; - src->binarysource_ = src; -} -#define BinarySource_BARE_INIT_PL(obj, pl) \ - TYPECHECK(&(obj)->binarysource_ == (BinarySource **)0, \ - BinarySource_INIT__(obj, pl)) -#define BinarySource_BARE_INIT(obj, data_, len_) \ - BinarySource_BARE_INIT_PL(obj, make_ptrlen(data_, len_)) -#define BinarySource_INIT_PL(obj, pl) \ - TYPECHECK(&(obj)->binarysource_ == (BinarySource (*)[1])0, \ - BinarySource_INIT__(BinarySource_UPCAST(obj), pl)) -#define BinarySource_INIT(obj, data_, len_) \ - BinarySource_INIT_PL(obj, make_ptrlen(data_, len_)) -#define BinarySource_DOWNCAST(object, type) \ - TYPECHECK((object) == ((type *)0)->binarysource_, \ - ((type *)(((char *)(object)) - offsetof(type, binarysource_)))) -#define BinarySource_UPCAST(object) \ - TYPECHECK((object)->binarysource_ == (BinarySource *)0, \ - (object)->binarysource_) -#define BinarySource_COPIED(obj) \ - ((obj)->binarysource_->binarysource_ = (obj)->binarysource_) - -#define get_data(src, len) \ - BinarySource_get_data(BinarySource_UPCAST(src), len) -#define get_byte(src) \ - BinarySource_get_byte(BinarySource_UPCAST(src)) -#define get_bool(src) \ - BinarySource_get_bool(BinarySource_UPCAST(src)) -#define get_uint16(src) \ - BinarySource_get_uint16(BinarySource_UPCAST(src)) -#define get_uint32(src) \ - BinarySource_get_uint32(BinarySource_UPCAST(src)) -#define get_uint64(src) \ - BinarySource_get_uint64(BinarySource_UPCAST(src)) -#define get_string(src) \ - BinarySource_get_string(BinarySource_UPCAST(src)) -#define get_asciz(src) \ - BinarySource_get_asciz(BinarySource_UPCAST(src)) -#define get_pstring(src) \ - BinarySource_get_pstring(BinarySource_UPCAST(src)) -#define get_mp_ssh1(src) \ - BinarySource_get_mp_ssh1(BinarySource_UPCAST(src)) -#define get_mp_ssh2(src) \ - BinarySource_get_mp_ssh2(BinarySource_UPCAST(src)) -#define get_rsa_ssh1_pub(src, rsa, order) \ - BinarySource_get_rsa_ssh1_pub(BinarySource_UPCAST(src), rsa, order) -#define get_rsa_ssh1_priv(src, rsa) \ - BinarySource_get_rsa_ssh1_priv(BinarySource_UPCAST(src), rsa) - -#define get_err(src) (BinarySource_UPCAST(src)->err) -#define get_avail(src) (BinarySource_UPCAST(src)->len - \ - BinarySource_UPCAST(src)->pos) -#define get_ptr(src) \ - ((const void *)( \ - (const unsigned char *)(BinarySource_UPCAST(src)->data) + \ - BinarySource_UPCAST(src)->pos)) - -ptrlen BinarySource_get_data(BinarySource *, size_t); -unsigned char BinarySource_get_byte(BinarySource *); -bool BinarySource_get_bool(BinarySource *); -unsigned BinarySource_get_uint16(BinarySource *); -unsigned long BinarySource_get_uint32(BinarySource *); -uint64_t BinarySource_get_uint64(BinarySource *); -ptrlen BinarySource_get_string(BinarySource *); -const char *BinarySource_get_asciz(BinarySource *); -ptrlen BinarySource_get_pstring(BinarySource *); -mp_int *BinarySource_get_mp_ssh1(BinarySource *src); -mp_int *BinarySource_get_mp_ssh2(BinarySource *src); - -/* - * A couple of useful standard BinarySink implementations, which live - * as sensibly here as anywhere else: one that makes a BinarySink - * whose effect is to write to a stdio stream, and one whose effect is - * to append to a bufchain. - */ -struct stdio_sink { - FILE *fp; - BinarySink_IMPLEMENTATION; -}; -struct bufchain_sink { - bufchain *ch; - BinarySink_IMPLEMENTATION; -}; -void stdio_sink_init(stdio_sink *sink, FILE *fp); -void bufchain_sink_init(bufchain_sink *sink, bufchain *ch); - -#endif /* PUTTY_MARSHAL_H */ +#ifndef PUTTY_MARSHAL_H +#define PUTTY_MARSHAL_H + +#include "defs.h" + +#include + +/* + * A sort of 'abstract base class' or 'interface' or 'trait' which is + * the common feature of all types that want to accept data formatted + * using the SSH binary conventions of uint32, string, mpint etc. + */ +struct BinarySink { + void (*write)(BinarySink *sink, const void *data, size_t len); + BinarySink *binarysink_; +}; + +/* + * To define a structure type as a valid target for binary formatted + * data, put 'BinarySink_IMPLEMENTATION' in its declaration, and when + * an instance is set up, use 'BinarySink_INIT' to initialise the + * 'base class' state, providing a function pointer to be the + * implementation of the write() call above. + */ +#define BinarySink_IMPLEMENTATION BinarySink binarysink_[1] +#define BinarySink_INIT(obj, writefn) \ + ((obj)->binarysink_->write = (writefn), \ + (obj)->binarysink_->binarysink_ = (obj)->binarysink_) + +/* + * To define a larger structure type as a valid BinarySink in such a + * way that it will delegate the write method to some other object, + * put 'BinarySink_DELEGATE_IMPLEMENTATION' in its declaration, and + * when an instance is set up, use 'BinarySink_DELEGATE_INIT' to point + * at the object it wants to delegate to. + * + * In such a delegated structure, you might sometimes want to have the + * delegation stop being valid (e.g. it might be delegating to an + * object that only sometimes exists). You can null out the delegate + * pointer using BinarySink_DELEGATE_CLEAR. + */ +#define BinarySink_DELEGATE_IMPLEMENTATION BinarySink *binarysink_ +#define BinarySink_DELEGATE_INIT(obj, othersink) \ + ((obj)->binarysink_ = BinarySink_UPCAST(othersink)) +#define BinarySink_DELEGATE_CLEAR(obj) ((obj)->binarysink_ = NULL) + +/* + * The implementing type's write function will want to downcast its + * 'BinarySink *' parameter back to the more specific type. Also, + * sometimes you'll want to upcast a pointer to a particular + * implementing type into an abstract 'BinarySink *' to pass to + * generic subroutines not defined in this file. These macros do that + * job. + * + * Importantly, BinarySink_UPCAST can also be applied to a BinarySink + * * itself (and leaves it unchanged). That's achieved by a small + * piece of C trickery: implementing structures and the BinarySink + * structure itself both contain a field called binarysink_, but in + * implementing objects it's a BinarySink[1] whereas in the abstract + * type it's a 'BinarySink *' pointing back to the same structure, + * meaning that you can say 'foo->binarysink_' in either case and get + * a pointer type by different methods. + */ +#define BinarySink_DOWNCAST(object, type) \ + TYPECHECK((object) == ((type *)0)->binarysink_, \ + ((type *)(((char *)(object)) - offsetof(type, binarysink_)))) +#define BinarySink_UPCAST(object) \ + TYPECHECK((object)->binarysink_ == (BinarySink *)0, \ + (object)->binarysink_) + +/* + * If you structure-copy an object that's implementing BinarySink, + * then that tricky self-pointer in its trait subobject will point to + * the wrong place. You could call BinarySink_INIT again, but this + * macro is terser and does all that's needed to fix up the copied + * object. + */ +#define BinarySink_COPIED(obj) \ + ((obj)->binarysink_->binarysink_ = (obj)->binarysink_) + +/* + * The put_* macros are the main client to this system. Any structure + * which implements the BinarySink 'trait' is valid for use as the + * first parameter of any of these put_* macros. + */ + +/* Basic big-endian integer types. */ +#define put_byte(bs, val) \ + BinarySink_put_byte(BinarySink_UPCAST(bs), val) +#define put_uint16(bs, val) \ + BinarySink_put_uint16(BinarySink_UPCAST(bs), val) +#define put_uint32(bs, val) \ + BinarySink_put_uint32(BinarySink_UPCAST(bs), val) +#define put_uint64(bs, val) \ + BinarySink_put_uint64(BinarySink_UPCAST(bs), val) + +/* SSH booleans, encoded as a single byte storing either 0 or 1. */ +#define put_bool(bs, val) \ + BinarySink_put_bool(BinarySink_UPCAST(bs), val) + +/* SSH strings, with a leading uint32 length field. 'stringz' is a + * convenience function that takes an ordinary C zero-terminated + * string as input. 'stringsb' takes a strbuf * as input, and + * finalises it as a side effect (handy for multi-level marshalling in + * which you use these same functions to format an inner blob of data + * that then gets wrapped into a string container in an outer one). */ +#define put_string(bs, val, len) \ + BinarySink_put_string(BinarySink_UPCAST(bs),val,len) +#define put_stringpl(bs, ptrlen) \ + BinarySink_put_stringpl(BinarySink_UPCAST(bs),ptrlen) +#define put_stringz(bs, val) \ + BinarySink_put_stringz(BinarySink_UPCAST(bs), val) +#define put_stringsb(bs, val) \ + BinarySink_put_stringsb(BinarySink_UPCAST(bs), val) + +/* Other string outputs: 'asciz' emits the string data directly into + * the output including the terminating \0, and 'pstring' emits the + * string in Pascal style with a leading _one_-byte length field. + * pstring can fail if the string is too long. */ +#define put_asciz(bs, val) \ + BinarySink_put_asciz(BinarySink_UPCAST(bs), val) +#define put_pstring(bs, val) \ + BinarySink_put_pstring(BinarySink_UPCAST(bs), val) + +/* Multiprecision integers, in both the SSH-1 and SSH-2 formats. */ +#define put_mp_ssh1(bs, val) \ + BinarySink_put_mp_ssh1(BinarySink_UPCAST(bs), val) +#define put_mp_ssh2(bs, val) \ + BinarySink_put_mp_ssh2(BinarySink_UPCAST(bs), val) + +/* Padding with a specified byte. */ +#define put_padding(bs, len, padbyte) \ + BinarySink_put_padding(BinarySink_UPCAST(bs), len, padbyte) + +/* Fallback: just emit raw data bytes, using a syntax that matches the + * rest of these macros. */ +#define put_data(bs, val, len) \ + BinarySink_put_data(BinarySink_UPCAST(bs), val, len) +#define put_datapl(bs, pl) \ + BinarySink_put_datapl(BinarySink_UPCAST(bs), pl) + +/* + * The underlying real C functions that implement most of those + * macros. Generally you won't want to call these directly, because + * they have such cumbersome names; you call the wrapper macros above + * instead. + * + * A few functions whose wrapper macros are defined above are actually + * declared in other headers, so as to guarantee that the + * declaration(s) of their other parameter type(s) are in scope. + */ +void BinarySink_put_data(BinarySink *, const void *data, size_t len); +void BinarySink_put_datapl(BinarySink *, ptrlen); +void BinarySink_put_padding(BinarySink *, size_t len, unsigned char padbyte); +void BinarySink_put_byte(BinarySink *, unsigned char); +void BinarySink_put_bool(BinarySink *, bool); +void BinarySink_put_uint16(BinarySink *, unsigned long); +void BinarySink_put_uint32(BinarySink *, unsigned long); +void BinarySink_put_uint64(BinarySink *, uint64_t); +void BinarySink_put_string(BinarySink *, const void *data, size_t len); +void BinarySink_put_stringpl(BinarySink *, ptrlen); +void BinarySink_put_stringz(BinarySink *, const char *str); +struct strbuf; +void BinarySink_put_stringsb(BinarySink *, struct strbuf *); +void BinarySink_put_asciz(BinarySink *, const char *str); +bool BinarySink_put_pstring(BinarySink *, const char *str); +void BinarySink_put_mp_ssh1(BinarySink *bs, mp_int *x); +void BinarySink_put_mp_ssh2(BinarySink *bs, mp_int *x); + +/* ---------------------------------------------------------------------- */ + +/* + * A complementary trait structure for _un_-marshalling. + * + * This structure contains client-visible data fields rather than + * methods, because that seemed more useful than leaving it totally + * opaque. But it's still got the self-pointer system that will allow + * the set of get_* macros to target one of these itself or any other + * type that 'derives' from it. So, for example, an SSH packet + * structure can act as a BinarySource while also having additional + * fields like the packet type. + */ +typedef enum BinarySourceError { + BSE_NO_ERROR, + BSE_OUT_OF_DATA, + BSE_INVALID +} BinarySourceError; +struct BinarySource { + /* + * (data, len) is the data block being decoded. pos is the current + * position within the block. + */ + const void *data; + size_t pos, len; + + /* + * 'err' indicates whether a decoding error has happened at any + * point. Once this has been set to something other than + * BSE_NO_ERROR, it shouldn't be changed by any unmarshalling + * function. So you can safely do a long sequence of get_foo() + * operations and then test err just once at the end, rather than + * having to conditionalise every single get. + * + * The unmarshalling functions should always return some value, + * even if a decoding error occurs. Generally on error they'll + * return zero (if numeric) or the empty string (if string-based), + * or some other appropriate default value for more complicated + * types. + * + * If the usual return value is dynamically allocated (e.g. a + * bignum, or a normal C 'char *' string), then the error value is + * also dynamic in the same way. So you have to free exactly the + * same set of things whether or not there was a decoding error, + * which simplifies exit paths - for example, you could call a big + * pile of get_foo functions, then put the actual handling of the + * results under 'if (!get_err(src))', and then free everything + * outside that if. + */ + BinarySourceError err; + + /* + * Self-pointer for the implicit derivation trick, same as + * BinarySink above. + */ + BinarySource *binarysource_; +}; + +/* + * Implementation macros, similar to BinarySink. + */ +#define BinarySource_IMPLEMENTATION BinarySource binarysource_[1] +static inline void BinarySource_INIT__(BinarySource *src, ptrlen data) +{ + src->data = data.ptr; + src->len = data.len; + src->pos = 0; + src->err = BSE_NO_ERROR; + src->binarysource_ = src; +} +#define BinarySource_BARE_INIT_PL(obj, pl) \ + TYPECHECK(&(obj)->binarysource_ == (BinarySource **)0, \ + BinarySource_INIT__(obj, pl)) +#define BinarySource_BARE_INIT(obj, data_, len_) \ + BinarySource_BARE_INIT_PL(obj, make_ptrlen(data_, len_)) +#define BinarySource_INIT_PL(obj, pl) \ + TYPECHECK(&(obj)->binarysource_ == (BinarySource (*)[1])0, \ + BinarySource_INIT__(BinarySource_UPCAST(obj), pl)) +#define BinarySource_INIT(obj, data_, len_) \ + BinarySource_INIT_PL(obj, make_ptrlen(data_, len_)) +#define BinarySource_DOWNCAST(object, type) \ + TYPECHECK((object) == ((type *)0)->binarysource_, \ + ((type *)(((char *)(object)) - offsetof(type, binarysource_)))) +#define BinarySource_UPCAST(object) \ + TYPECHECK((object)->binarysource_ == (BinarySource *)0, \ + (object)->binarysource_) +#define BinarySource_COPIED(obj) \ + ((obj)->binarysource_->binarysource_ = (obj)->binarysource_) +#define BinarySource_REWIND_TO(src, pos) \ + BinarySource_REWIND_TO__((src)->binarysource_, pos) +#define BinarySource_REWIND(src) \ + BinarySource_REWIND_TO__((src)->binarysource_, 0) + +#define get_data(src, len) \ + BinarySource_get_data(BinarySource_UPCAST(src), len) +#define get_byte(src) \ + BinarySource_get_byte(BinarySource_UPCAST(src)) +#define get_bool(src) \ + BinarySource_get_bool(BinarySource_UPCAST(src)) +#define get_uint16(src) \ + BinarySource_get_uint16(BinarySource_UPCAST(src)) +#define get_uint32(src) \ + BinarySource_get_uint32(BinarySource_UPCAST(src)) +#define get_uint64(src) \ + BinarySource_get_uint64(BinarySource_UPCAST(src)) +#define get_string(src) \ + BinarySource_get_string(BinarySource_UPCAST(src)) +#define get_asciz(src) \ + BinarySource_get_asciz(BinarySource_UPCAST(src)) +#define get_pstring(src) \ + BinarySource_get_pstring(BinarySource_UPCAST(src)) +#define get_mp_ssh1(src) \ + BinarySource_get_mp_ssh1(BinarySource_UPCAST(src)) +#define get_mp_ssh2(src) \ + BinarySource_get_mp_ssh2(BinarySource_UPCAST(src)) +#define get_rsa_ssh1_pub(src, rsa, order) \ + BinarySource_get_rsa_ssh1_pub(BinarySource_UPCAST(src), rsa, order) +#define get_rsa_ssh1_priv(src, rsa) \ + BinarySource_get_rsa_ssh1_priv(BinarySource_UPCAST(src), rsa) +#define get_rsa_ssh1_priv_agent(src) \ + BinarySource_get_rsa_ssh1_priv_agent(BinarySource_UPCAST(src)) + +#define get_err(src) (BinarySource_UPCAST(src)->err) +#define get_avail(src) (BinarySource_UPCAST(src)->len - \ + BinarySource_UPCAST(src)->pos) +#define get_ptr(src) \ + ((const void *)( \ + (const unsigned char *)(BinarySource_UPCAST(src)->data) + \ + BinarySource_UPCAST(src)->pos)) + +ptrlen BinarySource_get_data(BinarySource *, size_t); +unsigned char BinarySource_get_byte(BinarySource *); +bool BinarySource_get_bool(BinarySource *); +unsigned BinarySource_get_uint16(BinarySource *); +unsigned long BinarySource_get_uint32(BinarySource *); +uint64_t BinarySource_get_uint64(BinarySource *); +ptrlen BinarySource_get_string(BinarySource *); +const char *BinarySource_get_asciz(BinarySource *); +ptrlen BinarySource_get_pstring(BinarySource *); +mp_int *BinarySource_get_mp_ssh1(BinarySource *src); +mp_int *BinarySource_get_mp_ssh2(BinarySource *src); + +void BinarySource_REWIND_TO__(BinarySource *src, size_t pos); + +/* + * A couple of useful standard BinarySink implementations, which live + * as sensibly here as anywhere else: one that makes a BinarySink + * whose effect is to write to a stdio stream, and one whose effect is + * to append to a bufchain. + */ +struct stdio_sink { + FILE *fp; + BinarySink_IMPLEMENTATION; +}; +struct bufchain_sink { + bufchain *ch; + BinarySink_IMPLEMENTATION; +}; +void stdio_sink_init(stdio_sink *sink, FILE *fp); +void bufchain_sink_init(bufchain_sink *sink, bufchain *ch); + +#endif /* PUTTY_MARSHAL_H */ diff --git a/0.73_My_PuTTY/memory.c b/0.74_My_PuTTY/memory.c similarity index 81% rename from 0.73_My_PuTTY/memory.c rename to 0.74_My_PuTTY/memory.c index 5c8a3e6..144b0d9 100644 --- a/0.73_My_PuTTY/memory.c +++ b/0.74_My_PuTTY/memory.c @@ -46,22 +46,22 @@ void *saferealloc(void *ptr, size_t n, size_t size) void *p; if (n > INT_MAX / size) { - p = NULL; + p = NULL; } else { - size *= n; - if (!ptr) { + size *= n; + if (!ptr) { #ifdef MINEFIELD - p = minefield_c_malloc(size); + p = minefield_c_malloc(size); #else - p = malloc(size); + p = malloc(size); #endif - } else { + } else { #ifdef MINEFIELD - p = minefield_c_realloc(ptr, size); + p = minefield_c_realloc(ptr, size); #else - p = realloc(ptr, size); + p = realloc(ptr, size); #endif - } + } } if (!p) @@ -74,9 +74,9 @@ void safefree(void *ptr) { if (ptr) { #ifdef MINEFIELD - minefield_c_free(ptr); + minefield_c_free(ptr); #else - free(ptr); + free(ptr); #endif } } @@ -121,9 +121,11 @@ void *safegrowarray(void *ptr, size_t *allocated, size_t eltsize, void *toret; if (secret) { toret = safemalloc(newsize, eltsize, 0); - memcpy(toret, ptr, oldsize * eltsize); - smemclr(ptr, oldsize * eltsize); - sfree(ptr); + if (oldsize) { + memcpy(toret, ptr, oldsize * eltsize); + smemclr(ptr, oldsize * eltsize); + sfree(ptr); + } } else { toret = saferealloc(ptr, newsize, eltsize); } diff --git a/0.73_My_PuTTY/minibidi.c b/0.74_My_PuTTY/minibidi.c similarity index 100% rename from 0.73_My_PuTTY/minibidi.c rename to 0.74_My_PuTTY/minibidi.c diff --git a/0.73_My_PuTTY/misc.c b/0.74_My_PuTTY/misc.c similarity index 88% rename from 0.73_My_PuTTY/misc.c rename to 0.74_My_PuTTY/misc.c index 9bb8477..849402c 100644 --- a/0.73_My_PuTTY/misc.c +++ b/0.74_My_PuTTY/misc.c @@ -1,389 +1,380 @@ -/* - * Platform-independent routines shared between all PuTTY programs. - * - * This file contains functions that use the kind of infrastructure - * like conf.c that tends to only live in the main applications, or - * that do things that only something like a main PuTTY application - * would need. So standalone test programs should generally be able to - * avoid linking against it. - * - * More standalone functions that depend on nothing but the C library - * live in utils.c. - */ - -#include -#include -#include -#include -#include -#include - -#include "defs.h" -#include "putty.h" -#include "misc.h" - -void seat_connection_fatal(Seat *seat, const char *fmt, ...) -{ - va_list ap; - char *msg; - - va_start(ap, fmt); - msg = dupvprintf(fmt, ap); - va_end(ap); - - seat->vt->connection_fatal(seat, msg); - sfree(msg); /* if we return */ -} - -prompts_t *new_prompts(void) -{ - prompts_t *p = snew(prompts_t); - p->prompts = NULL; - p->n_prompts = p->prompts_size = 0; - p->data = NULL; - p->to_server = true; /* to be on the safe side */ - p->name = p->instruction = NULL; - p->name_reqd = p->instr_reqd = false; - return p; -} -void add_prompt(prompts_t *p, char *promptstr, bool echo) -{ - prompt_t *pr = snew(prompt_t); - pr->prompt = promptstr; - pr->echo = echo; - pr->result = NULL; - pr->resultsize = 0; - sgrowarray(p->prompts, p->prompts_size, p->n_prompts); - p->prompts[p->n_prompts++] = pr; -} -void prompt_ensure_result_size(prompt_t *pr, int newlen) -{ - if ((int)pr->resultsize < newlen) { - char *newbuf; - newlen = newlen * 5 / 4 + 512; /* avoid too many small allocs */ - - /* - * We don't use sresize / realloc here, because we will be - * storing sensitive stuff like passwords in here, and we want - * to make sure that the data doesn't get copied around in - * memory without the old copy being destroyed. - */ - newbuf = snewn(newlen, char); - memcpy(newbuf, pr->result, pr->resultsize); - smemclr(pr->result, pr->resultsize); - sfree(pr->result); - pr->result = newbuf; - pr->resultsize = newlen; - } -} -void prompt_set_result(prompt_t *pr, const char *newstr) -{ - prompt_ensure_result_size(pr, strlen(newstr) + 1); - strcpy(pr->result, newstr); -} -void free_prompts(prompts_t *p) -{ - size_t i; - for (i=0; i < p->n_prompts; i++) { - prompt_t *pr = p->prompts[i]; - smemclr(pr->result, pr->resultsize); /* burn the evidence */ - sfree(pr->result); - sfree(pr->prompt); - sfree(pr); - } - sfree(p->prompts); - sfree(p->name); - sfree(p->instruction); - sfree(p); -} - -/* - * Determine whether or not a Conf represents a session which can - * sensibly be launched right now. - */ -bool conf_launchable(Conf *conf) -{ - if (conf_get_int(conf, CONF_protocol) == PROT_SERIAL) - return conf_get_str(conf, CONF_serline)[0] != 0; - else - return conf_get_str(conf, CONF_host)[0] != 0; -} - -char const *conf_dest(Conf *conf) -{ - if (conf_get_int(conf, CONF_protocol) == PROT_SERIAL) - return conf_get_str(conf, CONF_serline); - else - return conf_get_str(conf, CONF_host); -} - -/* - * Validate a manual host key specification (either entered in the - * GUI, or via -hostkey). If valid, we return true, and update 'key' - * to contain a canonicalised version of the key string in 'key' - * (which is guaranteed to take up at most as much space as the - * original version), suitable for putting into the Conf. If not - * valid, we return false. - */ -bool validate_manual_hostkey(char *key) -{ - char *p, *q, *r, *s; - - /* - * Step through the string word by word, looking for a word that's - * in one of the formats we like. - */ - p = key; - while ((p += strspn(p, " \t"))[0]) { - q = p; - p += strcspn(p, " \t"); - if (*p) *p++ = '\0'; - - /* - * Now q is our word. - */ - - if (strlen(q) == 16*3 - 1 && - q[strspn(q, "0123456789abcdefABCDEF:")] == 0) { - /* - * Might be a key fingerprint. Check the colons are in the - * right places, and if so, return the same fingerprint - * canonicalised into lowercase. - */ - int i; - for (i = 0; i < 16; i++) - if (q[3*i] == ':' || q[3*i+1] == ':') - goto not_fingerprint; /* sorry */ - for (i = 0; i < 15; i++) - if (q[3*i+2] != ':') - goto not_fingerprint; /* sorry */ - for (i = 0; i < 16*3 - 1; i++) - key[i] = tolower(q[i]); - key[16*3 - 1] = '\0'; - return true; - } - not_fingerprint:; - - /* - * Before we check for a public-key blob, trim newlines out of - * the middle of the word, in case someone's managed to paste - * in a public-key blob _with_ them. - */ - for (r = s = q; *r; r++) - if (*r != '\n' && *r != '\r') - *s++ = *r; - *s = '\0'; - - if (strlen(q) % 4 == 0 && strlen(q) > 2*4 && - q[strspn(q, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz+/=")] == 0) { - /* - * Might be a base64-encoded SSH-2 public key blob. Check - * that it starts with a sensible algorithm string. No - * canonicalisation is necessary for this string type. - * - * The algorithm string must be at most 64 characters long - * (RFC 4251 section 6). - */ - unsigned char decoded[6]; - unsigned alglen; - int minlen; - int len = 0; - - len += base64_decode_atom(q, decoded+len); - if (len < 3) - goto not_ssh2_blob; /* sorry */ - len += base64_decode_atom(q+4, decoded+len); - if (len < 4) - goto not_ssh2_blob; /* sorry */ - - alglen = GET_32BIT_MSB_FIRST(decoded); - if (alglen > 64) - goto not_ssh2_blob; /* sorry */ - - minlen = ((alglen + 4) + 2) / 3; - if (strlen(q) < minlen) - goto not_ssh2_blob; /* sorry */ - - strcpy(key, q); - return true; - } - not_ssh2_blob:; - } - - return false; -} - -char *buildinfo(const char *newline) -{ - strbuf *buf = strbuf_new(); - - strbuf_catf(buf, "Build platform: %d-bit %s", - (int)(CHAR_BIT * sizeof(void *)), - BUILDINFO_PLATFORM); - -#ifdef __clang_version__ -#define FOUND_COMPILER - strbuf_catf(buf, "%sCompiler: clang %s", newline, __clang_version__); -#elif defined __GNUC__ && defined __VERSION__ -#define FOUND_COMPILER - strbuf_catf(buf, "%sCompiler: gcc %s", newline, __VERSION__); -#endif - -#if defined _MSC_VER -#ifndef FOUND_COMPILER -#define FOUND_COMPILER - strbuf_catf(buf, "%sCompiler: ", newline); -#else - strbuf_catf(buf, ", emulating "); -#endif - strbuf_catf(buf, "Visual Studio", newline); -#if 0 - /* - * List of _MSC_VER values and their translations taken from - * https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros - * except for 1920, which is not yet listed on that page as of - * 2019-03-22, and was determined experimentally by Sean Kain. - * - * The pointless #if 0 branch containing this comment is there so - * that every real clause can start with #elif and there's no - * anomalous first clause. That way the patch looks nicer when you - * add extra ones. - */ -#elif _MSC_VER == 1920 - strbuf_catf(buf, " 2019 (16.x)"); -#elif _MSC_VER == 1916 - strbuf_catf(buf, " 2017 version 15.9"); -#elif _MSC_VER == 1915 - strbuf_catf(buf, " 2017 version 15.8"); -#elif _MSC_VER == 1914 - strbuf_catf(buf, " 2017 version 15.7"); -#elif _MSC_VER == 1913 - strbuf_catf(buf, " 2017 version 15.6"); -#elif _MSC_VER == 1912 - strbuf_catf(buf, " 2017 version 15.5"); -#elif _MSC_VER == 1911 - strbuf_catf(buf, " 2017 version 15.3"); -#elif _MSC_VER == 1910 - strbuf_catf(buf, " 2017 RTW (15.0)"); -#elif _MSC_VER == 1900 - strbuf_catf(buf, " 2015 (14.0)"); -#elif _MSC_VER == 1800 - strbuf_catf(buf, " 2013 (12.0)"); -#elif _MSC_VER == 1700 - strbuf_catf(buf, " 2012 (11.0)"); -#elif _MSC_VER == 1600 - strbuf_catf(buf, " 2010 (10.0)"); -#elif _MSC_VER == 1500 - strbuf_catf(buf, " 2008 (9.0)"); -#elif _MSC_VER == 1400 - strbuf_catf(buf, " 2005 (8.0)"); -#elif _MSC_VER == 1310 - strbuf_catf(buf, " .NET 2003 (7.1)"); -#elif _MSC_VER == 1300 - strbuf_catf(buf, " .NET 2002 (7.0)"); -#elif _MSC_VER == 1200 - strbuf_catf(buf, " 6.0"); -#else - strbuf_catf(buf, ", unrecognised version"); -#endif - strbuf_catf(buf, ", _MSC_VER=%d", (int)_MSC_VER); -#endif - -#ifdef BUILDINFO_GTK - { - char *gtk_buildinfo = buildinfo_gtk_version(); - if (gtk_buildinfo) { - strbuf_catf(buf, "%sCompiled against GTK version %s", - newline, gtk_buildinfo); - sfree(gtk_buildinfo); - } - } -#endif -#if defined _WINDOWS - { - int echm = has_embedded_chm(); - if (echm >= 0) - strbuf_catf(buf, "%sEmbedded HTML Help file: %s", newline, - echm ? "yes" : "no"); - } -#endif - -#if defined _WINDOWS && defined MINEFIELD - strbuf_catf(buf, "%sBuild option: MINEFIELD", newline); -#endif -#ifdef NO_SECURITY - strbuf_catf(buf, "%sBuild option: NO_SECURITY", newline); -#endif -#ifdef NO_SECUREZEROMEMORY - strbuf_catf(buf, "%sBuild option: NO_SECUREZEROMEMORY", newline); -#endif -#ifdef NO_IPV6 - strbuf_catf(buf, "%sBuild option: NO_IPV6", newline); -#endif -#ifdef NO_GSSAPI - strbuf_catf(buf, "%sBuild option: NO_GSSAPI", newline); -#endif -#ifdef STATIC_GSSAPI - strbuf_catf(buf, "%sBuild option: STATIC_GSSAPI", newline); -#endif -#ifdef UNPROTECT - strbuf_catf(buf, "%sBuild option: UNPROTECT", newline); -#endif -#ifdef FUZZING - strbuf_catf(buf, "%sBuild option: FUZZING", newline); -#endif -#ifdef DEBUG - strbuf_catf(buf, "%sBuild option: DEBUG", newline); -#endif - - strbuf_catf(buf, "%sSource commit: %s", newline, commitid); - - return strbuf_to_str(buf); -} - -size_t nullseat_output( - Seat *seat, bool is_stderr, const void *data, size_t len) { return 0; } -bool nullseat_eof(Seat *seat) { return true; } -int nullseat_get_userpass_input( - Seat *seat, prompts_t *p, bufchain *input) { return 0; } -void nullseat_notify_remote_exit(Seat *seat) {} -void nullseat_connection_fatal(Seat *seat, const char *message) {} -void nullseat_update_specials_menu(Seat *seat) {} -char *nullseat_get_ttymode(Seat *seat, const char *mode) { return NULL; } -void nullseat_set_busy_status(Seat *seat, BusyStatus status) {} -int nullseat_verify_ssh_host_key( - Seat *seat, const char *host, int port, - const char *keytype, char *keystr, char *key_fingerprint, - void (*callback)(void *ctx, int result), void *ctx) { return 0; } -int nullseat_confirm_weak_crypto_primitive( - Seat *seat, const char *algtype, const char *algname, - void (*callback)(void *ctx, int result), void *ctx) { return 0; } -int nullseat_confirm_weak_cached_hostkey( - Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, int result), void *ctx) { return 0; } -bool nullseat_is_never_utf8(Seat *seat) { return false; } -bool nullseat_is_always_utf8(Seat *seat) { return true; } -void nullseat_echoedit_update(Seat *seat, bool echoing, bool editing) {} -const char *nullseat_get_x_display(Seat *seat) { return NULL; } -bool nullseat_get_windowid(Seat *seat, long *id_out) { return false; } -bool nullseat_get_window_pixel_size( - Seat *seat, int *width, int *height) { return false; } -StripCtrlChars *nullseat_stripctrl_new( - Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) {return NULL;} -bool nullseat_set_trust_status(Seat *seat, bool tr) { return false; } -bool nullseat_set_trust_status_vacuously(Seat *seat, bool tr) { return true; } - -void sk_free_peer_info(SocketPeerInfo *pi) -{ - if (pi) { - sfree((char *)pi->addr_text); - sfree((char *)pi->log_text); - sfree(pi); - } -} - -void out_of_memory(void) -{ - modalfatalbox("Out of memory"); -} +/* + * Platform-independent routines shared between all PuTTY programs. + * + * This file contains functions that use the kind of infrastructure + * like conf.c that tends to only live in the main applications, or + * that do things that only something like a main PuTTY application + * would need. So standalone test programs should generally be able to + * avoid linking against it. + * + * More standalone functions that depend on nothing but the C library + * live in utils.c. + */ + +#include +#include +#include +#include +#include +#include + +#include "defs.h" +#include "putty.h" +#include "misc.h" + +void seat_connection_fatal(Seat *seat, const char *fmt, ...) +{ + va_list ap; + char *msg; + + va_start(ap, fmt); + msg = dupvprintf(fmt, ap); + va_end(ap); + + seat->vt->connection_fatal(seat, msg); + sfree(msg); /* if we return */ +} + +prompts_t *new_prompts(void) +{ + prompts_t *p = snew(prompts_t); + p->prompts = NULL; + p->n_prompts = p->prompts_size = 0; + p->data = NULL; + p->to_server = true; /* to be on the safe side */ + p->name = p->instruction = NULL; + p->name_reqd = p->instr_reqd = false; + return p; +} +void add_prompt(prompts_t *p, char *promptstr, bool echo) +{ + prompt_t *pr = snew(prompt_t); + pr->prompt = promptstr; + pr->echo = echo; + pr->result = strbuf_new_nm(); + sgrowarray(p->prompts, p->prompts_size, p->n_prompts); + p->prompts[p->n_prompts++] = pr; +} +void prompt_set_result(prompt_t *pr, const char *newstr) +{ + strbuf_clear(pr->result); + put_datapl(pr->result, ptrlen_from_asciz(newstr)); +} +const char *prompt_get_result_ref(prompt_t *pr) +{ + return pr->result->s; +} +char *prompt_get_result(prompt_t *pr) +{ + return dupstr(pr->result->s); +} +void free_prompts(prompts_t *p) +{ + size_t i; + for (i=0; i < p->n_prompts; i++) { + prompt_t *pr = p->prompts[i]; + strbuf_free(pr->result); + sfree(pr->prompt); + sfree(pr); + } + sfree(p->prompts); + sfree(p->name); + sfree(p->instruction); + sfree(p); +} + +/* + * Determine whether or not a Conf represents a session which can + * sensibly be launched right now. + */ +bool conf_launchable(Conf *conf) +{ + if (conf_get_int(conf, CONF_protocol) == PROT_SERIAL) + return conf_get_str(conf, CONF_serline)[0] != 0; + else + return conf_get_str(conf, CONF_host)[0] != 0; +} + +char const *conf_dest(Conf *conf) +{ + if (conf_get_int(conf, CONF_protocol) == PROT_SERIAL) + return conf_get_str(conf, CONF_serline); + else + return conf_get_str(conf, CONF_host); +} + +/* + * Validate a manual host key specification (either entered in the + * GUI, or via -hostkey). If valid, we return true, and update 'key' + * to contain a canonicalised version of the key string in 'key' + * (which is guaranteed to take up at most as much space as the + * original version), suitable for putting into the Conf. If not + * valid, we return false. + */ +bool validate_manual_hostkey(char *key) +{ + char *p, *q, *r, *s; + + /* + * Step through the string word by word, looking for a word that's + * in one of the formats we like. + */ + p = key; + while ((p += strspn(p, " \t"))[0]) { + q = p; + p += strcspn(p, " \t"); + if (*p) *p++ = '\0'; + + /* + * Now q is our word. + */ + + if (strlen(q) == 16*3 - 1 && + q[strspn(q, "0123456789abcdefABCDEF:")] == 0) { + /* + * Might be a key fingerprint. Check the colons are in the + * right places, and if so, return the same fingerprint + * canonicalised into lowercase. + */ + int i; + for (i = 0; i < 16; i++) + if (q[3*i] == ':' || q[3*i+1] == ':') + goto not_fingerprint; /* sorry */ + for (i = 0; i < 15; i++) + if (q[3*i+2] != ':') + goto not_fingerprint; /* sorry */ + for (i = 0; i < 16*3 - 1; i++) + key[i] = tolower(q[i]); + key[16*3 - 1] = '\0'; + return true; + } + not_fingerprint:; + + /* + * Before we check for a public-key blob, trim newlines out of + * the middle of the word, in case someone's managed to paste + * in a public-key blob _with_ them. + */ + for (r = s = q; *r; r++) + if (*r != '\n' && *r != '\r') + *s++ = *r; + *s = '\0'; + + if (strlen(q) % 4 == 0 && strlen(q) > 2*4 && + q[strspn(q, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz+/=")] == 0) { + /* + * Might be a base64-encoded SSH-2 public key blob. Check + * that it starts with a sensible algorithm string. No + * canonicalisation is necessary for this string type. + * + * The algorithm string must be at most 64 characters long + * (RFC 4251 section 6). + */ + unsigned char decoded[6]; + unsigned alglen; + int minlen; + int len = 0; + + len += base64_decode_atom(q, decoded+len); + if (len < 3) + goto not_ssh2_blob; /* sorry */ + len += base64_decode_atom(q+4, decoded+len); + if (len < 4) + goto not_ssh2_blob; /* sorry */ + + alglen = GET_32BIT_MSB_FIRST(decoded); + if (alglen > 64) + goto not_ssh2_blob; /* sorry */ + + minlen = ((alglen + 4) + 2) / 3; + if (strlen(q) < minlen) + goto not_ssh2_blob; /* sorry */ + + strcpy(key, q); + return true; + } + not_ssh2_blob:; + } + + return false; +} + +char *buildinfo(const char *newline) +{ + strbuf *buf = strbuf_new(); + + strbuf_catf(buf, "Build platform: %d-bit %s", + (int)(CHAR_BIT * sizeof(void *)), + BUILDINFO_PLATFORM); + +#ifdef __clang_version__ +#define FOUND_COMPILER + strbuf_catf(buf, "%sCompiler: clang %s", newline, __clang_version__); +#elif defined __GNUC__ && defined __VERSION__ +#define FOUND_COMPILER + strbuf_catf(buf, "%sCompiler: gcc %s", newline, __VERSION__); +#endif + +#if defined _MSC_VER +#ifndef FOUND_COMPILER +#define FOUND_COMPILER + strbuf_catf(buf, "%sCompiler: ", newline); +#else + strbuf_catf(buf, ", emulating "); +#endif + strbuf_catf(buf, "Visual Studio"); + +#if 0 + /* + * List of _MSC_VER values and their translations taken from + * https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros + * + * The pointless #if 0 branch containing this comment is there so + * that every real clause can start with #elif and there's no + * anomalous first clause. That way the patch looks nicer when you + * add extra ones. + */ +#elif _MSC_VER == 1923 + strbuf_catf(buf, " 2019 (16.3)"); +#elif _MSC_VER == 1922 + strbuf_catf(buf, " 2019 (16.2)"); +#elif _MSC_VER == 1921 + strbuf_catf(buf, " 2019 (16.1)"); +#elif _MSC_VER == 1920 + strbuf_catf(buf, " 2019 (16.0)"); +#elif _MSC_VER == 1916 + strbuf_catf(buf, " 2017 version 15.9"); +#elif _MSC_VER == 1915 + strbuf_catf(buf, " 2017 version 15.8"); +#elif _MSC_VER == 1914 + strbuf_catf(buf, " 2017 version 15.7"); +#elif _MSC_VER == 1913 + strbuf_catf(buf, " 2017 version 15.6"); +#elif _MSC_VER == 1912 + strbuf_catf(buf, " 2017 version 15.5"); +#elif _MSC_VER == 1911 + strbuf_catf(buf, " 2017 version 15.3"); +#elif _MSC_VER == 1910 + strbuf_catf(buf, " 2017 RTW (15.0)"); +#elif _MSC_VER == 1900 + strbuf_catf(buf, " 2015 (14.0)"); +#elif _MSC_VER == 1800 + strbuf_catf(buf, " 2013 (12.0)"); +#elif _MSC_VER == 1700 + strbuf_catf(buf, " 2012 (11.0)"); +#elif _MSC_VER == 1600 + strbuf_catf(buf, " 2010 (10.0)"); +#elif _MSC_VER == 1500 + strbuf_catf(buf, " 2008 (9.0)"); +#elif _MSC_VER == 1400 + strbuf_catf(buf, " 2005 (8.0)"); +#elif _MSC_VER == 1310 + strbuf_catf(buf, " .NET 2003 (7.1)"); +#elif _MSC_VER == 1300 + strbuf_catf(buf, " .NET 2002 (7.0)"); +#elif _MSC_VER == 1200 + strbuf_catf(buf, " 6.0"); +#else + strbuf_catf(buf, ", unrecognised version"); +#endif + strbuf_catf(buf, ", _MSC_VER=%d", (int)_MSC_VER); +#endif + +#ifdef BUILDINFO_GTK + { + char *gtk_buildinfo = buildinfo_gtk_version(); + if (gtk_buildinfo) { + strbuf_catf(buf, "%sCompiled against GTK version %s", + newline, gtk_buildinfo); + sfree(gtk_buildinfo); + } + } +#endif +#if defined _WINDOWS + { + int echm = has_embedded_chm(); + if (echm >= 0) + strbuf_catf(buf, "%sEmbedded HTML Help file: %s", newline, + echm ? "yes" : "no"); + } +#endif + +#if defined _WINDOWS && defined MINEFIELD + strbuf_catf(buf, "%sBuild option: MINEFIELD", newline); +#endif +#ifdef NO_SECURITY + strbuf_catf(buf, "%sBuild option: NO_SECURITY", newline); +#endif +#ifdef NO_SECUREZEROMEMORY + strbuf_catf(buf, "%sBuild option: NO_SECUREZEROMEMORY", newline); +#endif +#ifdef NO_IPV6 + strbuf_catf(buf, "%sBuild option: NO_IPV6", newline); +#endif +#ifdef NO_GSSAPI + strbuf_catf(buf, "%sBuild option: NO_GSSAPI", newline); +#endif +#ifdef STATIC_GSSAPI + strbuf_catf(buf, "%sBuild option: STATIC_GSSAPI", newline); +#endif +#ifdef UNPROTECT + strbuf_catf(buf, "%sBuild option: UNPROTECT", newline); +#endif +#ifdef FUZZING + strbuf_catf(buf, "%sBuild option: FUZZING", newline); +#endif +#ifdef DEBUG + strbuf_catf(buf, "%sBuild option: DEBUG", newline); +#endif + + strbuf_catf(buf, "%sSource commit: %s", newline, commitid); + + return strbuf_to_str(buf); +} + +size_t nullseat_output( + Seat *seat, bool is_stderr, const void *data, size_t len) { return 0; } +bool nullseat_eof(Seat *seat) { return true; } +int nullseat_get_userpass_input( + Seat *seat, prompts_t *p, bufchain *input) { return 0; } +void nullseat_notify_remote_exit(Seat *seat) {} +void nullseat_connection_fatal(Seat *seat, const char *message) {} +void nullseat_update_specials_menu(Seat *seat) {} +char *nullseat_get_ttymode(Seat *seat, const char *mode) { return NULL; } +void nullseat_set_busy_status(Seat *seat, BusyStatus status) {} +int nullseat_verify_ssh_host_key( + Seat *seat, const char *host, int port, + const char *keytype, char *keystr, char *key_fingerprint, + void (*callback)(void *ctx, int result), void *ctx) { return 0; } +int nullseat_confirm_weak_crypto_primitive( + Seat *seat, const char *algtype, const char *algname, + void (*callback)(void *ctx, int result), void *ctx) { return 0; } +int nullseat_confirm_weak_cached_hostkey( + Seat *seat, const char *algname, const char *betteralgs, + void (*callback)(void *ctx, int result), void *ctx) { return 0; } +bool nullseat_is_never_utf8(Seat *seat) { return false; } +bool nullseat_is_always_utf8(Seat *seat) { return true; } +void nullseat_echoedit_update(Seat *seat, bool echoing, bool editing) {} +const char *nullseat_get_x_display(Seat *seat) { return NULL; } +bool nullseat_get_windowid(Seat *seat, long *id_out) { return false; } +bool nullseat_get_window_pixel_size( + Seat *seat, int *width, int *height) { return false; } +StripCtrlChars *nullseat_stripctrl_new( + Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) {return NULL;} +bool nullseat_set_trust_status(Seat *seat, bool tr) { return false; } +bool nullseat_set_trust_status_vacuously(Seat *seat, bool tr) { return true; } + +void sk_free_peer_info(SocketPeerInfo *pi) +{ + if (pi) { + sfree((char *)pi->addr_text); + sfree((char *)pi->log_text); + sfree(pi); + } +} + +void out_of_memory(void) +{ + modalfatalbox("Out of memory"); +} diff --git a/0.73_My_PuTTY/misc.h b/0.74_My_PuTTY/misc.h similarity index 93% rename from 0.73_My_PuTTY/misc.h rename to 0.74_My_PuTTY/misc.h index 465110a..ed671f2 100644 --- a/0.73_My_PuTTY/misc.h +++ b/0.74_My_PuTTY/misc.h @@ -1,420 +1,405 @@ -/* - * Header for misc.c. - */ - -#ifndef PUTTY_MISC_H -#define PUTTY_MISC_H - -#include "defs.h" -#include "puttymem.h" -#include "marshal.h" - -#include /* for FILE * */ -#include /* for va_list */ -#include /* for abort */ -#include /* for struct tm */ -#include /* for INT_MAX/MIN */ -#include /* for assert (obviously) */ - -unsigned long parse_blocksize(const char *bs); -char ctrlparse(char *s, char **next); - -size_t host_strcspn(const char *s, const char *set); -char *host_strchr(const char *s, int c); -char *host_strrchr(const char *s, int c); -char *host_strduptrim(const char *s); - -#ifdef __GNUC__ -/* - * On MinGW, the correct compiler format checking for vsnprintf() etc - * can depend on compile-time flags; these control whether you get - * ISO C or Microsoft's non-standard format strings. - * We sometimes use __attribute__ ((format)) for our own printf-like - * functions, which are ultimately interpreted by the toolchain-chosen - * printf, so we need to take that into account to get correct warnings. - */ -#ifdef __MINGW_PRINTF_FORMAT -#define PUTTY_PRINTF_ARCHETYPE __MINGW_PRINTF_FORMAT -#else -#define PUTTY_PRINTF_ARCHETYPE printf -#endif -#endif /* __GNUC__ */ - -char *dupstr(const char *s); -char *dupcat(const char *s1, ...); -char *dupprintf(const char *fmt, ...) -#ifdef __GNUC__ - __attribute__ ((format (PUTTY_PRINTF_ARCHETYPE, 1, 2))) -#endif - ; -char *dupvprintf(const char *fmt, va_list ap); -void burnstr(char *string); - -/* - * The visible part of a strbuf structure. There's a surrounding - * implementation struct in misc.c, which isn't exposed to client - * code. - */ -struct strbuf { - char *s; - unsigned char *u; - size_t len; - BinarySink_IMPLEMENTATION; -}; - -/* strbuf constructors: strbuf_new_nm and strbuf_new differ in that a - * strbuf constructed using the _nm version will resize itself by - * alloc/copy/smemclr/free instead of realloc. Use that version for - * data sensitive enough that it's worth costing performance to - * avoid copies of it lingering in process memory. */ -strbuf *strbuf_new(void); -strbuf *strbuf_new_nm(void); - -void strbuf_free(strbuf *buf); -void *strbuf_append(strbuf *buf, size_t len); -char *strbuf_to_str(strbuf *buf); /* does free buf, but you must free result */ -void strbuf_catf(strbuf *buf, const char *fmt, ...); -void strbuf_catfv(strbuf *buf, const char *fmt, va_list ap); - -strbuf *strbuf_new_for_agent_query(void); -void strbuf_finalise_agent_query(strbuf *buf); - -/* String-to-Unicode converters that auto-allocate the destination and - * work around the rather deficient interface of mb_to_wc. - * - * These actually live in miscucs.c, not misc.c (the distinction being - * that the former is only linked into tools that also have the main - * Unicode support). */ -wchar_t *dup_mb_to_wc_c(int codepage, int flags, const char *string, int len); -wchar_t *dup_mb_to_wc(int codepage, int flags, const char *string); - -static inline int toint(unsigned u) -{ - /* - * Convert an unsigned to an int, without running into the - * undefined behaviour which happens by the strict C standard if - * the value overflows. You'd hope that sensible compilers would - * do the sensible thing in response to a cast, but actually I - * don't trust modern compilers not to do silly things like - * assuming that _obviously_ you wouldn't have caused an overflow - * and so they can elide an 'if (i < 0)' test immediately after - * the cast. - * - * Sensible compilers ought of course to optimise this entire - * function into 'just return the input value', and since it's - * also declared inline, elide it completely in their output. - */ - if (u <= (unsigned)INT_MAX) - return (int)u; - else if (u >= (unsigned)INT_MIN) /* wrap in cast _to_ unsigned is OK */ - return INT_MIN + (int)(u - (unsigned)INT_MIN); - else - return INT_MIN; /* fallback; should never occur on binary machines */ -} - -char *fgetline(FILE *fp); -bool read_file_into(BinarySink *bs, FILE *fp); -char *chomp(char *str); -bool strstartswith(const char *s, const char *t); -bool strendswith(const char *s, const char *t); - -void base64_encode_atom(const unsigned char *data, int n, char *out); -int base64_decode_atom(const char *atom, unsigned char *out); - -struct bufchain_granule; -struct bufchain_tag { - struct bufchain_granule *head, *tail; - size_t buffersize; /* current amount of buffered data */ - - void (*queue_idempotent_callback)(IdempotentCallback *ic); - IdempotentCallback *ic; -}; - -void bufchain_init(bufchain *ch); -void bufchain_clear(bufchain *ch); -size_t bufchain_size(bufchain *ch); -void bufchain_add(bufchain *ch, const void *data, size_t len); -ptrlen bufchain_prefix(bufchain *ch); -void bufchain_consume(bufchain *ch, size_t len); -void bufchain_fetch(bufchain *ch, void *data, size_t len); -void bufchain_fetch_consume(bufchain *ch, void *data, size_t len); -bool bufchain_try_fetch_consume(bufchain *ch, void *data, size_t len); -size_t bufchain_fetch_consume_up_to(bufchain *ch, void *data, size_t len); -void bufchain_set_callback_inner( - bufchain *ch, IdempotentCallback *ic, - void (*queue_idempotent_callback)(IdempotentCallback *ic)); -static inline void bufchain_set_callback(bufchain *ch, IdempotentCallback *ic) -{ - extern void queue_idempotent_callback(struct IdempotentCallback *ic); - /* Wrapper that puts in the standard queue_idempotent_callback - * function. Lives here rather than in utils.c so that standalone - * programs can use the bufchain facility without this optional - * callback feature and not need to provide a stub of - * queue_idempotent_callback. */ - bufchain_set_callback_inner(ch, ic, queue_idempotent_callback); -} - -bool validate_manual_hostkey(char *key); - -struct tm ltime(void); - -/* - * Special form of strcmp which can cope with NULL inputs. NULL is - * defined to sort before even the empty string. - */ -int nullstrcmp(const char *a, const char *b); - -static inline ptrlen make_ptrlen(const void *ptr, size_t len) -{ - ptrlen pl; - pl.ptr = ptr; - pl.len = len; - return pl; -} - -static inline ptrlen ptrlen_from_asciz(const char *str) -{ - return make_ptrlen(str, strlen(str)); -} - -static inline ptrlen ptrlen_from_strbuf(strbuf *sb) -{ - return make_ptrlen(sb->u, sb->len); -} - -bool ptrlen_eq_string(ptrlen pl, const char *str); -bool ptrlen_eq_ptrlen(ptrlen pl1, ptrlen pl2); -int ptrlen_strcmp(ptrlen pl1, ptrlen pl2); -/* ptrlen_startswith and ptrlen_endswith write through their 'tail' - * argument if and only if it is non-NULL and they return true. Hence - * you can write ptrlen_startswith(thing, prefix, &thing), writing - * back to the same ptrlen it read from, to remove a prefix if present - * and say whether it did so. */ -bool ptrlen_startswith(ptrlen whole, ptrlen prefix, ptrlen *tail); -bool ptrlen_endswith(ptrlen whole, ptrlen suffix, ptrlen *tail); -ptrlen ptrlen_get_word(ptrlen *input, const char *separators); -char *mkstr(ptrlen pl); -int string_length_for_printf(size_t); -/* Derive two printf arguments from a ptrlen, suitable for "%.*s" */ -#define PTRLEN_PRINTF(pl) \ - string_length_for_printf((pl).len), (const char *)(pl).ptr -/* Make a ptrlen out of a compile-time string literal. We try to - * enforce that it _is_ a string literal by token-pasting "" on to it, - * which should provoke a compile error if it's any other kind of - * string. */ -#define PTRLEN_LITERAL(stringlit) \ - TYPECHECK("" stringlit "", make_ptrlen(stringlit, sizeof(stringlit)-1)) -/* Make a ptrlen out of a constant byte array. */ -#define PTRLEN_FROM_CONST_BYTES(a) make_ptrlen(a, sizeof(a)) - -/* Wipe sensitive data out of memory that's about to be freed. Simpler - * than memset because we don't need the fill char parameter; also - * attempts (by fiddly use of volatile) to inhibit the compiler from - * over-cleverly trying to optimise the memset away because it knows - * the variable is going out of scope. */ -void smemclr(void *b, size_t len); - -/* Compare two fixed-length chunks of memory for equality, without - * data-dependent control flow (so an attacker with a very accurate - * stopwatch can't try to guess where the first mismatching byte was). - * Returns false for mismatch or true for equality (unlike memcmp), - * hinted at by the 'eq' in the name. */ -bool smemeq(const void *av, const void *bv, size_t len); - -/* Encode a single UTF-8 character. Assumes that illegal characters - * (such as things in the surrogate range, or > 0x10FFFF) have already - * been removed. */ -size_t encode_utf8(void *output, unsigned long ch); - -char *buildinfo(const char *newline); - -/* - * A function you can put at points in the code where execution should - * never reach in the first place. Better than assert(false), or even - * assert(false && "some explanatory message"), because some compilers - * don't interpret assert(false) as a declaration of unreachability, - * so they may still warn about pointless things like some variable - * not being initialised on the unreachable code path. - * - * I follow the assertion with a call to abort() just in case someone - * compiles with -DNDEBUG, and I wrap that abort inside my own - * function labelled NORETURN just in case some unusual kind of system - * header wasn't foresighted enough to label abort() itself that way. - */ -static inline NORETURN void unreachable_internal(void) { abort(); } -#define unreachable(msg) (assert(false && msg), unreachable_internal()) - -/* - * Debugging functions. - * - * Output goes to debug.log - * - * debug() is like printf(). - * - * dmemdump() and dmemdumpl() both do memory dumps. The difference - * is that dmemdumpl() is more suited for when the memory address is - * important (say because you'll be recording pointer values later - * on). dmemdump() is more concise. - */ - -#ifdef DEBUG -void debug_printf(const char *fmt, ...); -void debug_memdump(const void *buf, int len, bool L); -#define debug(...) (debug_printf(__VA_ARGS__)) -#define dmemdump(buf,len) (debug_memdump(buf, len, false)) -#define dmemdumpl(buf,len) (debug_memdump(buf, len, true)) -#else -#define debug(...) ((void)0) -#define dmemdump(buf,len) ((void)0) -#define dmemdumpl(buf,len) ((void)0) -#endif - -#ifndef lenof -#define lenof(x) ( (sizeof((x))) / (sizeof(*(x)))) -#endif - -#ifndef min -#define min(x,y) ( (x) < (y) ? (x) : (y) ) -#endif -#ifndef max -#define max(x,y) ( (x) > (y) ? (x) : (y) ) -#endif - -static inline uint64_t GET_64BIT_LSB_FIRST(const void *vp) -{ - const uint8_t *p = (const uint8_t *)vp; - return (((uint64_t)p[0] ) | ((uint64_t)p[1] << 8) | - ((uint64_t)p[2] << 16) | ((uint64_t)p[3] << 24) | - ((uint64_t)p[4] << 32) | ((uint64_t)p[5] << 40) | - ((uint64_t)p[6] << 48) | ((uint64_t)p[7] << 56)); -} - -static inline void PUT_64BIT_LSB_FIRST(void *vp, uint64_t value) -{ - uint8_t *p = (uint8_t *)vp; - p[0] = (uint8_t)(value); - p[1] = (uint8_t)(value >> 8); - p[2] = (uint8_t)(value >> 16); - p[3] = (uint8_t)(value >> 24); - p[4] = (uint8_t)(value >> 32); - p[5] = (uint8_t)(value >> 40); - p[6] = (uint8_t)(value >> 48); - p[7] = (uint8_t)(value >> 56); -} - -static inline uint32_t GET_32BIT_LSB_FIRST(const void *vp) -{ - const uint8_t *p = (const uint8_t *)vp; - return (((uint32_t)p[0] ) | ((uint32_t)p[1] << 8) | - ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24)); -} - -static inline void PUT_32BIT_LSB_FIRST(void *vp, uint32_t value) -{ - uint8_t *p = (uint8_t *)vp; - p[0] = (uint8_t)(value); - p[1] = (uint8_t)(value >> 8); - p[2] = (uint8_t)(value >> 16); - p[3] = (uint8_t)(value >> 24); -} - -static inline uint16_t GET_16BIT_LSB_FIRST(const void *vp) -{ - const uint8_t *p = (const uint8_t *)vp; - return (((uint16_t)p[0] ) | ((uint16_t)p[1] << 8)); -} - -static inline void PUT_16BIT_LSB_FIRST(void *vp, uint16_t value) -{ - uint8_t *p = (uint8_t *)vp; - p[0] = (uint8_t)(value); - p[1] = (uint8_t)(value >> 8); -} - -static inline uint64_t GET_64BIT_MSB_FIRST(const void *vp) -{ - const uint8_t *p = (const uint8_t *)vp; - return (((uint64_t)p[7] ) | ((uint64_t)p[6] << 8) | - ((uint64_t)p[5] << 16) | ((uint64_t)p[4] << 24) | - ((uint64_t)p[3] << 32) | ((uint64_t)p[2] << 40) | - ((uint64_t)p[1] << 48) | ((uint64_t)p[0] << 56)); -} - -static inline void PUT_64BIT_MSB_FIRST(void *vp, uint64_t value) -{ - uint8_t *p = (uint8_t *)vp; - p[7] = (uint8_t)(value); - p[6] = (uint8_t)(value >> 8); - p[5] = (uint8_t)(value >> 16); - p[4] = (uint8_t)(value >> 24); - p[3] = (uint8_t)(value >> 32); - p[2] = (uint8_t)(value >> 40); - p[1] = (uint8_t)(value >> 48); - p[0] = (uint8_t)(value >> 56); -} - -static inline uint32_t GET_32BIT_MSB_FIRST(const void *vp) -{ - const uint8_t *p = (const uint8_t *)vp; - return (((uint32_t)p[3] ) | ((uint32_t)p[2] << 8) | - ((uint32_t)p[1] << 16) | ((uint32_t)p[0] << 24)); -} - -static inline void PUT_32BIT_MSB_FIRST(void *vp, uint32_t value) -{ - uint8_t *p = (uint8_t *)vp; - p[3] = (uint8_t)(value); - p[2] = (uint8_t)(value >> 8); - p[1] = (uint8_t)(value >> 16); - p[0] = (uint8_t)(value >> 24); -} - -static inline uint16_t GET_16BIT_MSB_FIRST(const void *vp) -{ - const uint8_t *p = (const uint8_t *)vp; - return (((uint16_t)p[1] ) | ((uint16_t)p[0] << 8)); -} - -static inline void PUT_16BIT_MSB_FIRST(void *vp, uint16_t value) -{ - uint8_t *p = (uint8_t *)vp; - p[1] = (uint8_t)(value); - p[0] = (uint8_t)(value >> 8); -} - -/* Replace NULL with the empty string, permitting an idiom in which we - * get a string (pointer,length) pair that might be NULL,0 and can - * then safely say things like printf("%.*s", length, NULLTOEMPTY(ptr)) */ -static inline const char *NULLTOEMPTY(const char *s) -{ - return s ? s : ""; -} - -/* StripCtrlChars, defined in stripctrl.c: an adapter you can put on - * the front of one BinarySink and which functions as one in turn. - * Interprets its input as a stream of multibyte characters in the - * system locale, and removes any that are not either printable - * characters or newlines. */ -struct StripCtrlChars { - BinarySink_IMPLEMENTATION; - /* and this is contained in a larger structure */ -}; -StripCtrlChars *stripctrl_new( - BinarySink *bs_out, bool permit_cr, wchar_t substitution); -StripCtrlChars *stripctrl_new_term_fn( - BinarySink *bs_out, bool permit_cr, wchar_t substitution, - Terminal *term, unsigned long (*translate)( - Terminal *, term_utf8_decode *, unsigned char)); -#define stripctrl_new_term(bs, cr, sub, term) \ - stripctrl_new_term_fn(bs, cr, sub, term, term_translate) -void stripctrl_retarget(StripCtrlChars *sccpub, BinarySink *new_bs_out); -void stripctrl_reset(StripCtrlChars *sccpub); -void stripctrl_free(StripCtrlChars *sanpub); -void stripctrl_enable_line_limiting(StripCtrlChars *sccpub); -char *stripctrl_string_ptrlen(StripCtrlChars *sccpub, ptrlen str); -static inline char *stripctrl_string(StripCtrlChars *sccpub, const char *str) -{ - return stripctrl_string_ptrlen(sccpub, ptrlen_from_asciz(str)); -} - -#endif +/* + * Header for misc.c. + */ + +#ifndef PUTTY_MISC_H +#define PUTTY_MISC_H + +#include "defs.h" +#include "puttymem.h" +#include "marshal.h" + +#include /* for FILE * */ +#include /* for va_list */ +#include /* for abort */ +#include /* for struct tm */ +#include /* for INT_MAX/MIN */ +#include /* for assert (obviously) */ + +unsigned long parse_blocksize(const char *bs); +char ctrlparse(char *s, char **next); + +size_t host_strcspn(const char *s, const char *set); +char *host_strchr(const char *s, int c); +char *host_strrchr(const char *s, int c); +char *host_strduptrim(const char *s); + +char *dupstr(const char *s); +char *dupcat_fn(const char *s1, ...); +#define dupcat(...) dupcat_fn(__VA_ARGS__, (const char *)NULL) +char *dupprintf(const char *fmt, ...) PRINTF_LIKE(1, 2); +char *dupvprintf(const char *fmt, va_list ap); +void burnstr(char *string); + +/* + * The visible part of a strbuf structure. There's a surrounding + * implementation struct in misc.c, which isn't exposed to client + * code. + */ +struct strbuf { + char *s; + unsigned char *u; + size_t len; + BinarySink_IMPLEMENTATION; +}; + +/* strbuf constructors: strbuf_new_nm and strbuf_new differ in that a + * strbuf constructed using the _nm version will resize itself by + * alloc/copy/smemclr/free instead of realloc. Use that version for + * data sensitive enough that it's worth costing performance to + * avoid copies of it lingering in process memory. */ +strbuf *strbuf_new(void); +strbuf *strbuf_new_nm(void); + +void strbuf_free(strbuf *buf); +void *strbuf_append(strbuf *buf, size_t len); +void strbuf_shrink_to(strbuf *buf, size_t new_len); +void strbuf_shrink_by(strbuf *buf, size_t amount_to_remove); +char *strbuf_to_str(strbuf *buf); /* does free buf, but you must free result */ +void strbuf_catf(strbuf *buf, const char *fmt, ...) PRINTF_LIKE(2, 3); +void strbuf_catfv(strbuf *buf, const char *fmt, va_list ap); +static inline void strbuf_clear(strbuf *buf) { strbuf_shrink_to(buf, 0); } +bool strbuf_chomp(strbuf *buf, char char_to_remove); + +strbuf *strbuf_new_for_agent_query(void); +void strbuf_finalise_agent_query(strbuf *buf); + +/* String-to-Unicode converters that auto-allocate the destination and + * work around the rather deficient interface of mb_to_wc. + * + * These actually live in miscucs.c, not misc.c (the distinction being + * that the former is only linked into tools that also have the main + * Unicode support). */ +wchar_t *dup_mb_to_wc_c(int codepage, int flags, const char *string, int len); +wchar_t *dup_mb_to_wc(int codepage, int flags, const char *string); + +static inline int toint(unsigned u) +{ + /* + * Convert an unsigned to an int, without running into the + * undefined behaviour which happens by the strict C standard if + * the value overflows. You'd hope that sensible compilers would + * do the sensible thing in response to a cast, but actually I + * don't trust modern compilers not to do silly things like + * assuming that _obviously_ you wouldn't have caused an overflow + * and so they can elide an 'if (i < 0)' test immediately after + * the cast. + * + * Sensible compilers ought of course to optimise this entire + * function into 'just return the input value', and since it's + * also declared inline, elide it completely in their output. + */ + if (u <= (unsigned)INT_MAX) + return (int)u; + else if (u >= (unsigned)INT_MIN) /* wrap in cast _to_ unsigned is OK */ + return INT_MIN + (int)(u - (unsigned)INT_MIN); + else + return INT_MIN; /* fallback; should never occur on binary machines */ +} + +char *fgetline(FILE *fp); +bool read_file_into(BinarySink *bs, FILE *fp); +char *chomp(char *str); +bool strstartswith(const char *s, const char *t); +bool strendswith(const char *s, const char *t); + +void base64_encode_atom(const unsigned char *data, int n, char *out); +int base64_decode_atom(const char *atom, unsigned char *out); + +struct bufchain_granule; +struct bufchain_tag { + struct bufchain_granule *head, *tail; + size_t buffersize; /* current amount of buffered data */ + + void (*queue_idempotent_callback)(IdempotentCallback *ic); + IdempotentCallback *ic; +}; + +void bufchain_init(bufchain *ch); +void bufchain_clear(bufchain *ch); +size_t bufchain_size(bufchain *ch); +void bufchain_add(bufchain *ch, const void *data, size_t len); +ptrlen bufchain_prefix(bufchain *ch); +void bufchain_consume(bufchain *ch, size_t len); +void bufchain_fetch(bufchain *ch, void *data, size_t len); +void bufchain_fetch_consume(bufchain *ch, void *data, size_t len); +bool bufchain_try_fetch_consume(bufchain *ch, void *data, size_t len); +size_t bufchain_fetch_consume_up_to(bufchain *ch, void *data, size_t len); +void bufchain_set_callback_inner( + bufchain *ch, IdempotentCallback *ic, + void (*queue_idempotent_callback)(IdempotentCallback *ic)); +static inline void bufchain_set_callback(bufchain *ch, IdempotentCallback *ic) +{ + extern void queue_idempotent_callback(struct IdempotentCallback *ic); + /* Wrapper that puts in the standard queue_idempotent_callback + * function. Lives here rather than in utils.c so that standalone + * programs can use the bufchain facility without this optional + * callback feature and not need to provide a stub of + * queue_idempotent_callback. */ + bufchain_set_callback_inner(ch, ic, queue_idempotent_callback); +} + +bool validate_manual_hostkey(char *key); + +struct tm ltime(void); + +/* + * Special form of strcmp which can cope with NULL inputs. NULL is + * defined to sort before even the empty string. + */ +int nullstrcmp(const char *a, const char *b); + +static inline ptrlen make_ptrlen(const void *ptr, size_t len) +{ + ptrlen pl; + pl.ptr = ptr; + pl.len = len; + return pl; +} + +static inline ptrlen ptrlen_from_asciz(const char *str) +{ + return make_ptrlen(str, strlen(str)); +} + +static inline ptrlen ptrlen_from_strbuf(strbuf *sb) +{ + return make_ptrlen(sb->u, sb->len); +} + +bool ptrlen_eq_string(ptrlen pl, const char *str); +bool ptrlen_eq_ptrlen(ptrlen pl1, ptrlen pl2); +int ptrlen_strcmp(ptrlen pl1, ptrlen pl2); +/* ptrlen_startswith and ptrlen_endswith write through their 'tail' + * argument if and only if it is non-NULL and they return true. Hence + * you can write ptrlen_startswith(thing, prefix, &thing), writing + * back to the same ptrlen it read from, to remove a prefix if present + * and say whether it did so. */ +bool ptrlen_startswith(ptrlen whole, ptrlen prefix, ptrlen *tail); +bool ptrlen_endswith(ptrlen whole, ptrlen suffix, ptrlen *tail); +ptrlen ptrlen_get_word(ptrlen *input, const char *separators); +char *mkstr(ptrlen pl); +int string_length_for_printf(size_t); +/* Derive two printf arguments from a ptrlen, suitable for "%.*s" */ +#define PTRLEN_PRINTF(pl) \ + string_length_for_printf((pl).len), (const char *)(pl).ptr +/* Make a ptrlen out of a compile-time string literal. We try to + * enforce that it _is_ a string literal by token-pasting "" on to it, + * which should provoke a compile error if it's any other kind of + * string. */ +#define PTRLEN_LITERAL(stringlit) \ + TYPECHECK("" stringlit "", make_ptrlen(stringlit, sizeof(stringlit)-1)) +/* Make a ptrlen out of a constant byte array. */ +#define PTRLEN_FROM_CONST_BYTES(a) make_ptrlen(a, sizeof(a)) + +/* Wipe sensitive data out of memory that's about to be freed. Simpler + * than memset because we don't need the fill char parameter; also + * attempts (by fiddly use of volatile) to inhibit the compiler from + * over-cleverly trying to optimise the memset away because it knows + * the variable is going out of scope. */ +void smemclr(void *b, size_t len); + +/* Compare two fixed-length chunks of memory for equality, without + * data-dependent control flow (so an attacker with a very accurate + * stopwatch can't try to guess where the first mismatching byte was). + * Returns false for mismatch or true for equality (unlike memcmp), + * hinted at by the 'eq' in the name. */ +bool smemeq(const void *av, const void *bv, size_t len); + +/* Encode a single UTF-8 character. Assumes that illegal characters + * (such as things in the surrogate range, or > 0x10FFFF) have already + * been removed. */ +size_t encode_utf8(void *output, unsigned long ch); + +char *buildinfo(const char *newline); + +/* + * A function you can put at points in the code where execution should + * never reach in the first place. Better than assert(false), or even + * assert(false && "some explanatory message"), because some compilers + * don't interpret assert(false) as a declaration of unreachability, + * so they may still warn about pointless things like some variable + * not being initialised on the unreachable code path. + * + * I follow the assertion with a call to abort() just in case someone + * compiles with -DNDEBUG, and I wrap that abort inside my own + * function labelled NORETURN just in case some unusual kind of system + * header wasn't foresighted enough to label abort() itself that way. + */ +static inline NORETURN void unreachable_internal(void) { abort(); } +#define unreachable(msg) (assert(false && msg), unreachable_internal()) + +/* + * Debugging functions. + * + * Output goes to debug.log + * + * debug() is like printf(). + * + * dmemdump() and dmemdumpl() both do memory dumps. The difference + * is that dmemdumpl() is more suited for when the memory address is + * important (say because you'll be recording pointer values later + * on). dmemdump() is more concise. + */ + +#ifdef DEBUG +void debug_printf(const char *fmt, ...) PRINTF_LIKE(1, 2); +void debug_memdump(const void *buf, int len, bool L); +#define debug(...) (debug_printf(__VA_ARGS__)) +#define dmemdump(buf,len) (debug_memdump(buf, len, false)) +#define dmemdumpl(buf,len) (debug_memdump(buf, len, true)) +#else +#define debug(...) ((void)0) +#define dmemdump(buf,len) ((void)0) +#define dmemdumpl(buf,len) ((void)0) +#endif + +#ifndef lenof +#define lenof(x) ( (sizeof((x))) / (sizeof(*(x)))) +#endif + +#ifndef min +#define min(x,y) ( (x) < (y) ? (x) : (y) ) +#endif +#ifndef max +#define max(x,y) ( (x) > (y) ? (x) : (y) ) +#endif + +static inline uint64_t GET_64BIT_LSB_FIRST(const void *vp) +{ + const uint8_t *p = (const uint8_t *)vp; + return (((uint64_t)p[0] ) | ((uint64_t)p[1] << 8) | + ((uint64_t)p[2] << 16) | ((uint64_t)p[3] << 24) | + ((uint64_t)p[4] << 32) | ((uint64_t)p[5] << 40) | + ((uint64_t)p[6] << 48) | ((uint64_t)p[7] << 56)); +} + +static inline void PUT_64BIT_LSB_FIRST(void *vp, uint64_t value) +{ + uint8_t *p = (uint8_t *)vp; + p[0] = (uint8_t)(value); + p[1] = (uint8_t)(value >> 8); + p[2] = (uint8_t)(value >> 16); + p[3] = (uint8_t)(value >> 24); + p[4] = (uint8_t)(value >> 32); + p[5] = (uint8_t)(value >> 40); + p[6] = (uint8_t)(value >> 48); + p[7] = (uint8_t)(value >> 56); +} + +static inline uint32_t GET_32BIT_LSB_FIRST(const void *vp) +{ + const uint8_t *p = (const uint8_t *)vp; + return (((uint32_t)p[0] ) | ((uint32_t)p[1] << 8) | + ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24)); +} + +static inline void PUT_32BIT_LSB_FIRST(void *vp, uint32_t value) +{ + uint8_t *p = (uint8_t *)vp; + p[0] = (uint8_t)(value); + p[1] = (uint8_t)(value >> 8); + p[2] = (uint8_t)(value >> 16); + p[3] = (uint8_t)(value >> 24); +} + +static inline uint16_t GET_16BIT_LSB_FIRST(const void *vp) +{ + const uint8_t *p = (const uint8_t *)vp; + return (((uint16_t)p[0] ) | ((uint16_t)p[1] << 8)); +} + +static inline void PUT_16BIT_LSB_FIRST(void *vp, uint16_t value) +{ + uint8_t *p = (uint8_t *)vp; + p[0] = (uint8_t)(value); + p[1] = (uint8_t)(value >> 8); +} + +static inline uint64_t GET_64BIT_MSB_FIRST(const void *vp) +{ + const uint8_t *p = (const uint8_t *)vp; + return (((uint64_t)p[7] ) | ((uint64_t)p[6] << 8) | + ((uint64_t)p[5] << 16) | ((uint64_t)p[4] << 24) | + ((uint64_t)p[3] << 32) | ((uint64_t)p[2] << 40) | + ((uint64_t)p[1] << 48) | ((uint64_t)p[0] << 56)); +} + +static inline void PUT_64BIT_MSB_FIRST(void *vp, uint64_t value) +{ + uint8_t *p = (uint8_t *)vp; + p[7] = (uint8_t)(value); + p[6] = (uint8_t)(value >> 8); + p[5] = (uint8_t)(value >> 16); + p[4] = (uint8_t)(value >> 24); + p[3] = (uint8_t)(value >> 32); + p[2] = (uint8_t)(value >> 40); + p[1] = (uint8_t)(value >> 48); + p[0] = (uint8_t)(value >> 56); +} + +static inline uint32_t GET_32BIT_MSB_FIRST(const void *vp) +{ + const uint8_t *p = (const uint8_t *)vp; + return (((uint32_t)p[3] ) | ((uint32_t)p[2] << 8) | + ((uint32_t)p[1] << 16) | ((uint32_t)p[0] << 24)); +} + +static inline void PUT_32BIT_MSB_FIRST(void *vp, uint32_t value) +{ + uint8_t *p = (uint8_t *)vp; + p[3] = (uint8_t)(value); + p[2] = (uint8_t)(value >> 8); + p[1] = (uint8_t)(value >> 16); + p[0] = (uint8_t)(value >> 24); +} + +static inline uint16_t GET_16BIT_MSB_FIRST(const void *vp) +{ + const uint8_t *p = (const uint8_t *)vp; + return (((uint16_t)p[1] ) | ((uint16_t)p[0] << 8)); +} + +static inline void PUT_16BIT_MSB_FIRST(void *vp, uint16_t value) +{ + uint8_t *p = (uint8_t *)vp; + p[1] = (uint8_t)(value); + p[0] = (uint8_t)(value >> 8); +} + +/* Replace NULL with the empty string, permitting an idiom in which we + * get a string (pointer,length) pair that might be NULL,0 and can + * then safely say things like printf("%.*s", length, NULLTOEMPTY(ptr)) */ +static inline const char *NULLTOEMPTY(const char *s) +{ + return s ? s : ""; +} + +/* StripCtrlChars, defined in stripctrl.c: an adapter you can put on + * the front of one BinarySink and which functions as one in turn. + * Interprets its input as a stream of multibyte characters in the + * system locale, and removes any that are not either printable + * characters or newlines. */ +struct StripCtrlChars { + BinarySink_IMPLEMENTATION; + /* and this is contained in a larger structure */ +}; +StripCtrlChars *stripctrl_new( + BinarySink *bs_out, bool permit_cr, wchar_t substitution); +StripCtrlChars *stripctrl_new_term_fn( + BinarySink *bs_out, bool permit_cr, wchar_t substitution, + Terminal *term, unsigned long (*translate)( + Terminal *, term_utf8_decode *, unsigned char)); +#define stripctrl_new_term(bs, cr, sub, term) \ + stripctrl_new_term_fn(bs, cr, sub, term, term_translate) +void stripctrl_retarget(StripCtrlChars *sccpub, BinarySink *new_bs_out); +void stripctrl_reset(StripCtrlChars *sccpub); +void stripctrl_free(StripCtrlChars *sanpub); +void stripctrl_enable_line_limiting(StripCtrlChars *sccpub); +char *stripctrl_string_ptrlen(StripCtrlChars *sccpub, ptrlen str); +static inline char *stripctrl_string(StripCtrlChars *sccpub, const char *str) +{ + return stripctrl_string_ptrlen(sccpub, ptrlen_from_asciz(str)); +} + +#endif diff --git a/0.73_My_PuTTY/miscucs.c b/0.74_My_PuTTY/miscucs.c similarity index 100% rename from 0.73_My_PuTTY/miscucs.c rename to 0.74_My_PuTTY/miscucs.c diff --git a/0.73_My_PuTTY/mpint.c b/0.74_My_PuTTY/mpint.c similarity index 100% rename from 0.73_My_PuTTY/mpint.c rename to 0.74_My_PuTTY/mpint.c diff --git a/0.73_My_PuTTY/mpint.h b/0.74_My_PuTTY/mpint.h similarity index 100% rename from 0.73_My_PuTTY/mpint.h rename to 0.74_My_PuTTY/mpint.h diff --git a/0.73_My_PuTTY/mpint_i.h b/0.74_My_PuTTY/mpint_i.h similarity index 100% rename from 0.73_My_PuTTY/mpint_i.h rename to 0.74_My_PuTTY/mpint_i.h diff --git a/0.73_My_PuTTY/network.h b/0.74_My_PuTTY/network.h similarity index 100% rename from 0.73_My_PuTTY/network.h rename to 0.74_My_PuTTY/network.h diff --git a/0.73_My_PuTTY/nocmdline.c b/0.74_My_PuTTY/nocmdline.c similarity index 100% rename from 0.73_My_PuTTY/nocmdline.c rename to 0.74_My_PuTTY/nocmdline.c diff --git a/0.73_My_PuTTY/nocproxy.c b/0.74_My_PuTTY/nocproxy.c similarity index 100% rename from 0.73_My_PuTTY/nocproxy.c rename to 0.74_My_PuTTY/nocproxy.c diff --git a/0.73_My_PuTTY/nogss.c b/0.74_My_PuTTY/nogss.c similarity index 100% rename from 0.73_My_PuTTY/nogss.c rename to 0.74_My_PuTTY/nogss.c diff --git a/0.73_My_PuTTY/noprint.c b/0.74_My_PuTTY/noprint.c similarity index 100% rename from 0.73_My_PuTTY/noprint.c rename to 0.74_My_PuTTY/noprint.c diff --git a/0.73_My_PuTTY/noshare.c b/0.74_My_PuTTY/noshare.c similarity index 100% rename from 0.73_My_PuTTY/noshare.c rename to 0.74_My_PuTTY/noshare.c diff --git a/0.73_My_PuTTY/noterm.c b/0.74_My_PuTTY/noterm.c similarity index 100% rename from 0.73_My_PuTTY/noterm.c rename to 0.74_My_PuTTY/noterm.c diff --git a/0.73_My_PuTTY/notiming.c b/0.74_My_PuTTY/notiming.c similarity index 100% rename from 0.73_My_PuTTY/notiming.c rename to 0.74_My_PuTTY/notiming.c diff --git a/0.73_My_PuTTY/nullplug.c b/0.74_My_PuTTY/nullplug.c similarity index 100% rename from 0.73_My_PuTTY/nullplug.c rename to 0.74_My_PuTTY/nullplug.c diff --git a/0.73_My_PuTTY/pageant.c b/0.74_My_PuTTY/pageant.c similarity index 98% rename from 0.73_My_PuTTY/pageant.c rename to 0.74_My_PuTTY/pageant.c index 292e52f..250af33 100644 --- a/0.73_My_PuTTY/pageant.c +++ b/0.74_My_PuTTY/pageant.c @@ -211,13 +211,8 @@ void pageant_make_keylist2(BinarySink *bs) } } -static void plog(void *logctx, pageant_logfn_t logfn, const char *fmt, ...) -#ifdef __GNUC__ -__attribute__ ((format (PUTTY_PRINTF_ARCHETYPE, 3, 4))) -#endif - ; - -static void plog(void *logctx, pageant_logfn_t logfn, const char *fmt, ...) +static PRINTF_LIKE(3, 4) void plog(void *logctx, pageant_logfn_t logfn, + const char *fmt, ...) { /* * This is the wrapper that takes a variadic argument list and @@ -481,19 +476,7 @@ void pageant_handle_msg(BinarySink *bs, plog(logctx, logfn, "request: SSH1_AGENTC_ADD_RSA_IDENTITY"); - key = snew(RSAKey); - memset(key, 0, sizeof(RSAKey)); - - get_rsa_ssh1_pub(msg, key, RSA_SSH1_MODULUS_FIRST); - get_rsa_ssh1_priv(msg, key); - - /* SSH-1 names p and q the other way round, i.e. we have - * the inverse of p mod q and not of q mod p. We swap the - * names, because our internal RSA wants iqmp. */ - key->iqmp = get_mp_ssh1(msg); - key->q = get_mp_ssh1(msg); - key->p = get_mp_ssh1(msg); - + key = get_rsa_ssh1_priv_agent(msg); key->comment = mkstr(get_string(msg)); if (get_err(msg)) { @@ -1166,11 +1149,7 @@ int pageant_add_keyfile(Filename *filename, const char *passphrase, * length, so add a placeholder here to fill in * afterwards */ put_uint32(blob, 0); -#ifdef MOD_WINCRYPT - if (!ssh2_userkey_loadpub((const Filename **)&filename, NULL, BinarySink_UPCAST(blob), -#else if (!ssh2_userkey_loadpub(filename, NULL, BinarySink_UPCAST(blob), -#endif NULL, &error)) { *retstr = dupprintf("Couldn't load private key (%s)", error); strbuf_free(blob); @@ -1418,10 +1397,14 @@ int pageant_add_keyfile(Filename *filename, const char *passphrase, if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS) { *retstr = dupstr("The already running Pageant " "refused to add the key."); + sfree(skey->comment); + ssh_key_free(skey->key); + sfree(skey); sfree(response); return PAGEANT_ACTION_FAILURE; } + sfree(skey->comment); ssh_key_free(skey->key); sfree(skey); sfree(response); @@ -1519,6 +1502,7 @@ int pageant_enum_keys(pageant_key_enum_fn_t callback, void *callback_ctx, callback(callback_ctx, fingerprint, cbkey.comment, &cbkey); sfree(fingerprint); sfree(cbkey.comment); + strbuf_free(cbkey.blob); } sfree(keylist); diff --git a/0.73_My_PuTTY/pageant.h b/0.74_My_PuTTY/pageant.h similarity index 100% rename from 0.73_My_PuTTY/pageant.h rename to 0.74_My_PuTTY/pageant.h diff --git a/0.73_My_PuTTY/pgssapi.c b/0.74_My_PuTTY/pgssapi.c similarity index 100% rename from 0.73_My_PuTTY/pgssapi.c rename to 0.74_My_PuTTY/pgssapi.c diff --git a/0.73_My_PuTTY/pgssapi.h b/0.74_My_PuTTY/pgssapi.h similarity index 100% rename from 0.73_My_PuTTY/pgssapi.h rename to 0.74_My_PuTTY/pgssapi.h diff --git a/0.73_My_PuTTY/pinger.c b/0.74_My_PuTTY/pinger.c similarity index 100% rename from 0.73_My_PuTTY/pinger.c rename to 0.74_My_PuTTY/pinger.c diff --git a/0.73_My_PuTTY/portfwd.c b/0.74_My_PuTTY/portfwd.c similarity index 100% rename from 0.73_My_PuTTY/portfwd.c rename to 0.74_My_PuTTY/portfwd.c diff --git a/0.73_My_PuTTY/pproxy.c b/0.74_My_PuTTY/pproxy.c similarity index 100% rename from 0.73_My_PuTTY/pproxy.c rename to 0.74_My_PuTTY/pproxy.c diff --git a/0.74_My_PuTTY/proxy.c b/0.74_My_PuTTY/proxy.c new file mode 100644 index 0000000..840dae3 --- /dev/null +++ b/0.74_My_PuTTY/proxy.c @@ -0,0 +1,1517 @@ +/* + * Network proxy abstraction in PuTTY + * + * A proxy layer, if necessary, wedges itself between the network + * code and the higher level backend. + */ + +#include +#include +#include + +#include "putty.h" +#include "network.h" +#include "proxy.h" + +#define do_proxy_dns(conf) \ + (conf_get_int(conf, CONF_proxy_dns) == FORCE_ON || \ + (conf_get_int(conf, CONF_proxy_dns) == AUTO && \ + conf_get_int(conf, CONF_proxy_type) != PROXY_SOCKS4)) + +/* + * Call this when proxy negotiation is complete, so that this + * socket can begin working normally. + */ +void proxy_activate (ProxySocket *p) +{ + size_t output_before, output_after; + + p->state = PROXY_STATE_ACTIVE; + + /* we want to ignore new receive events until we have sent + * all of our buffered receive data. + */ + sk_set_frozen(p->sub_socket, true); + + /* how many bytes of output have we buffered? */ + output_before = bufchain_size(&p->pending_oob_output_data) + + bufchain_size(&p->pending_output_data); + /* and keep track of how many bytes do not get sent. */ + output_after = 0; + + /* send buffered OOB writes */ + while (bufchain_size(&p->pending_oob_output_data) > 0) { + ptrlen data = bufchain_prefix(&p->pending_oob_output_data); + output_after += sk_write_oob(p->sub_socket, data.ptr, data.len); + bufchain_consume(&p->pending_oob_output_data, data.len); + } + + /* send buffered normal writes */ + while (bufchain_size(&p->pending_output_data) > 0) { + ptrlen data = bufchain_prefix(&p->pending_output_data); + output_after += sk_write(p->sub_socket, data.ptr, data.len); + bufchain_consume(&p->pending_output_data, data.len); + } + + /* if we managed to send any data, let the higher levels know. */ + if (output_after < output_before) + plug_sent(p->plug, output_after); + + /* if we have a pending EOF to send, send it */ + if (p->pending_eof) sk_write_eof(p->sub_socket); + + /* if the backend wanted the socket unfrozen, try to unfreeze. + * our set_frozen handler will flush buffered receive data before + * unfreezing the actual underlying socket. + */ + if (!p->freeze) + sk_set_frozen(&p->sock, 0); +} + +/* basic proxy socket functions */ + +static Plug *sk_proxy_plug (Socket *s, Plug *p) +{ + ProxySocket *ps = container_of(s, ProxySocket, sock); + Plug *ret = ps->plug; + if (p) + ps->plug = p; + return ret; +} + +static void sk_proxy_close (Socket *s) +{ + ProxySocket *ps = container_of(s, ProxySocket, sock); + + sk_close(ps->sub_socket); + sk_addr_free(ps->remote_addr); + sfree(ps); +} + +static size_t sk_proxy_write (Socket *s, const void *data, size_t len) +{ + ProxySocket *ps = container_of(s, ProxySocket, sock); + + if (ps->state != PROXY_STATE_ACTIVE) { + bufchain_add(&ps->pending_output_data, data, len); + return bufchain_size(&ps->pending_output_data); + } + return sk_write(ps->sub_socket, data, len); +} + +static size_t sk_proxy_write_oob (Socket *s, const void *data, size_t len) +{ + ProxySocket *ps = container_of(s, ProxySocket, sock); + + if (ps->state != PROXY_STATE_ACTIVE) { + bufchain_clear(&ps->pending_output_data); + bufchain_clear(&ps->pending_oob_output_data); + bufchain_add(&ps->pending_oob_output_data, data, len); + return len; + } + return sk_write_oob(ps->sub_socket, data, len); +} + +static void sk_proxy_write_eof (Socket *s) +{ + ProxySocket *ps = container_of(s, ProxySocket, sock); + + if (ps->state != PROXY_STATE_ACTIVE) { + ps->pending_eof = true; + return; + } + sk_write_eof(ps->sub_socket); +} + +static void sk_proxy_set_frozen (Socket *s, bool is_frozen) +{ + ProxySocket *ps = container_of(s, ProxySocket, sock); + + if (ps->state != PROXY_STATE_ACTIVE) { + ps->freeze = is_frozen; + return; + } + + /* handle any remaining buffered recv data first */ + if (bufchain_size(&ps->pending_input_data) > 0) { + ps->freeze = is_frozen; + + /* loop while we still have buffered data, and while we are + * unfrozen. the plug_receive call in the loop could result + * in a call back into this function refreezing the socket, + * so we have to check each time. + */ + while (!ps->freeze && bufchain_size(&ps->pending_input_data) > 0) { + char databuf[512]; + ptrlen data = bufchain_prefix(&ps->pending_input_data); + if (data.len > lenof(databuf)) + data.len = lenof(databuf); + memcpy(databuf, data.ptr, data.len); + bufchain_consume(&ps->pending_input_data, data.len); + plug_receive(ps->plug, 0, databuf, data.len); + } + + /* if we're still frozen, we'll have to wait for another + * call from the backend to finish unbuffering the data. + */ + if (ps->freeze) return; + } + + sk_set_frozen(ps->sub_socket, is_frozen); +} + +static const char * sk_proxy_socket_error (Socket *s) +{ + ProxySocket *ps = container_of(s, ProxySocket, sock); + if (ps->error != NULL || ps->sub_socket == NULL) { + return ps->error; + } + return sk_socket_error(ps->sub_socket); +} + +/* basic proxy plug functions */ + +static void plug_proxy_log(Plug *plug, int type, SockAddr *addr, int port, + const char *error_msg, int error_code) +{ + ProxySocket *ps = container_of(plug, ProxySocket, plugimpl); + + plug_log(ps->plug, type, addr, port, error_msg, error_code); +} + +static void plug_proxy_closing (Plug *p, const char *error_msg, + int error_code, bool calling_back) +{ + ProxySocket *ps = container_of(p, ProxySocket, plugimpl); + + if (ps->state != PROXY_STATE_ACTIVE) { + ps->closing_error_msg = error_msg; + ps->closing_error_code = error_code; + ps->closing_calling_back = calling_back; + ps->negotiate(ps, PROXY_CHANGE_CLOSING); + } else { + plug_closing(ps->plug, error_msg, error_code, calling_back); + } +} + +static void plug_proxy_receive( + Plug *p, int urgent, const char *data, size_t len) +{ + ProxySocket *ps = container_of(p, ProxySocket, plugimpl); + + if (ps->state != PROXY_STATE_ACTIVE) { + /* we will lose the urgentness of this data, but since most, + * if not all, of this data will be consumed by the negotiation + * process, hopefully it won't affect the protocol above us + */ + bufchain_add(&ps->pending_input_data, data, len); + ps->receive_urgent = (urgent != 0); + ps->receive_data = data; + ps->receive_len = len; + ps->negotiate(ps, PROXY_CHANGE_RECEIVE); + } else { + plug_receive(ps->plug, urgent, data, len); + } +} + +static void plug_proxy_sent (Plug *p, size_t bufsize) +{ + ProxySocket *ps = container_of(p, ProxySocket, plugimpl); + + if (ps->state != PROXY_STATE_ACTIVE) { + ps->negotiate(ps, PROXY_CHANGE_SENT); + return; + } + plug_sent(ps->plug, bufsize); +} + +static int plug_proxy_accepting(Plug *p, + accept_fn_t constructor, accept_ctx_t ctx) +{ + ProxySocket *ps = container_of(p, ProxySocket, plugimpl); + + if (ps->state != PROXY_STATE_ACTIVE) { + ps->accepting_constructor = constructor; + ps->accepting_ctx = ctx; + return ps->negotiate(ps, PROXY_CHANGE_ACCEPTING); + } + return plug_accepting(ps->plug, constructor, ctx); +} + +/* + * This function can accept a NULL pointer as `addr', in which case + * it will only check the host name. + */ +static bool proxy_for_destination(SockAddr *addr, const char *hostname, + int port, Conf *conf) +{ + int s = 0, e = 0; + char hostip[64]; + int hostip_len, hostname_len; + const char *exclude_list; + + /* + * Special local connections such as Unix-domain sockets + * unconditionally cannot be proxied, even in proxy-localhost + * mode. There just isn't any way to ask any known proxy type for + * them. + */ + if (addr && sk_address_is_special_local(addr)) + return false; /* do not proxy */ + + /* + * Check the host name and IP against the hard-coded + * representations of `localhost'. + */ + if (!conf_get_bool(conf, CONF_even_proxy_localhost) && + (sk_hostname_is_local(hostname) || + (addr && sk_address_is_local(addr)))) + return false; /* do not proxy */ + + /* we want a string representation of the IP address for comparisons */ + if (addr) { + sk_getaddr(addr, hostip, 64); + hostip_len = strlen(hostip); + } else + hostip_len = 0; /* placate gcc; shouldn't be required */ + + hostname_len = strlen(hostname); + + exclude_list = conf_get_str(conf, CONF_proxy_exclude_list); + + /* now parse the exclude list, and see if either our IP + * or hostname matches anything in it. + */ + + while (exclude_list[s]) { + while (exclude_list[s] && + (isspace((unsigned char)exclude_list[s]) || + exclude_list[s] == ',')) s++; + + if (!exclude_list[s]) break; + + e = s; + + while (exclude_list[e] && + (isalnum((unsigned char)exclude_list[e]) || + exclude_list[e] == '-' || + exclude_list[e] == '.' || + exclude_list[e] == '*')) e++; + + if (exclude_list[s] == '*') { + /* wildcard at beginning of entry */ + + if ((addr && strnicmp(hostip + hostip_len - (e - s - 1), + exclude_list + s + 1, e - s - 1) == 0) || + strnicmp(hostname + hostname_len - (e - s - 1), + exclude_list + s + 1, e - s - 1) == 0) { + /* IP/hostname range excluded. do not use proxy. */ + return false; + } + } else if (exclude_list[e-1] == '*') { + /* wildcard at end of entry */ + + if ((addr && strnicmp(hostip, exclude_list + s, e - s - 1) == 0) || + strnicmp(hostname, exclude_list + s, e - s - 1) == 0) { + /* IP/hostname range excluded. do not use proxy. */ + return false; + } + } else { + /* no wildcard at either end, so let's try an absolute + * match (ie. a specific IP) + */ + + if (addr && strnicmp(hostip, exclude_list + s, e - s) == 0) + return false; /* IP/hostname excluded. do not use proxy. */ + if (strnicmp(hostname, exclude_list + s, e - s) == 0) + return false; /* IP/hostname excluded. do not use proxy. */ + } + + s = e; + + /* Make sure we really have reached the next comma or end-of-string */ + while (exclude_list[s] && + !isspace((unsigned char)exclude_list[s]) && + exclude_list[s] != ',') s++; + } + + /* no matches in the exclude list, so use the proxy */ + return true; +} + +static char *dns_log_msg(const char *host, int addressfamily, + const char *reason) +{ + return dupprintf("Looking up host \"%s\"%s for %s", host, + (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" : + addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : + ""), reason); +} + +SockAddr *name_lookup(const char *host, int port, char **canonicalname, + Conf *conf, int addressfamily, LogContext *logctx, + const char *reason) +{ + if (conf_get_int(conf, CONF_proxy_type) != PROXY_NONE && + do_proxy_dns(conf) && + proxy_for_destination(NULL, host, port, conf)) { + + if (logctx) + logeventf(logctx, "Leaving host lookup to proxy of \"%s\"" + " (for %s)", host, reason); + + *canonicalname = dupstr(host); + return sk_nonamelookup(host); + } else { + if (logctx) + logevent_and_free( + logctx, dns_log_msg(host, addressfamily, reason)); + + return sk_namelookup(host, canonicalname, addressfamily); + } +} + +static const struct SocketVtable ProxySocket_sockvt = { + sk_proxy_plug, + sk_proxy_close, + sk_proxy_write, + sk_proxy_write_oob, + sk_proxy_write_eof, + sk_proxy_set_frozen, + sk_proxy_socket_error, + NULL, /* peer_info */ +}; + +static const struct PlugVtable ProxySocket_plugvt = { + plug_proxy_log, + plug_proxy_closing, + plug_proxy_receive, + plug_proxy_sent, + plug_proxy_accepting +}; + +Socket *new_connection(SockAddr *addr, const char *hostname, + int port, bool privport, + bool oobinline, bool nodelay, bool keepalive, + Plug *plug, Conf *conf) +{ + if (conf_get_int(conf, CONF_proxy_type) != PROXY_NONE && + proxy_for_destination(addr, hostname, port, conf)) + { + ProxySocket *ret; + SockAddr *proxy_addr; + char *proxy_canonical_name; + const char *proxy_type; + Socket *sret; + int type; + + if ((sret = platform_new_connection(addr, hostname, port, privport, + oobinline, nodelay, keepalive, + plug, conf)) != + NULL) + return sret; + + ret = snew(ProxySocket); + ret->sock.vt = &ProxySocket_sockvt; + ret->plugimpl.vt = &ProxySocket_plugvt; + ret->conf = conf_copy(conf); + ret->plug = plug; + ret->remote_addr = addr; /* will need to be freed on close */ + ret->remote_port = port; + + ret->error = NULL; + ret->pending_eof = false; + ret->freeze = false; + + bufchain_init(&ret->pending_input_data); + bufchain_init(&ret->pending_output_data); + bufchain_init(&ret->pending_oob_output_data); + + ret->sub_socket = NULL; + ret->state = PROXY_STATE_NEW; + ret->negotiate = NULL; + + type = conf_get_int(conf, CONF_proxy_type); + if (type == PROXY_HTTP) { + ret->negotiate = proxy_http_negotiate; + proxy_type = "HTTP"; + } else if (type == PROXY_SOCKS4) { + ret->negotiate = proxy_socks4_negotiate; + proxy_type = "SOCKS 4"; + } else if (type == PROXY_SOCKS5) { + ret->negotiate = proxy_socks5_negotiate; + proxy_type = "SOCKS 5"; + } else if (type == PROXY_TELNET) { + ret->negotiate = proxy_telnet_negotiate; + proxy_type = "Telnet"; + } else { + ret->error = "Proxy error: Unknown proxy method"; + return &ret->sock; + } + + { + char *logmsg = dupprintf("Will use %s proxy at %s:%d to connect" + " to %s:%d", proxy_type, + conf_get_str(conf, CONF_proxy_host), + conf_get_int(conf, CONF_proxy_port), + hostname, port); + plug_log(plug, 2, NULL, 0, logmsg, 0); + sfree(logmsg); + } + + { + char *logmsg = dns_log_msg(conf_get_str(conf, CONF_proxy_host), + conf_get_int(conf, CONF_addressfamily), + "proxy"); + plug_log(plug, 2, NULL, 0, logmsg, 0); + sfree(logmsg); + } + + /* look-up proxy */ + proxy_addr = sk_namelookup(conf_get_str(conf, CONF_proxy_host), + &proxy_canonical_name, + conf_get_int(conf, CONF_addressfamily)); + if (sk_addr_error(proxy_addr) != NULL) { + ret->error = "Proxy error: Unable to resolve proxy host name"; + sk_addr_free(proxy_addr); + return &ret->sock; + } + sfree(proxy_canonical_name); + + { + char addrbuf[256], *logmsg; + sk_getaddr(proxy_addr, addrbuf, lenof(addrbuf)); + logmsg = dupprintf("Connecting to %s proxy at %s port %d", + proxy_type, addrbuf, + conf_get_int(conf, CONF_proxy_port)); + plug_log(plug, 2, NULL, 0, logmsg, 0); + sfree(logmsg); + } + + /* create the actual socket we will be using, + * connected to our proxy server and port. + */ + ret->sub_socket = sk_new(proxy_addr, + conf_get_int(conf, CONF_proxy_port), + privport, oobinline, + nodelay, keepalive, &ret->plugimpl); + if (sk_socket_error(ret->sub_socket) != NULL) + return &ret->sock; + + /* start the proxy negotiation process... */ + sk_set_frozen(ret->sub_socket, 0); + ret->negotiate(ret, PROXY_CHANGE_NEW); + + return &ret->sock; + } + + /* no proxy, so just return the direct socket */ + return sk_new(addr, port, privport, oobinline, nodelay, keepalive, plug); +} + +Socket *new_listener(const char *srcaddr, int port, Plug *plug, + bool local_host_only, Conf *conf, int addressfamily) +{ + /* TODO: SOCKS (and potentially others) support inbound + * TODO: connections via the proxy. support them. + */ + + return sk_newlistener(srcaddr, port, plug, local_host_only, addressfamily); +} + +/* ---------------------------------------------------------------------- + * HTTP CONNECT proxy type. + */ + +static bool get_line_end(char *data, size_t len, size_t *out) +{ + size_t off = 0; + + while (off < len) + { + if (data[off] == '\n') { + /* we have a newline */ + off++; + + /* is that the only thing on this line? */ + if (off <= 2) { + *out = off; + return true; + } + + /* if not, then there is the possibility that this header + * continues onto the next line, if it starts with a space + * or a tab. + */ + + if (off + 1 < len && data[off+1] != ' ' && data[off+1] != '\t') { + *out = off; + return true; + } + + /* the line does continue, so we have to keep going + * until we see an the header's "real" end of line. + */ + off++; + } + + off++; + } + + return false; +} + +int proxy_http_negotiate (ProxySocket *p, int change) +{ + if (p->state == PROXY_STATE_NEW) { + /* we are just beginning the proxy negotiate process, + * so we'll send off the initial bits of the request. + * for this proxy method, it's just a simple HTTP + * request + */ + char *buf, dest[512]; + char *username, *password; + + sk_getaddr(p->remote_addr, dest, lenof(dest)); + + buf = dupprintf("CONNECT %s:%i HTTP/1.1\r\nHost: %s:%i\r\n", + dest, p->remote_port, dest, p->remote_port); + sk_write(p->sub_socket, buf, strlen(buf)); + sfree(buf); + + username = conf_get_str(p->conf, CONF_proxy_username); + password = conf_get_str(p->conf, CONF_proxy_password); + if (username[0] || password[0]) { + char *buf, *buf2; + int i, j, len; + buf = dupprintf("%s:%s", username, password); + len = strlen(buf); + buf2 = snewn(len * 4 / 3 + 100, char); + sprintf(buf2, "Proxy-Authorization: Basic "); + for (i = 0, j = strlen(buf2); i < len; i += 3, j += 4) + base64_encode_atom((unsigned char *)(buf+i), + (len-i > 3 ? 3 : len-i), buf2+j); + strcpy(buf2+j, "\r\n"); + sk_write(p->sub_socket, buf2, strlen(buf2)); + sfree(buf); + sfree(buf2); + } + + sk_write(p->sub_socket, "\r\n", 2); + + p->state = 1; + return 0; + } + + if (change == PROXY_CHANGE_CLOSING) { + /* if our proxy negotiation process involves closing and opening + * new sockets, then we would want to intercept this closing + * callback when we were expecting it. if we aren't anticipating + * a socket close, then some error must have occurred. we'll + * just pass those errors up to the backend. + */ + plug_closing(p->plug, p->closing_error_msg, p->closing_error_code, + p->closing_calling_back); + return 0; /* ignored */ + } + + if (change == PROXY_CHANGE_SENT) { + /* some (or all) of what we wrote to the proxy was sent. + * we don't do anything new, however, until we receive the + * proxy's response. we might want to set a timer so we can + * timeout the proxy negotiation after a while... + */ + return 0; + } + + if (change == PROXY_CHANGE_ACCEPTING) { + /* we should _never_ see this, as we are using our socket to + * connect to a proxy, not accepting inbound connections. + * what should we do? close the socket with an appropriate + * error message? + */ + return plug_accepting(p->plug, + p->accepting_constructor, p->accepting_ctx); + } + + if (change == PROXY_CHANGE_RECEIVE) { + /* we have received data from the underlying socket, which + * we'll need to parse, process, and respond to appropriately. + */ + + char *data, *datap; + size_t len, eol; + + if (p->state == 1) { + + int min_ver, maj_ver, status; + + /* get the status line */ + len = bufchain_size(&p->pending_input_data); + assert(len > 0); /* or we wouldn't be here */ + data = snewn(len+1, char); + bufchain_fetch(&p->pending_input_data, data, len); + /* + * We must NUL-terminate this data, because Windows + * sscanf appears to require a NUL at the end of the + * string because it strlens it _first_. Sigh. + */ + data[len] = '\0'; + + if (!get_line_end(data, len, &eol)) { + sfree(data); + return 1; + } + + status = -1; + /* We can't rely on whether the %n incremented the sscanf return */ + if (sscanf((char *)data, "HTTP/%i.%i %n", + &maj_ver, &min_ver, &status) < 2 || status == -1) { + plug_closing(p->plug, "Proxy error: HTTP response was absent", + PROXY_ERROR_GENERAL, 0); + sfree(data); + return 1; + } + + /* remove the status line from the input buffer. */ + bufchain_consume(&p->pending_input_data, eol); + if (data[status] != '2') { + /* error */ + char *buf; + data[eol] = '\0'; + while (eol > status && + (data[eol-1] == '\r' || data[eol-1] == '\n')) + data[--eol] = '\0'; + buf = dupprintf("Proxy error: %s", data+status); + plug_closing(p->plug, buf, PROXY_ERROR_GENERAL, 0); + sfree(buf); + sfree(data); + return 1; + } + + sfree(data); + + p->state = 2; + } + + if (p->state == 2) { + + /* get headers. we're done when we get a + * header of length 2, (ie. just "\r\n") + */ + + len = bufchain_size(&p->pending_input_data); + assert(len > 0); /* or we wouldn't be here */ + data = snewn(len, char); + datap = data; + bufchain_fetch(&p->pending_input_data, data, len); + + if (!get_line_end(datap, len, &eol)) { + sfree(data); + return 1; + } + while (eol > 2) { + bufchain_consume(&p->pending_input_data, eol); + datap += eol; + len -= eol; + if (!get_line_end(datap, len, &eol)) + eol = 0; /* terminate the loop */ + } + + if (eol == 2) { + /* we're done */ + bufchain_consume(&p->pending_input_data, 2); + proxy_activate(p); + /* proxy activate will have dealt with + * whatever is left of the buffer */ + sfree(data); + return 1; + } + + sfree(data); + return 1; + } + } + + plug_closing(p->plug, "Proxy error: unexpected proxy error", + PROXY_ERROR_UNEXPECTED, 0); + return 1; +} + +/* ---------------------------------------------------------------------- + * SOCKS proxy type. + */ + +/* SOCKS version 4 */ +int proxy_socks4_negotiate (ProxySocket *p, int change) +{ + if (p->state == PROXY_CHANGE_NEW) { + + /* request format: + * version number (1 byte) = 4 + * command code (1 byte) + * 1 = CONNECT + * 2 = BIND + * dest. port (2 bytes) [network order] + * dest. address (4 bytes) + * user ID (variable length, null terminated string) + */ + + strbuf *command = strbuf_new(); + char hostname[512]; + bool write_hostname = false; + + put_byte(command, 4); /* SOCKS version 4 */ + put_byte(command, 1); /* CONNECT command */ + put_uint16(command, p->remote_port); + + switch (sk_addrtype(p->remote_addr)) { + case ADDRTYPE_IPV4: + { + char addr[4]; + sk_addrcopy(p->remote_addr, addr); + put_data(command, addr, 4); + break; + } + case ADDRTYPE_NAME: + sk_getaddr(p->remote_addr, hostname, lenof(hostname)); + put_uint32(command, 1); + write_hostname = true; + break; + case ADDRTYPE_IPV6: + p->error = "Proxy error: SOCKS version 4 does not support IPv6"; + strbuf_free(command); + return 1; + } + + put_asciz(command, conf_get_str(p->conf, CONF_proxy_username)); + if (write_hostname) + put_asciz(command, hostname); + sk_write(p->sub_socket, command->s, command->len); + strbuf_free(command); + + p->state = 1; + return 0; + } + + if (change == PROXY_CHANGE_CLOSING) { + /* if our proxy negotiation process involves closing and opening + * new sockets, then we would want to intercept this closing + * callback when we were expecting it. if we aren't anticipating + * a socket close, then some error must have occurred. we'll + * just pass those errors up to the backend. + */ + plug_closing(p->plug, p->closing_error_msg, p->closing_error_code, + p->closing_calling_back); + return 0; /* ignored */ + } + + if (change == PROXY_CHANGE_SENT) { + /* some (or all) of what we wrote to the proxy was sent. + * we don't do anything new, however, until we receive the + * proxy's response. we might want to set a timer so we can + * timeout the proxy negotiation after a while... + */ + return 0; + } + + if (change == PROXY_CHANGE_ACCEPTING) { + /* we should _never_ see this, as we are using our socket to + * connect to a proxy, not accepting inbound connections. + * what should we do? close the socket with an appropriate + * error message? + */ + return plug_accepting(p->plug, + p->accepting_constructor, p->accepting_ctx); + } + + if (change == PROXY_CHANGE_RECEIVE) { + /* we have received data from the underlying socket, which + * we'll need to parse, process, and respond to appropriately. + */ + + if (p->state == 1) { + /* response format: + * version number (1 byte) = 4 + * reply code (1 byte) + * 90 = request granted + * 91 = request rejected or failed + * 92 = request rejected due to lack of IDENTD on client + * 93 = request rejected due to difference in user ID + * (what we sent vs. what IDENTD said) + * dest. port (2 bytes) + * dest. address (4 bytes) + */ + + char data[8]; + + if (bufchain_size(&p->pending_input_data) < 8) + return 1; /* not got anything yet */ + + /* get the response */ + bufchain_fetch(&p->pending_input_data, data, 8); + + if (data[0] != 0) { + plug_closing(p->plug, "Proxy error: SOCKS proxy responded with " + "unexpected reply code version", + PROXY_ERROR_GENERAL, 0); + return 1; + } + + if (data[1] != 90) { + + switch (data[1]) { + case 92: + plug_closing(p->plug, "Proxy error: SOCKS server wanted IDENTD on client", + PROXY_ERROR_GENERAL, 0); + break; + case 93: + plug_closing(p->plug, "Proxy error: Username and IDENTD on client don't agree", + PROXY_ERROR_GENERAL, 0); + break; + case 91: + default: + plug_closing(p->plug, "Proxy error: Error while communicating with proxy", + PROXY_ERROR_GENERAL, 0); + break; + } + + return 1; + } + bufchain_consume(&p->pending_input_data, 8); + + /* we're done */ + proxy_activate(p); + /* proxy activate will have dealt with + * whatever is left of the buffer */ + return 1; + } + } + + plug_closing(p->plug, "Proxy error: unexpected proxy error", + PROXY_ERROR_UNEXPECTED, 0); + return 1; +} + +/* SOCKS version 5 */ +int proxy_socks5_negotiate (ProxySocket *p, int change) +{ + if (p->state == PROXY_CHANGE_NEW) { + + /* initial command: + * version number (1 byte) = 5 + * number of available authentication methods (1 byte) + * available authentication methods (1 byte * previous value) + * authentication methods: + * 0x00 = no authentication + * 0x01 = GSSAPI + * 0x02 = username/password + * 0x03 = CHAP + */ + + strbuf *command; + char *username, *password; + int method_count_offset, methods_start; + + command = strbuf_new(); + put_byte(command, 5); /* SOCKS version 5 */ + username = conf_get_str(p->conf, CONF_proxy_username); + password = conf_get_str(p->conf, CONF_proxy_password); + + method_count_offset = command->len; + put_byte(command, 0); + methods_start = command->len; + + put_byte(command, 0x00); /* no authentication */ + + if (username[0] || password[0]) { + proxy_socks5_offerencryptedauth(BinarySink_UPCAST(command)); + put_byte(command, 0x02); /* username/password */ + } + + command->u[method_count_offset] = command->len - methods_start; + + sk_write(p->sub_socket, command->s, command->len); + strbuf_free(command); + + p->state = 1; + return 0; + } + + if (change == PROXY_CHANGE_CLOSING) { + /* if our proxy negotiation process involves closing and opening + * new sockets, then we would want to intercept this closing + * callback when we were expecting it. if we aren't anticipating + * a socket close, then some error must have occurred. we'll + * just pass those errors up to the backend. + */ + plug_closing(p->plug, p->closing_error_msg, p->closing_error_code, + p->closing_calling_back); + return 0; /* ignored */ + } + + if (change == PROXY_CHANGE_SENT) { + /* some (or all) of what we wrote to the proxy was sent. + * we don't do anything new, however, until we receive the + * proxy's response. we might want to set a timer so we can + * timeout the proxy negotiation after a while... + */ + return 0; + } + + if (change == PROXY_CHANGE_ACCEPTING) { + /* we should _never_ see this, as we are using our socket to + * connect to a proxy, not accepting inbound connections. + * what should we do? close the socket with an appropriate + * error message? + */ + return plug_accepting(p->plug, + p->accepting_constructor, p->accepting_ctx); + } + + if (change == PROXY_CHANGE_RECEIVE) { + /* we have received data from the underlying socket, which + * we'll need to parse, process, and respond to appropriately. + */ + + if (p->state == 1) { + + /* initial response: + * version number (1 byte) = 5 + * authentication method (1 byte) + * authentication methods: + * 0x00 = no authentication + * 0x01 = GSSAPI + * 0x02 = username/password + * 0x03 = CHAP + * 0xff = no acceptable methods + */ + char data[2]; + + if (bufchain_size(&p->pending_input_data) < 2) + return 1; /* not got anything yet */ + + /* get the response */ + bufchain_fetch(&p->pending_input_data, data, 2); + + if (data[0] != 5) { + plug_closing(p->plug, "Proxy error: SOCKS proxy returned unexpected version", + PROXY_ERROR_GENERAL, 0); + return 1; + } + + if (data[1] == 0x00) p->state = 2; /* no authentication needed */ + else if (data[1] == 0x01) p->state = 4; /* GSSAPI authentication */ + else if (data[1] == 0x02) p->state = 5; /* username/password authentication */ + else if (data[1] == 0x03) p->state = 6; /* CHAP authentication */ + else { + plug_closing(p->plug, "Proxy error: SOCKS proxy did not accept our authentication", + PROXY_ERROR_GENERAL, 0); + return 1; + } + bufchain_consume(&p->pending_input_data, 2); + } + + if (p->state == 7) { + + /* password authentication reply format: + * version number (1 bytes) = 1 + * reply code (1 byte) + * 0 = succeeded + * >0 = failed + */ + char data[2]; + + if (bufchain_size(&p->pending_input_data) < 2) + return 1; /* not got anything yet */ + + /* get the response */ + bufchain_fetch(&p->pending_input_data, data, 2); + + if (data[0] != 1) { + plug_closing(p->plug, "Proxy error: SOCKS password " + "subnegotiation contained wrong version number", + PROXY_ERROR_GENERAL, 0); + return 1; + } + + if (data[1] != 0) { + + plug_closing(p->plug, "Proxy error: SOCKS proxy refused" + " password authentication", + PROXY_ERROR_GENERAL, 0); + return 1; + } + + bufchain_consume(&p->pending_input_data, 2); + p->state = 2; /* now proceed as authenticated */ + } + + if (p->state == 8) { + int ret; + ret = proxy_socks5_handlechap(p); + if (ret) return ret; + } + + if (p->state == 2) { + + /* request format: + * version number (1 byte) = 5 + * command code (1 byte) + * 1 = CONNECT + * 2 = BIND + * 3 = UDP ASSOCIATE + * reserved (1 byte) = 0x00 + * address type (1 byte) + * 1 = IPv4 + * 3 = domainname (first byte has length, no terminating null) + * 4 = IPv6 + * dest. address (variable) + * dest. port (2 bytes) [network order] + */ + + strbuf *command = strbuf_new(); + put_byte(command, 5); /* SOCKS version 5 */ + put_byte(command, 1); /* CONNECT command */ + put_byte(command, 0x00); /* reserved byte */ + + switch (sk_addrtype(p->remote_addr)) { + case ADDRTYPE_IPV4: + put_byte(command, 1); /* IPv4 */ + sk_addrcopy(p->remote_addr, strbuf_append(command, 4)); + break; + case ADDRTYPE_IPV6: + put_byte(command, 4); /* IPv6 */ + sk_addrcopy(p->remote_addr, strbuf_append(command, 16)); + break; + case ADDRTYPE_NAME: + { + char hostname[512]; + put_byte(command, 3); /* domain name */ + sk_getaddr(p->remote_addr, hostname, lenof(hostname)); + if (!put_pstring(command, hostname)) { + p->error = "Proxy error: SOCKS 5 cannot " + "support host names longer than 255 chars"; + strbuf_free(command); + return 1; + } + } + break; + } + + put_uint16(command, p->remote_port); + + sk_write(p->sub_socket, command->s, command->len); + + strbuf_free(command); + + p->state = 3; + return 1; + } + + if (p->state == 3) { + + /* reply format: + * version number (1 bytes) = 5 + * reply code (1 byte) + * 0 = succeeded + * 1 = general SOCKS server failure + * 2 = connection not allowed by ruleset + * 3 = network unreachable + * 4 = host unreachable + * 5 = connection refused + * 6 = TTL expired + * 7 = command not supported + * 8 = address type not supported + * reserved (1 byte) = x00 + * address type (1 byte) + * 1 = IPv4 + * 3 = domainname (first byte has length, no terminating null) + * 4 = IPv6 + * server bound address (variable) + * server bound port (2 bytes) [network order] + */ + char data[5]; + int len; + + /* First 5 bytes of packet are enough to tell its length. */ + if (bufchain_size(&p->pending_input_data) < 5) + return 1; /* not got anything yet */ + + /* get the response */ + bufchain_fetch(&p->pending_input_data, data, 5); + + if (data[0] != 5) { + plug_closing(p->plug, "Proxy error: SOCKS proxy returned wrong version number", + PROXY_ERROR_GENERAL, 0); + return 1; + } + + if (data[1] != 0) { + char buf[256]; + + strcpy(buf, "Proxy error: "); + + switch (data[1]) { + case 1: strcat(buf, "General SOCKS server failure"); break; + case 2: strcat(buf, "Connection not allowed by ruleset"); break; + case 3: strcat(buf, "Network unreachable"); break; + case 4: strcat(buf, "Host unreachable"); break; + case 5: strcat(buf, "Connection refused"); break; + case 6: strcat(buf, "TTL expired"); break; + case 7: strcat(buf, "Command not supported"); break; + case 8: strcat(buf, "Address type not supported"); break; + default: sprintf(buf+strlen(buf), + "Unrecognised SOCKS error code %d", + data[1]); + break; + } + plug_closing(p->plug, buf, PROXY_ERROR_GENERAL, 0); + + return 1; + } + + /* + * Eat the rest of the reply packet. + */ + len = 6; /* first 4 bytes, last 2 */ + switch (data[3]) { + case 1: len += 4; break; /* IPv4 address */ + case 4: len += 16; break;/* IPv6 address */ + case 3: len += 1+(unsigned char)data[4]; break; /* domain name */ + default: + plug_closing(p->plug, "Proxy error: SOCKS proxy returned " + "unrecognised address format", + PROXY_ERROR_GENERAL, 0); + return 1; + } + if (bufchain_size(&p->pending_input_data) < len) + return 1; /* not got whole reply yet */ + bufchain_consume(&p->pending_input_data, len); + + /* we're done */ + proxy_activate(p); + return 1; + } + + if (p->state == 4) { + /* TODO: Handle GSSAPI authentication */ + plug_closing(p->plug, "Proxy error: We don't support GSSAPI authentication", + PROXY_ERROR_GENERAL, 0); + return 1; + } + + if (p->state == 5) { + const char *username = conf_get_str(p->conf, CONF_proxy_username); + const char *password = conf_get_str(p->conf, CONF_proxy_password); + if (username[0] || password[0]) { + strbuf *auth = strbuf_new_nm(); + put_byte(auth, 1); /* version number of subnegotiation */ + if (!put_pstring(auth, username)) { + p->error = "Proxy error: SOCKS 5 authentication cannot " + "support usernames longer than 255 chars"; + strbuf_free(auth); + return 1; + } + if (!put_pstring(auth, password)) { + p->error = "Proxy error: SOCKS 5 authentication cannot " + "support passwords longer than 255 chars"; + strbuf_free(auth); + return 1; + } + sk_write(p->sub_socket, auth->s, auth->len); + strbuf_free(auth); + p->state = 7; + } else + plug_closing(p->plug, "Proxy error: Server chose " + "username/password authentication but we " + "didn't offer it!", + PROXY_ERROR_GENERAL, 0); + return 1; + } + + if (p->state == 6) { + int ret; + ret = proxy_socks5_selectchap(p); + if (ret) return ret; + } + + } + + plug_closing(p->plug, "Proxy error: Unexpected proxy error", + PROXY_ERROR_UNEXPECTED, 0); + return 1; +} + +/* ---------------------------------------------------------------------- + * `Telnet' proxy type. + * + * (This is for ad-hoc proxies where you connect to the proxy's + * telnet port and send a command such as `connect host port'. The + * command is configurable, since this proxy type is typically not + * standardised or at all well-defined.) + */ + +char *format_telnet_command(SockAddr *addr, int port, Conf *conf) +{ + char *fmt = conf_get_str(conf, CONF_proxy_telnet_command); + int so = 0, eo = 0; + strbuf *buf = strbuf_new(); + + /* we need to escape \\, \%, \r, \n, \t, \x??, \0???, + * %%, %host, %port, %user, and %pass + */ + + while (fmt[eo] != 0) { + + /* scan forward until we hit end-of-line, + * or an escape character (\ or %) */ + while (fmt[eo] != 0 && fmt[eo] != '%' && fmt[eo] != '\\') + eo++; + + /* if we hit eol, break out of our escaping loop */ + if (fmt[eo] == 0) break; + + /* if there was any unescaped text before the escape + * character, send that now */ + if (eo != so) + put_data(buf, fmt + so, eo - so); + + so = eo++; + + /* if the escape character was the last character of + * the line, we'll just stop and send it. */ + if (fmt[eo] == 0) break; + + if (fmt[so] == '\\') { + + /* we recognize \\, \%, \r, \n, \t, \x??. + * anything else, we just send unescaped (including the \). + */ + + switch (fmt[eo]) { + + case '\\': + put_byte(buf, '\\'); + eo++; + break; + + case '%': + put_byte(buf, '%'); + eo++; + break; + + case 'r': + put_byte(buf, '\r'); + eo++; + break; + + case 'n': + put_byte(buf, '\n'); + eo++; + break; + + case 't': + put_byte(buf, '\t'); + eo++; + break; + + case 'x': + case 'X': + { + /* escaped hexadecimal value (ie. \xff) */ + unsigned char v = 0; + int i = 0; + + for (;;) { + eo++; + if (fmt[eo] >= '0' && fmt[eo] <= '9') + v += fmt[eo] - '0'; + else if (fmt[eo] >= 'a' && fmt[eo] <= 'f') + v += fmt[eo] - 'a' + 10; + else if (fmt[eo] >= 'A' && fmt[eo] <= 'F') + v += fmt[eo] - 'A' + 10; + else { + /* non hex character, so we abort and just + * send the whole thing unescaped (including \x) + */ + put_byte(buf, '\\'); + eo = so + 1; + break; + } + + /* we only extract two hex characters */ + if (i == 1) { + put_byte(buf, v); + eo++; + break; + } + + i++; + v <<= 4; + } + } + break; + + default: + put_data(buf, fmt + so, 2); + eo++; + break; + } + } else { + + /* % escape. we recognize %%, %host, %port, %user, %pass. + * %proxyhost, %proxyport. Anything else we just send + * unescaped (including the %). + */ + + if (fmt[eo] == '%') { + put_byte(buf, '%'); + eo++; + } + else if (strnicmp(fmt + eo, "host", 4) == 0) { + char dest[512]; + sk_getaddr(addr, dest, lenof(dest)); + put_data(buf, dest, strlen(dest)); + eo += 4; + } + else if (strnicmp(fmt + eo, "port", 4) == 0) { + strbuf_catf(buf, "%d", port); + eo += 4; + } + else if (strnicmp(fmt + eo, "user", 4) == 0) { + const char *username = conf_get_str(conf, CONF_proxy_username); + put_data(buf, username, strlen(username)); + eo += 4; + } + else if (strnicmp(fmt + eo, "pass", 4) == 0) { + const char *password = conf_get_str(conf, CONF_proxy_password); + put_data(buf, password, strlen(password)); + eo += 4; + } + else if (strnicmp(fmt + eo, "proxyhost", 9) == 0) { + const char *host = conf_get_str(conf, CONF_proxy_host); + put_data(buf, host, strlen(host)); + eo += 9; + } + else if (strnicmp(fmt + eo, "proxyport", 9) == 0) { + int port = conf_get_int(conf, CONF_proxy_port); + strbuf_catf(buf, "%d", port); + eo += 9; + } + else { + /* we don't escape this, so send the % now, and + * don't advance eo, so that we'll consider the + * text immediately following the % as unescaped. + */ + put_byte(buf, '%'); + } + } + + /* resume scanning for additional escapes after this one. */ + so = eo; + } + + /* if there is any unescaped text at the end of the line, send it */ + if (eo != so) { + put_data(buf, fmt + so, eo - so); + } + + return strbuf_to_str(buf); +} + +int proxy_telnet_negotiate (ProxySocket *p, int change) +{ + if (p->state == PROXY_CHANGE_NEW) { + char *formatted_cmd; + + formatted_cmd = format_telnet_command(p->remote_addr, p->remote_port, + p->conf); + + { + /* + * Re-escape control chars in the command, for logging. + */ + char *reescaped = snewn(4*strlen(formatted_cmd) + 1, char); + const char *in; + char *out; + char *logmsg; + + for (in = formatted_cmd, out = reescaped; *in; in++) { + if (*in == '\n') { + *out++ = '\\'; *out++ = 'n'; + } else if (*in == '\r') { + *out++ = '\\'; *out++ = 'r'; + } else if (*in == '\t') { + *out++ = '\\'; *out++ = 't'; + } else if (*in == '\\') { + *out++ = '\\'; *out++ = '\\'; + } else if ((unsigned)(((unsigned char)*in) - 0x20) < + (0x7F-0x20)) { + *out++ = *in; + } else { + out += sprintf(out, "\\x%02X", (unsigned)*in & 0xFF); + } + } + *out = '\0'; + + logmsg = dupprintf("Sending Telnet proxy command: %s", reescaped); + plug_log(p->plug, 2, NULL, 0, logmsg, 0); + sfree(logmsg); + sfree(reescaped); + } + + sk_write(p->sub_socket, formatted_cmd, strlen(formatted_cmd)); + sfree(formatted_cmd); + + p->state = 1; + return 0; + } + + if (change == PROXY_CHANGE_CLOSING) { + /* if our proxy negotiation process involves closing and opening + * new sockets, then we would want to intercept this closing + * callback when we were expecting it. if we aren't anticipating + * a socket close, then some error must have occurred. we'll + * just pass those errors up to the backend. + */ + plug_closing(p->plug, p->closing_error_msg, p->closing_error_code, + p->closing_calling_back); + return 0; /* ignored */ + } + + if (change == PROXY_CHANGE_SENT) { + /* some (or all) of what we wrote to the proxy was sent. + * we don't do anything new, however, until we receive the + * proxy's response. we might want to set a timer so we can + * timeout the proxy negotiation after a while... + */ + return 0; + } + + if (change == PROXY_CHANGE_ACCEPTING) { + /* we should _never_ see this, as we are using our socket to + * connect to a proxy, not accepting inbound connections. + * what should we do? close the socket with an appropriate + * error message? + */ + return plug_accepting(p->plug, + p->accepting_constructor, p->accepting_ctx); + } + + if (change == PROXY_CHANGE_RECEIVE) { + /* we have received data from the underlying socket, which + * we'll need to parse, process, and respond to appropriately. + */ + + /* we're done */ + proxy_activate(p); + /* proxy activate will have dealt with + * whatever is left of the buffer */ + return 1; + } + + plug_closing(p->plug, "Proxy error: Unexpected proxy error", + PROXY_ERROR_UNEXPECTED, 0); + return 1; +} diff --git a/0.73_My_PuTTY/proxy.h b/0.74_My_PuTTY/proxy.h similarity index 90% rename from 0.73_My_PuTTY/proxy.h rename to 0.74_My_PuTTY/proxy.h index 26a76c4..e409017 100644 --- a/0.73_My_PuTTY/proxy.h +++ b/0.74_My_PuTTY/proxy.h @@ -1,111 +1,111 @@ -/* - * Network proxy abstraction in PuTTY - * - * A proxy layer, if necessary, wedges itself between the - * network code and the higher level backend. - * - * Supported proxies: HTTP CONNECT, generic telnet, SOCKS 4 & 5 - */ - -#ifndef PUTTY_PROXY_H -#define PUTTY_PROXY_H - -#define PROXY_ERROR_GENERAL 8000 -#define PROXY_ERROR_UNEXPECTED 8001 - -typedef struct ProxySocket ProxySocket; - -struct ProxySocket { - const char *error; - - Socket *sub_socket; - Plug *plug; - SockAddr *remote_addr; - int remote_port; - - bufchain pending_output_data; - bufchain pending_oob_output_data; - bufchain pending_input_data; - bool pending_eof; - -#define PROXY_STATE_NEW -1 -#define PROXY_STATE_ACTIVE 0 - - int state; /* proxy states greater than 0 are implementation - * dependent, but represent various stages/states - * of the initialization/setup/negotiation with the - * proxy server. - */ - bool freeze; /* should we freeze the underlying socket when - * we are done with the proxy negotiation? this - * simply caches the value of sk_set_frozen calls. - */ - -#define PROXY_CHANGE_NEW -1 -#define PROXY_CHANGE_CLOSING 0 -#define PROXY_CHANGE_SENT 1 -#define PROXY_CHANGE_RECEIVE 2 -#define PROXY_CHANGE_ACCEPTING 3 - - /* something has changed (a call from the sub socket - * layer into our Proxy Plug layer, or we were just - * created, etc), so the proxy layer needs to handle - * this change (the type of which is the second argument) - * and further the proxy negotiation process. - */ - - int (*negotiate) (ProxySocket * /* this */, int /* change type */); - - /* current arguments of plug handlers - * (for use by proxy's negotiate function) - */ - - /* closing */ - const char *closing_error_msg; - int closing_error_code; - bool closing_calling_back; - - /* receive */ - bool receive_urgent; - const char *receive_data; - int receive_len; - - /* accepting */ - accept_fn_t accepting_constructor; - accept_ctx_t accepting_ctx; - - /* configuration, used to look up proxy settings */ - Conf *conf; - - /* CHAP transient data */ - int chap_num_attributes; - int chap_num_attributes_processed; - int chap_current_attribute; - int chap_current_datalen; - - Socket sock; - Plug plugimpl; -}; - -extern void proxy_activate (ProxySocket *); - -extern int proxy_http_negotiate (ProxySocket *, int); -extern int proxy_telnet_negotiate (ProxySocket *, int); -extern int proxy_socks4_negotiate (ProxySocket *, int); -extern int proxy_socks5_negotiate (ProxySocket *, int); - -/* - * This may be reused by local-command proxies on individual - * platforms. - */ -char *format_telnet_command(SockAddr *addr, int port, Conf *conf); - -/* - * These are implemented in cproxy.c or nocproxy.c, depending on - * whether encrypted proxy authentication is available. - */ -extern void proxy_socks5_offerencryptedauth(BinarySink *); -extern int proxy_socks5_handlechap (ProxySocket *); -extern int proxy_socks5_selectchap(ProxySocket *); - -#endif +/* + * Network proxy abstraction in PuTTY + * + * A proxy layer, if necessary, wedges itself between the + * network code and the higher level backend. + * + * Supported proxies: HTTP CONNECT, generic telnet, SOCKS 4 & 5 + */ + +#ifndef PUTTY_PROXY_H +#define PUTTY_PROXY_H + +#define PROXY_ERROR_GENERAL 8000 +#define PROXY_ERROR_UNEXPECTED 8001 + +typedef struct ProxySocket ProxySocket; + +struct ProxySocket { + const char *error; + + Socket *sub_socket; + Plug *plug; + SockAddr *remote_addr; + int remote_port; + + bufchain pending_output_data; + bufchain pending_oob_output_data; + bufchain pending_input_data; + bool pending_eof; + +#define PROXY_STATE_NEW -1 +#define PROXY_STATE_ACTIVE 0 + + int state; /* proxy states greater than 0 are implementation + * dependent, but represent various stages/states + * of the initialization/setup/negotiation with the + * proxy server. + */ + bool freeze; /* should we freeze the underlying socket when + * we are done with the proxy negotiation? this + * simply caches the value of sk_set_frozen calls. + */ + +#define PROXY_CHANGE_NEW -1 +#define PROXY_CHANGE_CLOSING 0 +#define PROXY_CHANGE_SENT 1 +#define PROXY_CHANGE_RECEIVE 2 +#define PROXY_CHANGE_ACCEPTING 3 + + /* something has changed (a call from the sub socket + * layer into our Proxy Plug layer, or we were just + * created, etc), so the proxy layer needs to handle + * this change (the type of which is the second argument) + * and further the proxy negotiation process. + */ + + int (*negotiate) (ProxySocket * /* this */, int /* change type */); + + /* current arguments of plug handlers + * (for use by proxy's negotiate function) + */ + + /* closing */ + const char *closing_error_msg; + int closing_error_code; + bool closing_calling_back; + + /* receive */ + bool receive_urgent; + const char *receive_data; + int receive_len; + + /* accepting */ + accept_fn_t accepting_constructor; + accept_ctx_t accepting_ctx; + + /* configuration, used to look up proxy settings */ + Conf *conf; + + /* CHAP transient data */ + int chap_num_attributes; + int chap_num_attributes_processed; + int chap_current_attribute; + int chap_current_datalen; + + Socket sock; + Plug plugimpl; +}; + +extern void proxy_activate (ProxySocket *); + +extern int proxy_http_negotiate (ProxySocket *, int); +extern int proxy_telnet_negotiate (ProxySocket *, int); +extern int proxy_socks4_negotiate (ProxySocket *, int); +extern int proxy_socks5_negotiate (ProxySocket *, int); + +/* + * This may be reused by local-command proxies on individual + * platforms. + */ +char *format_telnet_command(SockAddr *addr, int port, Conf *conf); + +/* + * These are implemented in cproxy.c or nocproxy.c, depending on + * whether encrypted proxy authentication is available. + */ +extern void proxy_socks5_offerencryptedauth(BinarySink *); +extern int proxy_socks5_handlechap (ProxySocket *); +extern int proxy_socks5_selectchap(ProxySocket *); + +#endif diff --git a/0.73_My_PuTTY/pscp.c b/0.74_My_PuTTY/pscp.c similarity index 98% rename from 0.73_My_PuTTY/pscp.c rename to 0.74_My_PuTTY/pscp.c index 1a93089..cf4b799 100644 --- a/0.73_My_PuTTY/pscp.c +++ b/0.74_My_PuTTY/pscp.c @@ -123,14 +123,14 @@ static void abandon_stats(void) } } -static void tell_user(FILE *stream, const char *fmt, ...) +static PRINTF_LIKE(2, 3) void tell_user(FILE *stream, const char *fmt, ...) { char *str, *str2; va_list ap; va_start(ap, fmt); str = dupvprintf(fmt, ap); va_end(ap); - str2 = dupcat(str, "\n", NULL); + str2 = dupcat(str, "\n"); sfree(str); abandon_stats(); tell_str(stream, str2); @@ -234,14 +234,14 @@ static void ssh_scp_init(void) /* * Print an error message and exit after closing the SSH link. */ -static NORETURN void bump(const char *fmt, ...) +static NORETURN PRINTF_LIKE(1, 2) void bump(const char *fmt, ...) { char *str, *str2; va_list ap; va_start(ap, fmt); str = dupvprintf(fmt, ap); va_end(ap); - str2 = dupcat(str, "\n", NULL); + str2 = dupcat(str, "\n"); sfree(str); abandon_stats(); tell_str(stderr, str2); @@ -775,7 +775,7 @@ int scp_send_filename(const char *name, uint64_t size, int permissions) struct fxp_attrs attrs; if (scp_sftp_targetisdir) { - fullname = dupcat(scp_sftp_remotepath, "/", name, NULL); + fullname = dupcat(scp_sftp_remotepath, "/", name); } else { fullname = dupstr(scp_sftp_remotepath); } @@ -825,6 +825,15 @@ int scp_send_filedata(char *data, int len) } while (!xfer_upload_ready(scp_sftp_xfer)) { + if (toplevel_callback_pending()) { + /* If we have pending callbacks, they might make + * xfer_upload_ready start to return true. So we should + * run them and then re-check xfer_upload_ready, before + * we go as far as waiting for an entire packet to + * arrive. */ + run_toplevel_callbacks(); + continue; + } pktin = sftp_recv(); ret = xfer_upload_gotpkt(scp_sftp_xfer, pktin); if (ret <= 0) { @@ -930,7 +939,7 @@ int scp_send_dirname(const char *name, int modes) bool ret; if (scp_sftp_targetisdir) { - fullname = dupcat(scp_sftp_remotepath, "/", name, NULL); + fullname = dupcat(scp_sftp_remotepath, "/", name); } else { fullname = dupstr(scp_sftp_remotepath); } @@ -1141,8 +1150,7 @@ int scp_get_sink_action(struct scp_sink_action *act) if (head->namepos < head->namelen) { head->matched_something = true; fname = dupcat(head->dirpath, "/", - head->names[head->namepos++].filename, - NULL); + head->names[head->namepos++].filename); must_free_fname = true; } else { /* @@ -1320,7 +1328,7 @@ int scp_get_sink_action(struct scp_sink_action *act) act->action = SCP_SINK_RETRY; } else { act->action = SCP_SINK_DIR; - act->buf->len = 0; + strbuf_clear(act->buf); put_asciz(act->buf, stripslashes(fname, false)); act->name = act->buf->s; act->size = 0; /* duhh, it's a directory */ @@ -1340,7 +1348,7 @@ int scp_get_sink_action(struct scp_sink_action *act) * It's a file. Return SCP_SINK_FILE. */ act->action = SCP_SINK_FILE; - act->buf->len = 0; + strbuf_clear(act->buf); put_asciz(act->buf, stripslashes(fname, false)); act->name = act->buf->s; if (attrs.flags & SSH_FILEXFER_ATTR_SIZE) { @@ -1368,7 +1376,7 @@ int scp_get_sink_action(struct scp_sink_action *act) char ch; act->settime = false; - act->buf->len = 0; + strbuf_clear(act->buf); while (!done) { if (!ssh_scp_recv(&ch, 1)) @@ -1401,7 +1409,7 @@ int scp_get_sink_action(struct scp_sink_action *act) &act->mtime, &act->atime) == 2) { act->settime = true; backend_send(backend, "", 1); - act->buf->len = 0; + strbuf_clear(act->buf); continue; /* go round again */ } bump("Protocol error: Illegal time format"); @@ -1555,14 +1563,14 @@ int scp_finish_filerecv(void) * Send an error message to the other side and to the screen. * Increment error counter. */ -static void run_err(const char *fmt, ...) +static PRINTF_LIKE(1, 2) void run_err(const char *fmt, ...) { char *str, *str2; va_list ap; va_start(ap, fmt); errs++; str = dupvprintf(fmt, ap); - str2 = dupcat("pscp: ", str, "\n", NULL); + str2 = dupcat("pscp: ", str, "\n"); sfree(str); scp_send_errmsg(str2); abandon_stats(); @@ -1710,7 +1718,7 @@ static void rsource(const char *src) if (dir != NULL) { char *filename; while ((filename = read_filename(dir)) != NULL) { - char *foundfile = dupcat(src, "/", filename, NULL); + char *foundfile = dupcat(src, "/", filename); source(foundfile); sfree(foundfile); sfree(filename); @@ -1803,7 +1811,7 @@ static void sink(const char *targ, const char *src) with_stripctrl(santarg, act.name) { tell_user(stderr, "warning: remote host sent a" " compound pathname '%s'", sanname); - tell_user(stderr, " renaming local", + tell_user(stderr, " renaming local" " file to '%s'", santarg); } } @@ -2249,7 +2257,7 @@ int psftp_main(int argc, char *argv[]) int i; bool sanitise_stderr = true; - default_protocol = PROT_TELNET; + default_protocol = PROT_SSH; flags = 0 #ifdef FLAG_SYNCAGENT diff --git a/0.73_My_PuTTY/psftp.c b/0.74_My_PuTTY/psftp.c similarity index 98% rename from 0.73_My_PuTTY/psftp.c rename to 0.74_My_PuTTY/psftp.c index 5298f8c..ab08307 100644 --- a/0.73_My_PuTTY/psftp.c +++ b/0.74_My_PuTTY/psftp.c @@ -134,7 +134,7 @@ char *canonify(const char *name) slash = ""; else slash = "/"; - fullname = dupcat(pwd, slash, name, NULL); + fullname = dupcat(pwd, slash, name); } req = fxp_realpath_send(fullname); @@ -213,8 +213,8 @@ char *canonify(const char *name) * component. Concatenate the last component and return. */ returnname = dupcat(canonname, - canonname[strlen(canonname) - 1] == - '/' ? "" : "/", fullname + i + 1, NULL); + (strendswith(canonname, "/") ? "" : "/"), + fullname + i + 1); sfree(fullname); sfree(canonname); return returnname; @@ -386,7 +386,7 @@ bool sftp_get_file(char *fname, char *outfname, bool recurse, bool restart) char *nextfname, *nextoutfname; bool retd; - nextfname = dupcat(fname, "/", ournames[i]->filename, NULL); + nextfname = dupcat(fname, "/", ournames[i]->filename); nextoutfname = dir_file_cat(outfname, ournames[i]->filename); retd = sftp_get_file( nextfname, nextoutfname, recurse, restart); @@ -611,7 +611,7 @@ bool sftp_put_file(char *fname, char *outfname, bool recurse, bool restart) if (restart) { while (i < nnames) { char *nextoutfname; - nextoutfname = dupcat(outfname, "/", ournames[i], NULL); + nextoutfname = dupcat(outfname, "/", ournames[i]); req = fxp_stat_send(nextoutfname); pktin = sftp_wait_for_reply(req); result = fxp_stat_recv(pktin, req, &attrs); @@ -635,7 +635,7 @@ bool sftp_put_file(char *fname, char *outfname, bool recurse, bool restart) bool retd; nextfname = dir_file_cat(fname, ournames[i]); - nextoutfname = dupcat(outfname, "/", ournames[i], NULL); + nextoutfname = dupcat(outfname, "/", ournames[i]); retd = sftp_put_file(nextfname, nextoutfname, recurse, restart); restart = false; /* after first partial file, do full */ sfree(nextoutfname); @@ -734,6 +734,16 @@ bool sftp_put_file(char *fname, char *outfname, bool recurse, bool restart) } } + if (toplevel_callback_pending() && !err && !eof) { + /* If we have pending callbacks, they might make + * xfer_upload_ready start to return true. So we should + * run them and then re-check xfer_upload_ready, before + * we go as far as waiting for an entire packet to + * arrive. */ + run_toplevel_callbacks(); + continue; + } + if (!xfer_done(xfer)) { pktin = sftp_recv(); ret = xfer_upload_gotpkt(xfer, pktin); @@ -1566,7 +1576,7 @@ static bool sftp_action_mv(void *vctx, char *srcfname) p = srcfname + strlen(srcfname); while (p > srcfname && p[-1] != '/') p--; - newname = dupcat(ctx->dstfname, "/", p, NULL); + newname = dupcat(ctx->dstfname, "/", p); newcanon = canonify(newname); sfree(newname); @@ -2315,6 +2325,16 @@ struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags) return cmd; } +static void sftp_cmd_free(struct sftp_command *cmd) +{ + if (cmd->words) { + for (size_t i = 0; i < cmd->nwords; i++) + sfree(cmd->words[i]); + sfree(cmd->words); + } + sfree(cmd); +} + static int do_sftp_init(void) { struct sftp_packet *pktin; @@ -2389,13 +2409,7 @@ int do_sftp(int mode, int modeflags, char *batchfile) if (!cmd) break; ret = cmd->obey(cmd); - if (cmd->words) { - int i; - for(i = 0; i < cmd->nwords; i++) - sfree(cmd->words[i]); - sfree(cmd->words); - } - sfree(cmd); + sftp_cmd_free(cmd); if (ret < 0) break; } @@ -2412,6 +2426,7 @@ int do_sftp(int mode, int modeflags, char *batchfile) if (!cmd) break; ret = cmd->obey(cmd); + sftp_cmd_free(cmd); if (ret < 0) break; if (ret == 0) { diff --git a/0.73_My_PuTTY/psftp.h b/0.74_My_PuTTY/psftp.h similarity index 100% rename from 0.73_My_PuTTY/psftp.h rename to 0.74_My_PuTTY/psftp.h diff --git a/0.73_My_PuTTY/psftpcommon.c b/0.74_My_PuTTY/psftpcommon.c similarity index 100% rename from 0.73_My_PuTTY/psftpcommon.c rename to 0.74_My_PuTTY/psftpcommon.c diff --git a/0.73_My_PuTTY/putty.h b/0.74_My_PuTTY/putty.h similarity index 98% rename from 0.73_My_PuTTY/putty.h rename to 0.74_My_PuTTY/putty.h index 57a45d2..5c88b5c 100644 --- a/0.73_My_PuTTY/putty.h +++ b/0.74_My_PuTTY/putty.h @@ -686,19 +686,7 @@ GLOBAL char *cmdline_session_name; typedef struct { char *prompt; bool echo; - /* - * 'result' must be a dynamically allocated array of exactly - * 'resultsize' chars. The code for actually reading input may - * realloc it bigger (and adjust resultsize accordingly) if it has - * to. The caller should free it again when finished with it. - * - * If resultsize==0, then result may be NULL. When setting up a - * prompt_t, it's therefore easiest to initialise them this way, - * which means all actual allocation is done by the callee. This - * is what add_prompt does. - */ - char *result; - size_t resultsize; + strbuf *result; } prompt_t; typedef struct { /* @@ -729,11 +717,11 @@ typedef struct { void *data; /* slot for housekeeping data, managed by * seat_get_userpass_input(); initially NULL */ } prompts_t; -prompts_t *new_prompts(); +prompts_t *new_prompts(void); void add_prompt(prompts_t *p, char *promptstr, bool echo); void prompt_set_result(prompt_t *pr, const char *newstr); -void prompt_ensure_result_size(prompt_t *pr, int len); -/* Burn the evidence. (Assumes _all_ strings want free()ing.) */ +char *prompt_get_result(prompt_t *pr); +const char *prompt_get_result_ref(prompt_t *pr); void free_prompts(prompts_t *p); /* @@ -1065,7 +1053,7 @@ static inline bool seat_set_trust_status(Seat *seat, bool trusted) /* Unlike the seat's actual method, the public entry point * seat_connection_fatal is a wrapper function with a printf-like API, * defined in misc.c. */ -void seat_connection_fatal(Seat *seat, const char *fmt, ...); +void seat_connection_fatal(Seat *seat, const char *fmt, ...) PRINTF_LIKE(2, 3); /* Handy aliases for seat_output which set is_stderr to a fixed value. */ static inline size_t seat_stdout(Seat *seat, const void *data, size_t len) @@ -1286,8 +1274,8 @@ static inline bool win_is_utf8(TermWin *win) /* * Global functions not specific to a connection instance. */ -void nonfatal(const char *, ...); -NORETURN void modalfatalbox(const char *, ...); +void nonfatal(const char *, ...) PRINTF_LIKE(1, 2); +NORETURN void modalfatalbox(const char *, ...) PRINTF_LIKE(1, 2); NORETURN void cleanup_exit(int); /* @@ -1324,6 +1312,7 @@ NORETURN void cleanup_exit(int); X(BOOL, NONE, compression) \ X(INT, INT, ssh_kexlist) \ X(INT, INT, ssh_hklist) \ + X(BOOL, NONE, ssh_prefer_known_hostkeys) \ X(INT, NONE, ssh_rekey_time) /* in minutes */ \ X(STR, NONE, ssh_rekey_data) /* string encoding e.g. "100K", "2M", "1G" */ \ X(BOOL, NONE, tryagent) \ @@ -1895,7 +1884,7 @@ void logfclose(LogContext *logctx); void logtraffic(LogContext *logctx, unsigned char c, int logmode); void logflush(LogContext *logctx); void logevent(LogContext *logctx, const char *event); -void logeventf(LogContext *logctx, const char *fmt, ...); +void logeventf(LogContext *logctx, const char *fmt, ...) PRINTF_LIKE(2, 3); void logeventvf(LogContext *logctx, const char *fmt, va_list ap); /* @@ -2118,7 +2107,8 @@ bool is_interactive(void); void console_print_error_msg(const char *prefix, const char *msg); void console_print_error_msg_fmt_v( const char *prefix, const char *fmt, va_list ap); -void console_print_error_msg_fmt(const char *prefix, const char *fmt, ...); +void console_print_error_msg_fmt(const char *prefix, const char *fmt, ...) + PRINTF_LIKE(2, 3); /* * Exports from printing.c. @@ -2156,7 +2146,7 @@ bool cmdline_host_ok(Conf *); #define TOOLTYPE_PORT_ARG 64 extern int cmdline_tooltype; -void cmdline_error(const char *, ...); +void cmdline_error(const char *, ...) PRINTF_LIKE(1, 2); /* * Exports from config.c. diff --git a/0.73_My_PuTTY/puttymem.h b/0.74_My_PuTTY/puttymem.h similarity index 100% rename from 0.73_My_PuTTY/puttymem.h rename to 0.74_My_PuTTY/puttymem.h diff --git a/0.73_My_PuTTY/puttyps.h b/0.74_My_PuTTY/puttyps.h similarity index 100% rename from 0.73_My_PuTTY/puttyps.h rename to 0.74_My_PuTTY/puttyps.h diff --git a/0.73_My_PuTTY/raw.c b/0.74_My_PuTTY/raw.c similarity index 100% rename from 0.73_My_PuTTY/raw.c rename to 0.74_My_PuTTY/raw.c diff --git a/0.73_My_PuTTY/resource.h b/0.74_My_PuTTY/resource.h similarity index 100% rename from 0.73_My_PuTTY/resource.h rename to 0.74_My_PuTTY/resource.h diff --git a/0.73_My_PuTTY/rlogin.c b/0.74_My_PuTTY/rlogin.c similarity index 80% rename from 0.73_My_PuTTY/rlogin.c rename to 0.74_My_PuTTY/rlogin.c index 7a8a3ba..400ecb6 100644 --- a/0.73_My_PuTTY/rlogin.c +++ b/0.74_My_PuTTY/rlogin.c @@ -1,426 +1,428 @@ -/* - * Rlogin backend. - */ - -#include -#include -#include -#include - -#include "putty.h" - -#define RLOGIN_MAX_BACKLOG 4096 - -typedef struct Rlogin Rlogin; -struct Rlogin { - Socket *s; - bool closed_on_socket_error; - int bufsize; - bool firstbyte; - bool cansize; - int term_width, term_height; - Seat *seat; - LogContext *logctx; - - Conf *conf; - - /* In case we need to read a username from the terminal before starting */ - prompts_t *prompt; - - Plug plug; - Backend backend; -}; - -static void c_write(Rlogin *rlogin, const void *buf, size_t len) -{ - size_t backlog = seat_stdout(rlogin->seat, buf, len); - sk_set_frozen(rlogin->s, backlog > RLOGIN_MAX_BACKLOG); -} - -static void rlogin_log(Plug *plug, int type, SockAddr *addr, int port, - const char *error_msg, int error_code) -{ - Rlogin *rlogin = container_of(plug, Rlogin, plug); - backend_socket_log(rlogin->seat, rlogin->logctx, type, addr, port, - error_msg, error_code, - rlogin->conf, !rlogin->firstbyte); -} - -static void rlogin_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) -{ - Rlogin *rlogin = container_of(plug, Rlogin, plug); - - /* - * We don't implement independent EOF in each direction for Telnet - * connections; as soon as we get word that the remote side has - * sent us EOF, we wind up the whole connection. - */ - - if (rlogin->s) { - sk_close(rlogin->s); - rlogin->s = NULL; - if (error_msg) - rlogin->closed_on_socket_error = true; - seat_notify_remote_exit(rlogin->seat); - } - if (error_msg) { - /* A socket error has occurred. */ - logevent(rlogin->logctx, error_msg); - seat_connection_fatal(rlogin->seat, "%s", error_msg); - } /* Otherwise, the remote side closed the connection normally. */ -} - -static void rlogin_receive( - Plug *plug, int urgent, const char *data, size_t len) -{ - Rlogin *rlogin = container_of(plug, Rlogin, plug); - if (len == 0) - return; - if (urgent == 2) { - char c; - - c = *data++; - len--; - if (c == '\x80') { - rlogin->cansize = true; - backend_size(&rlogin->backend, - rlogin->term_width, rlogin->term_height); - } - /* - * We should flush everything (aka Telnet SYNCH) if we see - * 0x02, and we should turn off and on _local_ flow control - * on 0x10 and 0x20 respectively. I'm not convinced it's - * worth it... - */ - } else { - /* - * Main rlogin protocol. This is really simple: the first - * byte is expected to be NULL and is ignored, and the rest - * is printed. - */ - if (rlogin->firstbyte) { - if (data[0] == '\0') { - data++; - len--; - } - rlogin->firstbyte = false; - } - if (len > 0) - c_write(rlogin, data, len); - } -} - -static void rlogin_sent(Plug *plug, size_t bufsize) -{ - Rlogin *rlogin = container_of(plug, Rlogin, plug); - rlogin->bufsize = bufsize; -} - -static void rlogin_startup(Rlogin *rlogin, const char *ruser) -{ - char z = 0; - char *p; - - sk_write(rlogin->s, &z, 1); - p = conf_get_str(rlogin->conf, CONF_localusername); - sk_write(rlogin->s, p, strlen(p)); - sk_write(rlogin->s, &z, 1); - sk_write(rlogin->s, ruser, strlen(ruser)); - sk_write(rlogin->s, &z, 1); - p = conf_get_str(rlogin->conf, CONF_termtype); - sk_write(rlogin->s, p, strlen(p)); - sk_write(rlogin->s, "/", 1); - p = conf_get_str(rlogin->conf, CONF_termspeed); - sk_write(rlogin->s, p, strspn(p, "0123456789")); - rlogin->bufsize = sk_write(rlogin->s, &z, 1); - - rlogin->prompt = NULL; -} - -static const PlugVtable Rlogin_plugvt = { - rlogin_log, - rlogin_closing, - rlogin_receive, - rlogin_sent -}; - -/* - * Called to set up the rlogin connection. - * - * Returns an error message, or NULL on success. - * - * Also places the canonical host name into `realhost'. It must be - * freed by the caller. - */ -static const char *rlogin_init(Seat *seat, Backend **backend_handle, - LogContext *logctx, Conf *conf, - const char *host, int port, char **realhost, - bool nodelay, bool keepalive) -{ - SockAddr *addr; - const char *err; - Rlogin *rlogin; - char *ruser; - int addressfamily; - char *loghost; - - rlogin = snew(Rlogin); - rlogin->plug.vt = &Rlogin_plugvt; - rlogin->backend.vt = &rlogin_backend; - rlogin->s = NULL; - rlogin->closed_on_socket_error = false; - rlogin->seat = seat; - rlogin->logctx = logctx; - rlogin->term_width = conf_get_int(conf, CONF_width); - rlogin->term_height = conf_get_int(conf, CONF_height); - rlogin->firstbyte = true; - rlogin->cansize = false; - rlogin->prompt = NULL; - rlogin->conf = conf_copy(conf); - *backend_handle = &rlogin->backend; - - addressfamily = conf_get_int(conf, CONF_addressfamily); - /* - * Try to find host. - */ - addr = name_lookup(host, port, realhost, conf, addressfamily, - rlogin->logctx, "rlogin connection"); - if ((err = sk_addr_error(addr)) != NULL) { - sk_addr_free(addr); - return err; - } - - if (port < 0) - port = 513; /* default rlogin port */ - - /* - * Open socket. - */ - rlogin->s = new_connection(addr, *realhost, port, true, false, - nodelay, keepalive, &rlogin->plug, conf); - if ((err = sk_socket_error(rlogin->s)) != NULL) - return err; - - loghost = conf_get_str(conf, CONF_loghost); - if (*loghost) { - char *colon; - - sfree(*realhost); - *realhost = dupstr(loghost); - - colon = host_strrchr(*realhost, ':'); - if (colon) - *colon++ = '\0'; - } - - /* - * Send local username, remote username, terminal type and - * terminal speed - unless we don't have the remote username yet, - * in which case we prompt for it and may end up deferring doing - * anything else until the local prompt mechanism returns. - */ - if ((ruser = get_remote_username(conf)) != NULL) { - /* Next terminal output will come from server */ - seat_set_trust_status(rlogin->seat, false); - rlogin_startup(rlogin, ruser); - sfree(ruser); - } else { - int ret; - - rlogin->prompt = new_prompts(); - rlogin->prompt->to_server = true; - rlogin->prompt->from_server = false; - rlogin->prompt->name = dupstr("Rlogin login name"); - add_prompt(rlogin->prompt, dupstr("rlogin username: "), true); - ret = seat_get_userpass_input(rlogin->seat, rlogin->prompt, NULL); - if (ret >= 0) { - /* Next terminal output will come from server */ - seat_set_trust_status(rlogin->seat, false); - rlogin_startup(rlogin, rlogin->prompt->prompts[0]->result); - } - } - - return NULL; -} - -static void rlogin_free(Backend *be) -{ - Rlogin *rlogin = container_of(be, Rlogin, backend); - - if (rlogin->prompt) - free_prompts(rlogin->prompt); - if (rlogin->s) - sk_close(rlogin->s); - conf_free(rlogin->conf); - sfree(rlogin); -} - -/* - * Stub routine (we don't have any need to reconfigure this backend). - */ -static void rlogin_reconfig(Backend *be, Conf *conf) -{ -} - -/* - * Called to send data down the rlogin connection. - */ -static size_t rlogin_send(Backend *be, const char *buf, size_t len) -{ - Rlogin *rlogin = container_of(be, Rlogin, backend); - bufchain bc; - - if (rlogin->s == NULL) - return 0; - - bufchain_init(&bc); - bufchain_add(&bc, buf, len); - - if (rlogin->prompt) { - /* - * We're still prompting for a username, and aren't talking - * directly to the network connection yet. - */ - int ret = seat_get_userpass_input(rlogin->seat, rlogin->prompt, &bc); - if (ret >= 0) { - /* Next terminal output will come from server */ - seat_set_trust_status(rlogin->seat, false); - rlogin_startup(rlogin, rlogin->prompt->prompts[0]->result); - /* that nulls out rlogin->prompt, so then we'll start sending - * data down the wire in the obvious way */ - } - } - - if (!rlogin->prompt) { - while (bufchain_size(&bc) > 0) { - ptrlen data = bufchain_prefix(&bc); - rlogin->bufsize = sk_write(rlogin->s, data.ptr, data.len); - bufchain_consume(&bc, len); - } - } - - bufchain_clear(&bc); - - return rlogin->bufsize; -} - -/* - * Called to query the current socket sendability status. - */ -static size_t rlogin_sendbuffer(Backend *be) -{ - Rlogin *rlogin = container_of(be, Rlogin, backend); - return rlogin->bufsize; -} - -/* - * Called to set the size of the window - */ -static void rlogin_size(Backend *be, int width, int height) -{ - Rlogin *rlogin = container_of(be, Rlogin, backend); - char b[12] = { '\xFF', '\xFF', 0x73, 0x73, 0, 0, 0, 0, 0, 0, 0, 0 }; - - rlogin->term_width = width; - rlogin->term_height = height; - - if (rlogin->s == NULL || !rlogin->cansize) - return; - - b[6] = rlogin->term_width >> 8; - b[7] = rlogin->term_width & 0xFF; - b[4] = rlogin->term_height >> 8; - b[5] = rlogin->term_height & 0xFF; - rlogin->bufsize = sk_write(rlogin->s, b, 12); - return; -} - -/* - * Send rlogin special codes. - */ -static void rlogin_special(Backend *be, SessionSpecialCode code, int arg) -{ - /* Do nothing! */ - return; -} - -/* - * Return a list of the special codes that make sense in this - * protocol. - */ -static const SessionSpecial *rlogin_get_specials(Backend *be) -{ - return NULL; -} - -static bool rlogin_connected(Backend *be) -{ - Rlogin *rlogin = container_of(be, Rlogin, backend); - return rlogin->s != NULL; -} - -static bool rlogin_sendok(Backend *be) -{ - /* Rlogin *rlogin = container_of(be, Rlogin, backend); */ - return true; -} - -static void rlogin_unthrottle(Backend *be, size_t backlog) -{ - Rlogin *rlogin = container_of(be, Rlogin, backend); - sk_set_frozen(rlogin->s, backlog > RLOGIN_MAX_BACKLOG); -} - -static bool rlogin_ldisc(Backend *be, int option) -{ - /* Rlogin *rlogin = container_of(be, Rlogin, backend); */ - return false; -} - -static void rlogin_provide_ldisc(Backend *be, Ldisc *ldisc) -{ - /* This is a stub. */ -} - -static int rlogin_exitcode(Backend *be) -{ - Rlogin *rlogin = container_of(be, Rlogin, backend); - if (rlogin->s != NULL) - return -1; /* still connected */ - else if (rlogin->closed_on_socket_error) - return INT_MAX; /* a socket error counts as an unclean exit */ - else - /* If we ever implement RSH, we'll probably need to do this properly */ - return 0; -} - -/* - * cfg_info for rlogin does nothing at all. - */ -static int rlogin_cfg_info(Backend *be) -{ - return 0; -} - -const struct BackendVtable rlogin_backend = { - rlogin_init, - rlogin_free, - rlogin_reconfig, - rlogin_send, - rlogin_sendbuffer, - rlogin_size, - rlogin_special, - rlogin_get_specials, - rlogin_connected, - rlogin_exitcode, - rlogin_sendok, - rlogin_ldisc, - rlogin_provide_ldisc, - rlogin_unthrottle, - rlogin_cfg_info, - NULL /* test_for_upstream */, - "rlogin", - PROT_RLOGIN, - 513 -}; +/* + * Rlogin backend. + */ + +#include +#include +#include +#include + +#include "putty.h" + +#define RLOGIN_MAX_BACKLOG 4096 + +typedef struct Rlogin Rlogin; +struct Rlogin { + Socket *s; + bool closed_on_socket_error; + int bufsize; + bool firstbyte; + bool cansize; + int term_width, term_height; + Seat *seat; + LogContext *logctx; + + Conf *conf; + + /* In case we need to read a username from the terminal before starting */ + prompts_t *prompt; + + Plug plug; + Backend backend; +}; + +static void c_write(Rlogin *rlogin, const void *buf, size_t len) +{ + size_t backlog = seat_stdout(rlogin->seat, buf, len); + sk_set_frozen(rlogin->s, backlog > RLOGIN_MAX_BACKLOG); +} + +static void rlogin_log(Plug *plug, int type, SockAddr *addr, int port, + const char *error_msg, int error_code) +{ + Rlogin *rlogin = container_of(plug, Rlogin, plug); + backend_socket_log(rlogin->seat, rlogin->logctx, type, addr, port, + error_msg, error_code, + rlogin->conf, !rlogin->firstbyte); +} + +static void rlogin_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back) +{ + Rlogin *rlogin = container_of(plug, Rlogin, plug); + + /* + * We don't implement independent EOF in each direction for Telnet + * connections; as soon as we get word that the remote side has + * sent us EOF, we wind up the whole connection. + */ + + if (rlogin->s) { + sk_close(rlogin->s); + rlogin->s = NULL; + if (error_msg) + rlogin->closed_on_socket_error = true; + seat_notify_remote_exit(rlogin->seat); + } + if (error_msg) { + /* A socket error has occurred. */ + logevent(rlogin->logctx, error_msg); + seat_connection_fatal(rlogin->seat, "%s", error_msg); + } /* Otherwise, the remote side closed the connection normally. */ +} + +static void rlogin_receive( + Plug *plug, int urgent, const char *data, size_t len) +{ + Rlogin *rlogin = container_of(plug, Rlogin, plug); + if (len == 0) + return; + if (urgent == 2) { + char c; + + c = *data++; + len--; + if (c == '\x80') { + rlogin->cansize = true; + backend_size(&rlogin->backend, + rlogin->term_width, rlogin->term_height); + } + /* + * We should flush everything (aka Telnet SYNCH) if we see + * 0x02, and we should turn off and on _local_ flow control + * on 0x10 and 0x20 respectively. I'm not convinced it's + * worth it... + */ + } else { + /* + * Main rlogin protocol. This is really simple: the first + * byte is expected to be NULL and is ignored, and the rest + * is printed. + */ + if (rlogin->firstbyte) { + if (data[0] == '\0') { + data++; + len--; + } + rlogin->firstbyte = false; + } + if (len > 0) + c_write(rlogin, data, len); + } +} + +static void rlogin_sent(Plug *plug, size_t bufsize) +{ + Rlogin *rlogin = container_of(plug, Rlogin, plug); + rlogin->bufsize = bufsize; +} + +static void rlogin_startup(Rlogin *rlogin, const char *ruser) +{ + char z = 0; + char *p; + + sk_write(rlogin->s, &z, 1); + p = conf_get_str(rlogin->conf, CONF_localusername); + sk_write(rlogin->s, p, strlen(p)); + sk_write(rlogin->s, &z, 1); + sk_write(rlogin->s, ruser, strlen(ruser)); + sk_write(rlogin->s, &z, 1); + p = conf_get_str(rlogin->conf, CONF_termtype); + sk_write(rlogin->s, p, strlen(p)); + sk_write(rlogin->s, "/", 1); + p = conf_get_str(rlogin->conf, CONF_termspeed); + sk_write(rlogin->s, p, strspn(p, "0123456789")); + rlogin->bufsize = sk_write(rlogin->s, &z, 1); + + rlogin->prompt = NULL; +} + +static const PlugVtable Rlogin_plugvt = { + rlogin_log, + rlogin_closing, + rlogin_receive, + rlogin_sent +}; + +/* + * Called to set up the rlogin connection. + * + * Returns an error message, or NULL on success. + * + * Also places the canonical host name into `realhost'. It must be + * freed by the caller. + */ +static const char *rlogin_init(Seat *seat, Backend **backend_handle, + LogContext *logctx, Conf *conf, + const char *host, int port, char **realhost, + bool nodelay, bool keepalive) +{ + SockAddr *addr; + const char *err; + Rlogin *rlogin; + char *ruser; + int addressfamily; + char *loghost; + + rlogin = snew(Rlogin); + rlogin->plug.vt = &Rlogin_plugvt; + rlogin->backend.vt = &rlogin_backend; + rlogin->s = NULL; + rlogin->closed_on_socket_error = false; + rlogin->seat = seat; + rlogin->logctx = logctx; + rlogin->term_width = conf_get_int(conf, CONF_width); + rlogin->term_height = conf_get_int(conf, CONF_height); + rlogin->firstbyte = true; + rlogin->cansize = false; + rlogin->prompt = NULL; + rlogin->conf = conf_copy(conf); + *backend_handle = &rlogin->backend; + + addressfamily = conf_get_int(conf, CONF_addressfamily); + /* + * Try to find host. + */ + addr = name_lookup(host, port, realhost, conf, addressfamily, + rlogin->logctx, "rlogin connection"); + if ((err = sk_addr_error(addr)) != NULL) { + sk_addr_free(addr); + return err; + } + + if (port < 0) + port = 513; /* default rlogin port */ + + /* + * Open socket. + */ + rlogin->s = new_connection(addr, *realhost, port, true, false, + nodelay, keepalive, &rlogin->plug, conf); + if ((err = sk_socket_error(rlogin->s)) != NULL) + return err; + + loghost = conf_get_str(conf, CONF_loghost); + if (*loghost) { + char *colon; + + sfree(*realhost); + *realhost = dupstr(loghost); + + colon = host_strrchr(*realhost, ':'); + if (colon) + *colon++ = '\0'; + } + + /* + * Send local username, remote username, terminal type and + * terminal speed - unless we don't have the remote username yet, + * in which case we prompt for it and may end up deferring doing + * anything else until the local prompt mechanism returns. + */ + if ((ruser = get_remote_username(conf)) != NULL) { + /* Next terminal output will come from server */ + seat_set_trust_status(rlogin->seat, false); + rlogin_startup(rlogin, ruser); + sfree(ruser); + } else { + int ret; + + rlogin->prompt = new_prompts(); + rlogin->prompt->to_server = true; + rlogin->prompt->from_server = false; + rlogin->prompt->name = dupstr("Rlogin login name"); + add_prompt(rlogin->prompt, dupstr("rlogin username: "), true); + ret = seat_get_userpass_input(rlogin->seat, rlogin->prompt, NULL); + if (ret >= 0) { + /* Next terminal output will come from server */ + seat_set_trust_status(rlogin->seat, false); + rlogin_startup(rlogin, prompt_get_result_ref( + rlogin->prompt->prompts[0])); + } + } + + return NULL; +} + +static void rlogin_free(Backend *be) +{ + Rlogin *rlogin = container_of(be, Rlogin, backend); + + if (rlogin->prompt) + free_prompts(rlogin->prompt); + if (rlogin->s) + sk_close(rlogin->s); + conf_free(rlogin->conf); + sfree(rlogin); +} + +/* + * Stub routine (we don't have any need to reconfigure this backend). + */ +static void rlogin_reconfig(Backend *be, Conf *conf) +{ +} + +/* + * Called to send data down the rlogin connection. + */ +static size_t rlogin_send(Backend *be, const char *buf, size_t len) +{ + Rlogin *rlogin = container_of(be, Rlogin, backend); + bufchain bc; + + if (rlogin->s == NULL) + return 0; + + bufchain_init(&bc); + bufchain_add(&bc, buf, len); + + if (rlogin->prompt) { + /* + * We're still prompting for a username, and aren't talking + * directly to the network connection yet. + */ + int ret = seat_get_userpass_input(rlogin->seat, rlogin->prompt, &bc); + if (ret >= 0) { + /* Next terminal output will come from server */ + seat_set_trust_status(rlogin->seat, false); + rlogin_startup(rlogin, prompt_get_result_ref( + rlogin->prompt->prompts[0])); + /* that nulls out rlogin->prompt, so then we'll start sending + * data down the wire in the obvious way */ + } + } + + if (!rlogin->prompt) { + while (bufchain_size(&bc) > 0) { + ptrlen data = bufchain_prefix(&bc); + rlogin->bufsize = sk_write(rlogin->s, data.ptr, data.len); + bufchain_consume(&bc, len); + } + } + + bufchain_clear(&bc); + + return rlogin->bufsize; +} + +/* + * Called to query the current socket sendability status. + */ +static size_t rlogin_sendbuffer(Backend *be) +{ + Rlogin *rlogin = container_of(be, Rlogin, backend); + return rlogin->bufsize; +} + +/* + * Called to set the size of the window + */ +static void rlogin_size(Backend *be, int width, int height) +{ + Rlogin *rlogin = container_of(be, Rlogin, backend); + char b[12] = { '\xFF', '\xFF', 0x73, 0x73, 0, 0, 0, 0, 0, 0, 0, 0 }; + + rlogin->term_width = width; + rlogin->term_height = height; + + if (rlogin->s == NULL || !rlogin->cansize) + return; + + b[6] = rlogin->term_width >> 8; + b[7] = rlogin->term_width & 0xFF; + b[4] = rlogin->term_height >> 8; + b[5] = rlogin->term_height & 0xFF; + rlogin->bufsize = sk_write(rlogin->s, b, 12); + return; +} + +/* + * Send rlogin special codes. + */ +static void rlogin_special(Backend *be, SessionSpecialCode code, int arg) +{ + /* Do nothing! */ + return; +} + +/* + * Return a list of the special codes that make sense in this + * protocol. + */ +static const SessionSpecial *rlogin_get_specials(Backend *be) +{ + return NULL; +} + +static bool rlogin_connected(Backend *be) +{ + Rlogin *rlogin = container_of(be, Rlogin, backend); + return rlogin->s != NULL; +} + +static bool rlogin_sendok(Backend *be) +{ + /* Rlogin *rlogin = container_of(be, Rlogin, backend); */ + return true; +} + +static void rlogin_unthrottle(Backend *be, size_t backlog) +{ + Rlogin *rlogin = container_of(be, Rlogin, backend); + sk_set_frozen(rlogin->s, backlog > RLOGIN_MAX_BACKLOG); +} + +static bool rlogin_ldisc(Backend *be, int option) +{ + /* Rlogin *rlogin = container_of(be, Rlogin, backend); */ + return false; +} + +static void rlogin_provide_ldisc(Backend *be, Ldisc *ldisc) +{ + /* This is a stub. */ +} + +static int rlogin_exitcode(Backend *be) +{ + Rlogin *rlogin = container_of(be, Rlogin, backend); + if (rlogin->s != NULL) + return -1; /* still connected */ + else if (rlogin->closed_on_socket_error) + return INT_MAX; /* a socket error counts as an unclean exit */ + else + /* If we ever implement RSH, we'll probably need to do this properly */ + return 0; +} + +/* + * cfg_info for rlogin does nothing at all. + */ +static int rlogin_cfg_info(Backend *be) +{ + return 0; +} + +const struct BackendVtable rlogin_backend = { + rlogin_init, + rlogin_free, + rlogin_reconfig, + rlogin_send, + rlogin_sendbuffer, + rlogin_size, + rlogin_special, + rlogin_get_specials, + rlogin_connected, + rlogin_exitcode, + rlogin_sendok, + rlogin_ldisc, + rlogin_provide_ldisc, + rlogin_unthrottle, + rlogin_cfg_info, + NULL /* test_for_upstream */, + "rlogin", + PROT_RLOGIN, + 513 +}; diff --git a/0.73_My_PuTTY/scpserver.c b/0.74_My_PuTTY/scpserver.c similarity index 96% rename from 0.73_My_PuTTY/scpserver.c rename to 0.74_My_PuTTY/scpserver.c index 9198382..45050e5 100644 --- a/0.73_My_PuTTY/scpserver.c +++ b/0.74_My_PuTTY/scpserver.c @@ -1,1395 +1,1399 @@ -/* - * Server side of the old-school SCP protocol. - */ - -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "sshcr.h" -#include "sshchan.h" -#include "sftp.h" - -/* - * I think it's worth actually documenting my understanding of what - * this protocol _is_, since I don't know of any other documentation - * of it anywhere. - * - * Format of data stream - * --------------------- - * - * The sending side of an SCP connection - the client, if you're - * uploading files, or the server if you're downloading - sends a data - * stream consisting of a sequence of 'commands', or header records, - * or whatever you want to call them, interleaved with file data. - * - * Each command starts with a letter indicating what type it is, and - * ends with a \n. - * - * The 'C' command introduces an actual file. It is followed by an - * octal file-permissions mask, then a space, then a decimal file - * size, then a space, then the file name up to the termating newline. - * For example, "C0644 12345 filename.txt\n" would be a plausible C - * command. - * - * After the 'C' command, the sending side will transmit exactly as - * many bytes of file data as specified by the size field in the - * header line, followed by a single zero byte. - * - * The 'D' command introduces a subdirectory. Its format is identical - * to 'C', including the size field, but the size field is sent as - * zero. - * - * After the 'D' command, all subsequent C and D commands are taken to - * indicate files that should be placed inside that subdirectory, - * until a terminating 'E' command. - * - * The 'E' command indicates the end of a subdirectory. It has no - * arguments at all (its format is always just "E\n"). After the E - * command, the receiver should revert to placing further downloaded - * files in whatever directory it was placing them before the - * subdirectory opened by the just-closed D. - * - * D and E commands match like parentheses: if you send, say, - * - * C0644 123 foo.txt ( followed by data ) - * D0755 0 subdir - * C0644 123 bar.txt ( followed by data ) - * D0755 0 subsubdir - * C0644 123 baz.txt ( followed by data ) - * E - * C0644 123 quux.txt ( followed by data ) - * E - * C0644 123 wibble.txt ( followed by data ) - * - * then foo.txt, subdir and wibble.txt go in the top-level destination - * directory; bar.txt, subsubdir and quux.txt go in 'subdir'; and - * baz.txt goes in 'subdir/subsubdir'. - * - * The sender terminates the data stream with EOF when it has no more - * files to send. I believe it is not _required_ for all D to be - * closed by an E before this happens - you can elide a trailing - * sequence of E commands without provoking an error message from the - * receiver. - * - * Finally, the 'T' command is sent immediately before a C or D. It is - * followed by four space-separated decimal integers giving an mtime - * and atime to be applied to the file or directory created by the - * following C or D command. The first two integers give the mtime, - * encoded as seconds and microseconds (respectively) since the Unix - * epoch; the next two give the atime, encoded similarly. So - * "T1540373455 0 1540373457 0\n" is an example of a valid T command. - * - * Acknowledgments - * --------------- - * - * The sending side waits for an ack from the receiving side before - * sending each command; before beginning to send the file data - * following a C command; and before sending the final EOF. - * - * (In particular, the receiving side is expected to send an initial - * ack before _anything_ is sent.) - * - * Normally an ack consists of a single zero byte. It's also allowable - * to send a byte with value 1 or 2 followed by a \n-terminated error - * message (where 1 means a non-fatal error and 2 means a fatal one). - * I have to suppose that sending an error message from client to - * server is of limited use, but apparently it's allowed. - * - * Initiation - * ---------- - * - * The protocol is begun by the client sending a command string to the - * server via the SSH-2 "exec" request (or the analogous - * SSH1_CMSG_EXEC_CMD), which indicates that this is an scp session - * rather than any other thing; specifies the direction of transfer; - * says what file(s) are to be sent by the server, or where the server - * should put files that the client is about to send; and a couple of - * other options. - * - * The command string takes the following form: - * - * Start with prefix "scp ", indicating that this is an SCP command at - * all. Otherwise it's a request to run some completely different - * command in the SSH session. - * - * Next the command can contain zero or more of the following options, - * each followed by a space: - * - * "-v" turns on verbose server diagnostics. Of course a server is not - * required to actually produce any, but this is an invitation for it - * to send any it might have available. Diagnostics are free-form, and - * sent as SSH standard-error extended data, so that they are separate - * from the actual data stream as described above. - * - * (Servers can send standard-error output anyway if they like, and in - * case of an actual error, they probably will with or without -v.) - * - * "-r" indicates recursive file transfer, i.e. potentially including - * subdirectories. For a download, this indicates that the client is - * willing to receive subdirectories (a D/E command pair bracketing - * further files and subdirs); without it, the server should only send - * C commands for individual files, followed by EOF. - * - * This flag must also be specified for a recursive upload, because I - * believe at least one server will reject D/E pairs sent by the - * client if the command didn't have -r in it. (Probably a consequence - * of sharing code between download and upload.) - * - * "-p" means preserve file times. In a download, this requests the - * server to send a T command before each C or D. I don't know whether - * any server will insist on having seen this option from the client - * before accepting T commands in an upload, but it is probably - * sensible to send it anyway. - * - * "-d", in an upload, means that the destination pathname (see below) - * is expected to be a directory, and that uploaded files (and - * subdirs) should be put inside it. Without -d, the semantics are - * that _if_ the destination exists and is a directory, then files - * will be put in it, whereas if it is not, then just a single file - * (or subdir) upload is expected, which will be placed at that exact - * pathname. - * - * In a download, I observe that clients tend to send -d if they are - * requesting multiple files or a wildcard, but as far as I know, - * servers ignore it. - * - * After all those optional options, there is a single mandatory - * option indicating the direction of transfer, which is either "-f" - * or "-t". "-f" indicates a download; "-t" indicates an upload. - * - * After that mandatory option, there is a single space, followed by - * the name(s) of files to transfer. - * - * This file name field is transmitted with NO QUOTING, in spite of - * the fact that a server will typically interpret it as a shell - * command. You'd think this couldn't possibly work, in the face of - * almost any filename with an interesting character in it - and you'd - * be right. Or rather (you might argue), it works 'as designed', but - * it's designed in a weird way, in that it's the user's - * responsibility to apply quoting on the client command line to get - * the filename through the shell that will decode things on the - * server side. - * - * But one effect of this is that if you issue a download command - * including a wildcard, say "scp -f somedir/foo*.txt", then the shell - * will expand the wildcard, and actually run the server-side scp - * program with multiple arguments, say "somedir/foo.txt - * somedir/quux.txt", leading to the download sending multiple C - * commands. This clearly _is_ intended: it's how a command such as - * 'scp server:somedir/foo*.txt destdir' can work at all. - * - * (You would think, given that, that it might also be legal to send - * multiple space-separated filenames in order to trigger a download - * of exactly those files. Given how scp is invoked in practice on a - * typical server, this would surely actually work, but my observation - * is that scp clients don't in fact try this - if you run OpenSSH's - * scp by saying 'scp server:foo server:bar destdir' then it will make - * two separate connections to the server for the two files, rather - * than sending a single space-separated remote command. PSCP won't - * even do that, and will make you do it in two separate runs.) - * - * So, some examples: - * - * - "scp -f filename.txt" - * - * Server should send a single C command (plus data) for that file. - * Client ought to ignore the filename in the C command, in favour - * of saving the file under the name implied by the user's command - * line. - * - * - "scp -f file*.txt" - * - * Server sends zero or more C commands, then EOF. Client will have - * been given a target directory to put them all in, and will name - * each one according to the name in the C command. - * - * (You'd like the client to validate the filenames against the - * wildcard it sent, to ensure a malicious server didn't try to - * overwrite some path like ".bashrc" when you thought you were - * downloading only normal text files. But wildcard semantics are - * chosen by the server, so this is essentially hopeless to do - * rigorously.) - * - * - "scp -f -r somedir" - * - * Assuming somedir is actually a directory, server sends a D/E - * pair, in between which are the contents of the directory - * (perhaps including further nested D/E pairs). Client probably - * ignores the name field of the outermost D - * - * - "scp -f -r some*wild*card*" - * - * Server sends multiple C or D-stuff-E, one for each top-level - * thing matching the wildcard, whether it's a file or a directory. - * - * - "scp -t -d some_dir" - * - * Client sends stuff, and server deposits each file at - * some_dir/. - * - * - "scp -t some_path_name" - * - * Client sends one C command, and server deposits it at - * some_path_name itself, or in some_path_name/, depending whether some_path_name was already a - * directory or not. - */ - -/* - * Here's a useful debugging aid: run over a binary file containing - * the complete contents of the sender's data stream (e.g. extracted - * by contrib/logparse.pl -d), it removes the file contents, leaving - * only the list of commands, so you can see what the server sent. - * - * perl -pe 'read ARGV,$x,1+$1 if/^C\S+ (\d+)/' - */ - -/* ---------------------------------------------------------------------- - * Shared system for receiving replies from the SftpServer, and - * putting them into a set of ordinary variables rather than - * marshalling them into actual SFTP reply packets that we'd only have - * to unmarshal again. - */ - -typedef struct ScpReplyReceiver ScpReplyReceiver; -struct ScpReplyReceiver { - bool err; - unsigned code; - char *errmsg; - struct fxp_attrs attrs; - ptrlen name, handle, data; - - SftpReplyBuilder srb; -}; - -static void scp_reply_ok(SftpReplyBuilder *srb) -{ - ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); - reply->err = false; -} - -static void scp_reply_error( - SftpReplyBuilder *srb, unsigned code, const char *msg) -{ - ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); - reply->err = true; - reply->code = code; - sfree(reply->errmsg); - reply->errmsg = dupstr(msg); -} - -static void scp_reply_name_count(SftpReplyBuilder *srb, unsigned count) -{ - ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); - reply->err = false; -} - -static void scp_reply_full_name( - SftpReplyBuilder *srb, ptrlen name, - ptrlen longname, struct fxp_attrs attrs) -{ - ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); - char *p; - reply->err = false; - sfree((void *)reply->name.ptr); - reply->name.ptr = p = mkstr(name); - reply->name.len = name.len; - reply->attrs = attrs; -} - -static void scp_reply_simple_name(SftpReplyBuilder *srb, ptrlen name) -{ - ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); - reply->err = false; -} - -static void scp_reply_handle(SftpReplyBuilder *srb, ptrlen handle) -{ - ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); - char *p; - reply->err = false; - sfree((void *)reply->handle.ptr); - reply->handle.ptr = p = mkstr(handle); - reply->handle.len = handle.len; -} - -static void scp_reply_data(SftpReplyBuilder *srb, ptrlen data) -{ - ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); - char *p; - reply->err = false; - sfree((void *)reply->data.ptr); - reply->data.ptr = p = mkstr(data); - reply->data.len = data.len; -} - -static void scp_reply_attrs( - SftpReplyBuilder *srb, struct fxp_attrs attrs) -{ - ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); - reply->err = false; - reply->attrs = attrs; -} - -static const struct SftpReplyBuilderVtable ScpReplyReceiver_vt = { - scp_reply_ok, - scp_reply_error, - scp_reply_simple_name, - scp_reply_name_count, - scp_reply_full_name, - scp_reply_handle, - scp_reply_data, - scp_reply_attrs, -}; - -static void scp_reply_setup(ScpReplyReceiver *reply) -{ - memset(reply, 0, sizeof(*reply)); - reply->srb.vt = &ScpReplyReceiver_vt; -} - -static void scp_reply_cleanup(ScpReplyReceiver *reply) -{ - sfree(reply->errmsg); - sfree((void *)reply->name.ptr); - sfree((void *)reply->handle.ptr); - sfree((void *)reply->data.ptr); -} - -/* ---------------------------------------------------------------------- - * Source end of the SCP protocol. - */ - -#define SCP_MAX_BACKLOG 65536 - -typedef struct ScpSource ScpSource; -typedef struct ScpSourceStackEntry ScpSourceStackEntry; - -struct ScpSource { - SftpServer *sf; - - int acks; - bool expect_newline, eof, throttled, finished; - - SshChannel *sc; - ScpSourceStackEntry *head; - bool recursive; - bool send_file_times; - - strbuf *pending_commands[3]; - int n_pending_commands; - - uint64_t file_offset, file_size; - - ScpReplyReceiver reply; - - ScpServer scpserver; -}; - -typedef enum ScpSourceNodeType ScpSourceNodeType; -enum ScpSourceNodeType { SCP_ROOTPATH, SCP_NAME, SCP_READDIR, SCP_READFILE }; - -struct ScpSourceStackEntry { - ScpSourceStackEntry *next; - ScpSourceNodeType type; - ptrlen pathname, handle; - const char *wildcard; - struct fxp_attrs attrs; -}; - -static void scp_source_push(ScpSource *scp, ScpSourceNodeType type, - ptrlen pathname, ptrlen handle, - const struct fxp_attrs *attrs, const char *wc) -{ - size_t wc_len = wc ? strlen(wc)+1 : 0; - ScpSourceStackEntry *node = snew_plus( - ScpSourceStackEntry, pathname.len + handle.len + wc_len); - char *namebuf = snew_plus_get_aux(node); - memcpy(namebuf, pathname.ptr, pathname.len); - node->pathname = make_ptrlen(namebuf, pathname.len); - memcpy(namebuf + pathname.len, handle.ptr, handle.len); - node->handle = make_ptrlen(namebuf + pathname.len, handle.len); - if (wc) { - strcpy(namebuf + pathname.len + handle.len, wc); - node->wildcard = namebuf + pathname.len + handle.len; - } else { - node->wildcard = NULL; - } - node->attrs = attrs ? *attrs : no_attrs; - node->type = type; - node->next = scp->head; - scp->head = node; -} - -static char *scp_source_err_base(ScpSource *scp, const char *fmt, va_list ap) -{ - char *msg = dupvprintf(fmt, ap); - sshfwd_write_ext(scp->sc, true, msg, strlen(msg)); - sshfwd_write_ext(scp->sc, true, "\012", 1); - return msg; -} -static void scp_source_err(ScpSource *scp, const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - sfree(scp_source_err_base(scp, fmt, ap)); - va_end(ap); -} -static void scp_source_abort(ScpSource *scp, const char *fmt, ...) -{ - va_list ap; - char *msg; - - va_start(ap, fmt); - msg = scp_source_err_base(scp, fmt, ap); - va_end(ap); - - sshfwd_send_exit_status(scp->sc, 1); - sshfwd_write_eof(scp->sc); - sshfwd_initiate_close(scp->sc, msg); - - scp->finished = true; -} - -static void scp_source_push_name( - ScpSource *scp, ptrlen pathname, struct fxp_attrs attrs, const char *wc) -{ - if (!(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) { - scp_source_err(scp, "unable to read file permissions for %.*s", - PTRLEN_PRINTF(pathname)); - return; - } - if (attrs.permissions & PERMS_DIRECTORY) { - if (!scp->recursive && !wc) { - scp_source_err(scp, "%.*s: is a directory", - PTRLEN_PRINTF(pathname)); - return; - } - } else { - if (!(attrs.flags & SSH_FILEXFER_ATTR_SIZE)) { - scp_source_err(scp, "unable to read file size for %.*s", - PTRLEN_PRINTF(pathname)); - return; - } - } - - scp_source_push(scp, SCP_NAME, pathname, PTRLEN_LITERAL(""), &attrs, wc); -} - -static void scp_source_free(ScpServer *s); -static size_t scp_source_send(ScpServer *s, const void *data, size_t length); -static void scp_source_eof(ScpServer *s); -static void scp_source_throttle(ScpServer *s, bool throttled); - -static struct ScpServerVtable ScpSource_ScpServer_vt = { - scp_source_free, - scp_source_send, - scp_source_throttle, - scp_source_eof, -}; - -static ScpSource *scp_source_new( - SshChannel *sc, const SftpServerVtable *sftpserver_vt, ptrlen pathname) -{ - ScpSource *scp = snew(ScpSource); - memset(scp, 0, sizeof(*scp)); - - scp->scpserver.vt = &ScpSource_ScpServer_vt; - scp_reply_setup(&scp->reply); - scp->sc = sc; - scp->sf = sftpsrv_new(sftpserver_vt); - scp->n_pending_commands = 0; - - scp_source_push(scp, SCP_ROOTPATH, pathname, PTRLEN_LITERAL(""), - NULL, NULL); - - return scp; -} - -static void scp_source_free(ScpServer *s) -{ - ScpSource *scp = container_of(s, ScpSource, scpserver); - scp_reply_cleanup(&scp->reply); - while (scp->n_pending_commands > 0) - strbuf_free(scp->pending_commands[--scp->n_pending_commands]); - while (scp->head) { - ScpSourceStackEntry *node = scp->head; - scp->head = node->next; - sfree(node); - } - delete_callbacks_for_context(scp); - - sfree(scp); -} - -static void scp_source_send_E(ScpSource *scp) -{ - strbuf *cmd; - - assert(scp->n_pending_commands == 0); - - scp->pending_commands[scp->n_pending_commands++] = cmd = strbuf_new(); - strbuf_catf(cmd, "E\012"); -} - -static void scp_source_send_CD( - ScpSource *scp, char cmdchar, - struct fxp_attrs attrs, uint64_t size, ptrlen name) -{ - strbuf *cmd; - - assert(scp->n_pending_commands == 0); - - if (scp->send_file_times && (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME)) { - scp->pending_commands[scp->n_pending_commands++] = cmd = strbuf_new(); - /* Our SFTP-based filesystem API doesn't support microsecond times */ - strbuf_catf(cmd, "T%lu 0 %lu 0\012", attrs.mtime, attrs.atime); - } - - const char *slash; - while ((slash = memchr(name.ptr, '/', name.len)) != NULL) - name = make_ptrlen( - slash+1, name.len - (slash+1 - (const char *)name.ptr)); - - scp->pending_commands[scp->n_pending_commands++] = cmd = strbuf_new(); - strbuf_catf(cmd, "%c%04o %"PRIu64" %.*s\012", cmdchar, - (unsigned)(attrs.permissions & 07777), - size, PTRLEN_PRINTF(name)); - - if (cmdchar == 'C') { - /* We'll also wait for an ack before sending the file data, - * which we record by saving a zero-length 'command' to be - * sent after the C. */ - scp->pending_commands[scp->n_pending_commands++] = cmd = strbuf_new(); - } -} - -static void scp_source_process_stack(ScpSource *scp); -static void scp_source_process_stack_cb(void *vscp) -{ - ScpSource *scp = (ScpSource *)vscp; - if (scp->finished) - return; /* this callback is out of date */ - scp_source_process_stack(scp); -} -static void scp_requeue(ScpSource *scp) -{ - queue_toplevel_callback(scp_source_process_stack_cb, scp); -} - -static void scp_source_process_stack(ScpSource *scp) -{ - if (scp->throttled) - return; - - while (scp->n_pending_commands > 0) { - /* Expect an ack, and consume it */ - if (scp->eof) { - scp_source_abort( - scp, "scp: received client EOF, abandoning transfer"); - return; - } - if (scp->acks == 0) - return; - scp->acks--; - - /* - * Now send the actual command (unless it was the phony - * zero-length one that indicates our need for an ack before - * beginning to send file data). - */ - - if (scp->pending_commands[0]->len) - sshfwd_write(scp->sc, scp->pending_commands[0]->s, - scp->pending_commands[0]->len); - - strbuf_free(scp->pending_commands[0]); - scp->n_pending_commands--; - if (scp->n_pending_commands > 0) { - /* - * We still have at least one pending command to send, so - * move up the queue. - * - * (We do that with a bodgy memmove, because there are at - * most a bounded number of commands ever pending at once, - * so no need to worry about quadratic time.) - */ - memmove(scp->pending_commands, scp->pending_commands+1, - scp->n_pending_commands * sizeof(*scp->pending_commands)); - } - } - - /* - * Mostly, we start by waiting for an ack byte from the receiver. - */ - if (scp->head && scp->head->type == SCP_READFILE && scp->file_offset) { - /* - * Exception: if we're already in the middle of transferring a - * file, we'll be called back here because the channel backlog - * has cleared; we don't need to wait for an ack. - */ - } else if (scp->head && scp->head->type == SCP_ROOTPATH) { - /* - * Another exception: the initial action node that makes us - * stat the root path. We'll translate it into an SCP_NAME, - * and _that_ will require an ack. - */ - ScpSourceStackEntry *node = scp->head; - scp->head = node->next; - - /* - * Start by checking if there's a wildcard involved in the - * root path. - */ - char *rootpath_str = mkstr(node->pathname); - char *rootpath_unesc = snewn(1+node->pathname.len, char); - ptrlen pathname; - const char *wildcard; - - if (wc_unescape(rootpath_unesc, rootpath_str)) { - /* - * We successfully removed instances of the escape - * character used in our wildcard syntax, without - * encountering any actual wildcard chars - i.e. this is - * not a wildcard, just a single file. The simple case. - */ - pathname = ptrlen_from_asciz(rootpath_str); - wildcard = NULL; - } else { - /* - * This is a wildcard. Separate it into a directory name - * (which we enforce mustn't contain wc characters, for - * simplicity) and a wildcard to match leaf names. - */ - char *last_slash = strrchr(rootpath_str, '/'); - - if (last_slash) { - wildcard = last_slash + 1; - *last_slash = '\0'; - if (!wc_unescape(rootpath_unesc, rootpath_str)) { - scp_source_abort(scp, "scp: wildcards in path components " - "before the file name not supported"); - sfree(rootpath_str); - sfree(rootpath_unesc); - return; - } - - pathname = ptrlen_from_asciz(rootpath_unesc); - } else { - pathname = PTRLEN_LITERAL("."); - wildcard = rootpath_str; - } - } - - /* - * Now we know what directory we're scanning, and what - * wildcard (if any) we're using to match the filenames we get - * back. - */ - sftpsrv_stat(scp->sf, &scp->reply.srb, pathname, true); - if (scp->reply.err) { - scp_source_abort( - scp, "%.*s: unable to access: %s", - PTRLEN_PRINTF(pathname), scp->reply.errmsg); - sfree(rootpath_str); - sfree(rootpath_unesc); - sfree(node); - return; - } - - scp_source_push_name(scp, pathname, scp->reply.attrs, wildcard); - - sfree(rootpath_str); - sfree(rootpath_unesc); - sfree(node); - scp_requeue(scp); - return; - } else { - } - - if (scp->head && scp->head->type == SCP_READFILE) { - /* - * Transfer file data if our backlog hasn't filled up. - */ - int backlog; - uint64_t limit = scp->file_size - scp->file_offset; - if (limit > 4096) - limit = 4096; - if (limit > 0) { - sftpsrv_read(scp->sf, &scp->reply.srb, scp->head->handle, - scp->file_offset, limit); - if (scp->reply.err) { - scp_source_abort( - scp, "%.*s: unable to read: %s", - PTRLEN_PRINTF(scp->head->pathname), scp->reply.errmsg); - return; - } - - backlog = sshfwd_write( - scp->sc, scp->reply.data.ptr, scp->reply.data.len); - scp->file_offset += scp->reply.data.len; - - if (backlog < SCP_MAX_BACKLOG) - scp_requeue(scp); - return; - } - - /* - * If we're done, send a terminating zero byte, close our file - * handle, and pop the stack. - */ - sshfwd_write(scp->sc, "\0", 1); - sftpsrv_close(scp->sf, &scp->reply.srb, scp->head->handle); - ScpSourceStackEntry *node = scp->head; - scp->head = node->next; - sfree(node); - scp_requeue(scp); - return; - } - - /* - * If our queue is actually empty, send outgoing EOF. - */ - if (!scp->head) { - sshfwd_send_exit_status(scp->sc, 0); - sshfwd_write_eof(scp->sc); - sshfwd_initiate_close(scp->sc, NULL); - scp->finished = true; - return; - } - - /* - * Otherwise, handle a command. - */ - ScpSourceStackEntry *node = scp->head; - scp->head = node->next; - - if (node->type == SCP_READDIR) { - sftpsrv_readdir(scp->sf, &scp->reply.srb, node->handle, 1, true); - if (scp->reply.err) { - if (scp->reply.code != SSH_FX_EOF) - scp_source_err(scp, "%.*s: unable to list directory: %s", - PTRLEN_PRINTF(node->pathname), - scp->reply.errmsg); - sftpsrv_close(scp->sf, &scp->reply.srb, node->handle); - - if (!node->wildcard) { - /* - * Send 'pop stack' or 'end of directory' command, - * unless this was the topmost READDIR in a - * wildcard-based retrieval (in which case we didn't - * send a D command to start, so an E now would have - * no stack entry to pop). - */ - scp_source_send_E(scp); - } - } else if (ptrlen_eq_string(scp->reply.name, ".") || - ptrlen_eq_string(scp->reply.name, "..") || - (node->wildcard && - !wc_match_pl(node->wildcard, scp->reply.name))) { - /* Skip special directory names . and .., and anything - * that doesn't match our wildcard (if we have one). */ - scp->head = node; /* put back the unfinished READDIR */ - node = NULL; /* and prevent it being freed */ - } else { - ptrlen subpath; - subpath.len = node->pathname.len + 1 + scp->reply.name.len; - char *subpath_space = snewn(subpath.len, char); - subpath.ptr = subpath_space; - memcpy(subpath_space, node->pathname.ptr, node->pathname.len); - subpath_space[node->pathname.len] = '/'; - memcpy(subpath_space + node->pathname.len + 1, - scp->reply.name.ptr, scp->reply.name.len); - - scp->head = node; /* put back the unfinished READDIR */ - node = NULL; /* and prevent it being freed */ - scp_source_push_name(scp, subpath, scp->reply.attrs, NULL); - - sfree(subpath_space); - } - } else if (node->attrs.permissions & PERMS_DIRECTORY) { - assert(scp->recursive || node->wildcard); - - if (!node->wildcard) - scp_source_send_CD(scp, 'D', node->attrs, 0, node->pathname); - sftpsrv_opendir(scp->sf, &scp->reply.srb, node->pathname); - if (scp->reply.err) { - scp_source_err( - scp, "%.*s: unable to access: %s", - PTRLEN_PRINTF(node->pathname), scp->reply.errmsg); - - if (!node->wildcard) { - /* Send 'pop stack' or 'end of directory' command. */ - scp_source_send_E(scp); - } - } else { - scp_source_push( - scp, SCP_READDIR, node->pathname, - scp->reply.handle, NULL, node->wildcard); - } - } else { - sftpsrv_open(scp->sf, &scp->reply.srb, - node->pathname, SSH_FXF_READ, no_attrs); - if (scp->reply.err) { - scp_source_err( - scp, "%.*s: unable to open: %s", - PTRLEN_PRINTF(node->pathname), scp->reply.errmsg); - scp_requeue(scp); - return; - } - sftpsrv_fstat(scp->sf, &scp->reply.srb, scp->reply.handle); - if (scp->reply.err) { - scp_source_err( - scp, "%.*s: unable to stat: %s", - PTRLEN_PRINTF(node->pathname), scp->reply.errmsg); - sftpsrv_close(scp->sf, &scp->reply.srb, scp->reply.handle); - scp_requeue(scp); - return; - } - scp->file_offset = 0; - scp->file_size = scp->reply.attrs.size; - scp_source_send_CD(scp, 'C', node->attrs, - scp->file_size, node->pathname); - scp_source_push( - scp, SCP_READFILE, node->pathname, scp->reply.handle, NULL, NULL); - } - sfree(node); - scp_requeue(scp); -} - -static size_t scp_source_send(ScpServer *s, const void *vdata, size_t length) -{ - ScpSource *scp = container_of(s, ScpSource, scpserver); - const char *data = (const char *)vdata; - size_t i; - - if (scp->finished) - return 0; - - for (i = 0; i < length; i++) { - if (scp->expect_newline) { - if (data[i] == '\012') { - /* End of an error message following a 1 byte */ - scp->expect_newline = false; - scp->acks++; - } - } else { - switch (data[i]) { - case 0: /* ordinary ack */ - scp->acks++; - break; - case 1: /* non-fatal error; consume it */ - scp->expect_newline = true; - break; - case 2: - scp_source_abort( - scp, "terminating on fatal error from client"); - return 0; - default: - scp_source_abort( - scp, "unrecognised response code from client"); - return 0; - } - } - } - - scp_source_process_stack(scp); - - return 0; -} - -static void scp_source_throttle(ScpServer *s, bool throttled) -{ - ScpSource *scp = container_of(s, ScpSource, scpserver); - - if (scp->finished) - return; - - scp->throttled = throttled; - if (!throttled) - scp_source_process_stack(scp); -} - -static void scp_source_eof(ScpServer *s) -{ - ScpSource *scp = container_of(s, ScpSource, scpserver); - - if (scp->finished) - return; - - scp->eof = true; - scp_source_process_stack(scp); -} - -/* ---------------------------------------------------------------------- - * Sink end of the SCP protocol. - */ - -typedef struct ScpSink ScpSink; -typedef struct ScpSinkStackEntry ScpSinkStackEntry; - -struct ScpSink { - SftpServer *sf; - - SshChannel *sc; - ScpSinkStackEntry *head; - - uint64_t file_offset, file_size; - unsigned long atime, mtime; - bool got_file_times; - - bufchain data; - bool input_eof; - strbuf *command; - char command_chr; - - strbuf *filename_sb; - ptrlen filename; - struct fxp_attrs attrs; - - char *errmsg; - - int crState; - - ScpReplyReceiver reply; - - ScpServer scpserver; -}; - -struct ScpSinkStackEntry { - ScpSinkStackEntry *next; - ptrlen destpath; - - /* - * If isdir is true, then destpath identifies a directory that the - * files we receive should be created inside. If it's false, then - * it identifies the exact pathname the next file we receive - * should be created _as_ - regardless of the filename in the 'C' - * command. - */ - bool isdir; -}; - -static void scp_sink_push(ScpSink *scp, ptrlen pathname, bool isdir) -{ - ScpSinkStackEntry *node = snew_plus(ScpSinkStackEntry, pathname.len); - char *p = snew_plus_get_aux(node); - - node->destpath.ptr = p; - node->destpath.len = pathname.len; - memcpy(p, pathname.ptr, pathname.len); - node->isdir = isdir; - - node->next = scp->head; - scp->head = node; -} - -static void scp_sink_pop(ScpSink *scp) -{ - ScpSinkStackEntry *node = scp->head; - scp->head = node->next; - sfree(node); -} - -static void scp_sink_free(ScpServer *s); -static size_t scp_sink_send(ScpServer *s, const void *data, size_t length); -static void scp_sink_eof(ScpServer *s); -static void scp_sink_throttle(ScpServer *s, bool throttled) {} - -static struct ScpServerVtable ScpSink_ScpServer_vt = { - scp_sink_free, - scp_sink_send, - scp_sink_throttle, - scp_sink_eof, -}; - -static void scp_sink_coroutine(ScpSink *scp); -static void scp_sink_start_callback(void *vscp) -{ - scp_sink_coroutine((ScpSink *)vscp); -} - -static ScpSink *scp_sink_new( - SshChannel *sc, const SftpServerVtable *sftpserver_vt, ptrlen pathname, - bool pathname_is_definitely_dir) -{ - ScpSink *scp = snew(ScpSink); - memset(scp, 0, sizeof(*scp)); - - scp->scpserver.vt = &ScpSink_ScpServer_vt; - scp_reply_setup(&scp->reply); - scp->sc = sc; - scp->sf = sftpsrv_new(sftpserver_vt); - bufchain_init(&scp->data); - scp->command = strbuf_new(); - scp->filename_sb = strbuf_new(); - - if (!pathname_is_definitely_dir) { - /* - * If our root pathname is not already expected to be a - * directory because of the -d option in the command line, - * test it ourself to see whether it is or not. - */ - sftpsrv_stat(scp->sf, &scp->reply.srb, pathname, true); - if (!scp->reply.err && - (scp->reply.attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) && - (scp->reply.attrs.permissions & PERMS_DIRECTORY)) - pathname_is_definitely_dir = true; - } - scp_sink_push(scp, pathname, pathname_is_definitely_dir); - - queue_toplevel_callback(scp_sink_start_callback, scp); - - return scp; -} - -static void scp_sink_free(ScpServer *s) -{ - ScpSink *scp = container_of(s, ScpSink, scpserver); - - scp_reply_cleanup(&scp->reply); - bufchain_clear(&scp->data); - strbuf_free(scp->command); - strbuf_free(scp->filename_sb); - while (scp->head) - scp_sink_pop(scp); - sfree(scp->errmsg); - - delete_callbacks_for_context(scp); - - sfree(scp); -} - -static void scp_sink_coroutine(ScpSink *scp) -{ - crBegin(scp->crState); - - while (1) { - /* - * Send an ack, and read a command. - */ - sshfwd_write(scp->sc, "\0", 1); - scp->command->len = 0; - while (1) { - crMaybeWaitUntilV(scp->input_eof || bufchain_size(&scp->data) > 0); - if (scp->input_eof) - goto done; - - ptrlen data = bufchain_prefix(&scp->data); - const char *cdata = data.ptr; - const char *newline = memchr(cdata, '\012', data.len); - if (newline) - data.len = (int)(newline+1 - cdata); - put_data(scp->command, cdata, data.len); - bufchain_consume(&scp->data, data.len); - - if (newline) - break; - } - - /* - * Parse the command. - */ - scp->command->len--; /* chomp the newline */ - scp->command_chr = scp->command->len > 0 ? scp->command->s[0] : '\0'; - if (scp->command_chr == 'T') { - unsigned long dummy1, dummy2; - if (sscanf(scp->command->s, "T%lu %lu %lu %lu", - &scp->mtime, &dummy1, &scp->atime, &dummy2) != 4) - goto parse_error; - scp->got_file_times = true; - } else if (scp->command_chr == 'C' || scp->command_chr == 'D') { - /* - * Common handling of the start of this case, because the - * messages are parsed similarly. We diverge later. - */ - const char *q, *p = scp->command->s + 1; /* skip the 'C' */ - - scp->attrs.flags = SSH_FILEXFER_ATTR_PERMISSIONS; - scp->attrs.permissions = 0; - while (*p >= '0' && *p <= '7') { - scp->attrs.permissions = - scp->attrs.permissions * 8 + (*p - '0'); - p++; - } - if (*p != ' ') - goto parse_error; - p++; - - q = p; - while (*p >= '0' && *p <= '9') - p++; - if (*p != ' ') - goto parse_error; - p++; - scp->file_size = strtoull(q, NULL, 10); - - ptrlen leafname = make_ptrlen( - p, scp->command->len - (p - scp->command->s)); - scp->filename_sb->len = 0; - put_datapl(scp->filename_sb, scp->head->destpath); - if (scp->head->isdir) { - if (scp->filename_sb->len > 0 && - scp->filename_sb->s[scp->filename_sb->len-1] - != '/') - put_byte(scp->filename_sb, '/'); - put_datapl(scp->filename_sb, leafname); - } - scp->filename = ptrlen_from_strbuf(scp->filename_sb); - - if (scp->got_file_times) { - scp->attrs.mtime = scp->mtime; - scp->attrs.atime = scp->atime; - scp->attrs.flags |= SSH_FILEXFER_ATTR_ACMODTIME; - } - scp->got_file_times = false; - - if (scp->command_chr == 'D') { - sftpsrv_mkdir(scp->sf, &scp->reply.srb, - scp->filename, scp->attrs); - - if (scp->reply.err) { - scp->errmsg = dupprintf( - "'%.*s': unable to create directory: %s", - PTRLEN_PRINTF(scp->filename), scp->reply.errmsg); - goto done; - } - - scp_sink_push(scp, scp->filename, true); - } else { - sftpsrv_open(scp->sf, &scp->reply.srb, scp->filename, - SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC, - scp->attrs); - if (scp->reply.err) { - scp->errmsg = dupprintf( - "'%.*s': unable to open file: %s", - PTRLEN_PRINTF(scp->filename), scp->reply.errmsg); - goto done; - } - - /* - * Now send an ack, and read the file data. - */ - sshfwd_write(scp->sc, "\0", 1); - scp->file_offset = 0; - while (scp->file_offset < scp->file_size) { - ptrlen data; - uint64_t this_len, remaining; - - crMaybeWaitUntilV( - scp->input_eof || bufchain_size(&scp->data) > 0); - if (scp->input_eof) { - sftpsrv_close(scp->sf, &scp->reply.srb, - scp->reply.handle); - goto done; - } - - data = bufchain_prefix(&scp->data); - this_len = data.len; - remaining = scp->file_size - scp->file_offset; - if (this_len > remaining) - this_len = remaining; - sftpsrv_write(scp->sf, &scp->reply.srb, - scp->reply.handle, scp->file_offset, - make_ptrlen(data.ptr, this_len)); - if (scp->reply.err) { - scp->errmsg = dupprintf( - "'%.*s': unable to write to file: %s", - PTRLEN_PRINTF(scp->filename), scp->reply.errmsg); - goto done; - } - bufchain_consume(&scp->data, this_len); - scp->file_offset += this_len; - } - - /* - * Wait for the trailing NUL byte. - */ - crMaybeWaitUntilV( - scp->input_eof || bufchain_size(&scp->data) > 0); - if (scp->input_eof) { - sftpsrv_close(scp->sf, &scp->reply.srb, - scp->reply.handle); - goto done; - } - bufchain_consume(&scp->data, 1); - } - } else if (scp->command_chr == 'E') { - if (!scp->head) { - scp->errmsg = dupstr("received E command without matching D"); - goto done; - } - scp_sink_pop(scp); - scp->got_file_times = false; - } else { - ptrlen cmd_pl; - - /* - * Also come here if any of the above cases run into - * parsing difficulties. - */ - parse_error: - cmd_pl = ptrlen_from_strbuf(scp->command); - scp->errmsg = dupprintf("unrecognised scp command '%.*s'", - PTRLEN_PRINTF(cmd_pl)); - goto done; - } - } - - done: - if (scp->errmsg) { - sshfwd_write_ext(scp->sc, true, scp->errmsg, strlen(scp->errmsg)); - sshfwd_write_ext(scp->sc, true, "\012", 1); - sshfwd_send_exit_status(scp->sc, 1); - } else { - sshfwd_send_exit_status(scp->sc, 0); - } - sshfwd_write_eof(scp->sc); - sshfwd_initiate_close(scp->sc, scp->errmsg); - while (1) crReturnV; - - crFinishV; -} - -static size_t scp_sink_send(ScpServer *s, const void *data, size_t length) -{ - ScpSink *scp = container_of(s, ScpSink, scpserver); - - if (!scp->input_eof) { - bufchain_add(&scp->data, data, length); - scp_sink_coroutine(scp); - } - return 0; -} - -static void scp_sink_eof(ScpServer *s) -{ - ScpSink *scp = container_of(s, ScpSink, scpserver); - - scp->input_eof = true; - scp_sink_coroutine(scp); -} - -/* ---------------------------------------------------------------------- - * Top-level error handler, instantiated in the case where the user - * sent a command starting with "scp " that we couldn't make sense of. - */ - -typedef struct ScpError ScpError; - -struct ScpError { - SshChannel *sc; - char *message; - ScpServer scpserver; -}; - -static void scp_error_free(ScpServer *s); - -static size_t scp_error_send(ScpServer *s, const void *data, size_t length) -{ return 0; } -static void scp_error_eof(ScpServer *s) {} -static void scp_error_throttle(ScpServer *s, bool throttled) {} - -static struct ScpServerVtable ScpError_ScpServer_vt = { - scp_error_free, - scp_error_send, - scp_error_throttle, - scp_error_eof, -}; - -static void scp_error_send_message_cb(void *vscp) -{ - ScpError *scp = (ScpError *)vscp; - sshfwd_write_ext(scp->sc, true, scp->message, strlen(scp->message)); - sshfwd_write_ext(scp->sc, true, "\n", 1); - sshfwd_send_exit_status(scp->sc, 1); - sshfwd_write_eof(scp->sc); - sshfwd_initiate_close(scp->sc, scp->message); -} - -static ScpError *scp_error_new(SshChannel *sc, const char *fmt, ...) -{ - va_list ap; - ScpError *scp = snew(ScpError); - - memset(scp, 0, sizeof(*scp)); - - scp->scpserver.vt = &ScpError_ScpServer_vt; - scp->sc = sc; - - va_start(ap, fmt); - scp->message = dupvprintf(fmt, ap); - va_end(ap); - - queue_toplevel_callback(scp_error_send_message_cb, scp); - - return scp; -} - -static void scp_error_free(ScpServer *s) -{ - ScpError *scp = container_of(s, ScpError, scpserver); - - sfree(scp->message); - - delete_callbacks_for_context(scp); - - sfree(scp); -} - -/* ---------------------------------------------------------------------- - * Top-level entry point, which parses a command sent from the SSH - * client, and if it recognises it as an scp command, instantiates an - * appropriate ScpServer implementation and returns it. - */ - -ScpServer *scp_recognise_exec( - SshChannel *sc, const SftpServerVtable *sftpserver_vt, ptrlen command) -{ - bool recursive = false, preserve = false; - bool targetshouldbedirectory = false; - ptrlen command_orig = command; - - if (!ptrlen_startswith(command, PTRLEN_LITERAL("scp "), &command)) - return NULL; - - while (1) { - if (ptrlen_startswith(command, PTRLEN_LITERAL("-v "), &command)) { - /* Enable verbose mode in the server, which we ignore */ - continue; - } - if (ptrlen_startswith(command, PTRLEN_LITERAL("-r "), &command)) { - recursive = true; - continue; - } - if (ptrlen_startswith(command, PTRLEN_LITERAL("-p "), &command)) { - preserve = true; - continue; - } - if (ptrlen_startswith(command, PTRLEN_LITERAL("-d "), &command)) { - targetshouldbedirectory = true; - continue; - } - break; - } - - if (ptrlen_startswith(command, PTRLEN_LITERAL("-t "), &command)) { - ScpSink *scp = scp_sink_new(sc, sftpserver_vt, command, - targetshouldbedirectory); - return &scp->scpserver; - } else if (ptrlen_startswith(command, PTRLEN_LITERAL("-f "), &command)) { - ScpSource *scp = scp_source_new(sc, sftpserver_vt, command); - scp->recursive = recursive; - scp->send_file_times = preserve; - return &scp->scpserver; - } else { - ScpError *scp = scp_error_new( - sc, "Unable to parse scp command: '%.*s'", - PTRLEN_PRINTF(command_orig)); - return &scp->scpserver; - } -} +/* + * Server side of the old-school SCP protocol. + */ + +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "sshcr.h" +#include "sshchan.h" +#include "sftp.h" + +/* + * I think it's worth actually documenting my understanding of what + * this protocol _is_, since I don't know of any other documentation + * of it anywhere. + * + * Format of data stream + * --------------------- + * + * The sending side of an SCP connection - the client, if you're + * uploading files, or the server if you're downloading - sends a data + * stream consisting of a sequence of 'commands', or header records, + * or whatever you want to call them, interleaved with file data. + * + * Each command starts with a letter indicating what type it is, and + * ends with a \n. + * + * The 'C' command introduces an actual file. It is followed by an + * octal file-permissions mask, then a space, then a decimal file + * size, then a space, then the file name up to the termating newline. + * For example, "C0644 12345 filename.txt\n" would be a plausible C + * command. + * + * After the 'C' command, the sending side will transmit exactly as + * many bytes of file data as specified by the size field in the + * header line, followed by a single zero byte. + * + * The 'D' command introduces a subdirectory. Its format is identical + * to 'C', including the size field, but the size field is sent as + * zero. + * + * After the 'D' command, all subsequent C and D commands are taken to + * indicate files that should be placed inside that subdirectory, + * until a terminating 'E' command. + * + * The 'E' command indicates the end of a subdirectory. It has no + * arguments at all (its format is always just "E\n"). After the E + * command, the receiver should revert to placing further downloaded + * files in whatever directory it was placing them before the + * subdirectory opened by the just-closed D. + * + * D and E commands match like parentheses: if you send, say, + * + * C0644 123 foo.txt ( followed by data ) + * D0755 0 subdir + * C0644 123 bar.txt ( followed by data ) + * D0755 0 subsubdir + * C0644 123 baz.txt ( followed by data ) + * E + * C0644 123 quux.txt ( followed by data ) + * E + * C0644 123 wibble.txt ( followed by data ) + * + * then foo.txt, subdir and wibble.txt go in the top-level destination + * directory; bar.txt, subsubdir and quux.txt go in 'subdir'; and + * baz.txt goes in 'subdir/subsubdir'. + * + * The sender terminates the data stream with EOF when it has no more + * files to send. I believe it is not _required_ for all D to be + * closed by an E before this happens - you can elide a trailing + * sequence of E commands without provoking an error message from the + * receiver. + * + * Finally, the 'T' command is sent immediately before a C or D. It is + * followed by four space-separated decimal integers giving an mtime + * and atime to be applied to the file or directory created by the + * following C or D command. The first two integers give the mtime, + * encoded as seconds and microseconds (respectively) since the Unix + * epoch; the next two give the atime, encoded similarly. So + * "T1540373455 0 1540373457 0\n" is an example of a valid T command. + * + * Acknowledgments + * --------------- + * + * The sending side waits for an ack from the receiving side before + * sending each command; before beginning to send the file data + * following a C command; and before sending the final EOF. + * + * (In particular, the receiving side is expected to send an initial + * ack before _anything_ is sent.) + * + * Normally an ack consists of a single zero byte. It's also allowable + * to send a byte with value 1 or 2 followed by a \n-terminated error + * message (where 1 means a non-fatal error and 2 means a fatal one). + * I have to suppose that sending an error message from client to + * server is of limited use, but apparently it's allowed. + * + * Initiation + * ---------- + * + * The protocol is begun by the client sending a command string to the + * server via the SSH-2 "exec" request (or the analogous + * SSH1_CMSG_EXEC_CMD), which indicates that this is an scp session + * rather than any other thing; specifies the direction of transfer; + * says what file(s) are to be sent by the server, or where the server + * should put files that the client is about to send; and a couple of + * other options. + * + * The command string takes the following form: + * + * Start with prefix "scp ", indicating that this is an SCP command at + * all. Otherwise it's a request to run some completely different + * command in the SSH session. + * + * Next the command can contain zero or more of the following options, + * each followed by a space: + * + * "-v" turns on verbose server diagnostics. Of course a server is not + * required to actually produce any, but this is an invitation for it + * to send any it might have available. Diagnostics are free-form, and + * sent as SSH standard-error extended data, so that they are separate + * from the actual data stream as described above. + * + * (Servers can send standard-error output anyway if they like, and in + * case of an actual error, they probably will with or without -v.) + * + * "-r" indicates recursive file transfer, i.e. potentially including + * subdirectories. For a download, this indicates that the client is + * willing to receive subdirectories (a D/E command pair bracketing + * further files and subdirs); without it, the server should only send + * C commands for individual files, followed by EOF. + * + * This flag must also be specified for a recursive upload, because I + * believe at least one server will reject D/E pairs sent by the + * client if the command didn't have -r in it. (Probably a consequence + * of sharing code between download and upload.) + * + * "-p" means preserve file times. In a download, this requests the + * server to send a T command before each C or D. I don't know whether + * any server will insist on having seen this option from the client + * before accepting T commands in an upload, but it is probably + * sensible to send it anyway. + * + * "-d", in an upload, means that the destination pathname (see below) + * is expected to be a directory, and that uploaded files (and + * subdirs) should be put inside it. Without -d, the semantics are + * that _if_ the destination exists and is a directory, then files + * will be put in it, whereas if it is not, then just a single file + * (or subdir) upload is expected, which will be placed at that exact + * pathname. + * + * In a download, I observe that clients tend to send -d if they are + * requesting multiple files or a wildcard, but as far as I know, + * servers ignore it. + * + * After all those optional options, there is a single mandatory + * option indicating the direction of transfer, which is either "-f" + * or "-t". "-f" indicates a download; "-t" indicates an upload. + * + * After that mandatory option, there is a single space, followed by + * the name(s) of files to transfer. + * + * This file name field is transmitted with NO QUOTING, in spite of + * the fact that a server will typically interpret it as a shell + * command. You'd think this couldn't possibly work, in the face of + * almost any filename with an interesting character in it - and you'd + * be right. Or rather (you might argue), it works 'as designed', but + * it's designed in a weird way, in that it's the user's + * responsibility to apply quoting on the client command line to get + * the filename through the shell that will decode things on the + * server side. + * + * But one effect of this is that if you issue a download command + * including a wildcard, say "scp -f somedir/foo*.txt", then the shell + * will expand the wildcard, and actually run the server-side scp + * program with multiple arguments, say "somedir/foo.txt + * somedir/quux.txt", leading to the download sending multiple C + * commands. This clearly _is_ intended: it's how a command such as + * 'scp server:somedir/foo*.txt destdir' can work at all. + * + * (You would think, given that, that it might also be legal to send + * multiple space-separated filenames in order to trigger a download + * of exactly those files. Given how scp is invoked in practice on a + * typical server, this would surely actually work, but my observation + * is that scp clients don't in fact try this - if you run OpenSSH's + * scp by saying 'scp server:foo server:bar destdir' then it will make + * two separate connections to the server for the two files, rather + * than sending a single space-separated remote command. PSCP won't + * even do that, and will make you do it in two separate runs.) + * + * So, some examples: + * + * - "scp -f filename.txt" + * + * Server should send a single C command (plus data) for that file. + * Client ought to ignore the filename in the C command, in favour + * of saving the file under the name implied by the user's command + * line. + * + * - "scp -f file*.txt" + * + * Server sends zero or more C commands, then EOF. Client will have + * been given a target directory to put them all in, and will name + * each one according to the name in the C command. + * + * (You'd like the client to validate the filenames against the + * wildcard it sent, to ensure a malicious server didn't try to + * overwrite some path like ".bashrc" when you thought you were + * downloading only normal text files. But wildcard semantics are + * chosen by the server, so this is essentially hopeless to do + * rigorously.) + * + * - "scp -f -r somedir" + * + * Assuming somedir is actually a directory, server sends a D/E + * pair, in between which are the contents of the directory + * (perhaps including further nested D/E pairs). Client probably + * ignores the name field of the outermost D + * + * - "scp -f -r some*wild*card*" + * + * Server sends multiple C or D-stuff-E, one for each top-level + * thing matching the wildcard, whether it's a file or a directory. + * + * - "scp -t -d some_dir" + * + * Client sends stuff, and server deposits each file at + * some_dir/. + * + * - "scp -t some_path_name" + * + * Client sends one C command, and server deposits it at + * some_path_name itself, or in some_path_name/, depending whether some_path_name was already a + * directory or not. + */ + +/* + * Here's a useful debugging aid: run over a binary file containing + * the complete contents of the sender's data stream (e.g. extracted + * by contrib/logparse.pl -d), it removes the file contents, leaving + * only the list of commands, so you can see what the server sent. + * + * perl -pe 'read ARGV,$x,1+$1 if/^C\S+ (\d+)/' + */ + +/* ---------------------------------------------------------------------- + * Shared system for receiving replies from the SftpServer, and + * putting them into a set of ordinary variables rather than + * marshalling them into actual SFTP reply packets that we'd only have + * to unmarshal again. + */ + +typedef struct ScpReplyReceiver ScpReplyReceiver; +struct ScpReplyReceiver { + bool err; + unsigned code; + char *errmsg; + struct fxp_attrs attrs; + ptrlen name, handle, data; + + SftpReplyBuilder srb; +}; + +static void scp_reply_ok(SftpReplyBuilder *srb) +{ + ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); + reply->err = false; +} + +static void scp_reply_error( + SftpReplyBuilder *srb, unsigned code, const char *msg) +{ + ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); + reply->err = true; + reply->code = code; + sfree(reply->errmsg); + reply->errmsg = dupstr(msg); +} + +static void scp_reply_name_count(SftpReplyBuilder *srb, unsigned count) +{ + ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); + reply->err = false; +} + +static void scp_reply_full_name( + SftpReplyBuilder *srb, ptrlen name, + ptrlen longname, struct fxp_attrs attrs) +{ + ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); + char *p; + reply->err = false; + sfree((void *)reply->name.ptr); + reply->name.ptr = p = mkstr(name); + reply->name.len = name.len; + reply->attrs = attrs; +} + +static void scp_reply_simple_name(SftpReplyBuilder *srb, ptrlen name) +{ + ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); + reply->err = false; +} + +static void scp_reply_handle(SftpReplyBuilder *srb, ptrlen handle) +{ + ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); + char *p; + reply->err = false; + sfree((void *)reply->handle.ptr); + reply->handle.ptr = p = mkstr(handle); + reply->handle.len = handle.len; +} + +static void scp_reply_data(SftpReplyBuilder *srb, ptrlen data) +{ + ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); + char *p; + reply->err = false; + sfree((void *)reply->data.ptr); + reply->data.ptr = p = mkstr(data); + reply->data.len = data.len; +} + +static void scp_reply_attrs( + SftpReplyBuilder *srb, struct fxp_attrs attrs) +{ + ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); + reply->err = false; + reply->attrs = attrs; +} + +static const struct SftpReplyBuilderVtable ScpReplyReceiver_vt = { + scp_reply_ok, + scp_reply_error, + scp_reply_simple_name, + scp_reply_name_count, + scp_reply_full_name, + scp_reply_handle, + scp_reply_data, + scp_reply_attrs, +}; + +static void scp_reply_setup(ScpReplyReceiver *reply) +{ + memset(reply, 0, sizeof(*reply)); + reply->srb.vt = &ScpReplyReceiver_vt; +} + +static void scp_reply_cleanup(ScpReplyReceiver *reply) +{ + sfree(reply->errmsg); + sfree((void *)reply->name.ptr); + sfree((void *)reply->handle.ptr); + sfree((void *)reply->data.ptr); +} + +/* ---------------------------------------------------------------------- + * Source end of the SCP protocol. + */ + +#define SCP_MAX_BACKLOG 65536 + +typedef struct ScpSource ScpSource; +typedef struct ScpSourceStackEntry ScpSourceStackEntry; + +struct ScpSource { + SftpServer *sf; + + int acks; + bool expect_newline, eof, throttled, finished; + + SshChannel *sc; + ScpSourceStackEntry *head; + bool recursive; + bool send_file_times; + + strbuf *pending_commands[3]; + int n_pending_commands; + + uint64_t file_offset, file_size; + + ScpReplyReceiver reply; + + ScpServer scpserver; +}; + +typedef enum ScpSourceNodeType ScpSourceNodeType; +enum ScpSourceNodeType { SCP_ROOTPATH, SCP_NAME, SCP_READDIR, SCP_READFILE }; + +struct ScpSourceStackEntry { + ScpSourceStackEntry *next; + ScpSourceNodeType type; + ptrlen pathname, handle; + const char *wildcard; + struct fxp_attrs attrs; +}; + +static void scp_source_push(ScpSource *scp, ScpSourceNodeType type, + ptrlen pathname, ptrlen handle, + const struct fxp_attrs *attrs, const char *wc) +{ + size_t wc_len = wc ? strlen(wc)+1 : 0; + ScpSourceStackEntry *node = snew_plus( + ScpSourceStackEntry, pathname.len + handle.len + wc_len); + char *namebuf = snew_plus_get_aux(node); + memcpy(namebuf, pathname.ptr, pathname.len); + node->pathname = make_ptrlen(namebuf, pathname.len); + memcpy(namebuf + pathname.len, handle.ptr, handle.len); + node->handle = make_ptrlen(namebuf + pathname.len, handle.len); + if (wc) { + strcpy(namebuf + pathname.len + handle.len, wc); + node->wildcard = namebuf + pathname.len + handle.len; + } else { + node->wildcard = NULL; + } + node->attrs = attrs ? *attrs : no_attrs; + node->type = type; + node->next = scp->head; + scp->head = node; +} + +static char *scp_source_err_base(ScpSource *scp, const char *fmt, va_list ap) +{ + char *msg = dupvprintf(fmt, ap); + sshfwd_write_ext(scp->sc, true, msg, strlen(msg)); + sshfwd_write_ext(scp->sc, true, "\012", 1); + return msg; +} +static PRINTF_LIKE(2, 3) void scp_source_err( + ScpSource *scp, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + sfree(scp_source_err_base(scp, fmt, ap)); + va_end(ap); +} +static PRINTF_LIKE(2, 3) void scp_source_abort( + ScpSource *scp, const char *fmt, ...) +{ + va_list ap; + char *msg; + + va_start(ap, fmt); + msg = scp_source_err_base(scp, fmt, ap); + va_end(ap); + + sshfwd_send_exit_status(scp->sc, 1); + sshfwd_write_eof(scp->sc); + sshfwd_initiate_close(scp->sc, msg); + + scp->finished = true; +} + +static void scp_source_push_name( + ScpSource *scp, ptrlen pathname, struct fxp_attrs attrs, const char *wc) +{ + if (!(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) { + scp_source_err(scp, "unable to read file permissions for %.*s", + PTRLEN_PRINTF(pathname)); + return; + } + if (attrs.permissions & PERMS_DIRECTORY) { + if (!scp->recursive && !wc) { + scp_source_err(scp, "%.*s: is a directory", + PTRLEN_PRINTF(pathname)); + return; + } + } else { + if (!(attrs.flags & SSH_FILEXFER_ATTR_SIZE)) { + scp_source_err(scp, "unable to read file size for %.*s", + PTRLEN_PRINTF(pathname)); + return; + } + } + + scp_source_push(scp, SCP_NAME, pathname, PTRLEN_LITERAL(""), &attrs, wc); +} + +static void scp_source_free(ScpServer *s); +static size_t scp_source_send(ScpServer *s, const void *data, size_t length); +static void scp_source_eof(ScpServer *s); +static void scp_source_throttle(ScpServer *s, bool throttled); + +static struct ScpServerVtable ScpSource_ScpServer_vt = { + scp_source_free, + scp_source_send, + scp_source_throttle, + scp_source_eof, +}; + +static ScpSource *scp_source_new( + SshChannel *sc, const SftpServerVtable *sftpserver_vt, ptrlen pathname) +{ + ScpSource *scp = snew(ScpSource); + memset(scp, 0, sizeof(*scp)); + + scp->scpserver.vt = &ScpSource_ScpServer_vt; + scp_reply_setup(&scp->reply); + scp->sc = sc; + scp->sf = sftpsrv_new(sftpserver_vt); + scp->n_pending_commands = 0; + + scp_source_push(scp, SCP_ROOTPATH, pathname, PTRLEN_LITERAL(""), + NULL, NULL); + + return scp; +} + +static void scp_source_free(ScpServer *s) +{ + ScpSource *scp = container_of(s, ScpSource, scpserver); + scp_reply_cleanup(&scp->reply); + while (scp->n_pending_commands > 0) + strbuf_free(scp->pending_commands[--scp->n_pending_commands]); + while (scp->head) { + ScpSourceStackEntry *node = scp->head; + scp->head = node->next; + sfree(node); + } + + delete_callbacks_for_context(scp); + + sfree(scp); +} + +static void scp_source_send_E(ScpSource *scp) +{ + strbuf *cmd; + + assert(scp->n_pending_commands == 0); + + scp->pending_commands[scp->n_pending_commands++] = cmd = strbuf_new(); + strbuf_catf(cmd, "E\012"); +} + +static void scp_source_send_CD( + ScpSource *scp, char cmdchar, + struct fxp_attrs attrs, uint64_t size, ptrlen name) +{ + strbuf *cmd; + + assert(scp->n_pending_commands == 0); + + if (scp->send_file_times && (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME)) { + scp->pending_commands[scp->n_pending_commands++] = cmd = strbuf_new(); + /* Our SFTP-based filesystem API doesn't support microsecond times */ + strbuf_catf(cmd, "T%lu 0 %lu 0\012", attrs.mtime, attrs.atime); + } + + const char *slash; + while ((slash = memchr(name.ptr, '/', name.len)) != NULL) + name = make_ptrlen( + slash+1, name.len - (slash+1 - (const char *)name.ptr)); + + scp->pending_commands[scp->n_pending_commands++] = cmd = strbuf_new(); + strbuf_catf(cmd, "%c%04o %"PRIu64" %.*s\012", cmdchar, + (unsigned)(attrs.permissions & 07777), + size, PTRLEN_PRINTF(name)); + + if (cmdchar == 'C') { + /* We'll also wait for an ack before sending the file data, + * which we record by saving a zero-length 'command' to be + * sent after the C. */ + scp->pending_commands[scp->n_pending_commands++] = cmd = strbuf_new(); + } +} + +static void scp_source_process_stack(ScpSource *scp); +static void scp_source_process_stack_cb(void *vscp) +{ + ScpSource *scp = (ScpSource *)vscp; + if (scp->finished) + return; /* this callback is out of date */ + scp_source_process_stack(scp); +} +static void scp_requeue(ScpSource *scp) +{ + queue_toplevel_callback(scp_source_process_stack_cb, scp); +} + +static void scp_source_process_stack(ScpSource *scp) +{ + if (scp->throttled) + return; + + while (scp->n_pending_commands > 0) { + /* Expect an ack, and consume it */ + if (scp->eof) { + scp_source_abort( + scp, "scp: received client EOF, abandoning transfer"); + return; + } + if (scp->acks == 0) + return; + scp->acks--; + + /* + * Now send the actual command (unless it was the phony + * zero-length one that indicates our need for an ack before + * beginning to send file data). + */ + + if (scp->pending_commands[0]->len) + sshfwd_write(scp->sc, scp->pending_commands[0]->s, + scp->pending_commands[0]->len); + + strbuf_free(scp->pending_commands[0]); + scp->n_pending_commands--; + if (scp->n_pending_commands > 0) { + /* + * We still have at least one pending command to send, so + * move up the queue. + * + * (We do that with a bodgy memmove, because there are at + * most a bounded number of commands ever pending at once, + * so no need to worry about quadratic time.) + */ + memmove(scp->pending_commands, scp->pending_commands+1, + scp->n_pending_commands * sizeof(*scp->pending_commands)); + } + } + + /* + * Mostly, we start by waiting for an ack byte from the receiver. + */ + if (scp->head && scp->head->type == SCP_READFILE && scp->file_offset) { + /* + * Exception: if we're already in the middle of transferring a + * file, we'll be called back here because the channel backlog + * has cleared; we don't need to wait for an ack. + */ + } else if (scp->head && scp->head->type == SCP_ROOTPATH) { + /* + * Another exception: the initial action node that makes us + * stat the root path. We'll translate it into an SCP_NAME, + * and _that_ will require an ack. + */ + ScpSourceStackEntry *node = scp->head; + scp->head = node->next; + + /* + * Start by checking if there's a wildcard involved in the + * root path. + */ + char *rootpath_str = mkstr(node->pathname); + char *rootpath_unesc = snewn(1+node->pathname.len, char); + ptrlen pathname; + const char *wildcard; + + if (wc_unescape(rootpath_unesc, rootpath_str)) { + /* + * We successfully removed instances of the escape + * character used in our wildcard syntax, without + * encountering any actual wildcard chars - i.e. this is + * not a wildcard, just a single file. The simple case. + */ + pathname = ptrlen_from_asciz(rootpath_str); + wildcard = NULL; + } else { + /* + * This is a wildcard. Separate it into a directory name + * (which we enforce mustn't contain wc characters, for + * simplicity) and a wildcard to match leaf names. + */ + char *last_slash = strrchr(rootpath_str, '/'); + + if (last_slash) { + wildcard = last_slash + 1; + *last_slash = '\0'; + if (!wc_unescape(rootpath_unesc, rootpath_str)) { + scp_source_abort(scp, "scp: wildcards in path components " + "before the file name not supported"); + sfree(rootpath_str); + sfree(rootpath_unesc); + return; + } + + pathname = ptrlen_from_asciz(rootpath_unesc); + } else { + pathname = PTRLEN_LITERAL("."); + wildcard = rootpath_str; + } + } + + /* + * Now we know what directory we're scanning, and what + * wildcard (if any) we're using to match the filenames we get + * back. + */ + sftpsrv_stat(scp->sf, &scp->reply.srb, pathname, true); + if (scp->reply.err) { + scp_source_abort( + scp, "%.*s: unable to access: %s", + PTRLEN_PRINTF(pathname), scp->reply.errmsg); + sfree(rootpath_str); + sfree(rootpath_unesc); + sfree(node); + return; + } + + scp_source_push_name(scp, pathname, scp->reply.attrs, wildcard); + + sfree(rootpath_str); + sfree(rootpath_unesc); + sfree(node); + scp_requeue(scp); + return; + } else { + } + + if (scp->head && scp->head->type == SCP_READFILE) { + /* + * Transfer file data if our backlog hasn't filled up. + */ + int backlog; + uint64_t limit = scp->file_size - scp->file_offset; + if (limit > 4096) + limit = 4096; + if (limit > 0) { + sftpsrv_read(scp->sf, &scp->reply.srb, scp->head->handle, + scp->file_offset, limit); + if (scp->reply.err) { + scp_source_abort( + scp, "%.*s: unable to read: %s", + PTRLEN_PRINTF(scp->head->pathname), scp->reply.errmsg); + return; + } + + backlog = sshfwd_write( + scp->sc, scp->reply.data.ptr, scp->reply.data.len); + scp->file_offset += scp->reply.data.len; + + if (backlog < SCP_MAX_BACKLOG) + scp_requeue(scp); + return; + } + + /* + * If we're done, send a terminating zero byte, close our file + * handle, and pop the stack. + */ + sshfwd_write(scp->sc, "\0", 1); + sftpsrv_close(scp->sf, &scp->reply.srb, scp->head->handle); + ScpSourceStackEntry *node = scp->head; + scp->head = node->next; + sfree(node); + scp_requeue(scp); + return; + } + + /* + * If our queue is actually empty, send outgoing EOF. + */ + if (!scp->head) { + sshfwd_send_exit_status(scp->sc, 0); + sshfwd_write_eof(scp->sc); + sshfwd_initiate_close(scp->sc, NULL); + scp->finished = true; + return; + } + + /* + * Otherwise, handle a command. + */ + ScpSourceStackEntry *node = scp->head; + scp->head = node->next; + + if (node->type == SCP_READDIR) { + sftpsrv_readdir(scp->sf, &scp->reply.srb, node->handle, 1, true); + if (scp->reply.err) { + if (scp->reply.code != SSH_FX_EOF) + scp_source_err(scp, "%.*s: unable to list directory: %s", + PTRLEN_PRINTF(node->pathname), + scp->reply.errmsg); + sftpsrv_close(scp->sf, &scp->reply.srb, node->handle); + + if (!node->wildcard) { + /* + * Send 'pop stack' or 'end of directory' command, + * unless this was the topmost READDIR in a + * wildcard-based retrieval (in which case we didn't + * send a D command to start, so an E now would have + * no stack entry to pop). + */ + scp_source_send_E(scp); + } + } else if (ptrlen_eq_string(scp->reply.name, ".") || + ptrlen_eq_string(scp->reply.name, "..") || + (node->wildcard && + !wc_match_pl(node->wildcard, scp->reply.name))) { + /* Skip special directory names . and .., and anything + * that doesn't match our wildcard (if we have one). */ + scp->head = node; /* put back the unfinished READDIR */ + node = NULL; /* and prevent it being freed */ + } else { + ptrlen subpath; + subpath.len = node->pathname.len + 1 + scp->reply.name.len; + char *subpath_space = snewn(subpath.len, char); + subpath.ptr = subpath_space; + memcpy(subpath_space, node->pathname.ptr, node->pathname.len); + subpath_space[node->pathname.len] = '/'; + memcpy(subpath_space + node->pathname.len + 1, + scp->reply.name.ptr, scp->reply.name.len); + + scp->head = node; /* put back the unfinished READDIR */ + node = NULL; /* and prevent it being freed */ + scp_source_push_name(scp, subpath, scp->reply.attrs, NULL); + + sfree(subpath_space); + } + } else if (node->attrs.permissions & PERMS_DIRECTORY) { + assert(scp->recursive || node->wildcard); + + if (!node->wildcard) + scp_source_send_CD(scp, 'D', node->attrs, 0, node->pathname); + sftpsrv_opendir(scp->sf, &scp->reply.srb, node->pathname); + if (scp->reply.err) { + scp_source_err( + scp, "%.*s: unable to access: %s", + PTRLEN_PRINTF(node->pathname), scp->reply.errmsg); + + if (!node->wildcard) { + /* Send 'pop stack' or 'end of directory' command. */ + scp_source_send_E(scp); + } + } else { + scp_source_push( + scp, SCP_READDIR, node->pathname, + scp->reply.handle, NULL, node->wildcard); + } + } else { + sftpsrv_open(scp->sf, &scp->reply.srb, + node->pathname, SSH_FXF_READ, no_attrs); + if (scp->reply.err) { + scp_source_err( + scp, "%.*s: unable to open: %s", + PTRLEN_PRINTF(node->pathname), scp->reply.errmsg); + scp_requeue(scp); + return; + } + sftpsrv_fstat(scp->sf, &scp->reply.srb, scp->reply.handle); + if (scp->reply.err) { + scp_source_err( + scp, "%.*s: unable to stat: %s", + PTRLEN_PRINTF(node->pathname), scp->reply.errmsg); + sftpsrv_close(scp->sf, &scp->reply.srb, scp->reply.handle); + scp_requeue(scp); + return; + } + scp->file_offset = 0; + scp->file_size = scp->reply.attrs.size; + scp_source_send_CD(scp, 'C', node->attrs, + scp->file_size, node->pathname); + scp_source_push( + scp, SCP_READFILE, node->pathname, scp->reply.handle, NULL, NULL); + } + sfree(node); + scp_requeue(scp); +} + +static size_t scp_source_send(ScpServer *s, const void *vdata, size_t length) +{ + ScpSource *scp = container_of(s, ScpSource, scpserver); + const char *data = (const char *)vdata; + size_t i; + + if (scp->finished) + return 0; + + for (i = 0; i < length; i++) { + if (scp->expect_newline) { + if (data[i] == '\012') { + /* End of an error message following a 1 byte */ + scp->expect_newline = false; + scp->acks++; + } + } else { + switch (data[i]) { + case 0: /* ordinary ack */ + scp->acks++; + break; + case 1: /* non-fatal error; consume it */ + scp->expect_newline = true; + break; + case 2: + scp_source_abort( + scp, "terminating on fatal error from client"); + return 0; + default: + scp_source_abort( + scp, "unrecognised response code from client"); + return 0; + } + } + } + + scp_source_process_stack(scp); + + return 0; +} + +static void scp_source_throttle(ScpServer *s, bool throttled) +{ + ScpSource *scp = container_of(s, ScpSource, scpserver); + + if (scp->finished) + return; + + scp->throttled = throttled; + if (!throttled) + scp_source_process_stack(scp); +} + +static void scp_source_eof(ScpServer *s) +{ + ScpSource *scp = container_of(s, ScpSource, scpserver); + + if (scp->finished) + return; + + scp->eof = true; + scp_source_process_stack(scp); +} + +/* ---------------------------------------------------------------------- + * Sink end of the SCP protocol. + */ + +typedef struct ScpSink ScpSink; +typedef struct ScpSinkStackEntry ScpSinkStackEntry; + +struct ScpSink { + SftpServer *sf; + + SshChannel *sc; + ScpSinkStackEntry *head; + + uint64_t file_offset, file_size; + unsigned long atime, mtime; + bool got_file_times; + + bufchain data; + bool input_eof; + strbuf *command; + char command_chr; + + strbuf *filename_sb; + ptrlen filename; + struct fxp_attrs attrs; + + char *errmsg; + + int crState; + + ScpReplyReceiver reply; + + ScpServer scpserver; +}; + +struct ScpSinkStackEntry { + ScpSinkStackEntry *next; + ptrlen destpath; + + /* + * If isdir is true, then destpath identifies a directory that the + * files we receive should be created inside. If it's false, then + * it identifies the exact pathname the next file we receive + * should be created _as_ - regardless of the filename in the 'C' + * command. + */ + bool isdir; +}; + +static void scp_sink_push(ScpSink *scp, ptrlen pathname, bool isdir) +{ + ScpSinkStackEntry *node = snew_plus(ScpSinkStackEntry, pathname.len); + char *p = snew_plus_get_aux(node); + + node->destpath.ptr = p; + node->destpath.len = pathname.len; + memcpy(p, pathname.ptr, pathname.len); + node->isdir = isdir; + + node->next = scp->head; + scp->head = node; +} + +static void scp_sink_pop(ScpSink *scp) +{ + ScpSinkStackEntry *node = scp->head; + scp->head = node->next; + sfree(node); +} + +static void scp_sink_free(ScpServer *s); +static size_t scp_sink_send(ScpServer *s, const void *data, size_t length); +static void scp_sink_eof(ScpServer *s); +static void scp_sink_throttle(ScpServer *s, bool throttled) {} + +static struct ScpServerVtable ScpSink_ScpServer_vt = { + scp_sink_free, + scp_sink_send, + scp_sink_throttle, + scp_sink_eof, +}; + +static void scp_sink_coroutine(ScpSink *scp); +static void scp_sink_start_callback(void *vscp) +{ + scp_sink_coroutine((ScpSink *)vscp); +} + +static ScpSink *scp_sink_new( + SshChannel *sc, const SftpServerVtable *sftpserver_vt, ptrlen pathname, + bool pathname_is_definitely_dir) +{ + ScpSink *scp = snew(ScpSink); + memset(scp, 0, sizeof(*scp)); + + scp->scpserver.vt = &ScpSink_ScpServer_vt; + scp_reply_setup(&scp->reply); + scp->sc = sc; + scp->sf = sftpsrv_new(sftpserver_vt); + bufchain_init(&scp->data); + scp->command = strbuf_new(); + scp->filename_sb = strbuf_new(); + + if (!pathname_is_definitely_dir) { + /* + * If our root pathname is not already expected to be a + * directory because of the -d option in the command line, + * test it ourself to see whether it is or not. + */ + sftpsrv_stat(scp->sf, &scp->reply.srb, pathname, true); + if (!scp->reply.err && + (scp->reply.attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) && + (scp->reply.attrs.permissions & PERMS_DIRECTORY)) + pathname_is_definitely_dir = true; + } + scp_sink_push(scp, pathname, pathname_is_definitely_dir); + + queue_toplevel_callback(scp_sink_start_callback, scp); + + return scp; +} + +static void scp_sink_free(ScpServer *s) +{ + ScpSink *scp = container_of(s, ScpSink, scpserver); + + scp_reply_cleanup(&scp->reply); + bufchain_clear(&scp->data); + strbuf_free(scp->command); + strbuf_free(scp->filename_sb); + while (scp->head) + scp_sink_pop(scp); + sfree(scp->errmsg); + + delete_callbacks_for_context(scp); + + sfree(scp); +} + +static void scp_sink_coroutine(ScpSink *scp) +{ + crBegin(scp->crState); + + while (1) { + /* + * Send an ack, and read a command. + */ + sshfwd_write(scp->sc, "\0", 1); + strbuf_clear(scp->command); + while (1) { + crMaybeWaitUntilV(scp->input_eof || bufchain_size(&scp->data) > 0); + if (scp->input_eof) + goto done; + + ptrlen data = bufchain_prefix(&scp->data); + const char *cdata = data.ptr; + const char *newline = memchr(cdata, '\012', data.len); + if (newline) + data.len = (int)(newline+1 - cdata); + put_data(scp->command, cdata, data.len); + bufchain_consume(&scp->data, data.len); + + if (newline) + break; + } + + /* + * Parse the command. + */ + strbuf_chomp(scp->command, '\n'); + scp->command_chr = scp->command->len > 0 ? scp->command->s[0] : '\0'; + if (scp->command_chr == 'T') { + unsigned long dummy1, dummy2; + if (sscanf(scp->command->s, "T%lu %lu %lu %lu", + &scp->mtime, &dummy1, &scp->atime, &dummy2) != 4) + goto parse_error; + scp->got_file_times = true; + } else if (scp->command_chr == 'C' || scp->command_chr == 'D') { + /* + * Common handling of the start of this case, because the + * messages are parsed similarly. We diverge later. + */ + const char *q, *p = scp->command->s + 1; /* skip the 'C' */ + + scp->attrs.flags = SSH_FILEXFER_ATTR_PERMISSIONS; + scp->attrs.permissions = 0; + while (*p >= '0' && *p <= '7') { + scp->attrs.permissions = + scp->attrs.permissions * 8 + (*p - '0'); + p++; + } + if (*p != ' ') + goto parse_error; + p++; + + q = p; + while (*p >= '0' && *p <= '9') + p++; + if (*p != ' ') + goto parse_error; + p++; + scp->file_size = strtoull(q, NULL, 10); + + ptrlen leafname = make_ptrlen( + p, scp->command->len - (p - scp->command->s)); + strbuf_clear(scp->filename_sb); + put_datapl(scp->filename_sb, scp->head->destpath); + if (scp->head->isdir) { + if (scp->filename_sb->len > 0 && + scp->filename_sb->s[scp->filename_sb->len-1] + != '/') + put_byte(scp->filename_sb, '/'); + put_datapl(scp->filename_sb, leafname); + } + scp->filename = ptrlen_from_strbuf(scp->filename_sb); + + if (scp->got_file_times) { + scp->attrs.mtime = scp->mtime; + scp->attrs.atime = scp->atime; + scp->attrs.flags |= SSH_FILEXFER_ATTR_ACMODTIME; + } + scp->got_file_times = false; + + if (scp->command_chr == 'D') { + sftpsrv_mkdir(scp->sf, &scp->reply.srb, + scp->filename, scp->attrs); + + if (scp->reply.err) { + scp->errmsg = dupprintf( + "'%.*s': unable to create directory: %s", + PTRLEN_PRINTF(scp->filename), scp->reply.errmsg); + goto done; + } + + scp_sink_push(scp, scp->filename, true); + } else { + sftpsrv_open(scp->sf, &scp->reply.srb, scp->filename, + SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC, + scp->attrs); + if (scp->reply.err) { + scp->errmsg = dupprintf( + "'%.*s': unable to open file: %s", + PTRLEN_PRINTF(scp->filename), scp->reply.errmsg); + goto done; + } + + /* + * Now send an ack, and read the file data. + */ + sshfwd_write(scp->sc, "\0", 1); + scp->file_offset = 0; + while (scp->file_offset < scp->file_size) { + ptrlen data; + uint64_t this_len, remaining; + + crMaybeWaitUntilV( + scp->input_eof || bufchain_size(&scp->data) > 0); + if (scp->input_eof) { + sftpsrv_close(scp->sf, &scp->reply.srb, + scp->reply.handle); + goto done; + } + + data = bufchain_prefix(&scp->data); + this_len = data.len; + remaining = scp->file_size - scp->file_offset; + if (this_len > remaining) + this_len = remaining; + sftpsrv_write(scp->sf, &scp->reply.srb, + scp->reply.handle, scp->file_offset, + make_ptrlen(data.ptr, this_len)); + if (scp->reply.err) { + scp->errmsg = dupprintf( + "'%.*s': unable to write to file: %s", + PTRLEN_PRINTF(scp->filename), scp->reply.errmsg); + goto done; + } + bufchain_consume(&scp->data, this_len); + scp->file_offset += this_len; + } + + /* + * Wait for the trailing NUL byte. + */ + crMaybeWaitUntilV( + scp->input_eof || bufchain_size(&scp->data) > 0); + if (scp->input_eof) { + sftpsrv_close(scp->sf, &scp->reply.srb, + scp->reply.handle); + goto done; + } + bufchain_consume(&scp->data, 1); + } + } else if (scp->command_chr == 'E') { + if (!scp->head) { + scp->errmsg = dupstr("received E command without matching D"); + goto done; + } + scp_sink_pop(scp); + scp->got_file_times = false; + } else { + ptrlen cmd_pl; + + /* + * Also come here if any of the above cases run into + * parsing difficulties. + */ + parse_error: + cmd_pl = ptrlen_from_strbuf(scp->command); + scp->errmsg = dupprintf("unrecognised scp command '%.*s'", + PTRLEN_PRINTF(cmd_pl)); + goto done; + } + } + + done: + if (scp->errmsg) { + sshfwd_write_ext(scp->sc, true, scp->errmsg, strlen(scp->errmsg)); + sshfwd_write_ext(scp->sc, true, "\012", 1); + sshfwd_send_exit_status(scp->sc, 1); + } else { + sshfwd_send_exit_status(scp->sc, 0); + } + sshfwd_write_eof(scp->sc); + sshfwd_initiate_close(scp->sc, scp->errmsg); + while (1) crReturnV; + + crFinishV; +} + +static size_t scp_sink_send(ScpServer *s, const void *data, size_t length) +{ + ScpSink *scp = container_of(s, ScpSink, scpserver); + + if (!scp->input_eof) { + bufchain_add(&scp->data, data, length); + scp_sink_coroutine(scp); + } + return 0; +} + +static void scp_sink_eof(ScpServer *s) +{ + ScpSink *scp = container_of(s, ScpSink, scpserver); + + scp->input_eof = true; + scp_sink_coroutine(scp); +} + +/* ---------------------------------------------------------------------- + * Top-level error handler, instantiated in the case where the user + * sent a command starting with "scp " that we couldn't make sense of. + */ + +typedef struct ScpError ScpError; + +struct ScpError { + SshChannel *sc; + char *message; + ScpServer scpserver; +}; + +static void scp_error_free(ScpServer *s); + +static size_t scp_error_send(ScpServer *s, const void *data, size_t length) +{ return 0; } +static void scp_error_eof(ScpServer *s) {} +static void scp_error_throttle(ScpServer *s, bool throttled) {} + +static struct ScpServerVtable ScpError_ScpServer_vt = { + scp_error_free, + scp_error_send, + scp_error_throttle, + scp_error_eof, +}; + +static void scp_error_send_message_cb(void *vscp) +{ + ScpError *scp = (ScpError *)vscp; + sshfwd_write_ext(scp->sc, true, scp->message, strlen(scp->message)); + sshfwd_write_ext(scp->sc, true, "\n", 1); + sshfwd_send_exit_status(scp->sc, 1); + sshfwd_write_eof(scp->sc); + sshfwd_initiate_close(scp->sc, scp->message); +} + +static PRINTF_LIKE(2, 3) ScpError *scp_error_new( + SshChannel *sc, const char *fmt, ...) +{ + va_list ap; + ScpError *scp = snew(ScpError); + + memset(scp, 0, sizeof(*scp)); + + scp->scpserver.vt = &ScpError_ScpServer_vt; + scp->sc = sc; + + va_start(ap, fmt); + scp->message = dupvprintf(fmt, ap); + va_end(ap); + + queue_toplevel_callback(scp_error_send_message_cb, scp); + + return scp; +} + +static void scp_error_free(ScpServer *s) +{ + ScpError *scp = container_of(s, ScpError, scpserver); + + sfree(scp->message); + + delete_callbacks_for_context(scp); + + sfree(scp); +} + +/* ---------------------------------------------------------------------- + * Top-level entry point, which parses a command sent from the SSH + * client, and if it recognises it as an scp command, instantiates an + * appropriate ScpServer implementation and returns it. + */ + +ScpServer *scp_recognise_exec( + SshChannel *sc, const SftpServerVtable *sftpserver_vt, ptrlen command) +{ + bool recursive = false, preserve = false; + bool targetshouldbedirectory = false; + ptrlen command_orig = command; + + if (!ptrlen_startswith(command, PTRLEN_LITERAL("scp "), &command)) + return NULL; + + while (1) { + if (ptrlen_startswith(command, PTRLEN_LITERAL("-v "), &command)) { + /* Enable verbose mode in the server, which we ignore */ + continue; + } + if (ptrlen_startswith(command, PTRLEN_LITERAL("-r "), &command)) { + recursive = true; + continue; + } + if (ptrlen_startswith(command, PTRLEN_LITERAL("-p "), &command)) { + preserve = true; + continue; + } + if (ptrlen_startswith(command, PTRLEN_LITERAL("-d "), &command)) { + targetshouldbedirectory = true; + continue; + } + break; + } + + if (ptrlen_startswith(command, PTRLEN_LITERAL("-t "), &command)) { + ScpSink *scp = scp_sink_new(sc, sftpserver_vt, command, + targetshouldbedirectory); + return &scp->scpserver; + } else if (ptrlen_startswith(command, PTRLEN_LITERAL("-f "), &command)) { + ScpSource *scp = scp_source_new(sc, sftpserver_vt, command); + scp->recursive = recursive; + scp->send_file_times = preserve; + return &scp->scpserver; + } else { + ScpError *scp = scp_error_new( + sc, "Unable to parse scp command: '%.*s'", + PTRLEN_PRINTF(command_orig)); + return &scp->scpserver; + } +} diff --git a/0.73_My_PuTTY/sercfg.c b/0.74_My_PuTTY/sercfg.c similarity index 100% rename from 0.73_My_PuTTY/sercfg.c rename to 0.74_My_PuTTY/sercfg.c diff --git a/0.73_My_PuTTY/sesschan.c b/0.74_My_PuTTY/sesschan.c similarity index 96% rename from 0.73_My_PuTTY/sesschan.c rename to 0.74_My_PuTTY/sesschan.c index 423d75f..36d46f6 100644 --- a/0.73_My_PuTTY/sesschan.c +++ b/0.74_My_PuTTY/sesschan.c @@ -368,7 +368,7 @@ static int xfwd_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx) chan = portfwd_raw_new(sess->c->cl, &plug); s = constructor(ctx, plug); if ((err = sk_socket_error(s)) != NULL) { - portfwd_raw_free(chan); + portfwd_raw_free(chan); return 1; } pi = sk_peer_info(s); @@ -444,7 +444,7 @@ static int agentfwd_accepting( chan = portfwd_raw_new(sess->c->cl, &plug); s = constructor(ctx, plug); if ((err = sk_socket_error(s)) != NULL) { - portfwd_raw_free(chan); + portfwd_raw_free(chan); return 1; } portfwd_raw_setup(chan, s, ssh_serverside_agent_open(sess->c->cl, chan)); @@ -640,10 +640,10 @@ static void sesschan_notify_remote_exit(Seat *seat) sshfwd_send_exit_signal( sess->c, signame, false, ptrlen_from_asciz(sigmsg)); - sfree(sigmsg); - got_signal = true; } + + sfree(sigmsg); } else { int signum = pty_backend_exit_signum(sess->backend); diff --git a/0.73_My_PuTTY/sessprep.c b/0.74_My_PuTTY/sessprep.c similarity index 100% rename from 0.73_My_PuTTY/sessprep.c rename to 0.74_My_PuTTY/sessprep.c diff --git a/0.73_My_PuTTY/settings.c b/0.74_My_PuTTY/settings.c similarity index 99% rename from 0.73_My_PuTTY/settings.c rename to 0.74_My_PuTTY/settings.c index 7854453..73d4cbc 100644 --- a/0.73_My_PuTTY/settings.c +++ b/0.74_My_PuTTY/settings.c @@ -510,8 +510,7 @@ static void write_clip_setting(settings_w *sesskey, const char *savekey, break; case CLIPUI_CUSTOM: { - char *sval = dupcat("custom:", conf_get_str(conf, strconfkey), - (const char *)NULL); + char *sval = dupcat("custom:", conf_get_str(conf, strconfkey)); write_setting_s(sesskey, savekey, sval); sfree(sval); } @@ -619,6 +618,7 @@ void save_open_settings(settings_w *sesskey, Conf *conf) wprefs(sesskey, "Cipher", ciphernames, CIPHER_MAX, conf, CONF_ssh_cipherlist); wprefs(sesskey, "KEX", kexnames, KEX_MAX, conf, CONF_ssh_kexlist); wprefs(sesskey, "HostKey", hknames, HK_MAX, conf, CONF_ssh_hklist); + write_setting_b(sesskey, "PreferKnownHostKeys", conf_get_bool(conf, CONF_ssh_prefer_known_hostkeys)); write_setting_i(sesskey, "RekeyTime", conf_get_int(conf, CONF_ssh_rekey_time)); #ifndef NO_GSSAPI write_setting_i(sesskey, "GssapiRekey", conf_get_int(conf, CONF_gssapirekey)); @@ -1176,6 +1176,7 @@ void load_open_settings(settings_r *sesskey, Conf *conf) } gprefs(sesskey, "HostKey", "ed25519,ecdsa,rsa,dsa,WARN", hknames, HK_MAX, conf, CONF_ssh_hklist); + gppb(sesskey, "PreferKnownHostKeys", true, conf, CONF_ssh_prefer_known_hostkeys); gppi(sesskey, "RekeyTime", 60, conf, CONF_ssh_rekey_time); #ifndef NO_GSSAPI gppi(sesskey, "GssapiRekey", GSS_DEF_REKEY_MINS, conf, CONF_gssapirekey); diff --git a/0.73_My_PuTTY/sftp.c b/0.74_My_PuTTY/sftp.c similarity index 100% rename from 0.73_My_PuTTY/sftp.c rename to 0.74_My_PuTTY/sftp.c diff --git a/0.73_My_PuTTY/sftp.h b/0.74_My_PuTTY/sftp.h similarity index 100% rename from 0.73_My_PuTTY/sftp.h rename to 0.74_My_PuTTY/sftp.h diff --git a/0.73_My_PuTTY/sftpcommon.c b/0.74_My_PuTTY/sftpcommon.c similarity index 100% rename from 0.73_My_PuTTY/sftpcommon.c rename to 0.74_My_PuTTY/sftpcommon.c diff --git a/0.73_My_PuTTY/sftpserver.c b/0.74_My_PuTTY/sftpserver.c similarity index 96% rename from 0.73_My_PuTTY/sftpserver.c rename to 0.74_My_PuTTY/sftpserver.c index 1ff3d37..61d9b41 100644 --- a/0.73_My_PuTTY/sftpserver.c +++ b/0.74_My_PuTTY/sftpserver.c @@ -1,278 +1,279 @@ -/* - * Implement the centralised parts of the server side of SFTP. - */ - -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "sftp.h" - -struct sftp_packet *sftp_handle_request( - SftpServer *srv, struct sftp_packet *req) -{ - struct sftp_packet *reply; - unsigned id; - ptrlen path, dstpath, handle, data; - uint64_t offset; - unsigned length; - struct fxp_attrs attrs; - DefaultSftpReplyBuilder dsrb; - SftpReplyBuilder *rb; - - if (req->type == SSH_FXP_INIT) { - /* - * Special case which doesn't have a request id at the start. - */ - reply = sftp_pkt_init(SSH_FXP_VERSION); - /* - * Since we support only the lowest protocol version, we don't - * need to take the min of this and the client's version, or - * even to bother reading the client version number out of the - * input packet. - */ - put_uint32(reply, SFTP_PROTO_VERSION); - return reply; - } - - /* - * Centralise the request id handling. We'll overwrite the type - * code of the output packet later. - */ - id = get_uint32(req); - reply = sftp_pkt_init(0); - put_uint32(reply, id); - - dsrb.rb.vt = &DefaultSftpReplyBuilder_vt; - dsrb.pkt = reply; - rb = &dsrb.rb; - - switch (req->type) { - case SSH_FXP_REALPATH: - path = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_realpath(srv, rb, path); - break; - - case SSH_FXP_OPEN: - path = get_string(req); - flags = get_uint32(req); - get_fxp_attrs(req, &attrs); - if (get_err(req)) - goto decode_error; - if ((flags & (SSH_FXF_READ|SSH_FXF_WRITE)) == 0) { - fxp_reply_error(rb, SSH_FX_BAD_MESSAGE, - "open without READ or WRITE flag"); - } else if ((flags & (SSH_FXF_CREAT|SSH_FXF_TRUNC)) == SSH_FXF_TRUNC) { - fxp_reply_error(rb, SSH_FX_BAD_MESSAGE, - "open with TRUNC but not CREAT"); - } else if ((flags & (SSH_FXF_CREAT|SSH_FXF_EXCL)) == SSH_FXF_EXCL) { - fxp_reply_error(rb, SSH_FX_BAD_MESSAGE, - "open with EXCL but not CREAT"); - } else { - sftpsrv_open(srv, rb, path, flags, attrs); - } - break; - - case SSH_FXP_OPENDIR: - path = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_opendir(srv, rb, path); - break; - - case SSH_FXP_CLOSE: - handle = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_close(srv, rb, handle); - break; - - case SSH_FXP_MKDIR: - path = get_string(req); - get_fxp_attrs(req, &attrs); - if (get_err(req)) - goto decode_error; - sftpsrv_mkdir(srv, rb, path, attrs); - break; - - case SSH_FXP_RMDIR: - path = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_rmdir(srv, rb, path); - break; - - case SSH_FXP_REMOVE: - path = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_remove(srv, rb, path); - break; - - case SSH_FXP_RENAME: - path = get_string(req); - dstpath = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_rename(srv, rb, path, dstpath); - break; - - case SSH_FXP_STAT: - path = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_stat(srv, rb, path, true); - break; - - case SSH_FXP_LSTAT: - path = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_stat(srv, rb, path, false); - break; - - case SSH_FXP_FSTAT: - handle = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_fstat(srv, rb, handle); - break; - - case SSH_FXP_SETSTAT: - path = get_string(req); - get_fxp_attrs(req, &attrs); - if (get_err(req)) - goto decode_error; - sftpsrv_setstat(srv, rb, path, attrs); - break; - - case SSH_FXP_FSETSTAT: - handle = get_string(req); - get_fxp_attrs(req, &attrs); - if (get_err(req)) - goto decode_error; - sftpsrv_fsetstat(srv, rb, handle, attrs); - break; - - case SSH_FXP_READ: - handle = get_string(req); - offset = get_uint64(req); - length = get_uint32(req); - if (get_err(req)) - goto decode_error; - sftpsrv_read(srv, rb, handle, offset, length); - break; - - case SSH_FXP_READDIR: - handle = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_readdir(srv, rb, handle, INT_MAX, false); - break; - - case SSH_FXP_WRITE: - handle = get_string(req); - offset = get_uint64(req); - data = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_write(srv, rb, handle, offset, data); - break; - - default: - if (get_err(req)) - goto decode_error; - fxp_reply_error(rb, SSH_FX_OP_UNSUPPORTED, - "Unrecognised request type"); - break; - - decode_error: - fxp_reply_error(rb, SSH_FX_BAD_MESSAGE, "Unable to decode request"); - } - - return reply; -} - -static void default_reply_ok(SftpReplyBuilder *reply) -{ - DefaultSftpReplyBuilder *d = - container_of(reply, DefaultSftpReplyBuilder, rb); - d->pkt->type = SSH_FXP_STATUS; - put_uint32(d->pkt, SSH_FX_OK); - put_stringz(d->pkt, ""); -} - -static void default_reply_error( - SftpReplyBuilder *reply, unsigned code, const char *msg) -{ - DefaultSftpReplyBuilder *d = - container_of(reply, DefaultSftpReplyBuilder, rb); - d->pkt->type = SSH_FXP_STATUS; - put_uint32(d->pkt, code); - put_stringz(d->pkt, msg); -} - -static void default_reply_name_count(SftpReplyBuilder *reply, unsigned count) -{ - DefaultSftpReplyBuilder *d = - container_of(reply, DefaultSftpReplyBuilder, rb); - d->pkt->type = SSH_FXP_NAME; - put_uint32(d->pkt, count); -} - -static void default_reply_full_name(SftpReplyBuilder *reply, ptrlen name, - ptrlen longname, struct fxp_attrs attrs) -{ - DefaultSftpReplyBuilder *d = - container_of(reply, DefaultSftpReplyBuilder, rb); - d->pkt->type = SSH_FXP_NAME; - put_stringpl(d->pkt, name); - put_stringpl(d->pkt, longname); - put_fxp_attrs(d->pkt, attrs); -} - -static void default_reply_simple_name(SftpReplyBuilder *reply, ptrlen name) -{ - fxp_reply_name_count(reply, 1); - fxp_reply_full_name(reply, name, PTRLEN_LITERAL(""), no_attrs); -} - -static void default_reply_handle(SftpReplyBuilder *reply, ptrlen handle) -{ - DefaultSftpReplyBuilder *d = - container_of(reply, DefaultSftpReplyBuilder, rb); - d->pkt->type = SSH_FXP_HANDLE; - put_stringpl(d->pkt, handle); -} - -static void default_reply_data(SftpReplyBuilder *reply, ptrlen data) -{ - DefaultSftpReplyBuilder *d = - container_of(reply, DefaultSftpReplyBuilder, rb); - d->pkt->type = SSH_FXP_DATA; - put_stringpl(d->pkt, data); -} - -static void default_reply_attrs( - SftpReplyBuilder *reply, struct fxp_attrs attrs) -{ - DefaultSftpReplyBuilder *d = - container_of(reply, DefaultSftpReplyBuilder, rb); - d->pkt->type = SSH_FXP_ATTRS; - put_fxp_attrs(d->pkt, attrs); -} - -const struct SftpReplyBuilderVtable DefaultSftpReplyBuilder_vt = { - default_reply_ok, - default_reply_error, - default_reply_simple_name, - default_reply_name_count, - default_reply_full_name, - default_reply_handle, - default_reply_data, - default_reply_attrs, -}; +/* + * Implement the centralised parts of the server side of SFTP. + */ + +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "sftp.h" + +struct sftp_packet *sftp_handle_request( + SftpServer *srv, struct sftp_packet *req) +{ + struct sftp_packet *reply; + unsigned id; + uint32_t flags; + ptrlen path, dstpath, handle, data; + uint64_t offset; + unsigned length; + struct fxp_attrs attrs; + DefaultSftpReplyBuilder dsrb; + SftpReplyBuilder *rb; + + if (req->type == SSH_FXP_INIT) { + /* + * Special case which doesn't have a request id at the start. + */ + reply = sftp_pkt_init(SSH_FXP_VERSION); + /* + * Since we support only the lowest protocol version, we don't + * need to take the min of this and the client's version, or + * even to bother reading the client version number out of the + * input packet. + */ + put_uint32(reply, SFTP_PROTO_VERSION); + return reply; + } + + /* + * Centralise the request id handling. We'll overwrite the type + * code of the output packet later. + */ + id = get_uint32(req); + reply = sftp_pkt_init(0); + put_uint32(reply, id); + + dsrb.rb.vt = &DefaultSftpReplyBuilder_vt; + dsrb.pkt = reply; + rb = &dsrb.rb; + + switch (req->type) { + case SSH_FXP_REALPATH: + path = get_string(req); + if (get_err(req)) + goto decode_error; + sftpsrv_realpath(srv, rb, path); + break; + + case SSH_FXP_OPEN: + path = get_string(req); + flags = get_uint32(req); + get_fxp_attrs(req, &attrs); + if (get_err(req)) + goto decode_error; + if ((flags & (SSH_FXF_READ|SSH_FXF_WRITE)) == 0) { + fxp_reply_error(rb, SSH_FX_BAD_MESSAGE, + "open without READ or WRITE flag"); + } else if ((flags & (SSH_FXF_CREAT|SSH_FXF_TRUNC)) == SSH_FXF_TRUNC) { + fxp_reply_error(rb, SSH_FX_BAD_MESSAGE, + "open with TRUNC but not CREAT"); + } else if ((flags & (SSH_FXF_CREAT|SSH_FXF_EXCL)) == SSH_FXF_EXCL) { + fxp_reply_error(rb, SSH_FX_BAD_MESSAGE, + "open with EXCL but not CREAT"); + } else { + sftpsrv_open(srv, rb, path, flags, attrs); + } + break; + + case SSH_FXP_OPENDIR: + path = get_string(req); + if (get_err(req)) + goto decode_error; + sftpsrv_opendir(srv, rb, path); + break; + + case SSH_FXP_CLOSE: + handle = get_string(req); + if (get_err(req)) + goto decode_error; + sftpsrv_close(srv, rb, handle); + break; + + case SSH_FXP_MKDIR: + path = get_string(req); + get_fxp_attrs(req, &attrs); + if (get_err(req)) + goto decode_error; + sftpsrv_mkdir(srv, rb, path, attrs); + break; + + case SSH_FXP_RMDIR: + path = get_string(req); + if (get_err(req)) + goto decode_error; + sftpsrv_rmdir(srv, rb, path); + break; + + case SSH_FXP_REMOVE: + path = get_string(req); + if (get_err(req)) + goto decode_error; + sftpsrv_remove(srv, rb, path); + break; + + case SSH_FXP_RENAME: + path = get_string(req); + dstpath = get_string(req); + if (get_err(req)) + goto decode_error; + sftpsrv_rename(srv, rb, path, dstpath); + break; + + case SSH_FXP_STAT: + path = get_string(req); + if (get_err(req)) + goto decode_error; + sftpsrv_stat(srv, rb, path, true); + break; + + case SSH_FXP_LSTAT: + path = get_string(req); + if (get_err(req)) + goto decode_error; + sftpsrv_stat(srv, rb, path, false); + break; + + case SSH_FXP_FSTAT: + handle = get_string(req); + if (get_err(req)) + goto decode_error; + sftpsrv_fstat(srv, rb, handle); + break; + + case SSH_FXP_SETSTAT: + path = get_string(req); + get_fxp_attrs(req, &attrs); + if (get_err(req)) + goto decode_error; + sftpsrv_setstat(srv, rb, path, attrs); + break; + + case SSH_FXP_FSETSTAT: + handle = get_string(req); + get_fxp_attrs(req, &attrs); + if (get_err(req)) + goto decode_error; + sftpsrv_fsetstat(srv, rb, handle, attrs); + break; + + case SSH_FXP_READ: + handle = get_string(req); + offset = get_uint64(req); + length = get_uint32(req); + if (get_err(req)) + goto decode_error; + sftpsrv_read(srv, rb, handle, offset, length); + break; + + case SSH_FXP_READDIR: + handle = get_string(req); + if (get_err(req)) + goto decode_error; + sftpsrv_readdir(srv, rb, handle, INT_MAX, false); + break; + + case SSH_FXP_WRITE: + handle = get_string(req); + offset = get_uint64(req); + data = get_string(req); + if (get_err(req)) + goto decode_error; + sftpsrv_write(srv, rb, handle, offset, data); + break; + + default: + if (get_err(req)) + goto decode_error; + fxp_reply_error(rb, SSH_FX_OP_UNSUPPORTED, + "Unrecognised request type"); + break; + + decode_error: + fxp_reply_error(rb, SSH_FX_BAD_MESSAGE, "Unable to decode request"); + } + + return reply; +} + +static void default_reply_ok(SftpReplyBuilder *reply) +{ + DefaultSftpReplyBuilder *d = + container_of(reply, DefaultSftpReplyBuilder, rb); + d->pkt->type = SSH_FXP_STATUS; + put_uint32(d->pkt, SSH_FX_OK); + put_stringz(d->pkt, ""); +} + +static void default_reply_error( + SftpReplyBuilder *reply, unsigned code, const char *msg) +{ + DefaultSftpReplyBuilder *d = + container_of(reply, DefaultSftpReplyBuilder, rb); + d->pkt->type = SSH_FXP_STATUS; + put_uint32(d->pkt, code); + put_stringz(d->pkt, msg); +} + +static void default_reply_name_count(SftpReplyBuilder *reply, unsigned count) +{ + DefaultSftpReplyBuilder *d = + container_of(reply, DefaultSftpReplyBuilder, rb); + d->pkt->type = SSH_FXP_NAME; + put_uint32(d->pkt, count); +} + +static void default_reply_full_name(SftpReplyBuilder *reply, ptrlen name, + ptrlen longname, struct fxp_attrs attrs) +{ + DefaultSftpReplyBuilder *d = + container_of(reply, DefaultSftpReplyBuilder, rb); + d->pkt->type = SSH_FXP_NAME; + put_stringpl(d->pkt, name); + put_stringpl(d->pkt, longname); + put_fxp_attrs(d->pkt, attrs); +} + +static void default_reply_simple_name(SftpReplyBuilder *reply, ptrlen name) +{ + fxp_reply_name_count(reply, 1); + fxp_reply_full_name(reply, name, PTRLEN_LITERAL(""), no_attrs); +} + +static void default_reply_handle(SftpReplyBuilder *reply, ptrlen handle) +{ + DefaultSftpReplyBuilder *d = + container_of(reply, DefaultSftpReplyBuilder, rb); + d->pkt->type = SSH_FXP_HANDLE; + put_stringpl(d->pkt, handle); +} + +static void default_reply_data(SftpReplyBuilder *reply, ptrlen data) +{ + DefaultSftpReplyBuilder *d = + container_of(reply, DefaultSftpReplyBuilder, rb); + d->pkt->type = SSH_FXP_DATA; + put_stringpl(d->pkt, data); +} + +static void default_reply_attrs( + SftpReplyBuilder *reply, struct fxp_attrs attrs) +{ + DefaultSftpReplyBuilder *d = + container_of(reply, DefaultSftpReplyBuilder, rb); + d->pkt->type = SSH_FXP_ATTRS; + put_fxp_attrs(d->pkt, attrs); +} + +const struct SftpReplyBuilderVtable DefaultSftpReplyBuilder_vt = { + default_reply_ok, + default_reply_error, + default_reply_simple_name, + default_reply_name_count, + default_reply_full_name, + default_reply_handle, + default_reply_data, + default_reply_attrs, +}; diff --git a/0.73_My_PuTTY/ssh.c b/0.74_My_PuTTY/ssh.c similarity index 96% rename from 0.73_My_PuTTY/ssh.c rename to 0.74_My_PuTTY/ssh.c index 9751dba..8a21163 100644 --- a/0.73_My_PuTTY/ssh.c +++ b/0.74_My_PuTTY/ssh.c @@ -562,7 +562,7 @@ void ssh_deferred_abort_callback(void *vctx) Ssh *ssh = (Ssh *)vctx; char *msg = ssh->deferred_abort_message; ssh->deferred_abort_message = NULL; - ssh_sw_abort(ssh, msg); + ssh_sw_abort(ssh, "%s", msg); sfree(msg); } @@ -996,7 +996,8 @@ static size_t ssh_sendbuffer(Backend *be) backlog = ssh_stdin_backlog(ssh->cl); - /* FIXME: also include sizes of pqs */ + if (ssh->base_layer) + backlog += ssh_ppl_queued_data_size(ssh->base_layer); /* * If the SSH socket itself has backed up, add the total backup diff --git a/0.73_My_PuTTY/ssh.h b/0.74_My_PuTTY/ssh.h similarity index 99% rename from 0.73_My_PuTTY/ssh.h rename to 0.74_My_PuTTY/ssh.h index 76c75b7..62e80d6 100644 --- a/0.73_My_PuTTY/ssh.h +++ b/0.74_My_PuTTY/ssh.h @@ -62,6 +62,7 @@ struct ssh_channel; typedef struct PacketQueueNode PacketQueueNode; struct PacketQueueNode { PacketQueueNode *next, *prev; + size_t formal_size; /* contribution to PacketQueueBase's total_size */ bool on_free_queue; /* is this packet scheduled for freeing? */ }; @@ -94,6 +95,7 @@ typedef struct PktOut { typedef struct PacketQueueBase { PacketQueueNode end; + size_t total_size; /* sum of all formal_size fields on the queue */ struct IdempotentCallback *ic; } PacketQueueBase; @@ -413,12 +415,12 @@ void ssh_conn_processed_data(Ssh *ssh); void ssh_check_frozen(Ssh *ssh); /* Functions to abort the connection, for various reasons. */ -void ssh_remote_error(Ssh *ssh, const char *fmt, ...); -void ssh_remote_eof(Ssh *ssh, const char *fmt, ...); -void ssh_proto_error(Ssh *ssh, const char *fmt, ...); -void ssh_sw_abort(Ssh *ssh, const char *fmt, ...); -void ssh_sw_abort_deferred(Ssh *ssh, const char *fmt, ...); -void ssh_user_close(Ssh *ssh, const char *fmt, ...); +void ssh_remote_error(Ssh *ssh, const char *fmt, ...) PRINTF_LIKE(2, 3); +void ssh_remote_eof(Ssh *ssh, const char *fmt, ...) PRINTF_LIKE(2, 3); +void ssh_proto_error(Ssh *ssh, const char *fmt, ...) PRINTF_LIKE(2, 3); +void ssh_sw_abort(Ssh *ssh, const char *fmt, ...) PRINTF_LIKE(2, 3); +void ssh_sw_abort_deferred(Ssh *ssh, const char *fmt, ...) PRINTF_LIKE(2, 3); +void ssh_user_close(Ssh *ssh, const char *fmt, ...) PRINTF_LIKE(2, 3); /* Bit positions in the SSH-1 cipher protocol word */ #define SSH1_CIPHER_IDEA 1 @@ -549,6 +551,7 @@ void BinarySource_get_rsa_ssh1_pub( BinarySource *src, RSAKey *result, RsaSsh1Order order); void BinarySource_get_rsa_ssh1_priv( BinarySource *src, RSAKey *rsa); +RSAKey *BinarySource_get_rsa_ssh1_priv_agent(BinarySource *src); bool rsa_ssh1_encrypt(unsigned char *data, int length, RSAKey *key); mp_int *rsa_ssh1_decrypt(mp_int *input, RSAKey *key); bool rsa_ssh1_decrypt_pkcs1(mp_int *input, RSAKey *key, strbuf *outbuf); @@ -951,12 +954,6 @@ extern const ssh_kex ssh_ec_kex_nistp521; extern const ssh_kexes ssh_ecdh_kex; extern const ssh_keyalg ssh_dss; extern const ssh_keyalg ssh_rsa; -#ifdef MOD_WINCRYPT -#ifdef HAS_WINX509 -extern const ssh_keyalg ssh_rsa_wincrypt; -extern const ssh_keyalg ssh_x509_wincrypt; -#endif /* HAS_WINX509 */ -#endif extern const ssh_keyalg ssh_ecdsa_ed25519; extern const ssh_keyalg ssh_ecdsa_nistp256; extern const ssh_keyalg ssh_ecdsa_nistp384; @@ -1168,11 +1165,7 @@ bool ssh2_userkey_encrypted(const Filename *filename, char **comment); ssh2_userkey *ssh2_load_userkey( const Filename *filename, const char *passphrase, const char **errorstr); bool ssh2_userkey_loadpub( -#ifdef MOD_WINCRYPT - const Filename **filename, char **algorithm, BinarySink *bs, -#else const Filename *filename, char **algorithm, BinarySink *bs, -#endif char **commentptr, const char **errorstr); bool ssh2_save_userkey( const Filename *filename, ssh2_userkey *key, char *passphrase); diff --git a/0.73_My_PuTTY/ssh1bpp.c b/0.74_My_PuTTY/ssh1bpp.c similarity index 95% rename from 0.73_My_PuTTY/ssh1bpp.c rename to 0.74_My_PuTTY/ssh1bpp.c index 5e5f696..71b164c 100644 --- a/0.73_My_PuTTY/ssh1bpp.c +++ b/0.74_My_PuTTY/ssh1bpp.c @@ -236,6 +236,7 @@ static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp) NULL, 0, NULL); } + s->pktin->qnode.formal_size = get_avail(s->pktin); pq_push(&s->bpp.in_pq, s->pktin); { @@ -286,7 +287,7 @@ static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp) static PktOut *ssh1_bpp_new_pktout(int pkt_type) { PktOut *pkt = ssh_new_packet(); - pkt->length = 4 + 8; /* space for length + max padding */ + pkt->length = 4 + 8; /* space for length + max padding */ put_byte(pkt, pkt_type); pkt->prefix = pkt->length; pkt->type = pkt_type; diff --git a/0.73_My_PuTTY/ssh1censor.c b/0.74_My_PuTTY/ssh1censor.c similarity index 100% rename from 0.73_My_PuTTY/ssh1censor.c rename to 0.74_My_PuTTY/ssh1censor.c diff --git a/0.73_My_PuTTY/ssh1connection-client.c b/0.74_My_PuTTY/ssh1connection-client.c similarity index 100% rename from 0.73_My_PuTTY/ssh1connection-client.c rename to 0.74_My_PuTTY/ssh1connection-client.c diff --git a/0.73_My_PuTTY/ssh1connection-server.c b/0.74_My_PuTTY/ssh1connection-server.c similarity index 100% rename from 0.73_My_PuTTY/ssh1connection-server.c rename to 0.74_My_PuTTY/ssh1connection-server.c diff --git a/0.73_My_PuTTY/ssh1connection.c b/0.74_My_PuTTY/ssh1connection.c similarity index 99% rename from 0.73_My_PuTTY/ssh1connection.c rename to 0.74_My_PuTTY/ssh1connection.c index b06efe7..a511845 100644 --- a/0.73_My_PuTTY/ssh1connection.c +++ b/0.74_My_PuTTY/ssh1connection.c @@ -43,6 +43,7 @@ static const struct PacketProtocolLayerVtable ssh1_connection_vtable = { ssh1_connection_want_user_input, ssh1_connection_got_user_input, ssh1_connection_reconfigure, + ssh_ppl_default_queued_data_size, NULL /* no layer names in SSH-1 */, }; @@ -522,10 +523,12 @@ static void ssh1_channel_close_local(struct ssh1_channel *c, { struct ssh1_connection_state *s = c->connlayer; PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - const char *msg = chan_log_close_msg(c->chan); + char *msg = chan_log_close_msg(c->chan); - if (msg != NULL) + if (msg != NULL) { ppl_logevent("%s%s%s", msg, reason ? " " : "", reason ? reason : ""); + sfree(msg); + } chan_free(c->chan); c->chan = zombiechan_new(); diff --git a/0.73_My_PuTTY/ssh1connection.h b/0.74_My_PuTTY/ssh1connection.h similarity index 100% rename from 0.73_My_PuTTY/ssh1connection.h rename to 0.74_My_PuTTY/ssh1connection.h diff --git a/0.73_My_PuTTY/ssh1login-server.c b/0.74_My_PuTTY/ssh1login-server.c similarity index 91% rename from 0.73_My_PuTTY/ssh1login-server.c rename to 0.74_My_PuTTY/ssh1login-server.c index 831a719..9856ef3 100644 --- a/0.73_My_PuTTY/ssh1login-server.c +++ b/0.74_My_PuTTY/ssh1login-server.c @@ -43,7 +43,7 @@ struct ssh1_login_server_state { PacketProtocolLayer ppl; }; -static void ssh1_login_server_free(PacketProtocolLayer *); +static void ssh1_login_server_free(PacketProtocolLayer *); static void ssh1_login_server_process_queue(PacketProtocolLayer *); static bool ssh1_login_server_get_specials( @@ -65,6 +65,7 @@ static const struct PacketProtocolLayerVtable ssh1_login_server_vtable = { ssh1_login_server_want_user_input, ssh1_login_server_got_user_input, ssh1_login_server_reconfigure, + ssh_ppl_default_queued_data_size, NULL /* no layer names in SSH-1 */, }; @@ -218,7 +219,7 @@ static void ssh1_login_server_process_queue(PacketProtocolLayer *ppl) if (rsa_ssh1_decrypt_pkcs1(s->sesskey, larger, data)) { mp_free(s->sesskey); s->sesskey = mp_from_bytes_be(ptrlen_from_strbuf(data)); - data->len = 0; + strbuf_clear(data); if (rsa_ssh1_decrypt_pkcs1(s->sesskey, smaller, data) && data->len == sizeof(s->session_key)) { memcpy(s->session_key, data->u, sizeof(s->session_key)); @@ -291,18 +292,34 @@ static void ssh1_login_server_process_queue(PacketProtocolLayer *ppl) mp_int *modulus = get_mp_ssh1(pktin); s->authkey = auth_publickey_ssh1( s->authpolicy, s->username, modulus); + + if (!s->authkey && + s->ssc->stunt_pretend_to_accept_any_pubkey) { + mp_int *zero = mp_from_integer(0); + mp_int *fake_challenge = mp_random_in_range(zero, modulus); + + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_SMSG_AUTH_RSA_CHALLENGE); + put_mp_ssh1(pktout, fake_challenge); + pq_push(s->ppl.out_pq, pktout); + + mp_free(zero); + mp_free(fake_challenge); + } + mp_free(modulus); } - if (!s->authkey) + if (!s->authkey && + !s->ssc->stunt_pretend_to_accept_any_pubkey) continue; - if (s->authkey->bytes < 32) { + if (s->authkey && s->authkey->bytes < 32) { ppl_logevent("Auth key far too small"); continue; } - { + if (s->authkey) { unsigned char *rsabuf = snewn(s->authkey->bytes, unsigned char); @@ -342,6 +359,9 @@ static void ssh1_login_server_process_queue(PacketProtocolLayer *ppl) return; } + if (!s->authkey) + continue; + { ptrlen response = get_data(pktin, 16); ptrlen expected = make_ptrlen( diff --git a/0.73_My_PuTTY/ssh1login.c b/0.74_My_PuTTY/ssh1login.c similarity index 81% rename from 0.73_My_PuTTY/ssh1login.c rename to 0.74_My_PuTTY/ssh1login.c index de16f7e..ac3ab04 100644 --- a/0.73_My_PuTTY/ssh1login.c +++ b/0.74_My_PuTTY/ssh1login.c @@ -1,1186 +1,1245 @@ -/* - * Packet protocol layer for the SSH-1 login phase (combining what - * SSH-2 would think of as key exchange and user authentication). - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "mpint.h" -#include "sshbpp.h" -#include "sshppl.h" -#include "sshcr.h" - -struct ssh1_login_state { - int crState; - - PacketProtocolLayer *successor_layer; - - Conf *conf; - - char *savedhost; - int savedport; - bool try_agent_auth; - - int remote_protoflags; - int local_protoflags; - unsigned char session_key[32]; - char *username; - agent_pending_query *auth_agent_query; - - int len; - unsigned char *rsabuf; - unsigned long supported_ciphers_mask, supported_auths_mask; - bool tried_publickey, tried_agent; - bool tis_auth_refused, ccard_auth_refused; - unsigned char cookie[8]; - unsigned char session_id[16]; - int cipher_type; - strbuf *publickey_blob; - char *publickey_comment; - bool privatekey_available, privatekey_encrypted; - prompts_t *cur_prompt; - int userpass_ret; - char c; - int pwpkt_type; - void *agent_response_to_free; - ptrlen agent_response; - BinarySource asrc[1]; /* response from SSH agent */ - int keyi, nkeys; - bool authed; - RSAKey key; - mp_int *challenge; - strbuf *agent_comment; - int dlgret; - Filename *keyfile; - RSAKey servkey, hostkey; - bool want_user_input; - - StripCtrlChars *tis_scc; - bool tis_scc_initialised; - - PacketProtocolLayer ppl; -}; - -static void ssh1_login_free(PacketProtocolLayer *); -static void ssh1_login_process_queue(PacketProtocolLayer *); -static void ssh1_login_dialog_callback(void *, int); -static void ssh1_login_special_cmd(PacketProtocolLayer *ppl, - SessionSpecialCode code, int arg); -static bool ssh1_login_want_user_input(PacketProtocolLayer *ppl); -static void ssh1_login_got_user_input(PacketProtocolLayer *ppl); -static void ssh1_login_reconfigure(PacketProtocolLayer *ppl, Conf *conf); - -static const struct PacketProtocolLayerVtable ssh1_login_vtable = { - ssh1_login_free, - ssh1_login_process_queue, - ssh1_common_get_specials, - ssh1_login_special_cmd, - ssh1_login_want_user_input, - ssh1_login_got_user_input, - ssh1_login_reconfigure, - NULL /* no layer names in SSH-1 */, -}; - -static void ssh1_login_agent_query(struct ssh1_login_state *s, strbuf *req); -static void ssh1_login_agent_callback(void *loginv, void *reply, int replylen); - -PacketProtocolLayer *ssh1_login_new( - Conf *conf, const char *host, int port, - PacketProtocolLayer *successor_layer) -{ - struct ssh1_login_state *s = snew(struct ssh1_login_state); - memset(s, 0, sizeof(*s)); - s->ppl.vt = &ssh1_login_vtable; - - s->conf = conf_copy(conf); - s->savedhost = dupstr(host); - s->savedport = port; - s->successor_layer = successor_layer; - s->agent_comment = strbuf_new(); - return &s->ppl; -} - -static void ssh1_login_free(PacketProtocolLayer *ppl) -{ - struct ssh1_login_state *s = - container_of(ppl, struct ssh1_login_state, ppl); - - if (s->successor_layer) - ssh_ppl_free(s->successor_layer); - - conf_free(s->conf); - sfree(s->savedhost); - sfree(s->rsabuf); - sfree(s->username); - if (s->publickey_blob) - strbuf_free(s->publickey_blob); - sfree(s->publickey_comment); - strbuf_free(s->agent_comment); - if (s->cur_prompt) - free_prompts(s->cur_prompt); - sfree(s->agent_response_to_free); - if (s->auth_agent_query) - agent_cancel_query(s->auth_agent_query); - sfree(s); -} - -static bool ssh1_login_filter_queue(struct ssh1_login_state *s) -{ - return ssh1_common_filter_queue(&s->ppl); -} - -static PktIn *ssh1_login_pop(struct ssh1_login_state *s) -{ - if (ssh1_login_filter_queue(s)) - return NULL; - return pq_pop(s->ppl.in_pq); -} - -static void ssh1_login_setup_tis_scc(struct ssh1_login_state *s); - -static void ssh1_login_process_queue(PacketProtocolLayer *ppl) -{ - struct ssh1_login_state *s = - container_of(ppl, struct ssh1_login_state, ppl); - PktIn *pktin; - PktOut *pkt; - int i; - - /* Filter centrally handled messages off the front of the queue on - * every entry to this coroutine, no matter where we're resuming - * from, even if we're _not_ looping on pq_pop. That way we can - * still proactively handle those messages even if we're waiting - * for a user response. */ - if (ssh1_login_filter_queue(s)) - return; - - crBegin(s->crState); - - crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); - - if (pktin->type != SSH1_SMSG_PUBLIC_KEY) { - ssh_proto_error(s->ppl.ssh, "Public key packet not received"); - return; - } - - ppl_logevent("Received public keys"); - - { - ptrlen pl = get_data(pktin, 8); - memcpy(s->cookie, pl.ptr, pl.len); - } - - get_rsa_ssh1_pub(pktin, &s->servkey, RSA_SSH1_EXPONENT_FIRST); - get_rsa_ssh1_pub(pktin, &s->hostkey, RSA_SSH1_EXPONENT_FIRST); - - s->hostkey.comment = NULL; /* avoid confusing rsa_ssh1_fingerprint */ - - /* - * Log the host key fingerprint. - */ - if (!get_err(pktin)) { - char *fingerprint = rsa_ssh1_fingerprint(&s->hostkey); - ppl_logevent("Host key fingerprint is:"); - ppl_logevent(" %s", fingerprint); - sfree(fingerprint); - } - - s->remote_protoflags = get_uint32(pktin); - s->supported_ciphers_mask = get_uint32(pktin); - s->supported_auths_mask = get_uint32(pktin); - - if (get_err(pktin)) { - ssh_proto_error(s->ppl.ssh, "Bad SSH-1 public key packet"); - return; - } - - if ((s->ppl.remote_bugs & BUG_CHOKES_ON_RSA)) - s->supported_auths_mask &= ~(1 << SSH1_AUTH_RSA); - - s->local_protoflags = - s->remote_protoflags & SSH1_PROTOFLAGS_SUPPORTED; - s->local_protoflags |= SSH1_PROTOFLAG_SCREEN_NUMBER; - - ssh1_compute_session_id(s->session_id, s->cookie, - &s->hostkey, &s->servkey); - - random_read(s->session_key, 32); - - /* - * Verify that the `bits' and `bytes' parameters match. - */ - if (s->hostkey.bits > s->hostkey.bytes * 8 || - s->servkey.bits > s->servkey.bytes * 8) { - ssh_proto_error(s->ppl.ssh, "SSH-1 public keys were badly formatted"); - return; - } - - s->len = 32; - if (s->len < s->hostkey.bytes) - s->len = s->hostkey.bytes; - if (s->len < s->servkey.bytes) - s->len = s->servkey.bytes; - - s->rsabuf = snewn(s->len, unsigned char); - - /* - * Verify the host key. - */ - { - /* - * First format the key into a string. - */ - char *fingerprint; - char *keystr = rsastr_fmt(&s->hostkey); - fingerprint = rsa_ssh1_fingerprint(&s->hostkey); - - /* First check against manually configured host keys. */ - s->dlgret = verify_ssh_manual_host_key(s->conf, fingerprint, NULL); - if (s->dlgret == 0) { /* did not match */ - sfree(fingerprint); - sfree(keystr); - ssh_proto_error(s->ppl.ssh, "Host key did not appear in manually " - "configured list"); - return; - } else if (s->dlgret < 0) { /* none configured; use standard handling */ - s->dlgret = seat_verify_ssh_host_key( - s->ppl.seat, s->savedhost, s->savedport, - "rsa", keystr, fingerprint, ssh1_login_dialog_callback, s); - sfree(fingerprint); - sfree(keystr); -#ifdef FUZZING - s->dlgret = 1; -#endif - crMaybeWaitUntilV(s->dlgret >= 0); - - if (s->dlgret == 0) { - ssh_user_close(s->ppl.ssh, - "User aborted at host key verification"); - return; - } - } else { - sfree(fingerprint); - sfree(keystr); - } - } - - for (i = 0; i < 32; i++) { - s->rsabuf[i] = s->session_key[i]; - if (i < 16) - s->rsabuf[i] ^= s->session_id[i]; - } - - { - RSAKey *smaller = (s->hostkey.bytes > s->servkey.bytes ? - &s->servkey : &s->hostkey); - RSAKey *larger = (s->hostkey.bytes > s->servkey.bytes ? - &s->hostkey : &s->servkey); - - if (!rsa_ssh1_encrypt(s->rsabuf, 32, smaller) || - !rsa_ssh1_encrypt(s->rsabuf, smaller->bytes, larger)) { - ssh_proto_error(s->ppl.ssh, "SSH-1 public key encryptions failed " - "due to bad formatting"); - return; - } - } - - ppl_logevent("Encrypted session key"); - - { - bool cipher_chosen = false, warn = false; - const char *cipher_string = NULL; - int i; - for (i = 0; !cipher_chosen && i < CIPHER_MAX; i++) { - int next_cipher = conf_get_int_int( - s->conf, CONF_ssh_cipherlist, i); - if (next_cipher == CIPHER_WARN) { - /* If/when we choose a cipher, warn about it */ - warn = true; - } else if (next_cipher == CIPHER_AES) { - /* XXX Probably don't need to mention this. */ - ppl_logevent("AES not supported in SSH-1, skipping"); - } else { - switch (next_cipher) { - case CIPHER_3DES: s->cipher_type = SSH1_CIPHER_3DES; - cipher_string = "3DES"; break; - case CIPHER_BLOWFISH: s->cipher_type = SSH1_CIPHER_BLOWFISH; - cipher_string = "Blowfish"; break; - case CIPHER_DES: s->cipher_type = SSH1_CIPHER_DES; - cipher_string = "single-DES"; break; - } - if (s->supported_ciphers_mask & (1 << s->cipher_type)) - cipher_chosen = true; - } - } - if (!cipher_chosen) { - if ((s->supported_ciphers_mask & (1 << SSH1_CIPHER_3DES)) == 0) { - ssh_proto_error(s->ppl.ssh, "Server violates SSH-1 protocol " - "by not supporting 3DES encryption"); - } else { - /* shouldn't happen */ - ssh_sw_abort(s->ppl.ssh, "No supported ciphers found"); - } - return; - } - - /* Warn about chosen cipher if necessary. */ - if (warn) { - s->dlgret = seat_confirm_weak_crypto_primitive( - s->ppl.seat, "cipher", cipher_string, - ssh1_login_dialog_callback, s); - crMaybeWaitUntilV(s->dlgret >= 0); - if (s->dlgret == 0) { - ssh_user_close(s->ppl.ssh, "User aborted at cipher warning"); - return; - } - } - } - - switch (s->cipher_type) { - case SSH1_CIPHER_3DES: - ppl_logevent("Using 3DES encryption"); - break; - case SSH1_CIPHER_DES: - ppl_logevent("Using single-DES encryption"); - break; - case SSH1_CIPHER_BLOWFISH: - ppl_logevent("Using Blowfish encryption"); - break; - } - - pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_SESSION_KEY); - put_byte(pkt, s->cipher_type); - put_data(pkt, s->cookie, 8); - put_uint16(pkt, s->len * 8); - put_data(pkt, s->rsabuf, s->len); - put_uint32(pkt, s->local_protoflags); - pq_push(s->ppl.out_pq, pkt); - - ppl_logevent("Trying to enable encryption..."); - - sfree(s->rsabuf); - s->rsabuf = NULL; - - /* - * Force the BPP to synchronously marshal all packets up to and - * including the SESSION_KEY into wire format, before we turn on - * crypto. - */ - ssh_bpp_handle_output(s->ppl.bpp); - - { - const ssh_cipheralg *cipher = - (s->cipher_type == SSH1_CIPHER_BLOWFISH ? &ssh_blowfish_ssh1 : - s->cipher_type == SSH1_CIPHER_DES ? &ssh_des : &ssh_3des_ssh1); - ssh1_bpp_new_cipher(s->ppl.bpp, cipher, s->session_key); - } - - freersakey(&s->servkey); - freersakey(&s->hostkey); - crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); - - if (pktin->type != SSH1_SMSG_SUCCESS) { - ssh_proto_error(s->ppl.ssh, "Encryption not successfully enabled"); - return; - } - - ppl_logevent("Successfully started encryption"); - - if ((s->username = get_remote_username(s->conf)) == NULL) { - s->cur_prompt = new_prompts(); - s->cur_prompt->to_server = true; - s->cur_prompt->from_server = false; - s->cur_prompt->name = dupstr("SSH login name"); - add_prompt(s->cur_prompt, dupstr("login as: "), true); - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, s->ppl.user_input); - - if (s->userpass_ret >= 0) - break; - - s->want_user_input = true; - crReturnV; - s->want_user_input = false; - } - if (!s->userpass_ret) { - /* - * Failed to get a username. Terminate. - */ - ssh_user_close(s->ppl.ssh, "No username provided"); - return; - } - s->username = dupstr(s->cur_prompt->prompts[0]->result); -#ifdef MOD_PERSO - SetUsernameInConfig( s->username ) ; -#endif - free_prompts(s->cur_prompt); - s->cur_prompt = NULL; - } - - pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_USER); - put_stringz(pkt, s->username); - pq_push(s->ppl.out_pq, pkt); - - ppl_logevent("Sent username \"%s\"", s->username); - if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) - ppl_printf("Sent username \"%s\"\r\n", s->username); - - crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); - - if (!(s->supported_auths_mask & (1 << SSH1_AUTH_RSA))) { - /* We must not attempt PK auth. Pretend we've already tried it. */ - s->tried_publickey = s->tried_agent = true; - } else { - s->tried_publickey = s->tried_agent = false; - } - s->tis_auth_refused = s->ccard_auth_refused = false; - - /* - * Load the public half of any configured keyfile for later use. - */ - s->keyfile = conf_get_filename(s->conf, CONF_keyfile); - if (!filename_is_null(s->keyfile)) { - int keytype; - ppl_logevent("Reading key file \"%s\"", filename_to_str(s->keyfile)); - keytype = key_type(s->keyfile); - if (keytype == SSH_KEYTYPE_SSH1 || - keytype == SSH_KEYTYPE_SSH1_PUBLIC) { - const char *error; - s->publickey_blob = strbuf_new(); - if (rsa_ssh1_loadpub(s->keyfile, - BinarySink_UPCAST(s->publickey_blob), - &s->publickey_comment, &error)) { - s->privatekey_available = (keytype == SSH_KEYTYPE_SSH1); - if (!s->privatekey_available) - ppl_logevent("Key file contains public key only"); - s->privatekey_encrypted = rsa_ssh1_encrypted(s->keyfile, NULL); - } else { - ppl_logevent("Unable to load key (%s)", error); - ppl_printf("Unable to load key file \"%s\" (%s)\r\n", - filename_to_str(s->keyfile), error); - - strbuf_free(s->publickey_blob); - s->publickey_blob = NULL; - } - } else { - ppl_logevent("Unable to use this key file (%s)", - key_type_to_str(keytype)); - ppl_printf("Unable to use key file \"%s\" (%s)\r\n", - filename_to_str(s->keyfile), - key_type_to_str(keytype)); - } - } - - /* Check whether we're configured to try Pageant, and also whether - * it's available. */ - s->try_agent_auth = (conf_get_bool(s->conf, CONF_tryagent) && - agent_exists()); - - while (pktin->type == SSH1_SMSG_FAILURE) { - s->pwpkt_type = SSH1_CMSG_AUTH_PASSWORD; - - if (s->try_agent_auth && !s->tried_agent) { - /* - * Attempt RSA authentication using Pageant. - */ - s->authed = false; - s->tried_agent = true; - ppl_logevent("Pageant is running. Requesting keys."); - - /* Request the keys held by the agent. */ - { - strbuf *request = strbuf_new_for_agent_query(); - put_byte(request, SSH1_AGENTC_REQUEST_RSA_IDENTITIES); - ssh1_login_agent_query(s, request); - strbuf_free(request); - crMaybeWaitUntilV(!s->auth_agent_query); - } - BinarySource_BARE_INIT_PL(s->asrc, s->agent_response); - - get_uint32(s->asrc); /* skip length field */ - if (get_byte(s->asrc) == SSH1_AGENT_RSA_IDENTITIES_ANSWER) { - s->nkeys = toint(get_uint32(s->asrc)); - if (s->nkeys < 0) { - ppl_logevent("Pageant reported negative key count %d", - s->nkeys); - s->nkeys = 0; - } - ppl_logevent("Pageant has %d SSH-1 keys", s->nkeys); - for (s->keyi = 0; s->keyi < s->nkeys; s->keyi++) { - size_t start, end; - start = s->asrc->pos; - get_rsa_ssh1_pub(s->asrc, &s->key, - RSA_SSH1_EXPONENT_FIRST); - end = s->asrc->pos; - s->agent_comment->len = 0; - put_datapl(s->agent_comment, get_string(s->asrc)); - if (get_err(s->asrc)) { - ppl_logevent("Pageant key list packet was truncated"); - break; - } - if (s->publickey_blob) { - ptrlen keystr = make_ptrlen( - (const char *)s->asrc->data + start, end - start); - - if (keystr.len == s->publickey_blob->len && - !memcmp(keystr.ptr, s->publickey_blob->s, - s->publickey_blob->len)) { - ppl_logevent("Pageant key #%d matches " - "configured key file", s->keyi); - s->tried_publickey = true; - } else - /* Skip non-configured key */ - continue; - } - ppl_logevent("Trying Pageant key #%d", s->keyi); - pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_RSA); - put_mp_ssh1(pkt, s->key.modulus); - pq_push(s->ppl.out_pq, pkt); - crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) - != NULL); - if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) { - ppl_logevent("Key refused"); - continue; - } - ppl_logevent("Received RSA challenge"); - s->challenge = get_mp_ssh1(pktin); - if (get_err(pktin)) { - mp_free(s->challenge); - ssh_proto_error(s->ppl.ssh, "Server's RSA challenge " - "was badly formatted"); - return; - } - - { - strbuf *agentreq; - const char *ret; - - agentreq = strbuf_new_for_agent_query(); - put_byte(agentreq, SSH1_AGENTC_RSA_CHALLENGE); - put_uint32(agentreq, mp_get_nbits(s->key.modulus)); - put_mp_ssh1(agentreq, s->key.exponent); - put_mp_ssh1(agentreq, s->key.modulus); - put_mp_ssh1(agentreq, s->challenge); - put_data(agentreq, s->session_id, 16); - put_uint32(agentreq, 1); /* response format */ - ssh1_login_agent_query(s, agentreq); - strbuf_free(agentreq); - crMaybeWaitUntilV(!s->auth_agent_query); - - ret = s->agent_response.ptr; - if (ret) { - if (s->agent_response.len >= 5+16 && - ret[4] == SSH1_AGENT_RSA_RESPONSE) { - ppl_logevent("Sending Pageant's response"); - pkt = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_CMSG_AUTH_RSA_RESPONSE); - put_data(pkt, ret + 5, 16); - pq_push(s->ppl.out_pq, pkt); - crMaybeWaitUntilV( - (pktin = ssh1_login_pop(s)) - != NULL); - if (pktin->type == SSH1_SMSG_SUCCESS) { - ppl_logevent("Pageant's response " - "accepted"); - if (flags & FLAG_VERBOSE) { - ptrlen comment = ptrlen_from_strbuf( - s->agent_comment); - ppl_printf("Authenticated using RSA " - "key \"%.*s\" from " - "agent\r\n", - PTRLEN_PRINTF(comment)); - } - s->authed = true; - } else - ppl_logevent("Pageant's response not " - "accepted"); - } else { - ppl_logevent("Pageant failed to answer " - "challenge"); - sfree((char *)ret); - } - } else { - ppl_logevent("No reply received from Pageant"); - } - } - mp_free(s->key.exponent); - mp_free(s->key.modulus); - mp_free(s->challenge); - if (s->authed) - break; - } - sfree(s->agent_response_to_free); - s->agent_response_to_free = NULL; - if (s->publickey_blob && !s->tried_publickey) - ppl_logevent("Configured key file not in Pageant"); - } else { - ppl_logevent("Failed to get reply from Pageant"); - } - if (s->authed) - break; - } - if (s->publickey_blob && s->privatekey_available && - !s->tried_publickey) { - /* - * Try public key authentication with the specified - * key file. - */ - bool got_passphrase; /* need not be kept over crReturn */ - if (flags & FLAG_VERBOSE) - ppl_printf("Trying public key authentication.\r\n"); - ppl_logevent("Trying public key \"%s\"", - filename_to_str(s->keyfile)); - s->tried_publickey = true; - got_passphrase = false; - while (!got_passphrase) { - /* - * Get a passphrase, if necessary. - */ - int retd; - char *passphrase = NULL; /* only written after crReturn */ - const char *error; - if (!s->privatekey_encrypted) { - if (flags & FLAG_VERBOSE) - ppl_printf("No passphrase required.\r\n"); - passphrase = NULL; - } else { - s->cur_prompt = new_prompts(s->ppl.seat); - s->cur_prompt->to_server = false; - s->cur_prompt->from_server = false; - s->cur_prompt->name = dupstr("SSH key passphrase"); - add_prompt(s->cur_prompt, - dupprintf("Passphrase for key \"%s\": ", - s->publickey_comment), false); - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, s->ppl.user_input); - - if (s->userpass_ret >= 0) - break; - - s->want_user_input = true; - crReturnV; - s->want_user_input = false; - } - if (!s->userpass_ret) { - /* Failed to get a passphrase. Terminate. */ - ssh_user_close(s->ppl.ssh, - "User aborted at passphrase prompt"); - return; - } - passphrase = dupstr(s->cur_prompt->prompts[0]->result); - free_prompts(s->cur_prompt); - s->cur_prompt = NULL; - } - /* - * Try decrypting key with passphrase. - */ - retd = rsa_ssh1_loadkey( - s->keyfile, &s->key, passphrase, &error); - if (passphrase) { - smemclr(passphrase, strlen(passphrase)); - sfree(passphrase); - } - if (retd == 1) { - /* Correct passphrase. */ - got_passphrase = true; - } else if (retd == 0) { - ppl_printf("Couldn't load private key from %s (%s).\r\n", - filename_to_str(s->keyfile), error); - got_passphrase = false; - break; /* go and try something else */ - } else if (retd == -1) { - ppl_printf("Wrong passphrase.\r\n"); - got_passphrase = false; - /* and try again */ - } else { - unreachable("unexpected return from rsa_ssh1_loadkey()"); - } - } - - if (got_passphrase) { - - /* - * Send a public key attempt. - */ - pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_RSA); - put_mp_ssh1(pkt, s->key.modulus); - pq_push(s->ppl.out_pq, pkt); - - crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) - != NULL); - if (pktin->type == SSH1_SMSG_FAILURE) { - ppl_printf("Server refused our public key.\r\n"); - continue; /* go and try something else */ - } - if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet" - " in response to offer of public key, " - "type %d (%s)", pktin->type, - ssh1_pkt_type(pktin->type)); - return; - } - - { - int i; - unsigned char buffer[32]; - mp_int *challenge, *response; - - challenge = get_mp_ssh1(pktin); - if (get_err(pktin)) { - mp_free(challenge); - ssh_proto_error(s->ppl.ssh, "Server's RSA challenge " - "was badly formatted"); - return; - } - response = rsa_ssh1_decrypt(challenge, &s->key); - freersapriv(&s->key); /* burn the evidence */ - - for (i = 0; i < 32; i++) { - buffer[i] = mp_get_byte(response, 31 - i); - } - - { - ssh_hash *h = ssh_hash_new(&ssh_md5); - put_data(h, buffer, 32); - put_data(h, s->session_id, 16); - ssh_hash_final(h, buffer); - } - - pkt = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_CMSG_AUTH_RSA_RESPONSE); - put_data(pkt, buffer, 16); - pq_push(s->ppl.out_pq, pkt); - - mp_free(challenge); - mp_free(response); - } - - crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) - != NULL); - if (pktin->type == SSH1_SMSG_FAILURE) { - if (flags & FLAG_VERBOSE) - ppl_printf("Failed to authenticate with" - " our public key.\r\n"); - continue; /* go and try something else */ - } else if (pktin->type != SSH1_SMSG_SUCCESS) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet" - " in response to RSA authentication, " - "type %d (%s)", pktin->type, - ssh1_pkt_type(pktin->type)); - return; - } - - break; /* we're through! */ - } - - } - - /* - * Otherwise, try various forms of password-like authentication. - */ - s->cur_prompt = new_prompts(s->ppl.seat); - - if (conf_get_bool(s->conf, CONF_try_tis_auth) && - (s->supported_auths_mask & (1 << SSH1_AUTH_TIS)) && - !s->tis_auth_refused) { - ssh1_login_setup_tis_scc(s); - s->pwpkt_type = SSH1_CMSG_AUTH_TIS_RESPONSE; - ppl_logevent("Requested TIS authentication"); - pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_TIS); - pq_push(s->ppl.out_pq, pkt); - crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); - if (pktin->type == SSH1_SMSG_FAILURE) { - ppl_logevent("TIS authentication declined"); - if (flags & FLAG_INTERACTIVE) - ppl_printf("TIS authentication refused.\r\n"); - s->tis_auth_refused = true; - continue; - } else if (pktin->type == SSH1_SMSG_AUTH_TIS_CHALLENGE) { - ptrlen challenge = get_string(pktin); - if (get_err(pktin)) { - ssh_proto_error(s->ppl.ssh, "TIS challenge packet was " - "badly formed"); - return; - } - ppl_logevent("Received TIS challenge"); - s->cur_prompt->to_server = true; - s->cur_prompt->from_server = true; - s->cur_prompt->name = dupstr("SSH TIS authentication"); - - strbuf *sb = strbuf_new(); - put_datapl(sb, PTRLEN_LITERAL("\ --- TIS authentication challenge from server: ---------------------------------\ -\r\n")); - if (s->tis_scc) { - stripctrl_retarget(s->tis_scc, BinarySink_UPCAST(sb)); - put_datapl(s->tis_scc, challenge); - stripctrl_retarget(s->tis_scc, NULL); - } else { - put_datapl(sb, challenge); - } - if (!ptrlen_endswith(challenge, PTRLEN_LITERAL("\n"), NULL)) - put_datapl(sb, PTRLEN_LITERAL("\r\n")); - put_datapl(sb, PTRLEN_LITERAL("\ --- End of TIS authentication challenge from server: --------------------------\ -\r\n")); - - s->cur_prompt->instruction = strbuf_to_str(sb); - s->cur_prompt->instr_reqd = true; - add_prompt(s->cur_prompt, dupstr( - "TIS authentication response: "), false); - } else { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet" - " in response to TIS authentication, " - "type %d (%s)", pktin->type, - ssh1_pkt_type(pktin->type)); - return; - } - } else if (conf_get_bool(s->conf, CONF_try_tis_auth) && - (s->supported_auths_mask & (1 << SSH1_AUTH_CCARD)) && - !s->ccard_auth_refused) { - ssh1_login_setup_tis_scc(s); - s->pwpkt_type = SSH1_CMSG_AUTH_CCARD_RESPONSE; - ppl_logevent("Requested CryptoCard authentication"); - pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_CCARD); - pq_push(s->ppl.out_pq, pkt); - crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); - if (pktin->type == SSH1_SMSG_FAILURE) { - ppl_logevent("CryptoCard authentication declined"); - ppl_printf("CryptoCard authentication refused.\r\n"); - s->ccard_auth_refused = true; - continue; - } else if (pktin->type == SSH1_SMSG_AUTH_CCARD_CHALLENGE) { - ptrlen challenge = get_string(pktin); - if (get_err(pktin)) { - ssh_proto_error(s->ppl.ssh, "CryptoCard challenge packet " - "was badly formed"); - return; - } - ppl_logevent("Received CryptoCard challenge"); - s->cur_prompt->to_server = true; - s->cur_prompt->from_server = true; - s->cur_prompt->name = dupstr("SSH CryptoCard authentication"); - - strbuf *sb = strbuf_new(); - put_datapl(sb, PTRLEN_LITERAL("\ --- CryptoCard authentication challenge from server: --------------------------\ -\r\n")); - if (s->tis_scc) { - stripctrl_retarget(s->tis_scc, BinarySink_UPCAST(sb)); - put_datapl(s->tis_scc, challenge); - stripctrl_retarget(s->tis_scc, NULL); - } else { - put_datapl(sb, challenge); - } - if (!ptrlen_endswith(challenge, PTRLEN_LITERAL("\n"), NULL)) - put_datapl(sb, PTRLEN_LITERAL("\r\n")); - put_datapl(sb, PTRLEN_LITERAL("\ --- End of CryptoCard authentication challenge from server: -------------------\ -\r\n")); - - s->cur_prompt->instruction = strbuf_to_str(sb); - s->cur_prompt->instr_reqd = true; - add_prompt(s->cur_prompt, dupstr( - "CryptoCard authentication response: "), false); - } else { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet" - " in response to TIS authentication, " - "type %d (%s)", pktin->type, - ssh1_pkt_type(pktin->type)); - return; - } - } - if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) { - if ((s->supported_auths_mask & (1 << SSH1_AUTH_PASSWORD)) == 0) { - ssh_sw_abort(s->ppl.ssh, "No supported authentication methods " - "available"); - return; - } - s->cur_prompt->to_server = true; - s->cur_prompt->from_server = false; - s->cur_prompt->name = dupstr("SSH password"); - add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ", - s->username, s->savedhost), - false); - } - - /* - * Show password prompt, having first obtained it via a TIS - * or CryptoCard exchange if we're doing TIS or CryptoCard - * authentication. - */ - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, NULL); - while (1) { - while (s->userpass_ret < 0 && - bufchain_size(s->ppl.user_input) > 0) - s->userpass_ret = seat_get_userpass_input( - s->ppl.seat, s->cur_prompt, s->ppl.user_input); - - if (s->userpass_ret >= 0) - break; - - s->want_user_input = true; - crReturnV; - s->want_user_input = false; - } - if (!s->userpass_ret) { - /* - * Failed to get a password (for example - * because one was supplied on the command line - * which has already failed to work). Terminate. - */ - ssh_user_close(s->ppl.ssh, "User aborted at password prompt"); - return; - } - - if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) { - /* - * Defence against traffic analysis: we send a - * whole bunch of packets containing strings of - * different lengths. One of these strings is the - * password, in a SSH1_CMSG_AUTH_PASSWORD packet. - * The others are all random data in - * SSH1_MSG_IGNORE packets. This way a passive - * listener can't tell which is the password, and - * hence can't deduce the password length. - * - * Anybody with a password length greater than 16 - * bytes is going to have enough entropy in their - * password that a listener won't find it _that_ - * much help to know how long it is. So what we'll - * do is: - * - * - if password length < 16, we send 15 packets - * containing string lengths 1 through 15 - * - * - otherwise, we let N be the nearest multiple - * of 8 below the password length, and send 8 - * packets containing string lengths N through - * N+7. This won't obscure the order of - * magnitude of the password length, but it will - * introduce a bit of extra uncertainty. - * - * A few servers can't deal with SSH1_MSG_IGNORE, at - * least in this context. For these servers, we need - * an alternative defence. We make use of the fact - * that the password is interpreted as a C string: - * so we can append a NUL, then some random data. - * - * A few servers can deal with neither SSH1_MSG_IGNORE - * here _nor_ a padded password string. - * For these servers we are left with no defences - * against password length sniffing. - */ - if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE) && - !(s->ppl.remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) { - /* - * The server can deal with SSH1_MSG_IGNORE, so - * we can use the primary defence. - */ - int bottom, top, pwlen, i; - - pwlen = strlen(s->cur_prompt->prompts[0]->result); - if (pwlen < 16) { - bottom = 0; /* zero length passwords are OK! :-) */ - top = 15; - } else { - bottom = pwlen & ~7; - top = bottom + 7; - } - - assert(pwlen >= bottom && pwlen <= top); - - for (i = bottom; i <= top; i++) { - if (i == pwlen) { - pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type); - put_stringz(pkt, s->cur_prompt->prompts[0]->result); - pq_push(s->ppl.out_pq, pkt); - } else { - strbuf *random_data = strbuf_new_nm(); - random_read(strbuf_append(random_data, i), i); - - pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_IGNORE); - put_stringsb(pkt, random_data); - pq_push(s->ppl.out_pq, pkt); - } - } - ppl_logevent("Sending password with camouflage packets"); - } - else if (!(s->ppl.remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) { - /* - * The server can't deal with SSH1_MSG_IGNORE - * but can deal with padded passwords, so we - * can use the secondary defence. - */ - strbuf *padded_pw = strbuf_new_nm(); - - ppl_logevent("Sending length-padded password"); - pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type); - put_asciz(padded_pw, s->cur_prompt->prompts[0]->result); - size_t pad = 63 & -padded_pw->len; - random_read(strbuf_append(padded_pw, pad), pad); - put_stringsb(pkt, padded_pw); - pq_push(s->ppl.out_pq, pkt); - } else { - /* - * The server is believed unable to cope with - * any of our password camouflage methods. - */ - ppl_logevent("Sending unpadded password"); - pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type); - put_stringz(pkt, s->cur_prompt->prompts[0]->result); - pq_push(s->ppl.out_pq, pkt); - } - } else { - pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type); - put_stringz(pkt, s->cur_prompt->prompts[0]->result); - pq_push(s->ppl.out_pq, pkt); - } - ppl_logevent("Sent password"); - free_prompts(s->cur_prompt); - s->cur_prompt = NULL; - crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); - if (pktin->type == SSH1_SMSG_FAILURE) { - if (flags & FLAG_VERBOSE) - ppl_printf("Access denied\r\n"); - ppl_logevent("Authentication refused"); - } else if (pktin->type != SSH1_SMSG_SUCCESS) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet" - " in response to password authentication, type %d " - "(%s)", pktin->type, ssh1_pkt_type(pktin->type)); - return; - } - } - - ppl_logevent("Authentication successful"); - - if (conf_get_bool(s->conf, CONF_compression)) { - ppl_logevent("Requesting compression"); - pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_REQUEST_COMPRESSION); - put_uint32(pkt, 6); /* gzip compression level */ - pq_push(s->ppl.out_pq, pkt); - crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); - if (pktin->type == SSH1_SMSG_SUCCESS) { - /* - * We don't have to actually do anything here: the SSH-1 - * BPP will take care of automatically starting the - * compression, by recognising our outgoing request packet - * and the success response. (Horrible, but it's the - * easiest way to avoid race conditions if other packets - * cross in transit.) - */ - } else if (pktin->type == SSH1_SMSG_FAILURE) { - ppl_logevent("Server refused to enable compression"); - ppl_printf("Server refused to compress\r\n"); - } else { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet" - " in response to compression request, type %d " - "(%s)", pktin->type, ssh1_pkt_type(pktin->type)); - return; - } - } - - ssh1_connection_set_protoflags( - s->successor_layer, s->local_protoflags, s->remote_protoflags); - { - PacketProtocolLayer *successor = s->successor_layer; - s->successor_layer = NULL; /* avoid freeing it ourself */ - ssh_ppl_replace(&s->ppl, successor); - return; /* we've just freed s, so avoid even touching s->crState */ - } - - crFinishV; -} - -static void ssh1_login_setup_tis_scc(struct ssh1_login_state *s) -{ - if (s->tis_scc_initialised) - return; - s->tis_scc = seat_stripctrl_new(s->ppl.seat, NULL, SIC_KI_PROMPTS); - if (s->tis_scc) - stripctrl_enable_line_limiting(s->tis_scc); - s->tis_scc_initialised = true; -} - -static void ssh1_login_dialog_callback(void *loginv, int ret) -{ - struct ssh1_login_state *s = (struct ssh1_login_state *)loginv; - s->dlgret = ret; - ssh_ppl_process_queue(&s->ppl); -} - -static void ssh1_login_agent_query(struct ssh1_login_state *s, strbuf *req) -{ - void *response; - int response_len; - - sfree(s->agent_response_to_free); - s->agent_response_to_free = NULL; - - s->auth_agent_query = agent_query(req, &response, &response_len, - ssh1_login_agent_callback, s); - if (!s->auth_agent_query) - ssh1_login_agent_callback(s, response, response_len); -} - -static void ssh1_login_agent_callback(void *loginv, void *reply, int replylen) -{ - struct ssh1_login_state *s = (struct ssh1_login_state *)loginv; - - s->auth_agent_query = NULL; - s->agent_response_to_free = reply; - s->agent_response = make_ptrlen(reply, replylen); - - queue_idempotent_callback(&s->ppl.ic_process_queue); -} - -static void ssh1_login_special_cmd(PacketProtocolLayer *ppl, - SessionSpecialCode code, int arg) -{ - struct ssh1_login_state *s = - container_of(ppl, struct ssh1_login_state, ppl); - PktOut *pktout; - - if (code == SS_PING || code == SS_NOP) { - if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) { - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_IGNORE); - put_stringz(pktout, ""); - pq_push(s->ppl.out_pq, pktout); - } - } -} - -static bool ssh1_login_want_user_input(PacketProtocolLayer *ppl) -{ - struct ssh1_login_state *s = - container_of(ppl, struct ssh1_login_state, ppl); - return s->want_user_input; -} - -static void ssh1_login_got_user_input(PacketProtocolLayer *ppl) -{ - struct ssh1_login_state *s = - container_of(ppl, struct ssh1_login_state, ppl); - if (s->want_user_input) - queue_idempotent_callback(&s->ppl.ic_process_queue); -} - -static void ssh1_login_reconfigure(PacketProtocolLayer *ppl, Conf *conf) -{ - struct ssh1_login_state *s = - container_of(ppl, struct ssh1_login_state, ppl); - ssh_ppl_reconfigure(s->successor_layer, conf); -} +/* + * Packet protocol layer for the SSH-1 login phase (combining what + * SSH-2 would think of as key exchange and user authentication). + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "mpint.h" +#include "sshbpp.h" +#include "sshppl.h" +#include "sshcr.h" + +typedef struct agent_key { + RSAKey key; + strbuf *comment; + ptrlen blob; /* only used during initial parsing of agent response */ +} agent_key; + +struct ssh1_login_state { + int crState; + + PacketProtocolLayer *successor_layer; + + Conf *conf; + + char *savedhost; + int savedport; + bool try_agent_auth; + + int remote_protoflags; + int local_protoflags; + unsigned char session_key[32]; + char *username; + agent_pending_query *auth_agent_query; + + int len; + unsigned char *rsabuf; + unsigned long supported_ciphers_mask, supported_auths_mask; + bool tried_publickey, tried_agent; + bool tis_auth_refused, ccard_auth_refused; + unsigned char cookie[8]; + unsigned char session_id[16]; + int cipher_type; + strbuf *publickey_blob; + char *publickey_comment; + bool privatekey_available, privatekey_encrypted; + prompts_t *cur_prompt; + int userpass_ret; + char c; + int pwpkt_type; + void *agent_response_to_free; + ptrlen agent_response; + BinarySource asrc[1]; /* response from SSH agent */ + size_t agent_keys_len; + agent_key *agent_keys; + size_t agent_key_index, agent_key_limit; + bool authed; + RSAKey key; + int dlgret; + Filename *keyfile; + RSAKey servkey, hostkey; + bool want_user_input; + + StripCtrlChars *tis_scc; + bool tis_scc_initialised; + + PacketProtocolLayer ppl; +}; + +static void ssh1_login_free(PacketProtocolLayer *); +static void ssh1_login_process_queue(PacketProtocolLayer *); +static void ssh1_login_dialog_callback(void *, int); +static void ssh1_login_special_cmd(PacketProtocolLayer *ppl, + SessionSpecialCode code, int arg); +static bool ssh1_login_want_user_input(PacketProtocolLayer *ppl); +static void ssh1_login_got_user_input(PacketProtocolLayer *ppl); +static void ssh1_login_reconfigure(PacketProtocolLayer *ppl, Conf *conf); + +static const struct PacketProtocolLayerVtable ssh1_login_vtable = { + ssh1_login_free, + ssh1_login_process_queue, + ssh1_common_get_specials, + ssh1_login_special_cmd, + ssh1_login_want_user_input, + ssh1_login_got_user_input, + ssh1_login_reconfigure, + ssh_ppl_default_queued_data_size, + NULL /* no layer names in SSH-1 */, +}; + +static void ssh1_login_agent_query(struct ssh1_login_state *s, strbuf *req); +static void ssh1_login_agent_callback(void *loginv, void *reply, int replylen); + +PacketProtocolLayer *ssh1_login_new( + Conf *conf, const char *host, int port, + PacketProtocolLayer *successor_layer) +{ + struct ssh1_login_state *s = snew(struct ssh1_login_state); + memset(s, 0, sizeof(*s)); + s->ppl.vt = &ssh1_login_vtable; + + s->conf = conf_copy(conf); + s->savedhost = dupstr(host); + s->savedport = port; + s->successor_layer = successor_layer; + return &s->ppl; +} + +static void ssh1_login_free(PacketProtocolLayer *ppl) +{ + struct ssh1_login_state *s = + container_of(ppl, struct ssh1_login_state, ppl); + + if (s->successor_layer) + ssh_ppl_free(s->successor_layer); + + conf_free(s->conf); + sfree(s->savedhost); + sfree(s->rsabuf); + sfree(s->username); + if (s->publickey_blob) + strbuf_free(s->publickey_blob); + sfree(s->publickey_comment); + if (s->cur_prompt) + free_prompts(s->cur_prompt); + if (s->agent_keys) { + for (size_t i = 0; i < s->agent_keys_len; i++) { + freersakey(&s->agent_keys[i].key); + strbuf_free(s->agent_keys[i].comment); + } + sfree(s->agent_keys); + } + sfree(s->agent_response_to_free); + if (s->auth_agent_query) + agent_cancel_query(s->auth_agent_query); + sfree(s); +} + +static bool ssh1_login_filter_queue(struct ssh1_login_state *s) +{ + return ssh1_common_filter_queue(&s->ppl); +} + +static PktIn *ssh1_login_pop(struct ssh1_login_state *s) +{ + if (ssh1_login_filter_queue(s)) + return NULL; + return pq_pop(s->ppl.in_pq); +} + +static void ssh1_login_setup_tis_scc(struct ssh1_login_state *s); + +static void ssh1_login_process_queue(PacketProtocolLayer *ppl) +{ + struct ssh1_login_state *s = + container_of(ppl, struct ssh1_login_state, ppl); + PktIn *pktin; + PktOut *pkt; + int i; + + /* Filter centrally handled messages off the front of the queue on + * every entry to this coroutine, no matter where we're resuming + * from, even if we're _not_ looping on pq_pop. That way we can + * still proactively handle those messages even if we're waiting + * for a user response. */ + if (ssh1_login_filter_queue(s)) + return; + + crBegin(s->crState); + + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); + + if (pktin->type != SSH1_SMSG_PUBLIC_KEY) { + ssh_proto_error(s->ppl.ssh, "Public key packet not received"); + return; + } + + ppl_logevent("Received public keys"); + + { + ptrlen pl = get_data(pktin, 8); + memcpy(s->cookie, pl.ptr, pl.len); + } + + get_rsa_ssh1_pub(pktin, &s->servkey, RSA_SSH1_EXPONENT_FIRST); + get_rsa_ssh1_pub(pktin, &s->hostkey, RSA_SSH1_EXPONENT_FIRST); + + s->hostkey.comment = NULL; /* avoid confusing rsa_ssh1_fingerprint */ + + /* + * Log the host key fingerprint. + */ + if (!get_err(pktin)) { + char *fingerprint = rsa_ssh1_fingerprint(&s->hostkey); + ppl_logevent("Host key fingerprint is:"); + ppl_logevent(" %s", fingerprint); + sfree(fingerprint); + } + + s->remote_protoflags = get_uint32(pktin); + s->supported_ciphers_mask = get_uint32(pktin); + s->supported_auths_mask = get_uint32(pktin); + + if (get_err(pktin)) { + ssh_proto_error(s->ppl.ssh, "Bad SSH-1 public key packet"); + return; + } + + if ((s->ppl.remote_bugs & BUG_CHOKES_ON_RSA)) + s->supported_auths_mask &= ~(1 << SSH1_AUTH_RSA); + + s->local_protoflags = + s->remote_protoflags & SSH1_PROTOFLAGS_SUPPORTED; + s->local_protoflags |= SSH1_PROTOFLAG_SCREEN_NUMBER; + + ssh1_compute_session_id(s->session_id, s->cookie, + &s->hostkey, &s->servkey); + + random_read(s->session_key, 32); + + /* + * Verify that the `bits' and `bytes' parameters match. + */ + if (s->hostkey.bits > s->hostkey.bytes * 8 || + s->servkey.bits > s->servkey.bytes * 8) { + ssh_proto_error(s->ppl.ssh, "SSH-1 public keys were badly formatted"); + return; + } + + s->len = 32; + if (s->len < s->hostkey.bytes) + s->len = s->hostkey.bytes; + if (s->len < s->servkey.bytes) + s->len = s->servkey.bytes; + + s->rsabuf = snewn(s->len, unsigned char); + + /* + * Verify the host key. + */ + { + /* + * First format the key into a string. + */ + char *fingerprint; + char *keystr = rsastr_fmt(&s->hostkey); + fingerprint = rsa_ssh1_fingerprint(&s->hostkey); + + /* First check against manually configured host keys. */ + s->dlgret = verify_ssh_manual_host_key(s->conf, fingerprint, NULL); + if (s->dlgret == 0) { /* did not match */ + sfree(fingerprint); + sfree(keystr); + ssh_proto_error(s->ppl.ssh, "Host key did not appear in manually " + "configured list"); + return; + } else if (s->dlgret < 0) { /* none configured; use standard handling */ + s->dlgret = seat_verify_ssh_host_key( + s->ppl.seat, s->savedhost, s->savedport, + "rsa", keystr, fingerprint, ssh1_login_dialog_callback, s); + sfree(fingerprint); + sfree(keystr); +#ifdef FUZZING + s->dlgret = 1; +#endif + crMaybeWaitUntilV(s->dlgret >= 0); + + if (s->dlgret == 0) { + ssh_user_close(s->ppl.ssh, + "User aborted at host key verification"); + return; + } + } else { + sfree(fingerprint); + sfree(keystr); + } + } + + for (i = 0; i < 32; i++) { + s->rsabuf[i] = s->session_key[i]; + if (i < 16) + s->rsabuf[i] ^= s->session_id[i]; + } + + { + RSAKey *smaller = (s->hostkey.bytes > s->servkey.bytes ? + &s->servkey : &s->hostkey); + RSAKey *larger = (s->hostkey.bytes > s->servkey.bytes ? + &s->hostkey : &s->servkey); + + if (!rsa_ssh1_encrypt(s->rsabuf, 32, smaller) || + !rsa_ssh1_encrypt(s->rsabuf, smaller->bytes, larger)) { + ssh_proto_error(s->ppl.ssh, "SSH-1 public key encryptions failed " + "due to bad formatting"); + return; + } + } + + ppl_logevent("Encrypted session key"); + + { + bool cipher_chosen = false, warn = false; + const char *cipher_string = NULL; + int i; + for (i = 0; !cipher_chosen && i < CIPHER_MAX; i++) { + int next_cipher = conf_get_int_int( + s->conf, CONF_ssh_cipherlist, i); + if (next_cipher == CIPHER_WARN) { + /* If/when we choose a cipher, warn about it */ + warn = true; + } else if (next_cipher == CIPHER_AES) { + /* XXX Probably don't need to mention this. */ + ppl_logevent("AES not supported in SSH-1, skipping"); + } else { + switch (next_cipher) { + case CIPHER_3DES: s->cipher_type = SSH1_CIPHER_3DES; + cipher_string = "3DES"; break; + case CIPHER_BLOWFISH: s->cipher_type = SSH1_CIPHER_BLOWFISH; + cipher_string = "Blowfish"; break; + case CIPHER_DES: s->cipher_type = SSH1_CIPHER_DES; + cipher_string = "single-DES"; break; + } + if (s->supported_ciphers_mask & (1 << s->cipher_type)) + cipher_chosen = true; + } + } + if (!cipher_chosen) { + if ((s->supported_ciphers_mask & (1 << SSH1_CIPHER_3DES)) == 0) { + ssh_proto_error(s->ppl.ssh, "Server violates SSH-1 protocol " + "by not supporting 3DES encryption"); + } else { + /* shouldn't happen */ + ssh_sw_abort(s->ppl.ssh, "No supported ciphers found"); + } + return; + } + + /* Warn about chosen cipher if necessary. */ + if (warn) { + s->dlgret = seat_confirm_weak_crypto_primitive( + s->ppl.seat, "cipher", cipher_string, + ssh1_login_dialog_callback, s); + crMaybeWaitUntilV(s->dlgret >= 0); + if (s->dlgret == 0) { + ssh_user_close(s->ppl.ssh, "User aborted at cipher warning"); + return; + } + } + } + + switch (s->cipher_type) { + case SSH1_CIPHER_3DES: + ppl_logevent("Using 3DES encryption"); + break; + case SSH1_CIPHER_DES: + ppl_logevent("Using single-DES encryption"); + break; + case SSH1_CIPHER_BLOWFISH: + ppl_logevent("Using Blowfish encryption"); + break; + } + + pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_SESSION_KEY); + put_byte(pkt, s->cipher_type); + put_data(pkt, s->cookie, 8); + put_uint16(pkt, s->len * 8); + put_data(pkt, s->rsabuf, s->len); + put_uint32(pkt, s->local_protoflags); + pq_push(s->ppl.out_pq, pkt); + + ppl_logevent("Trying to enable encryption..."); + + sfree(s->rsabuf); + s->rsabuf = NULL; + + /* + * Force the BPP to synchronously marshal all packets up to and + * including the SESSION_KEY into wire format, before we turn on + * crypto. + */ + ssh_bpp_handle_output(s->ppl.bpp); + + { + const ssh_cipheralg *cipher = + (s->cipher_type == SSH1_CIPHER_BLOWFISH ? &ssh_blowfish_ssh1 : + s->cipher_type == SSH1_CIPHER_DES ? &ssh_des : &ssh_3des_ssh1); + ssh1_bpp_new_cipher(s->ppl.bpp, cipher, s->session_key); + } + + freersakey(&s->servkey); + freersakey(&s->hostkey); + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); + + if (pktin->type != SSH1_SMSG_SUCCESS) { + ssh_proto_error(s->ppl.ssh, "Encryption not successfully enabled"); + return; + } + + ppl_logevent("Successfully started encryption"); + + if ((s->username = get_remote_username(s->conf)) == NULL) { + s->cur_prompt = new_prompts(); + s->cur_prompt->to_server = true; + s->cur_prompt->from_server = false; + s->cur_prompt->name = dupstr("SSH login name"); + add_prompt(s->cur_prompt, dupstr("login as: "), true); + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, NULL); + while (1) { + while (s->userpass_ret < 0 && + bufchain_size(s->ppl.user_input) > 0) + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, s->ppl.user_input); + + if (s->userpass_ret >= 0) + break; + + s->want_user_input = true; + crReturnV; + s->want_user_input = false; + } + if (!s->userpass_ret) { + /* + * Failed to get a username. Terminate. + */ + ssh_user_close(s->ppl.ssh, "No username provided"); + return; + } + s->username = prompt_get_result(s->cur_prompt->prompts[0]); +#ifdef MOD_PERSO + SetUsernameInConfig( s->username ) ; +#endif + free_prompts(s->cur_prompt); + s->cur_prompt = NULL; + } + + pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_USER); + put_stringz(pkt, s->username); + pq_push(s->ppl.out_pq, pkt); + + ppl_logevent("Sent username \"%s\"", s->username); + if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) + ppl_printf("Sent username \"%s\"\r\n", s->username); + + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); + + if (!(s->supported_auths_mask & (1 << SSH1_AUTH_RSA))) { + /* We must not attempt PK auth. Pretend we've already tried it. */ + s->tried_publickey = s->tried_agent = true; + } else { + s->tried_publickey = s->tried_agent = false; + } + s->tis_auth_refused = s->ccard_auth_refused = false; + + /* + * Load the public half of any configured keyfile for later use. + */ + s->keyfile = conf_get_filename(s->conf, CONF_keyfile); + if (!filename_is_null(s->keyfile)) { + int keytype; + ppl_logevent("Reading key file \"%s\"", filename_to_str(s->keyfile)); + keytype = key_type(s->keyfile); + if (keytype == SSH_KEYTYPE_SSH1 || + keytype == SSH_KEYTYPE_SSH1_PUBLIC) { + const char *error; + s->publickey_blob = strbuf_new(); + if (rsa_ssh1_loadpub(s->keyfile, + BinarySink_UPCAST(s->publickey_blob), + &s->publickey_comment, &error)) { + s->privatekey_available = (keytype == SSH_KEYTYPE_SSH1); + if (!s->privatekey_available) + ppl_logevent("Key file contains public key only"); + s->privatekey_encrypted = rsa_ssh1_encrypted(s->keyfile, NULL); + } else { + ppl_logevent("Unable to load key (%s)", error); + ppl_printf("Unable to load key file \"%s\" (%s)\r\n", + filename_to_str(s->keyfile), error); + + strbuf_free(s->publickey_blob); + s->publickey_blob = NULL; + } + } else { + ppl_logevent("Unable to use this key file (%s)", + key_type_to_str(keytype)); + ppl_printf("Unable to use key file \"%s\" (%s)\r\n", + filename_to_str(s->keyfile), + key_type_to_str(keytype)); + } + } + + /* Check whether we're configured to try Pageant, and also whether + * it's available. */ + s->try_agent_auth = (conf_get_bool(s->conf, CONF_tryagent) && + agent_exists()); + + while (pktin->type == SSH1_SMSG_FAILURE) { + s->pwpkt_type = SSH1_CMSG_AUTH_PASSWORD; + + if (s->try_agent_auth && !s->tried_agent) { + /* + * Attempt RSA authentication using Pageant. + */ + s->authed = false; + s->tried_agent = true; + ppl_logevent("Pageant is running. Requesting keys."); + + /* Request the keys held by the agent. */ + { + strbuf *request = strbuf_new_for_agent_query(); + put_byte(request, SSH1_AGENTC_REQUEST_RSA_IDENTITIES); + ssh1_login_agent_query(s, request); + strbuf_free(request); + crMaybeWaitUntilV(!s->auth_agent_query); + } + BinarySource_BARE_INIT_PL(s->asrc, s->agent_response); + + get_uint32(s->asrc); /* skip length field */ + if (get_byte(s->asrc) == SSH1_AGENT_RSA_IDENTITIES_ANSWER) { + size_t nkeys = get_uint32(s->asrc); + size_t origpos = s->asrc->pos; + + /* + * Check that the agent response is well formed. + */ + for (size_t i = 0; i < nkeys; i++) { + get_rsa_ssh1_pub(s->asrc, NULL, RSA_SSH1_EXPONENT_FIRST); + get_string(s->asrc); /* comment */ + if (get_err(s->asrc)) { + ppl_logevent("Pageant's response was truncated"); + goto parsed_agent_query; + } + } + + /* + * Copy the list of public-key blobs out of the Pageant + * response. + */ + BinarySource_REWIND_TO(s->asrc, origpos); + s->agent_keys_len = nkeys; + s->agent_keys = snewn(s->agent_keys_len, agent_key); + for (size_t i = 0; i < nkeys; i++) { + memset(&s->agent_keys[i].key, 0, + sizeof(s->agent_keys[i].key)); + + const char *blobstart = get_ptr(s->asrc); + get_rsa_ssh1_pub(s->asrc, &s->agent_keys[i].key, + RSA_SSH1_EXPONENT_FIRST); + const char *blobend = get_ptr(s->asrc); + + s->agent_keys[i].comment = strbuf_new(); + put_datapl(s->agent_keys[i].comment, get_string(s->asrc)); + + s->agent_keys[i].blob = make_ptrlen( + blobstart, blobend - blobstart); + } + + ppl_logevent("Pageant has %"SIZEu" SSH-1 keys", nkeys); + + if (s->publickey_blob) { + /* + * If we've been given a specific public key blob, + * filter the list of keys to try from the agent + * down to only that one, or none if it's not + * there. + */ + ptrlen our_blob = ptrlen_from_strbuf(s->publickey_blob); + size_t i; + + for (i = 0; i < nkeys; i++) { + if (ptrlen_eq_ptrlen(our_blob, s->agent_keys[i].blob)) + break; + } + + if (i < nkeys) { + ppl_logevent("Pageant key #%"SIZEu" matches " + "configured key file", i); + s->agent_key_index = i; + s->agent_key_limit = i+1; + } else { + ppl_logevent("Configured key file not in Pageant"); + s->agent_key_index = 0; + s->agent_key_limit = 0; + } + } else { + /* + * Otherwise, try them all. + */ + s->agent_key_index = 0; + s->agent_key_limit = nkeys; + } + } else { + ppl_logevent("Failed to get reply from Pageant"); + } + parsed_agent_query:; + + for (; s->agent_key_index < s->agent_key_limit; + s->agent_key_index++) { + ppl_logevent("Trying Pageant key #%"SIZEu, s->agent_key_index); + pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_RSA); + put_mp_ssh1(pkt, + s->agent_keys[s->agent_key_index].key.modulus); + pq_push(s->ppl.out_pq, pkt); + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) + != NULL); + if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) { + ppl_logevent("Key refused"); + continue; + } + ppl_logevent("Received RSA challenge"); + + { + mp_int *challenge = get_mp_ssh1(pktin); + if (get_err(pktin)) { + mp_free(challenge); + ssh_proto_error(s->ppl.ssh, "Server's RSA challenge " + "was badly formatted"); + return; + } + + strbuf *agentreq = strbuf_new_for_agent_query(); + put_byte(agentreq, SSH1_AGENTC_RSA_CHALLENGE); + + rsa_ssh1_public_blob( + BinarySink_UPCAST(agentreq), + &s->agent_keys[s->agent_key_index].key, + RSA_SSH1_EXPONENT_FIRST); + + put_mp_ssh1(agentreq, challenge); + mp_free(challenge); + + put_data(agentreq, s->session_id, 16); + put_uint32(agentreq, 1); /* response format */ + ssh1_login_agent_query(s, agentreq); + strbuf_free(agentreq); + crMaybeWaitUntilV(!s->auth_agent_query); + } + + { + const unsigned char *ret = s->agent_response.ptr; + if (ret) { + if (s->agent_response.len >= 5+16 && + ret[4] == SSH1_AGENT_RSA_RESPONSE) { + ppl_logevent("Sending Pageant's response"); + pkt = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_CMSG_AUTH_RSA_RESPONSE); + put_data(pkt, ret + 5, 16); + pq_push(s->ppl.out_pq, pkt); + crMaybeWaitUntilV( + (pktin = ssh1_login_pop(s)) + != NULL); + if (pktin->type == SSH1_SMSG_SUCCESS) { + ppl_logevent("Pageant's response " + "accepted"); + if (flags & FLAG_VERBOSE) { + ptrlen comment = ptrlen_from_strbuf( + s->agent_keys[s->agent_key_index]. + comment); + ppl_printf("Authenticated using RSA " + "key \"%.*s\" from " + "agent\r\n", + PTRLEN_PRINTF(comment)); + } + s->authed = true; + } else + ppl_logevent("Pageant's response not " + "accepted"); + } else { + ppl_logevent("Pageant failed to answer " + "challenge"); + sfree((char *)ret); + } + } else { + ppl_logevent("No reply received from Pageant"); + } + } + if (s->authed) + break; + } + if (s->authed) + break; + } + if (s->publickey_blob && s->privatekey_available && + !s->tried_publickey) { + /* + * Try public key authentication with the specified + * key file. + */ + bool got_passphrase; /* need not be kept over crReturn */ + if (flags & FLAG_VERBOSE) + ppl_printf("Trying public key authentication.\r\n"); + ppl_logevent("Trying public key \"%s\"", + filename_to_str(s->keyfile)); + s->tried_publickey = true; + got_passphrase = false; + while (!got_passphrase) { + /* + * Get a passphrase, if necessary. + */ + int retd; + char *passphrase = NULL; /* only written after crReturn */ + const char *error; + if (!s->privatekey_encrypted) { + if (flags & FLAG_VERBOSE) + ppl_printf("No passphrase required.\r\n"); + passphrase = NULL; + } else { + s->cur_prompt = new_prompts(); + s->cur_prompt->to_server = false; + s->cur_prompt->from_server = false; + s->cur_prompt->name = dupstr("SSH key passphrase"); + add_prompt(s->cur_prompt, + dupprintf("Passphrase for key \"%s\": ", + s->publickey_comment), false); + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, NULL); + while (1) { + while (s->userpass_ret < 0 && + bufchain_size(s->ppl.user_input) > 0) + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, s->ppl.user_input); + + if (s->userpass_ret >= 0) + break; + + s->want_user_input = true; + crReturnV; + s->want_user_input = false; + } + if (!s->userpass_ret) { + /* Failed to get a passphrase. Terminate. */ + ssh_user_close(s->ppl.ssh, + "User aborted at passphrase prompt"); + return; + } + passphrase = prompt_get_result(s->cur_prompt->prompts[0]); + free_prompts(s->cur_prompt); + s->cur_prompt = NULL; + } + /* + * Try decrypting key with passphrase. + */ + retd = rsa_ssh1_loadkey( + s->keyfile, &s->key, passphrase, &error); + if (passphrase) { + smemclr(passphrase, strlen(passphrase)); + sfree(passphrase); + } + if (retd == 1) { + /* Correct passphrase. */ + got_passphrase = true; + } else if (retd == 0) { + ppl_printf("Couldn't load private key from %s (%s).\r\n", + filename_to_str(s->keyfile), error); + got_passphrase = false; + break; /* go and try something else */ + } else if (retd == -1) { + ppl_printf("Wrong passphrase.\r\n"); + got_passphrase = false; + /* and try again */ + } else { + unreachable("unexpected return from rsa_ssh1_loadkey()"); + } + } + + if (got_passphrase) { + + /* + * Send a public key attempt. + */ + pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_RSA); + put_mp_ssh1(pkt, s->key.modulus); + pq_push(s->ppl.out_pq, pkt); + + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) + != NULL); + if (pktin->type == SSH1_SMSG_FAILURE) { + ppl_printf("Server refused our public key.\r\n"); + continue; /* go and try something else */ + } + if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet" + " in response to offer of public key, " + "type %d (%s)", pktin->type, + ssh1_pkt_type(pktin->type)); + return; + } + + { + int i; + unsigned char buffer[32]; + mp_int *challenge, *response; + + challenge = get_mp_ssh1(pktin); + if (get_err(pktin)) { + mp_free(challenge); + ssh_proto_error(s->ppl.ssh, "Server's RSA challenge " + "was badly formatted"); + return; + } + response = rsa_ssh1_decrypt(challenge, &s->key); + freersapriv(&s->key); /* burn the evidence */ + + for (i = 0; i < 32; i++) { + buffer[i] = mp_get_byte(response, 31 - i); + } + + { + ssh_hash *h = ssh_hash_new(&ssh_md5); + put_data(h, buffer, 32); + put_data(h, s->session_id, 16); + ssh_hash_final(h, buffer); + } + + pkt = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_CMSG_AUTH_RSA_RESPONSE); + put_data(pkt, buffer, 16); + pq_push(s->ppl.out_pq, pkt); + + mp_free(challenge); + mp_free(response); + } + + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) + != NULL); + if (pktin->type == SSH1_SMSG_FAILURE) { + if (flags & FLAG_VERBOSE) + ppl_printf("Failed to authenticate with" + " our public key.\r\n"); + continue; /* go and try something else */ + } else if (pktin->type != SSH1_SMSG_SUCCESS) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet" + " in response to RSA authentication, " + "type %d (%s)", pktin->type, + ssh1_pkt_type(pktin->type)); + return; + } + + break; /* we're through! */ + } + + } + + /* + * Otherwise, try various forms of password-like authentication. + */ + s->cur_prompt = new_prompts(); + + if (conf_get_bool(s->conf, CONF_try_tis_auth) && + (s->supported_auths_mask & (1 << SSH1_AUTH_TIS)) && + !s->tis_auth_refused) { + ssh1_login_setup_tis_scc(s); + s->pwpkt_type = SSH1_CMSG_AUTH_TIS_RESPONSE; + ppl_logevent("Requested TIS authentication"); + pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_TIS); + pq_push(s->ppl.out_pq, pkt); + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); + if (pktin->type == SSH1_SMSG_FAILURE) { + ppl_logevent("TIS authentication declined"); + if (flags & FLAG_INTERACTIVE) + ppl_printf("TIS authentication refused.\r\n"); + s->tis_auth_refused = true; + continue; + } else if (pktin->type == SSH1_SMSG_AUTH_TIS_CHALLENGE) { + ptrlen challenge = get_string(pktin); + if (get_err(pktin)) { + ssh_proto_error(s->ppl.ssh, "TIS challenge packet was " + "badly formed"); + return; + } + ppl_logevent("Received TIS challenge"); + s->cur_prompt->to_server = true; + s->cur_prompt->from_server = true; + s->cur_prompt->name = dupstr("SSH TIS authentication"); + + strbuf *sb = strbuf_new(); + put_datapl(sb, PTRLEN_LITERAL("\ +-- TIS authentication challenge from server: ---------------------------------\ +\r\n")); + if (s->tis_scc) { + stripctrl_retarget(s->tis_scc, BinarySink_UPCAST(sb)); + put_datapl(s->tis_scc, challenge); + stripctrl_retarget(s->tis_scc, NULL); + } else { + put_datapl(sb, challenge); + } + if (!ptrlen_endswith(challenge, PTRLEN_LITERAL("\n"), NULL)) + put_datapl(sb, PTRLEN_LITERAL("\r\n")); + put_datapl(sb, PTRLEN_LITERAL("\ +-- End of TIS authentication challenge from server: --------------------------\ +\r\n")); + + s->cur_prompt->instruction = strbuf_to_str(sb); + s->cur_prompt->instr_reqd = true; + add_prompt(s->cur_prompt, dupstr( + "TIS authentication response: "), false); + } else { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet" + " in response to TIS authentication, " + "type %d (%s)", pktin->type, + ssh1_pkt_type(pktin->type)); + return; + } + } else if (conf_get_bool(s->conf, CONF_try_tis_auth) && + (s->supported_auths_mask & (1 << SSH1_AUTH_CCARD)) && + !s->ccard_auth_refused) { + ssh1_login_setup_tis_scc(s); + s->pwpkt_type = SSH1_CMSG_AUTH_CCARD_RESPONSE; + ppl_logevent("Requested CryptoCard authentication"); + pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_CCARD); + pq_push(s->ppl.out_pq, pkt); + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); + if (pktin->type == SSH1_SMSG_FAILURE) { + ppl_logevent("CryptoCard authentication declined"); + ppl_printf("CryptoCard authentication refused.\r\n"); + s->ccard_auth_refused = true; + continue; + } else if (pktin->type == SSH1_SMSG_AUTH_CCARD_CHALLENGE) { + ptrlen challenge = get_string(pktin); + if (get_err(pktin)) { + ssh_proto_error(s->ppl.ssh, "CryptoCard challenge packet " + "was badly formed"); + return; + } + ppl_logevent("Received CryptoCard challenge"); + s->cur_prompt->to_server = true; + s->cur_prompt->from_server = true; + s->cur_prompt->name = dupstr("SSH CryptoCard authentication"); + + strbuf *sb = strbuf_new(); + put_datapl(sb, PTRLEN_LITERAL("\ +-- CryptoCard authentication challenge from server: --------------------------\ +\r\n")); + if (s->tis_scc) { + stripctrl_retarget(s->tis_scc, BinarySink_UPCAST(sb)); + put_datapl(s->tis_scc, challenge); + stripctrl_retarget(s->tis_scc, NULL); + } else { + put_datapl(sb, challenge); + } + if (!ptrlen_endswith(challenge, PTRLEN_LITERAL("\n"), NULL)) + put_datapl(sb, PTRLEN_LITERAL("\r\n")); + put_datapl(sb, PTRLEN_LITERAL("\ +-- End of CryptoCard authentication challenge from server: -------------------\ +\r\n")); + + s->cur_prompt->instruction = strbuf_to_str(sb); + s->cur_prompt->instr_reqd = true; + add_prompt(s->cur_prompt, dupstr( + "CryptoCard authentication response: "), false); + } else { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet" + " in response to TIS authentication, " + "type %d (%s)", pktin->type, + ssh1_pkt_type(pktin->type)); + return; + } + } + if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) { + if ((s->supported_auths_mask & (1 << SSH1_AUTH_PASSWORD)) == 0) { + ssh_sw_abort(s->ppl.ssh, "No supported authentication methods " + "available"); + return; + } + s->cur_prompt->to_server = true; + s->cur_prompt->from_server = false; + s->cur_prompt->name = dupstr("SSH password"); + add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ", + s->username, s->savedhost), + false); + } + + /* + * Show password prompt, having first obtained it via a TIS + * or CryptoCard exchange if we're doing TIS or CryptoCard + * authentication. + */ + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, NULL); + while (1) { + while (s->userpass_ret < 0 && + bufchain_size(s->ppl.user_input) > 0) + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, s->ppl.user_input); + + if (s->userpass_ret >= 0) + break; + + s->want_user_input = true; + crReturnV; + s->want_user_input = false; + } + if (!s->userpass_ret) { + /* + * Failed to get a password (for example + * because one was supplied on the command line + * which has already failed to work). Terminate. + */ + ssh_user_close(s->ppl.ssh, "User aborted at password prompt"); + return; + } + + if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) { + /* + * Defence against traffic analysis: we send a + * whole bunch of packets containing strings of + * different lengths. One of these strings is the + * password, in a SSH1_CMSG_AUTH_PASSWORD packet. + * The others are all random data in + * SSH1_MSG_IGNORE packets. This way a passive + * listener can't tell which is the password, and + * hence can't deduce the password length. + * + * Anybody with a password length greater than 16 + * bytes is going to have enough entropy in their + * password that a listener won't find it _that_ + * much help to know how long it is. So what we'll + * do is: + * + * - if password length < 16, we send 15 packets + * containing string lengths 1 through 15 + * + * - otherwise, we let N be the nearest multiple + * of 8 below the password length, and send 8 + * packets containing string lengths N through + * N+7. This won't obscure the order of + * magnitude of the password length, but it will + * introduce a bit of extra uncertainty. + * + * A few servers can't deal with SSH1_MSG_IGNORE, at + * least in this context. For these servers, we need + * an alternative defence. We make use of the fact + * that the password is interpreted as a C string: + * so we can append a NUL, then some random data. + * + * A few servers can deal with neither SSH1_MSG_IGNORE + * here _nor_ a padded password string. + * For these servers we are left with no defences + * against password length sniffing. + */ + if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE) && + !(s->ppl.remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) { + /* + * The server can deal with SSH1_MSG_IGNORE, so + * we can use the primary defence. + */ + int bottom, top, pwlen, i; + const char *pw = prompt_get_result_ref( + s->cur_prompt->prompts[0]); + + pwlen = strlen(pw); + if (pwlen < 16) { + bottom = 0; /* zero length passwords are OK! :-) */ + top = 15; + } else { + bottom = pwlen & ~7; + top = bottom + 7; + } + + assert(pwlen >= bottom && pwlen <= top); + + for (i = bottom; i <= top; i++) { + if (i == pwlen) { + pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type); + put_stringz(pkt, pw); + pq_push(s->ppl.out_pq, pkt); + } else { + strbuf *random_data = strbuf_new_nm(); + random_read(strbuf_append(random_data, i), i); + + pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_IGNORE); + put_stringsb(pkt, random_data); + pq_push(s->ppl.out_pq, pkt); + } + } + ppl_logevent("Sending password with camouflage packets"); + } + else if (!(s->ppl.remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) { + /* + * The server can't deal with SSH1_MSG_IGNORE + * but can deal with padded passwords, so we + * can use the secondary defence. + */ + strbuf *padded_pw = strbuf_new_nm(); + + ppl_logevent("Sending length-padded password"); + pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type); + put_asciz(padded_pw, prompt_get_result_ref( + s->cur_prompt->prompts[0])); + size_t pad = 63 & -padded_pw->len; + random_read(strbuf_append(padded_pw, pad), pad); + put_stringsb(pkt, padded_pw); + pq_push(s->ppl.out_pq, pkt); + } else { + /* + * The server is believed unable to cope with + * any of our password camouflage methods. + */ + ppl_logevent("Sending unpadded password"); + pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type); + put_stringz(pkt, prompt_get_result_ref( + s->cur_prompt->prompts[0])); + pq_push(s->ppl.out_pq, pkt); + } + } else { + pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type); + put_stringz(pkt, prompt_get_result_ref(s->cur_prompt->prompts[0])); + pq_push(s->ppl.out_pq, pkt); + } + ppl_logevent("Sent password"); + free_prompts(s->cur_prompt); + s->cur_prompt = NULL; + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); + if (pktin->type == SSH1_SMSG_FAILURE) { + if (flags & FLAG_VERBOSE) + ppl_printf("Access denied\r\n"); + ppl_logevent("Authentication refused"); + } else if (pktin->type != SSH1_SMSG_SUCCESS) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet" + " in response to password authentication, type %d " + "(%s)", pktin->type, ssh1_pkt_type(pktin->type)); + return; + } + } + + ppl_logevent("Authentication successful"); + + if (conf_get_bool(s->conf, CONF_compression)) { + ppl_logevent("Requesting compression"); + pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_REQUEST_COMPRESSION); + put_uint32(pkt, 6); /* gzip compression level */ + pq_push(s->ppl.out_pq, pkt); + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); + if (pktin->type == SSH1_SMSG_SUCCESS) { + /* + * We don't have to actually do anything here: the SSH-1 + * BPP will take care of automatically starting the + * compression, by recognising our outgoing request packet + * and the success response. (Horrible, but it's the + * easiest way to avoid race conditions if other packets + * cross in transit.) + */ + } else if (pktin->type == SSH1_SMSG_FAILURE) { + ppl_logevent("Server refused to enable compression"); + ppl_printf("Server refused to compress\r\n"); + } else { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet" + " in response to compression request, type %d " + "(%s)", pktin->type, ssh1_pkt_type(pktin->type)); + return; + } + } + + ssh1_connection_set_protoflags( + s->successor_layer, s->local_protoflags, s->remote_protoflags); + { + PacketProtocolLayer *successor = s->successor_layer; + s->successor_layer = NULL; /* avoid freeing it ourself */ + ssh_ppl_replace(&s->ppl, successor); + return; /* we've just freed s, so avoid even touching s->crState */ + } + + crFinishV; +} + +static void ssh1_login_setup_tis_scc(struct ssh1_login_state *s) +{ + if (s->tis_scc_initialised) + return; + s->tis_scc = seat_stripctrl_new(s->ppl.seat, NULL, SIC_KI_PROMPTS); + if (s->tis_scc) + stripctrl_enable_line_limiting(s->tis_scc); + s->tis_scc_initialised = true; +} + +static void ssh1_login_dialog_callback(void *loginv, int ret) +{ + struct ssh1_login_state *s = (struct ssh1_login_state *)loginv; + s->dlgret = ret; + ssh_ppl_process_queue(&s->ppl); +} + +static void ssh1_login_agent_query(struct ssh1_login_state *s, strbuf *req) +{ + void *response; + int response_len; + + sfree(s->agent_response_to_free); + s->agent_response_to_free = NULL; + + s->auth_agent_query = agent_query(req, &response, &response_len, + ssh1_login_agent_callback, s); + if (!s->auth_agent_query) + ssh1_login_agent_callback(s, response, response_len); +} + +static void ssh1_login_agent_callback(void *loginv, void *reply, int replylen) +{ + struct ssh1_login_state *s = (struct ssh1_login_state *)loginv; + + s->auth_agent_query = NULL; + s->agent_response_to_free = reply; + s->agent_response = make_ptrlen(reply, replylen); + + queue_idempotent_callback(&s->ppl.ic_process_queue); +} + +static void ssh1_login_special_cmd(PacketProtocolLayer *ppl, + SessionSpecialCode code, int arg) +{ + struct ssh1_login_state *s = + container_of(ppl, struct ssh1_login_state, ppl); + PktOut *pktout; + + if (code == SS_PING || code == SS_NOP) { + if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) { + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_IGNORE); + put_stringz(pktout, ""); + pq_push(s->ppl.out_pq, pktout); + } + } +} + +static bool ssh1_login_want_user_input(PacketProtocolLayer *ppl) +{ + struct ssh1_login_state *s = + container_of(ppl, struct ssh1_login_state, ppl); + return s->want_user_input; +} + +static void ssh1_login_got_user_input(PacketProtocolLayer *ppl) +{ + struct ssh1_login_state *s = + container_of(ppl, struct ssh1_login_state, ppl); + if (s->want_user_input) + queue_idempotent_callback(&s->ppl.ic_process_queue); +} + +static void ssh1_login_reconfigure(PacketProtocolLayer *ppl, Conf *conf) +{ + struct ssh1_login_state *s = + container_of(ppl, struct ssh1_login_state, ppl); + ssh_ppl_reconfigure(s->successor_layer, conf); +} diff --git a/0.73_My_PuTTY/ssh2bpp-bare.c b/0.74_My_PuTTY/ssh2bpp-bare.c similarity index 96% rename from 0.73_My_PuTTY/ssh2bpp-bare.c rename to 0.74_My_PuTTY/ssh2bpp-bare.c index 07aedd9..7e4171c 100644 --- a/0.73_My_PuTTY/ssh2bpp-bare.c +++ b/0.74_My_PuTTY/ssh2bpp-bare.c @@ -129,6 +129,7 @@ static void ssh2_bare_bpp_handle_input(BinaryPacketProtocol *bpp) continue; } + s->pktin->qnode.formal_size = get_avail(s->pktin); pq_push(&s->bpp.in_pq, s->pktin); s->pktin = NULL; } diff --git a/0.73_My_PuTTY/ssh2bpp.c b/0.74_My_PuTTY/ssh2bpp.c similarity index 97% rename from 0.73_My_PuTTY/ssh2bpp.c rename to 0.74_My_PuTTY/ssh2bpp.c index af98abe..c2ea87b 100644 --- a/0.73_My_PuTTY/ssh2bpp.c +++ b/0.74_My_PuTTY/ssh2bpp.c @@ -589,6 +589,7 @@ static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp) continue; } + s->pktin->qnode.formal_size = get_avail(s->pktin); pq_push(&s->bpp.in_pq, s->pktin); { diff --git a/0.73_My_PuTTY/ssh2censor.c b/0.74_My_PuTTY/ssh2censor.c similarity index 100% rename from 0.73_My_PuTTY/ssh2censor.c rename to 0.74_My_PuTTY/ssh2censor.c diff --git a/0.73_My_PuTTY/ssh2connection-client.c b/0.74_My_PuTTY/ssh2connection-client.c similarity index 91% rename from 0.73_My_PuTTY/ssh2connection-client.c rename to 0.74_My_PuTTY/ssh2connection-client.c index 47bd4eb..4ed077b 100644 --- a/0.73_My_PuTTY/ssh2connection-client.c +++ b/0.74_My_PuTTY/ssh2connection-client.c @@ -181,11 +181,11 @@ static int ssh2_rportfwd_cmp(void *av, void *bv) struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv; int i; if ( (i = strcmp(a->shost, b->shost)) != 0) - return i < 0 ? -1 : +1; + return i < 0 ? -1 : +1; if (a->sport > b->sport) - return +1; + return +1; if (a->sport < b->sport) - return -1; + return -1; return 0; } @@ -196,16 +196,16 @@ static void ssh2_rportfwd_globreq_response(struct ssh2_connection_state *s, struct ssh_rportfwd *rpf = (struct ssh_rportfwd *)ctx; if (pktin->type == SSH2_MSG_REQUEST_SUCCESS) { - ppl_logevent("Remote port forwarding from %s enabled", + ppl_logevent("Remote port forwarding from %s enabled", rpf->log_description); } else { - ppl_logevent("Remote port forwarding from %s refused", + ppl_logevent("Remote port forwarding from %s refused", rpf->log_description); - struct ssh_rportfwd *realpf = del234(s->rportfwds, rpf); - assert(realpf == rpf); + struct ssh_rportfwd *realpf = del234(s->rportfwds, rpf); + assert(realpf == rpf); portfwdmgr_close(s->portfwdmgr, rpf->pfr); - free_rportfwd(rpf); + free_rportfwd(rpf); } } @@ -315,7 +315,11 @@ SshChannel *ssh2_serverside_agent_open(ConnectionLayer *cl, Channel *chan) static void ssh2_channel_response( struct ssh2_channel *c, PktIn *pkt, void *ctx) { - chan_request_response(c->chan, pkt->type == SSH2_MSG_CHANNEL_SUCCESS); + /* If pkt==NULL (because this handler has been called in response + * to CHANNEL_CLOSE arriving while the request was still + * outstanding), we treat that the same as CHANNEL_FAILURE. */ + chan_request_response(c->chan, + pkt && pkt->type == SSH2_MSG_CHANNEL_SUCCESS); } void ssh2channel_start_shell(SshChannel *sc, bool want_reply) @@ -410,8 +414,8 @@ void ssh2channel_request_pty( put_stringz(pktout, conf_get_str(conf, CONF_termtype)); put_uint32(pktout, w); put_uint32(pktout, h); - put_uint32(pktout, 0); /* pixel width */ - put_uint32(pktout, 0); /* pixel height */ + put_uint32(pktout, 0); /* pixel width */ + put_uint32(pktout, 0); /* pixel height */ modebuf = strbuf_new(); write_ttymodes_to_packet( BinarySink_UPCAST(modebuf), 2, @@ -470,8 +474,8 @@ void ssh2channel_send_terminal_size_change(SshChannel *sc, int w, int h) PktOut *pktout = ssh2_chanreq_init(c, "window-change", NULL, NULL); put_uint32(pktout, w); put_uint32(pktout, h); - put_uint32(pktout, 0); /* pixel width */ - put_uint32(pktout, 0); /* pixel height */ + put_uint32(pktout, 0); /* pixel width */ + put_uint32(pktout, 0); /* pixel height */ pq_push(s->ppl.out_pq, pktout); } diff --git a/0.73_My_PuTTY/ssh2connection-server.c b/0.74_My_PuTTY/ssh2connection-server.c similarity index 100% rename from 0.73_My_PuTTY/ssh2connection-server.c rename to 0.74_My_PuTTY/ssh2connection-server.c diff --git a/0.73_My_PuTTY/ssh2connection.c b/0.74_My_PuTTY/ssh2connection.c similarity index 99% rename from 0.73_My_PuTTY/ssh2connection.c rename to 0.74_My_PuTTY/ssh2connection.c index 68ae43b..28c3500 100644 --- a/0.73_My_PuTTY/ssh2connection.c +++ b/0.74_My_PuTTY/ssh2connection.c @@ -34,6 +34,7 @@ static const struct PacketProtocolLayerVtable ssh2_connection_vtable = { ssh2_connection_want_user_input, ssh2_connection_got_user_input, ssh2_connection_reconfigure, + ssh_ppl_default_queued_data_size, "ssh-connection", }; @@ -754,7 +755,8 @@ static bool ssh2_connection_filter_queue(struct ssh2_connection_state *s) "Received %s for channel %d with no outstanding " "channel request", ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, pktin->type)); + s->ppl.bpp->pls->actx, pktin->type), + c->localid); return true; } ocr->handler(c, pktin, ocr->ctx); diff --git a/0.73_My_PuTTY/ssh2connection.h b/0.74_My_PuTTY/ssh2connection.h similarity index 100% rename from 0.73_My_PuTTY/ssh2connection.h rename to 0.74_My_PuTTY/ssh2connection.h diff --git a/0.73_My_PuTTY/ssh2kex-client.c b/0.74_My_PuTTY/ssh2kex-client.c similarity index 100% rename from 0.73_My_PuTTY/ssh2kex-client.c rename to 0.74_My_PuTTY/ssh2kex-client.c diff --git a/0.73_My_PuTTY/ssh2kex-server.c b/0.74_My_PuTTY/ssh2kex-server.c similarity index 95% rename from 0.73_My_PuTTY/ssh2kex-server.c rename to 0.74_My_PuTTY/ssh2kex-server.c index 328690d..ac59f42 100644 --- a/0.73_My_PuTTY/ssh2kex-server.c +++ b/0.74_My_PuTTY/ssh2kex-server.c @@ -57,7 +57,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) assert(s->hkey); } - s->hostkeyblob->len = 0; + strbuf_clear(s->hostkeyblob); ssh_key_public_blob(s->hkey, BinarySink_UPCAST(s->hostkeyblob)); s->hostkeydata = ptrlen_from_strbuf(s->hostkeyblob); @@ -261,9 +261,9 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) if (!s->rsa_kex_key) { ppl_logevent("Generating a %d-bit RSA key", extra->minklen); - s->rsa_kex_key = snew(RSAKey); - rsa_generate(s->rsa_kex_key, extra->minklen, no_progress, NULL); - s->rsa_kex_key->comment = NULL; + s->rsa_kex_key = snew(RSAKey); + rsa_generate(s->rsa_kex_key, extra->minklen, no_progress, NULL); + s->rsa_kex_key->comment = NULL; s->rsa_kex_key_needs_freeing = true; } diff --git a/0.73_My_PuTTY/ssh2transhk.c b/0.74_My_PuTTY/ssh2transhk.c similarity index 100% rename from 0.73_My_PuTTY/ssh2transhk.c rename to 0.74_My_PuTTY/ssh2transhk.c diff --git a/0.73_My_PuTTY/ssh2transport.c b/0.74_My_PuTTY/ssh2transport.c similarity index 95% rename from 0.73_My_PuTTY/ssh2transport.c rename to 0.74_My_PuTTY/ssh2transport.c index 3813af2..ad384e8 100644 --- a/0.73_My_PuTTY/ssh2transport.c +++ b/0.74_My_PuTTY/ssh2transport.c @@ -71,6 +71,7 @@ static void ssh2_transport_special_cmd(PacketProtocolLayer *ppl, static bool ssh2_transport_want_user_input(PacketProtocolLayer *ppl); static void ssh2_transport_got_user_input(PacketProtocolLayer *ppl); static void ssh2_transport_reconfigure(PacketProtocolLayer *ppl, Conf *conf); +static size_t ssh2_transport_queued_data_size(PacketProtocolLayer *ppl); static void ssh2_transport_set_max_data_size(struct ssh2_transport_state *s); static unsigned long sanitise_rekey_time(int rekey_time, unsigned long def); @@ -84,6 +85,7 @@ static const struct PacketProtocolLayerVtable ssh2_transport_vtable = { ssh2_transport_want_user_input, ssh2_transport_got_user_input, ssh2_transport_reconfigure, + ssh2_transport_queued_data_size, NULL, /* no protocol name for this layer */ }; @@ -265,7 +267,7 @@ static void ssh2_mkkey( */ keylen_padded = ((keylen + hlen - 1) / hlen) * hlen; - out->len = 0; + strbuf_clear(out); key = strbuf_append(out, keylen_padded); /* First hlen bytes. */ @@ -314,6 +316,7 @@ static struct kexinit_algorithm *ssh2_kexinit_addalg(struct kexinit_algorithm list[i].name = name; return &list[i]; } + unreachable("Should never run out of space in KEXINIT list"); } @@ -568,9 +571,10 @@ static void ssh2_write_kexinit_lists( } } else if (first_time) { /* - * In the first key exchange, we list all the algorithms - * we're prepared to cope with, but prefer those algorithms - * for which we have a host key for this host. + * In the first key exchange, we list all the algorithms we're + * prepared to cope with, but (if configured to) we prefer + * those algorithms for which we have a host key for this + * host. * * If the host key algorithm is below the warning * threshold, we warn even if we did already have a key @@ -586,7 +590,8 @@ static void ssh2_write_kexinit_lists( for (j = 0; j < lenof(ssh2_hostkey_algs); j++) { if (ssh2_hostkey_algs[j].id != preferred_hk[i]) continue; - if (have_ssh_host_key(hk_host, hk_port, + if (conf_get_bool(conf, CONF_ssh_prefer_known_hostkeys) && + have_ssh_host_key(hk_host, hk_port, ssh2_hostkey_algs[j].alg->cache_id)) { alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], ssh2_hostkey_algs[j].alg->ssh_id); @@ -1082,7 +1087,7 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) * Construct our KEXINIT packet, in a strbuf so we can refer to it * later. */ - s->client_kexinit->len = 0; + strbuf_clear(s->client_kexinit); put_byte(s->outgoing_kexinit, SSH2_MSG_KEXINIT); random_read(strbuf_append(s->outgoing_kexinit, 16), 16); ssh2_write_kexinit_lists( @@ -1120,7 +1125,7 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) s->ppl.bpp->pls->actx, pktin->type)); return; } - s->incoming_kexinit->len = 0; + strbuf_clear(s->incoming_kexinit); put_byte(s->incoming_kexinit, SSH2_MSG_KEXINIT); put_data(s->incoming_kexinit, get_ptr(pktin), get_avail(pktin)); @@ -1199,9 +1204,7 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) if (better) { if (betteralgs) { char *old_ba = betteralgs; - betteralgs = dupcat(betteralgs, ",", - hktype->alg->ssh_id, - (const char *)NULL); + betteralgs = dupcat(betteralgs, ",", hktype->alg->ssh_id); sfree(old_ba); } else { betteralgs = dupstr(hktype->alg->ssh_id); @@ -1578,7 +1581,7 @@ static void ssh2_transport_timer(void *ctx, unsigned long now) mins = sanitise_rekey_time(conf_get_int(s->conf, CONF_ssh_rekey_time), 60); if (mins == 0) - return; + return; /* Rekey if enough time has elapsed */ ticks = mins * 60 * TICKSPERSEC; @@ -1883,18 +1886,18 @@ static void ssh2_transport_special_cmd(PacketProtocolLayer *ppl, container_of(ppl, struct ssh2_transport_state, ppl); if (code == SS_REKEY) { - if (!s->kex_in_progress) { + if (!s->kex_in_progress) { s->rekey_reason = "at user request"; s->rekey_class = RK_NORMAL; queue_idempotent_callback(&s->ppl.ic_process_queue); - } + } } else if (code == SS_XCERT) { - if (!s->kex_in_progress) { + if (!s->kex_in_progress) { s->cross_certifying = s->hostkey_alg = ssh2_hostkey_algs[arg].alg; s->rekey_reason = "cross-certifying new host key"; s->rekey_class = RK_NORMAL; queue_idempotent_callback(&s->ppl.ic_process_queue); - } + } } else { /* Send everything else to the next layer up. This includes * SS_PING/SS_NOP, which we _could_ handle here - but it's @@ -1937,7 +1940,7 @@ static void ssh2_transport_reconfigure(PacketProtocolLayer *ppl, Conf *conf) old_max_data_size = s->max_data_size; ssh2_transport_set_max_data_size(s); if (old_max_data_size != s->max_data_size && - s->max_data_size != 0) { + s->max_data_size != 0) { if (s->max_data_size < old_max_data_size) { unsigned long diff = old_max_data_size - s->max_data_size; @@ -1955,19 +1958,19 @@ static void ssh2_transport_reconfigure(PacketProtocolLayer *ppl, Conf *conf) } if (conf_get_bool(s->conf, CONF_compression) != - conf_get_bool(conf, CONF_compression)) { + conf_get_bool(conf, CONF_compression)) { rekey_reason = "compression setting changed"; rekey_mandatory = true; } for (i = 0; i < CIPHER_MAX; i++) - if (conf_get_int_int(s->conf, CONF_ssh_cipherlist, i) != - conf_get_int_int(conf, CONF_ssh_cipherlist, i)) { + if (conf_get_int_int(s->conf, CONF_ssh_cipherlist, i) != + conf_get_int_int(conf, CONF_ssh_cipherlist, i)) { rekey_reason = "cipher settings changed"; rekey_mandatory = true; } if (conf_get_bool(s->conf, CONF_ssh2_des_cbc) != - conf_get_bool(conf, CONF_ssh2_des_cbc)) { + conf_get_bool(conf, CONF_ssh2_des_cbc)) { rekey_reason = "cipher settings changed"; rekey_mandatory = true; } @@ -2029,3 +2032,12 @@ static int ssh2_transport_confirm_weak_crypto_primitive( return seat_confirm_weak_crypto_primitive( s->ppl.seat, type, name, ssh2_transport_dialog_callback, s); } + +static size_t ssh2_transport_queued_data_size(PacketProtocolLayer *ppl) +{ + struct ssh2_transport_state *s = + container_of(ppl, struct ssh2_transport_state, ppl); + + return (ssh_ppl_default_queued_data_size(ppl) + + ssh_ppl_queued_data_size(s->higher_layer)); +} diff --git a/0.73_My_PuTTY/ssh2transport.h b/0.74_My_PuTTY/ssh2transport.h similarity index 100% rename from 0.73_My_PuTTY/ssh2transport.h rename to 0.74_My_PuTTY/ssh2transport.h diff --git a/0.73_My_PuTTY/ssh2userauth-server.c b/0.74_My_PuTTY/ssh2userauth-server.c similarity index 91% rename from 0.73_My_PuTTY/ssh2userauth-server.c rename to 0.74_My_PuTTY/ssh2userauth-server.c index 465a710..9feb53c 100644 --- a/0.73_My_PuTTY/ssh2userauth-server.c +++ b/0.74_My_PuTTY/ssh2userauth-server.c @@ -1,364 +1,381 @@ -/* - * Packet protocol layer for the server side of the SSH-2 userauth - * protocol (RFC 4252). - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "sshbpp.h" -#include "sshppl.h" -#include "sshcr.h" -#include "sshserver.h" - -#ifndef NO_GSSAPI -#include "sshgssc.h" -#include "sshgss.h" -#endif - -struct ssh2_userauth_server_state { - int crState; - - PacketProtocolLayer *transport_layer, *successor_layer; - ptrlen session_id; - - AuthPolicy *authpolicy; - - ptrlen username, service, method; - unsigned methods, this_method; - bool partial_success; - - AuthKbdInt *aki; - - PacketProtocolLayer ppl; -}; - -static void ssh2_userauth_server_free(PacketProtocolLayer *); -static void ssh2_userauth_server_process_queue(PacketProtocolLayer *); - -static const struct PacketProtocolLayerVtable ssh2_userauth_server_vtable = { - ssh2_userauth_server_free, - ssh2_userauth_server_process_queue, - NULL /* get_specials */, - NULL /* special_cmd */, - NULL /* want_user_input */, - NULL /* got_user_input */, - NULL /* reconfigure */, - "ssh-userauth", -}; - -static void free_auth_kbdint(AuthKbdInt *aki) -{ - int i; - - if (!aki) - return; - - sfree(aki->title); - sfree(aki->instruction); - for (i = 0; i < aki->nprompts; i++) - sfree(aki->prompts[i].prompt); - sfree(aki->prompts); - sfree(aki); -} - -PacketProtocolLayer *ssh2_userauth_server_new( - PacketProtocolLayer *successor_layer, AuthPolicy *authpolicy) -{ - struct ssh2_userauth_server_state *s = - snew(struct ssh2_userauth_server_state); - memset(s, 0, sizeof(*s)); - s->ppl.vt = &ssh2_userauth_server_vtable; - - s->successor_layer = successor_layer; - s->authpolicy = authpolicy; - - return &s->ppl; -} - -void ssh2_userauth_server_set_transport_layer(PacketProtocolLayer *userauth, - PacketProtocolLayer *transport) -{ - struct ssh2_userauth_server_state *s = - container_of(userauth, struct ssh2_userauth_server_state, ppl); - s->transport_layer = transport; -} - -static void ssh2_userauth_server_free(PacketProtocolLayer *ppl) -{ - struct ssh2_userauth_server_state *s = - container_of(ppl, struct ssh2_userauth_server_state, ppl); - - if (s->successor_layer) - ssh_ppl_free(s->successor_layer); - - free_auth_kbdint(s->aki); - - sfree(s); -} - -static PktIn *ssh2_userauth_server_pop(struct ssh2_userauth_server_state *s) -{ - return pq_pop(s->ppl.in_pq); -} - -static void ssh2_userauth_server_add_session_id( - struct ssh2_userauth_server_state *s, strbuf *sigdata) -{ - if (s->ppl.remote_bugs & BUG_SSH2_PK_SESSIONID) { - put_datapl(sigdata, s->session_id); - } else { - put_stringpl(sigdata, s->session_id); - } -} - -static void ssh2_userauth_server_process_queue(PacketProtocolLayer *ppl) -{ - struct ssh2_userauth_server_state *s = - container_of(ppl, struct ssh2_userauth_server_state, ppl); - PktIn *pktin; - PktOut *pktout; - - crBegin(s->crState); - - s->session_id = ssh2_transport_get_session_id(s->transport_layer); - - while (1) { - crMaybeWaitUntilV((pktin = ssh2_userauth_server_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_USERAUTH_REQUEST) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " - "expecting USERAUTH_REQUEST, type %d (%s)", - pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, pktin->type)); - return; - } - - s->username = get_string(pktin); - s->service = get_string(pktin); - s->method = get_string(pktin); - - if (!ptrlen_eq_string(s->service, s->successor_layer->vt->name)) { - /* - * Unconditionally reject authentication for any service - * other than the one we're going to hand over to. - */ - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_FAILURE); - put_stringz(pktout, ""); - put_bool(pktout, false); - pq_push(s->ppl.out_pq, pktout); - continue; - } - - s->methods = auth_methods(s->authpolicy); - s->partial_success = false; - - if (ptrlen_eq_string(s->method, "none")) { - s->this_method = AUTHMETHOD_NONE; - if (!(s->methods & s->this_method)) - goto failure; - - if (!auth_none(s->authpolicy, s->username)) - goto failure; - } else if (ptrlen_eq_string(s->method, "password")) { - bool changing; - ptrlen password, new_password, *new_password_ptr; - - s->this_method = AUTHMETHOD_PASSWORD; - if (!(s->methods & s->this_method)) - goto failure; - - changing = get_bool(pktin); - password = get_string(pktin); - - if (changing) { - new_password = get_string(pktin); - new_password_ptr = &new_password; - } else { - new_password_ptr = NULL; - } - - int result = auth_password(s->authpolicy, s->username, - password, new_password_ptr); - if (result == 2) { - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ); - put_stringz(pktout, "Please change your password"); - put_stringz(pktout, ""); /* language tag */ - pq_push(s->ppl.out_pq, pktout); - continue; /* skip USERAUTH_{SUCCESS,FAILURE} epilogue */ - } else if (result != 1) { - goto failure; - } - } else if (ptrlen_eq_string(s->method, "publickey")) { - bool has_signature, success; - ptrlen algorithm, blob, signature; - const ssh_keyalg *keyalg; - ssh_key *key; - strbuf *sigdata; - - s->this_method = AUTHMETHOD_PUBLICKEY; - if (!(s->methods & s->this_method)) - goto failure; - - has_signature = get_bool(pktin); - algorithm = get_string(pktin); - blob = get_string(pktin); - - if (!auth_publickey(s->authpolicy, s->username, blob)) - goto failure; - - keyalg = find_pubkey_alg_len(algorithm); - if (!keyalg) - goto failure; - key = ssh_key_new_pub(keyalg, blob); - if (!key) - goto failure; - - if (!has_signature) { - ssh_key_free(key); - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_PK_OK); - put_stringpl(pktout, algorithm); - put_stringpl(pktout, blob); - pq_push(s->ppl.out_pq, pktout); - continue; /* skip USERAUTH_{SUCCESS,FAILURE} epilogue */ - } - - sigdata = strbuf_new(); - ssh2_userauth_server_add_session_id(s, sigdata); - put_byte(sigdata, SSH2_MSG_USERAUTH_REQUEST); - put_stringpl(sigdata, s->username); - put_stringpl(sigdata, s->service); - put_stringpl(sigdata, s->method); - put_bool(sigdata, has_signature); - put_stringpl(sigdata, algorithm); - put_stringpl(sigdata, blob); - - signature = get_string(pktin); - success = ssh_key_verify(key, signature, - ptrlen_from_strbuf(sigdata)); - ssh_key_free(key); - strbuf_free(sigdata); - - if (!success) - goto failure; - } else if (ptrlen_eq_string(s->method, "keyboard-interactive")) { - int i, ok; - unsigned n; - - s->this_method = AUTHMETHOD_KBDINT; - if (!(s->methods & s->this_method)) - goto failure; - - do { - s->aki = auth_kbdint_prompts(s->authpolicy, s->username); - if (!s->aki) - goto failure; - - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_REQUEST); - put_stringz(pktout, s->aki->title); - put_stringz(pktout, s->aki->instruction); - put_stringz(pktout, ""); /* language tag */ - put_uint32(pktout, s->aki->nprompts); - for (i = 0; i < s->aki->nprompts; i++) { - put_stringz(pktout, s->aki->prompts[i].prompt); - put_bool(pktout, s->aki->prompts[i].echo); - } - pq_push(s->ppl.out_pq, pktout); - - crMaybeWaitUntilV( - (pktin = ssh2_userauth_server_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_USERAUTH_INFO_RESPONSE) { - ssh_proto_error( - s->ppl.ssh, "Received unexpected packet when " - "expecting USERAUTH_INFO_RESPONSE, type %d (%s)", - pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, pktin->type)); - return; - } - - n = get_uint32(pktin); - if (n != s->aki->nprompts) { - ssh_proto_error( - s->ppl.ssh, "Received %u keyboard-interactive " - "responses after sending %u prompts", - n, s->aki->nprompts); - return; - } - - { - ptrlen *responses = snewn(s->aki->nprompts, ptrlen); - for (i = 0; i < s->aki->nprompts; i++) - responses[i] = get_string(pktin); - ok = auth_kbdint_responses(s->authpolicy, responses); - sfree(responses); - } - - free_auth_kbdint(s->aki); - s->aki = NULL; - } while (ok == 0); - - if (ok <= 0) - goto failure; - } else { - goto failure; - } - - /* - * If we get here, we've successfully completed this - * authentication step. - */ - if (auth_successful(s->authpolicy, s->username, s->this_method)) { - /* - * ... and it was the last one, so we're completely done. - */ - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_SUCCESS); - pq_push(s->ppl.out_pq, pktout); - break; - } else { - /* - * ... but another is required, so fall through to - * generation of USERAUTH_FAILURE, having first refreshed - * the bit mask of available methods. - */ - s->methods = auth_methods(s->authpolicy); - } - s->partial_success = true; - - failure: - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_FAILURE); - { - strbuf *list = strbuf_new(); - if (s->methods & AUTHMETHOD_NONE) - add_to_commasep(list, "none"); - if (s->methods & AUTHMETHOD_PASSWORD) - add_to_commasep(list, "password"); - if (s->methods & AUTHMETHOD_PUBLICKEY) - add_to_commasep(list, "publickey"); - if (s->methods & AUTHMETHOD_KBDINT) - add_to_commasep(list, "keyboard-interactive"); - put_stringsb(pktout, list); - } - put_bool(pktout, s->partial_success); - pq_push(s->ppl.out_pq, pktout); - } - - /* - * Finally, hand over to our successor layer, and return - * immediately without reaching the crFinishV: ssh_ppl_replace - * will have freed us, so crFinishV's zeroing-out of crState would - * be a use-after-free bug. - */ - { - PacketProtocolLayer *successor = s->successor_layer; - s->successor_layer = NULL; /* avoid freeing it ourself */ - ssh_ppl_replace(&s->ppl, successor); - return; /* we've just freed s, so avoid even touching s->crState */ - } - - crFinishV; -} +/* + * Packet protocol layer for the server side of the SSH-2 userauth + * protocol (RFC 4252). + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "sshbpp.h" +#include "sshppl.h" +#include "sshcr.h" +#include "sshserver.h" + +#ifndef NO_GSSAPI +#include "sshgssc.h" +#include "sshgss.h" +#endif + +struct ssh2_userauth_server_state { + int crState; + + PacketProtocolLayer *transport_layer, *successor_layer; + ptrlen session_id; + + AuthPolicy *authpolicy; + const SshServerConfig *ssc; + + ptrlen username, service, method; + unsigned methods, this_method; + bool partial_success; + + AuthKbdInt *aki; + + PacketProtocolLayer ppl; +}; + +static void ssh2_userauth_server_free(PacketProtocolLayer *); +static void ssh2_userauth_server_process_queue(PacketProtocolLayer *); + +static const struct PacketProtocolLayerVtable ssh2_userauth_server_vtable = { + ssh2_userauth_server_free, + ssh2_userauth_server_process_queue, + NULL /* get_specials */, + NULL /* special_cmd */, + NULL /* want_user_input */, + NULL /* got_user_input */, + NULL /* reconfigure */, + ssh_ppl_default_queued_data_size, + "ssh-userauth", +}; + +static void free_auth_kbdint(AuthKbdInt *aki) +{ + int i; + + if (!aki) + return; + + sfree(aki->title); + sfree(aki->instruction); + for (i = 0; i < aki->nprompts; i++) + sfree(aki->prompts[i].prompt); + sfree(aki->prompts); + sfree(aki); +} + +PacketProtocolLayer *ssh2_userauth_server_new( + PacketProtocolLayer *successor_layer, AuthPolicy *authpolicy, + const SshServerConfig *ssc) +{ + struct ssh2_userauth_server_state *s = + snew(struct ssh2_userauth_server_state); + memset(s, 0, sizeof(*s)); + s->ppl.vt = &ssh2_userauth_server_vtable; + + s->successor_layer = successor_layer; + s->authpolicy = authpolicy; + s->ssc = ssc; + + return &s->ppl; +} + +void ssh2_userauth_server_set_transport_layer(PacketProtocolLayer *userauth, + PacketProtocolLayer *transport) +{ + struct ssh2_userauth_server_state *s = + container_of(userauth, struct ssh2_userauth_server_state, ppl); + s->transport_layer = transport; +} + +static void ssh2_userauth_server_free(PacketProtocolLayer *ppl) +{ + struct ssh2_userauth_server_state *s = + container_of(ppl, struct ssh2_userauth_server_state, ppl); + + if (s->successor_layer) + ssh_ppl_free(s->successor_layer); + + free_auth_kbdint(s->aki); + + sfree(s); +} + +static PktIn *ssh2_userauth_server_pop(struct ssh2_userauth_server_state *s) +{ + return pq_pop(s->ppl.in_pq); +} + +static void ssh2_userauth_server_add_session_id( + struct ssh2_userauth_server_state *s, strbuf *sigdata) +{ + if (s->ppl.remote_bugs & BUG_SSH2_PK_SESSIONID) { + put_datapl(sigdata, s->session_id); + } else { + put_stringpl(sigdata, s->session_id); + } +} + +static void ssh2_userauth_server_process_queue(PacketProtocolLayer *ppl) +{ + struct ssh2_userauth_server_state *s = + container_of(ppl, struct ssh2_userauth_server_state, ppl); + PktIn *pktin; + PktOut *pktout; + + crBegin(s->crState); + + s->session_id = ssh2_transport_get_session_id(s->transport_layer); + + if (s->ssc->banner.ptr) { + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_BANNER); + put_stringpl(pktout, s->ssc->banner); + put_stringz(pktout, ""); /* language tag */ + pq_push(s->ppl.out_pq, pktout); + } + + while (1) { + crMaybeWaitUntilV((pktin = ssh2_userauth_server_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_USERAUTH_REQUEST) { + ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " + "expecting USERAUTH_REQUEST, type %d (%s)", + pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, pktin->type)); + return; + } + + s->username = get_string(pktin); + s->service = get_string(pktin); + s->method = get_string(pktin); + + if (!ptrlen_eq_string(s->service, s->successor_layer->vt->name)) { + /* + * Unconditionally reject authentication for any service + * other than the one we're going to hand over to. + */ + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_FAILURE); + put_stringz(pktout, ""); + put_bool(pktout, false); + pq_push(s->ppl.out_pq, pktout); + continue; + } + + s->methods = auth_methods(s->authpolicy); + s->partial_success = false; + + if (ptrlen_eq_string(s->method, "none")) { + s->this_method = AUTHMETHOD_NONE; + if (!(s->methods & s->this_method)) + goto failure; + + if (!auth_none(s->authpolicy, s->username)) + goto failure; + } else if (ptrlen_eq_string(s->method, "password")) { + bool changing; + ptrlen password, new_password, *new_password_ptr; + + s->this_method = AUTHMETHOD_PASSWORD; + if (!(s->methods & s->this_method)) + goto failure; + + changing = get_bool(pktin); + password = get_string(pktin); + + if (changing) { + new_password = get_string(pktin); + new_password_ptr = &new_password; + } else { + new_password_ptr = NULL; + } + + int result = auth_password(s->authpolicy, s->username, + password, new_password_ptr); + if (result == 2) { + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ); + put_stringz(pktout, "Please change your password"); + put_stringz(pktout, ""); /* language tag */ + pq_push(s->ppl.out_pq, pktout); + continue; /* skip USERAUTH_{SUCCESS,FAILURE} epilogue */ + } else if (result != 1) { + goto failure; + } + } else if (ptrlen_eq_string(s->method, "publickey")) { + bool has_signature, success, send_pk_ok, key_really_ok; + ptrlen algorithm, blob, signature; + const ssh_keyalg *keyalg; + ssh_key *key; + strbuf *sigdata; + + s->this_method = AUTHMETHOD_PUBLICKEY; + if (!(s->methods & s->this_method)) + goto failure; + + has_signature = get_bool(pktin); + algorithm = get_string(pktin); + blob = get_string(pktin); + + key_really_ok = auth_publickey(s->authpolicy, s->username, blob); + send_pk_ok = key_really_ok || + s->ssc->stunt_pretend_to_accept_any_pubkey; + + if (!has_signature) { + if (!send_pk_ok) + goto failure; + + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_PK_OK); + put_stringpl(pktout, algorithm); + put_stringpl(pktout, blob); + pq_push(s->ppl.out_pq, pktout); + continue; /* skip USERAUTH_{SUCCESS,FAILURE} epilogue */ + } + + if (!key_really_ok) + goto failure; + + keyalg = find_pubkey_alg_len(algorithm); + if (!keyalg) + goto failure; + key = ssh_key_new_pub(keyalg, blob); + if (!key) + goto failure; + + sigdata = strbuf_new(); + ssh2_userauth_server_add_session_id(s, sigdata); + put_byte(sigdata, SSH2_MSG_USERAUTH_REQUEST); + put_stringpl(sigdata, s->username); + put_stringpl(sigdata, s->service); + put_stringpl(sigdata, s->method); + put_bool(sigdata, has_signature); + put_stringpl(sigdata, algorithm); + put_stringpl(sigdata, blob); + + signature = get_string(pktin); + success = ssh_key_verify(key, signature, + ptrlen_from_strbuf(sigdata)); + ssh_key_free(key); + strbuf_free(sigdata); + + if (!success) + goto failure; + } else if (ptrlen_eq_string(s->method, "keyboard-interactive")) { + int i, ok; + unsigned n; + + s->this_method = AUTHMETHOD_KBDINT; + if (!(s->methods & s->this_method)) + goto failure; + + do { + s->aki = auth_kbdint_prompts(s->authpolicy, s->username); + if (!s->aki) + goto failure; + + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_REQUEST); + put_stringz(pktout, s->aki->title); + put_stringz(pktout, s->aki->instruction); + put_stringz(pktout, ""); /* language tag */ + put_uint32(pktout, s->aki->nprompts); + for (i = 0; i < s->aki->nprompts; i++) { + put_stringz(pktout, s->aki->prompts[i].prompt); + put_bool(pktout, s->aki->prompts[i].echo); + } + pq_push(s->ppl.out_pq, pktout); + + crMaybeWaitUntilV( + (pktin = ssh2_userauth_server_pop(s)) != NULL); + if (pktin->type != SSH2_MSG_USERAUTH_INFO_RESPONSE) { + ssh_proto_error( + s->ppl.ssh, "Received unexpected packet when " + "expecting USERAUTH_INFO_RESPONSE, type %d (%s)", + pktin->type, + ssh2_pkt_type(s->ppl.bpp->pls->kctx, + s->ppl.bpp->pls->actx, pktin->type)); + return; + } + + n = get_uint32(pktin); + if (n != s->aki->nprompts) { + ssh_proto_error( + s->ppl.ssh, "Received %u keyboard-interactive " + "responses after sending %u prompts", + n, s->aki->nprompts); + return; + } + + { + ptrlen *responses = snewn(s->aki->nprompts, ptrlen); + for (i = 0; i < s->aki->nprompts; i++) + responses[i] = get_string(pktin); + ok = auth_kbdint_responses(s->authpolicy, responses); + sfree(responses); + } + + free_auth_kbdint(s->aki); + s->aki = NULL; + } while (ok == 0); + + if (ok <= 0) + goto failure; + } else { + goto failure; + } + + /* + * If we get here, we've successfully completed this + * authentication step. + */ + if (auth_successful(s->authpolicy, s->username, s->this_method)) { + /* + * ... and it was the last one, so we're completely done. + */ + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_SUCCESS); + pq_push(s->ppl.out_pq, pktout); + break; + } else { + /* + * ... but another is required, so fall through to + * generation of USERAUTH_FAILURE, having first refreshed + * the bit mask of available methods. + */ + s->methods = auth_methods(s->authpolicy); + } + s->partial_success = true; + + failure: + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_FAILURE); + { + strbuf *list = strbuf_new(); + if (s->methods & AUTHMETHOD_NONE) + add_to_commasep(list, "none"); + if (s->methods & AUTHMETHOD_PASSWORD) + add_to_commasep(list, "password"); + if (s->methods & AUTHMETHOD_PUBLICKEY) + add_to_commasep(list, "publickey"); + if (s->methods & AUTHMETHOD_KBDINT) + add_to_commasep(list, "keyboard-interactive"); + put_stringsb(pktout, list); + } + put_bool(pktout, s->partial_success); + pq_push(s->ppl.out_pq, pktout); + } + + /* + * Finally, hand over to our successor layer, and return + * immediately without reaching the crFinishV: ssh_ppl_replace + * will have freed us, so crFinishV's zeroing-out of crState would + * be a use-after-free bug. + */ + { + PacketProtocolLayer *successor = s->successor_layer; + s->successor_layer = NULL; /* avoid freeing it ourself */ + ssh_ppl_replace(&s->ppl, successor); + return; /* we've just freed s, so avoid even touching s->crState */ + } + + crFinishV; +} diff --git a/0.73_My_PuTTY/ssh2userauth.c b/0.74_My_PuTTY/ssh2userauth.c similarity index 93% rename from 0.73_My_PuTTY/ssh2userauth.c rename to 0.74_My_PuTTY/ssh2userauth.c index 3856a42..0bfa17f 100644 --- a/0.73_My_PuTTY/ssh2userauth.c +++ b/0.74_My_PuTTY/ssh2userauth.c @@ -18,6 +18,11 @@ #define BANNER_LIMIT 131072 +typedef struct agent_key { + strbuf *blob, *comment; + ptrlen algorithm; +} agent_key; + #ifdef MOD_PERSO #include "kitty.h" void SetSSHConnected( int flag ); @@ -75,9 +80,9 @@ struct ssh2_userauth_state { void *agent_response_to_free; ptrlen agent_response; BinarySource asrc[1]; /* for reading SSH agent response */ - size_t pkblob_pos_in_agent; - int keyi, nkeys; - ptrlen pk, alg, comment; + size_t agent_keys_len; + agent_key *agent_keys; + size_t agent_key_index, agent_key_limit; int len; PktOut *pktout; bool want_user_input; @@ -126,6 +131,7 @@ static const struct PacketProtocolLayerVtable ssh2_userauth_vtable = { ssh2_userauth_want_user_input, ssh2_userauth_got_user_input, ssh2_userauth_reconfigure, + ssh_ppl_default_queued_data_size, "ssh-userauth", }; @@ -178,6 +184,13 @@ static void ssh2_userauth_free(PacketProtocolLayer *ppl) if (s->successor_layer) ssh_ppl_free(s->successor_layer); + if (s->agent_keys) { + for (size_t i = 0; i < s->agent_keys_len; i++) { + strbuf_free(s->agent_keys[i].blob); + strbuf_free(s->agent_keys[i].comment); + } + sfree(s->agent_keys); + } sfree(s->agent_response_to_free); if (s->auth_agent_query) agent_cancel_query(s->auth_agent_query); @@ -279,11 +292,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) { const char *error; s->publickey_blob = strbuf_new(); -#ifdef MOD_WINCRYPT - if (ssh2_userkey_loadpub(&(s->keyfile), -#else if (ssh2_userkey_loadpub(s->keyfile, -#endif &s->publickey_algorithm, BinarySink_UPCAST(s->publickey_blob), &s->publickey_comment, &error)) { @@ -313,8 +322,6 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) * Find out about any keys Pageant has (but if there's a public * key configured, filter out all others). */ - s->nkeys = 0; - s->pkblob_pos_in_agent = 0; if (s->tryagent && agent_exists()) { ppl_logevent("Pageant is running. Requesting keys."); @@ -330,48 +337,75 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) get_uint32(s->asrc); /* skip length field */ if (get_byte(s->asrc) == SSH2_AGENT_IDENTITIES_ANSWER) { - int keyi; - - s->nkeys = toint(get_uint32(s->asrc)); + size_t nkeys = get_uint32(s->asrc); + size_t origpos = s->asrc->pos; /* - * Vet the Pageant response to ensure that the key count - * and blob lengths make sense. + * Check that the agent response is well formed. */ - if (s->nkeys < 0) { - ppl_logevent("Pageant response contained a negative" - " key count %d", s->nkeys); - s->nkeys = 0; - goto done_agent_query; - } else { - ppl_logevent("Pageant has %d SSH-2 keys", s->nkeys); - - /* See if configured key is in agent. */ - for (keyi = 0; keyi < s->nkeys; keyi++) { - size_t pos = s->asrc->pos; - ptrlen blob = get_string(s->asrc); - get_string(s->asrc); /* skip comment */ + for (size_t i = 0; i < nkeys; i++) { + get_string(s->asrc); /* blob */ + get_string(s->asrc); /* comment */ if (get_err(s->asrc)) { - ppl_logevent("Pageant response was truncated"); - s->nkeys = 0; + ppl_logevent("Pageant's response was truncated"); goto done_agent_query; } - if (s->publickey_blob && - blob.len == s->publickey_blob->len && - !memcmp(blob.ptr, s->publickey_blob->s, - s->publickey_blob->len)) { - ppl_logevent("Pageant key #%d matches " - "configured key file", keyi); - s->keyi = keyi; - s->pkblob_pos_in_agent = pos; + } + + /* + * Copy the list of public-key blobs out of the Pageant + * response. + */ + BinarySource_REWIND_TO(s->asrc, origpos); + s->agent_keys_len = nkeys; + s->agent_keys = snewn(s->agent_keys_len, agent_key); + for (size_t i = 0; i < nkeys; i++) { + s->agent_keys[i].blob = strbuf_new(); + put_datapl(s->agent_keys[i].blob, get_string(s->asrc)); + s->agent_keys[i].comment = strbuf_new(); + put_datapl(s->agent_keys[i].comment, get_string(s->asrc)); + + /* Also, extract the algorithm string from the start + * of the public-key blob. */ + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf( + s->agent_keys[i].blob)); + s->agent_keys[i].algorithm = get_string(src); + } + + ppl_logevent("Pageant has %"SIZEu" SSH-2 keys", nkeys); + + if (s->publickey_blob) { + /* + * If we've been given a specific public key blob, + * filter the list of keys to try from the agent down + * to only that one, or none if it's not there. + */ + ptrlen our_blob = ptrlen_from_strbuf(s->publickey_blob); + size_t i; + + for (i = 0; i < nkeys; i++) { + if (ptrlen_eq_ptrlen(our_blob, ptrlen_from_strbuf( + s->agent_keys[i].blob))) break; } - } - if (s->publickey_blob && !s->pkblob_pos_in_agent) { + if (i < nkeys) { + ppl_logevent("Pageant key #%"SIZEu" matches " + "configured key file", i); + s->agent_key_index = i; + s->agent_key_limit = i+1; + } else { ppl_logevent("Configured key file not in Pageant"); - s->nkeys = 0; + s->agent_key_index = 0; + s->agent_key_limit = 0; } + } else { + /* + * Otherwise, try them all. + */ + s->agent_key_index = 0; + s->agent_key_limit = nkeys; } } else { ppl_logevent("Failed to get reply from Pageant"); @@ -446,7 +480,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) } sfree(s->locally_allocated_username); /* for change_username */ s->username = s->locally_allocated_username = - dupstr(s->cur_prompt->prompts[0]->result); + prompt_get_result(s->cur_prompt->prompts[0]); #ifdef MOD_PERSO SetUsernameInConfig( (char*)s->username ) ; #endif @@ -473,17 +507,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) s->tried_pubkey_config = false; s->kbd_inter_refused = false; - - /* Reset agent request state. */ s->done_agent = false; - if (s->agent_response.ptr) { - if (s->pkblob_pos_in_agent) { - s->asrc->pos = s->pkblob_pos_in_agent; - } else { - s->asrc->pos = 9; /* skip length + type + key count */ - s->keyi = 0; - } - } while (1) { /* @@ -526,9 +550,9 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) while (bufchain_size(&s->banner) > 0) { ptrlen data = bufchain_prefix(&s->banner); seat_stderr_pl(s->ppl.seat, data); - bufchain_consume(&s->banner, data.len); mid_line = (((const char *)data.ptr)[data.len-1] != '\n'); + bufchain_consume(&s->banner, data.len); } bufchain_clear(&s->banner); @@ -643,7 +667,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) /* * Save the methods string for use in error messages. */ - s->last_methods_string->len = 0; + strbuf_clear(s->last_methods_string); put_datapl(s->last_methods_string, methods); /* @@ -710,7 +734,8 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) } else #endif /* NO_GSSAPI */ - if (s->can_pubkey && !s->done_agent && s->nkeys) { + if (s->can_pubkey && !s->done_agent && + s->agent_key_index < s->agent_key_limit) { /* * Attempt public-key authentication using a key from Pageant. @@ -718,25 +743,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) s->ppl.bpp->pls->actx = SSH2_PKTCTX_PUBLICKEY; - ppl_logevent("Trying Pageant key #%d", s->keyi); - - /* Unpack key from agent response */ - s->pk = get_string(s->asrc); - s->comment = get_string(s->asrc); - { - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, s->pk); -#ifdef MOD_WINCRYPT -#ifdef HAS_WINX509 - if ((s->comment.len > 7) - && (0 == strncmp("x509://", s->comment.ptr, 7))) { - s->alg = make_ptrlen(dupstr(ssh_x509_wincrypt.ssh_id), strlen(ssh_x509_wincrypt.ssh_id)); - } else - -#endif /* HAS_WINX509 */ -#endif - s->alg = get_string(src); - } + ppl_logevent("Trying Pageant key #%"SIZEu, s->agent_key_index); /* See if server will accept it */ s->pktout = ssh_bpp_new_pktout( @@ -746,8 +753,10 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) put_stringz(s->pktout, "publickey"); /* method */ put_bool(s->pktout, false); /* no signature included */ - put_stringpl(s->pktout, s->alg); - put_stringpl(s->pktout, s->pk); + put_stringpl(s->pktout, + s->agent_keys[s->agent_key_index].algorithm); + put_stringpl(s->pktout, ptrlen_from_strbuf( + s->agent_keys[s->agent_key_index].blob)); pq_push(s->ppl.out_pq, s->pktout); s->type = AUTH_TYPE_PUBLICKEY_OFFER_QUIET; @@ -760,11 +769,13 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) } else { strbuf *agentreq, *sigdata; + ptrlen comment = ptrlen_from_strbuf( + s->agent_keys[s->agent_key_index].comment); if (flags & FLAG_VERBOSE) ppl_printf("Authenticating with public key " "\"%.*s\" from agent\r\n", - PTRLEN_PRINTF(s->comment)); + PTRLEN_PRINTF(comment)); /* * Server is willing to accept the key. @@ -777,13 +788,16 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) put_stringz(s->pktout, "publickey"); /* method */ put_bool(s->pktout, true); /* signature included */ - put_stringpl(s->pktout, s->alg); - put_stringpl(s->pktout, s->pk); + put_stringpl(s->pktout, + s->agent_keys[s->agent_key_index].algorithm); + put_stringpl(s->pktout, ptrlen_from_strbuf( + s->agent_keys[s->agent_key_index].blob)); /* Ask agent for signature. */ agentreq = strbuf_new_for_agent_query(); put_byte(agentreq, SSH2_AGENTC_SIGN_REQUEST); - put_stringpl(agentreq, s->pk); + put_stringpl(agentreq, ptrlen_from_strbuf( + s->agent_keys[s->agent_key_index].blob)); /* Now the data to be signed... */ sigdata = strbuf_new(); ssh2_userauth_add_session_id(s, sigdata); @@ -805,8 +819,11 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) if (get_byte(src) == SSH2_AGENT_SIGN_RESPONSE && (sigblob = get_string(src), !get_err(src))) { ppl_logevent("Sending Pageant's response"); - ssh2_userauth_add_sigblob(s, s->pktout, - s->pk, sigblob); + ssh2_userauth_add_sigblob( + s, s->pktout, + ptrlen_from_strbuf( + s->agent_keys[s->agent_key_index].blob), + sigblob); pq_push(s->ppl.out_pq, s->pktout); s->type = AUTH_TYPE_PUBLICKEY; } else { @@ -814,19 +831,21 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) ppl_printf("Pageant failed to " "provide a signature\r\n"); s->suppress_wait_for_response_packet = true; + ssh_free_pktout(s->pktout); } + } else { + ppl_logevent("Pageant failed to respond to " + "signing request"); + ppl_printf("Pageant failed to " + "respond to signing request\r\n"); + s->suppress_wait_for_response_packet = true; + ssh_free_pktout(s->pktout); } } /* Do we have any keys left to try? */ - if (s->pkblob_pos_in_agent) { + if (++s->agent_key_index >= s->agent_key_limit) s->done_agent = true; - s->tried_pubkey_config = true; - } else { - s->keyi++; - if (s->keyi >= s->nkeys) - s->done_agent = true; - } } else if (s->can_pubkey && s->publickey_blob && s->privatekey_available && !s->tried_pubkey_config) { @@ -932,7 +951,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) return; } passphrase = - dupstr(s->cur_prompt->prompts[0]->result); + prompt_get_result(s->cur_prompt->prompts[0]); free_prompts(s->cur_prompt); } else { passphrase = NULL; /* no passphrase needed */ @@ -1391,6 +1410,8 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) } if (sb->len) s->cur_prompt->instruction = strbuf_to_str(sb); + else + strbuf_free(sb); /* * Our prompts_t is fully constructed now. Get the @@ -1470,8 +1491,8 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) if(s->cur_prompt->prompts[i]->result!=NULL) { SetPasswordInConfig( s->cur_prompt->prompts[i]->result ) ; } } #endif - put_stringz(s->pktout, - s->cur_prompt->prompts[i]->result); + put_stringz(s->pktout, prompt_get_result_ref( + s->cur_prompt->prompts[i])); } s->pktout->minlen = 256; pq_push(s->ppl.out_pq, s->pktout); @@ -1556,7 +1577,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) * Squirrel away the password. (We may need it later if * asked to change it.) */ - s->password = dupstr(s->cur_prompt->prompts[0]->result); + s->password = prompt_get_result(s->cur_prompt->prompts[0]); free_prompts(s->cur_prompt); /* @@ -1719,20 +1740,20 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) * (A side effect is that the user doesn't have to * re-enter it if they louse up the new password.) */ - if (s->cur_prompt->prompts[0]->result[0]) { + if (s->cur_prompt->prompts[0]->result->s[0]) { smemclr(s->password, strlen(s->password)); /* burn the evidence */ sfree(s->password); - s->password = - dupstr(s->cur_prompt->prompts[0]->result); + s->password = prompt_get_result( + s->cur_prompt->prompts[0]); } /* * Check the two new passwords match. */ - got_new = (strcmp(s->cur_prompt->prompts[1]->result, - s->cur_prompt->prompts[2]->result) - == 0); + got_new = !strcmp( + prompt_get_result_ref(s->cur_prompt->prompts[1]), + prompt_get_result_ref(s->cur_prompt->prompts[2])); if (!got_new) /* They don't. Silly user. */ ppl_printf("Passwords do not match\r\n"); @@ -1750,8 +1771,8 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) put_stringz(s->pktout, "password"); put_bool(s->pktout, true); put_stringz(s->pktout, s->password); - put_stringz(s->pktout, - s->cur_prompt->prompts[1]->result); + put_stringz(s->pktout, prompt_get_result_ref( + s->cur_prompt->prompts[1])); free_prompts(s->cur_prompt); s->pktout->minlen = 256; pq_push(s->ppl.out_pq, s->pktout); @@ -1916,7 +1937,7 @@ static void ssh2_userauth_add_sigblob( /* debug("modulus length is %d\n", len); */ /* debug("signature length is %d\n", siglen); */ - if (mod_mp.len != sig_mp.len) { + if (mod_mp.len > sig_mp.len) { strbuf *substr = strbuf_new(); put_data(substr, sigblob.ptr, sig_prefix_len); put_uint32(substr, mod_mp.len); diff --git a/0.73_My_PuTTY/sshaes.c b/0.74_My_PuTTY/sshaes.c similarity index 100% rename from 0.73_My_PuTTY/sshaes.c rename to 0.74_My_PuTTY/sshaes.c diff --git a/0.73_My_PuTTY/ssharcf.c b/0.74_My_PuTTY/ssharcf.c similarity index 100% rename from 0.73_My_PuTTY/ssharcf.c rename to 0.74_My_PuTTY/ssharcf.c diff --git a/0.73_My_PuTTY/sshauxcrypt.c b/0.74_My_PuTTY/sshauxcrypt.c similarity index 100% rename from 0.73_My_PuTTY/sshauxcrypt.c rename to 0.74_My_PuTTY/sshauxcrypt.c diff --git a/0.73_My_PuTTY/sshbcrypt.c b/0.74_My_PuTTY/sshbcrypt.c similarity index 100% rename from 0.73_My_PuTTY/sshbcrypt.c rename to 0.74_My_PuTTY/sshbcrypt.c diff --git a/0.73_My_PuTTY/sshblowf.c b/0.74_My_PuTTY/sshblowf.c similarity index 100% rename from 0.73_My_PuTTY/sshblowf.c rename to 0.74_My_PuTTY/sshblowf.c diff --git a/0.73_My_PuTTY/sshblowf.h b/0.74_My_PuTTY/sshblowf.h similarity index 100% rename from 0.73_My_PuTTY/sshblowf.h rename to 0.74_My_PuTTY/sshblowf.h diff --git a/0.73_My_PuTTY/sshbpp.h b/0.74_My_PuTTY/sshbpp.h similarity index 100% rename from 0.73_My_PuTTY/sshbpp.h rename to 0.74_My_PuTTY/sshbpp.h diff --git a/0.73_My_PuTTY/sshccp.c b/0.74_My_PuTTY/sshccp.c similarity index 100% rename from 0.73_My_PuTTY/sshccp.c rename to 0.74_My_PuTTY/sshccp.c diff --git a/0.73_My_PuTTY/sshchan.h b/0.74_My_PuTTY/sshchan.h similarity index 100% rename from 0.73_My_PuTTY/sshchan.h rename to 0.74_My_PuTTY/sshchan.h diff --git a/0.73_My_PuTTY/sshcommon.c b/0.74_My_PuTTY/sshcommon.c similarity index 93% rename from 0.73_My_PuTTY/sshcommon.c rename to 0.74_My_PuTTY/sshcommon.c index 2607384..c288c80 100644 --- a/0.73_My_PuTTY/sshcommon.c +++ b/0.74_My_PuTTY/sshcommon.c @@ -1,1031 +1,1063 @@ -/* - * Supporting routines used in common by all the various components of - * the SSH system. - */ - -#include -#include - -#include "putty.h" -#include "mpint.h" -#include "ssh.h" -#include "sshbpp.h" -#include "sshppl.h" -#include "sshchan.h" - -/* ---------------------------------------------------------------------- - * Implementation of PacketQueue. - */ - -static void pq_ensure_unlinked(PacketQueueNode *node) -{ - if (node->on_free_queue) { - node->next->prev = node->prev; - node->prev->next = node->next; - } else { - assert(!node->next); - assert(!node->prev); - } -} - -void pq_base_push(PacketQueueBase *pqb, PacketQueueNode *node) -{ - pq_ensure_unlinked(node); - node->next = &pqb->end; - node->prev = pqb->end.prev; - node->next->prev = node; - node->prev->next = node; - - if (pqb->ic) - queue_idempotent_callback(pqb->ic); -} - -void pq_base_push_front(PacketQueueBase *pqb, PacketQueueNode *node) -{ - pq_ensure_unlinked(node); - node->prev = &pqb->end; - node->next = pqb->end.next; - node->next->prev = node; - node->prev->next = node; - - if (pqb->ic) - queue_idempotent_callback(pqb->ic); -} - -static PacketQueueNode pktin_freeq_head = { - &pktin_freeq_head, &pktin_freeq_head, true -}; - -static void pktin_free_queue_callback(void *vctx) -{ - while (pktin_freeq_head.next != &pktin_freeq_head) { - PacketQueueNode *node = pktin_freeq_head.next; - PktIn *pktin = container_of(node, PktIn, qnode); - pktin_freeq_head.next = node->next; - sfree(pktin); - } - - pktin_freeq_head.prev = &pktin_freeq_head; -} - -static IdempotentCallback ic_pktin_free = { - pktin_free_queue_callback, NULL, false -}; - -static PktIn *pq_in_after(PacketQueueBase *pqb, - PacketQueueNode *prev, bool pop) -{ - PacketQueueNode *node = prev->next; - if (node == &pqb->end) - return NULL; - - if (pop) { - node->next->prev = node->prev; - node->prev->next = node->next; - - node->prev = pktin_freeq_head.prev; - node->next = &pktin_freeq_head; - node->next->prev = node; - node->prev->next = node; - node->on_free_queue = true; - queue_idempotent_callback(&ic_pktin_free); - } - - return container_of(node, PktIn, qnode); -} - -static PktOut *pq_out_after(PacketQueueBase *pqb, - PacketQueueNode *prev, bool pop) -{ - PacketQueueNode *node = prev->next; - if (node == &pqb->end) - return NULL; - - if (pop) { - node->next->prev = node->prev; - node->prev->next = node->next; - node->prev = node->next = NULL; - } - - return container_of(node, PktOut, qnode); -} - -void pq_in_init(PktInQueue *pq) -{ - pq->pqb.ic = NULL; - pq->pqb.end.next = pq->pqb.end.prev = &pq->pqb.end; - pq->after = pq_in_after; -} - -void pq_out_init(PktOutQueue *pq) -{ - pq->pqb.ic = NULL; - pq->pqb.end.next = pq->pqb.end.prev = &pq->pqb.end; - pq->after = pq_out_after; -} - -void pq_in_clear(PktInQueue *pq) -{ - PktIn *pkt; - pq->pqb.ic = NULL; - while ((pkt = pq_pop(pq)) != NULL) { - /* No need to actually free these packets: pq_pop on a - * PktInQueue will automatically move them to the free - * queue. */ - } -} - -void pq_out_clear(PktOutQueue *pq) -{ - PktOut *pkt; - pq->pqb.ic = NULL; - while ((pkt = pq_pop(pq)) != NULL) - ssh_free_pktout(pkt); -} - -/* - * Concatenate the contents of the two queues q1 and q2, and leave the - * result in qdest. qdest must be either empty, or one of the input - * queues. - */ -void pq_base_concatenate(PacketQueueBase *qdest, - PacketQueueBase *q1, PacketQueueBase *q2) -{ - struct PacketQueueNode *head1, *tail1, *head2, *tail2; - - /* - * Extract the contents from both input queues, and empty them. - */ - - head1 = (q1->end.next == &q1->end ? NULL : q1->end.next); - tail1 = (q1->end.prev == &q1->end ? NULL : q1->end.prev); - head2 = (q2->end.next == &q2->end ? NULL : q2->end.next); - tail2 = (q2->end.prev == &q2->end ? NULL : q2->end.prev); - - q1->end.next = q1->end.prev = &q1->end; - q2->end.next = q2->end.prev = &q2->end; - - /* - * Link the two lists together, handling the case where one or - * both is empty. - */ - - if (tail1) - tail1->next = head2; - else - head1 = head2; - - if (head2) - head2->prev = tail1; - else - tail2 = tail1; - - /* - * Check the destination queue is currently empty. (If it was one - * of the input queues, then it will be, because we emptied both - * of those just a moment ago.) - */ - - assert(qdest->end.next == &qdest->end); - assert(qdest->end.prev == &qdest->end); - - /* - * If our concatenated list has anything in it, then put it in - * dest. - */ - - if (!head1) { - assert(!tail2); - } else { - assert(tail2); - qdest->end.next = head1; - qdest->end.prev = tail2; - head1->prev = &qdest->end; - tail2->next = &qdest->end; - - if (qdest->ic) - queue_idempotent_callback(qdest->ic); - } -} - -/* ---------------------------------------------------------------------- - * Low-level functions for the packet structures themselves. - */ - -static void ssh_pkt_BinarySink_write(BinarySink *bs, - const void *data, size_t len); -PktOut *ssh_new_packet(void) -{ - PktOut *pkt = snew(PktOut); - - BinarySink_INIT(pkt, ssh_pkt_BinarySink_write); - pkt->data = NULL; - pkt->length = 0; - pkt->maxlen = 0; - pkt->downstream_id = 0; - pkt->additional_log_text = NULL; - pkt->qnode.next = pkt->qnode.prev = NULL; - pkt->qnode.on_free_queue = false; - - return pkt; -} - -static void ssh_pkt_adddata(PktOut *pkt, const void *data, int len) -{ - sgrowarrayn_nm(pkt->data, pkt->maxlen, pkt->length, len); - memcpy(pkt->data + pkt->length, data, len); - pkt->length += len; -} - -static void ssh_pkt_BinarySink_write(BinarySink *bs, - const void *data, size_t len) -{ - PktOut *pkt = BinarySink_DOWNCAST(bs, PktOut); - ssh_pkt_adddata(pkt, data, len); -} - -void ssh_free_pktout(PktOut *pkt) -{ - sfree(pkt->data); - sfree(pkt); -} - -/* ---------------------------------------------------------------------- - * Implement zombiechan_new() and its trivial vtable. - */ - -static void zombiechan_free(Channel *chan); -static size_t zombiechan_send( - Channel *chan, bool is_stderr, const void *, size_t); -static void zombiechan_set_input_wanted(Channel *chan, bool wanted); -static void zombiechan_do_nothing(Channel *chan); -static void zombiechan_open_failure(Channel *chan, const char *); -static bool zombiechan_want_close(Channel *chan, bool sent_eof, bool rcvd_eof); -static char *zombiechan_log_close_msg(Channel *chan) { return NULL; } - -static const struct ChannelVtable zombiechan_channelvt = { - zombiechan_free, - zombiechan_do_nothing, /* open_confirmation */ - zombiechan_open_failure, - zombiechan_send, - zombiechan_do_nothing, /* send_eof */ - zombiechan_set_input_wanted, - zombiechan_log_close_msg, - zombiechan_want_close, - chan_no_exit_status, - chan_no_exit_signal, - chan_no_exit_signal_numeric, - chan_no_run_shell, - chan_no_run_command, - chan_no_run_subsystem, - chan_no_enable_x11_forwarding, - chan_no_enable_agent_forwarding, - chan_no_allocate_pty, - chan_no_set_env, - chan_no_send_break, - chan_no_send_signal, - chan_no_change_window_size, - chan_no_request_response, -}; - -Channel *zombiechan_new(void) -{ - Channel *chan = snew(Channel); - chan->vt = &zombiechan_channelvt; - chan->initial_fixed_window_size = 0; - return chan; -} - -static void zombiechan_free(Channel *chan) -{ - assert(chan->vt == &zombiechan_channelvt); - sfree(chan); -} - -static void zombiechan_do_nothing(Channel *chan) -{ - assert(chan->vt == &zombiechan_channelvt); -} - -static void zombiechan_open_failure(Channel *chan, const char *errtext) -{ - assert(chan->vt == &zombiechan_channelvt); -} - -static size_t zombiechan_send(Channel *chan, bool is_stderr, - const void *data, size_t length) -{ - assert(chan->vt == &zombiechan_channelvt); - return 0; -} - -static void zombiechan_set_input_wanted(Channel *chan, bool enable) -{ - assert(chan->vt == &zombiechan_channelvt); -} - -static bool zombiechan_want_close(Channel *chan, bool sent_eof, bool rcvd_eof) -{ - return true; -} - -/* ---------------------------------------------------------------------- - * Centralised standard methods for other channel implementations to - * borrow. - */ - -void chan_remotely_opened_confirmation(Channel *chan) -{ - unreachable("this channel type should never receive OPEN_CONFIRMATION"); -} - -void chan_remotely_opened_failure(Channel *chan, const char *errtext) -{ - unreachable("this channel type should never receive OPEN_FAILURE"); -} - -bool chan_default_want_close( - Channel *chan, bool sent_local_eof, bool rcvd_remote_eof) -{ - /* - * Default close policy: we start initiating the CHANNEL_CLOSE - * procedure as soon as both sides of the channel have seen EOF. - */ - return sent_local_eof && rcvd_remote_eof; -} - -bool chan_no_exit_status(Channel *chan, int status) -{ - return false; -} - -bool chan_no_exit_signal( - Channel *chan, ptrlen signame, bool core_dumped, ptrlen msg) -{ - return false; -} - -bool chan_no_exit_signal_numeric( - Channel *chan, int signum, bool core_dumped, ptrlen msg) -{ - return false; -} - -bool chan_no_run_shell(Channel *chan) -{ - return false; -} - -bool chan_no_run_command(Channel *chan, ptrlen command) -{ - return false; -} - -bool chan_no_run_subsystem(Channel *chan, ptrlen subsys) -{ - return false; -} - -bool chan_no_enable_x11_forwarding( - Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata, - unsigned screen_number) -{ - return false; -} - -bool chan_no_enable_agent_forwarding(Channel *chan) -{ - return false; -} - -bool chan_no_allocate_pty( - Channel *chan, ptrlen termtype, unsigned width, unsigned height, - unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes) -{ - return false; -} - -bool chan_no_set_env(Channel *chan, ptrlen var, ptrlen value) -{ - return false; -} - -bool chan_no_send_break(Channel *chan, unsigned length) -{ - return false; -} - -bool chan_no_send_signal(Channel *chan, ptrlen signame) -{ - return false; -} - -bool chan_no_change_window_size( - Channel *chan, unsigned width, unsigned height, - unsigned pixwidth, unsigned pixheight) -{ - return false; -} - -void chan_no_request_response(Channel *chan, bool success) -{ - unreachable("this channel type should never send a want-reply request"); -} - -/* ---------------------------------------------------------------------- - * Common routines for handling SSH tty modes. - */ - -static unsigned real_ttymode_opcode(unsigned our_opcode, int ssh_version) -{ - switch (our_opcode) { - case TTYMODE_ISPEED: - return ssh_version == 1 ? TTYMODE_ISPEED_SSH1 : TTYMODE_ISPEED_SSH2; - case TTYMODE_OSPEED: - return ssh_version == 1 ? TTYMODE_OSPEED_SSH1 : TTYMODE_OSPEED_SSH2; - default: - return our_opcode; - } -} - -static unsigned our_ttymode_opcode(unsigned real_opcode, int ssh_version) -{ - if (ssh_version == 1) { - switch (real_opcode) { - case TTYMODE_ISPEED_SSH1: - return TTYMODE_ISPEED; - case TTYMODE_OSPEED_SSH1: - return TTYMODE_OSPEED; - default: - return real_opcode; - } - } else { - switch (real_opcode) { - case TTYMODE_ISPEED_SSH2: - return TTYMODE_ISPEED; - case TTYMODE_OSPEED_SSH2: - return TTYMODE_OSPEED; - default: - return real_opcode; - } - } -} - -struct ssh_ttymodes get_ttymodes_from_conf(Seat *seat, Conf *conf) -{ - struct ssh_ttymodes modes; - size_t i; - - static const struct mode_name_type { - const char *mode; - int opcode; - enum { TYPE_CHAR, TYPE_BOOL } type; - } modes_names_types[] = { - #define TTYMODE_CHAR(name, val, index) { #name, val, TYPE_CHAR }, - #define TTYMODE_FLAG(name, val, field, mask) { #name, val, TYPE_BOOL }, - #include "sshttymodes.h" - #undef TTYMODE_CHAR - #undef TTYMODE_FLAG - }; - - memset(&modes, 0, sizeof(modes)); - - for (i = 0; i < lenof(modes_names_types); i++) { - const struct mode_name_type *mode = &modes_names_types[i]; - const char *sval = conf_get_str_str(conf, CONF_ttymodes, mode->mode); - char *to_free = NULL; - - if (!sval) - sval = "N"; /* just in case */ - - /* - * sval[0] can be - * - 'V', indicating that an explicit value follows it; - * - 'A', indicating that we should pass the value through from - * the local environment via get_ttymode; or - * - 'N', indicating that we should explicitly not send this - * mode. - */ - if (sval[0] == 'A') { - sval = to_free = seat_get_ttymode(seat, mode->mode); - } else if (sval[0] == 'V') { - sval++; /* skip the 'V' */ - } else { - /* else 'N', or something from the future we don't understand */ - continue; - } - - if (sval) { - /* - * Parse the string representation of the tty mode - * into the integer value it will take on the wire. - */ - unsigned ival = 0; - - switch (mode->type) { - case TYPE_CHAR: - if (*sval) { - char *next = NULL; - /* We know ctrlparse won't write to the string, so - * casting away const is ugly but allowable. */ - ival = ctrlparse((char *)sval, &next); - if (!next) - ival = sval[0]; - } else { - ival = 255; /* special value meaning "don't set" */ - } - break; - case TYPE_BOOL: - if (stricmp(sval, "yes") == 0 || - stricmp(sval, "on") == 0 || - stricmp(sval, "true") == 0 || - stricmp(sval, "+") == 0) - ival = 1; /* true */ - else if (stricmp(sval, "no") == 0 || - stricmp(sval, "off") == 0 || - stricmp(sval, "false") == 0 || - stricmp(sval, "-") == 0) - ival = 0; /* false */ - else - ival = (atoi(sval) != 0); - break; - default: - unreachable("Bad mode->type"); - } - - modes.have_mode[mode->opcode] = true; - modes.mode_val[mode->opcode] = ival; - } - - sfree(to_free); - } - - { - unsigned ospeed, ispeed; - - /* Unpick the terminal-speed config string. */ - ospeed = ispeed = 38400; /* last-resort defaults */ - sscanf(conf_get_str(conf, CONF_termspeed), "%u,%u", &ospeed, &ispeed); - /* Currently we unconditionally set these */ - modes.have_mode[TTYMODE_ISPEED] = true; - modes.mode_val[TTYMODE_ISPEED] = ispeed; - modes.have_mode[TTYMODE_OSPEED] = true; - modes.mode_val[TTYMODE_OSPEED] = ospeed; - } - - return modes; -} - -struct ssh_ttymodes read_ttymodes_from_packet( - BinarySource *bs, int ssh_version) -{ - struct ssh_ttymodes modes; - memset(&modes, 0, sizeof(modes)); - - while (1) { - unsigned real_opcode, our_opcode; - - real_opcode = get_byte(bs); - if (real_opcode == TTYMODE_END_OF_LIST) - break; - if (real_opcode >= 160) { - /* - * RFC 4254 (and the SSH 1.5 spec): "Opcodes 160 to 255 - * are not yet defined, and cause parsing to stop (they - * should only be used after any other data)." - * - * My interpretation of this is that if one of these - * opcodes appears, it's not a parse _error_, but it is - * something that we don't know how to parse even well - * enough to step over it to find the next opcode, so we - * stop parsing now and assume that the rest of the string - * is composed entirely of things we don't understand and - * (as usual for unsupported terminal modes) silently - * ignore. - */ - return modes; - } - - our_opcode = our_ttymode_opcode(real_opcode, ssh_version); - assert(our_opcode < TTYMODE_LIMIT); - modes.have_mode[our_opcode] = true; - - if (ssh_version == 1 && real_opcode >= 1 && real_opcode <= 127) - modes.mode_val[our_opcode] = get_byte(bs); - else - modes.mode_val[our_opcode] = get_uint32(bs); - } - - return modes; -} - -void write_ttymodes_to_packet(BinarySink *bs, int ssh_version, - struct ssh_ttymodes modes) -{ - unsigned i; - - for (i = 0; i < TTYMODE_LIMIT; i++) { - if (modes.have_mode[i]) { - unsigned val = modes.mode_val[i]; - unsigned opcode = real_ttymode_opcode(i, ssh_version); - - put_byte(bs, opcode); - if (ssh_version == 1 && opcode >= 1 && opcode <= 127) - put_byte(bs, val); - else - put_uint32(bs, val); - } - } - - put_byte(bs, TTYMODE_END_OF_LIST); -} - -/* ---------------------------------------------------------------------- - * Routine for allocating a new channel ID, given a means of finding - * the index field in a given channel structure. - */ - -unsigned alloc_channel_id_general(tree234 *channels, size_t localid_offset) -{ - const unsigned CHANNEL_NUMBER_OFFSET = 256; - search234_state ss; - - /* - * First-fit allocation of channel numbers: we always pick the - * lowest unused one. - * - * Every channel before that, and no channel after it, has an ID - * exactly equal to its tree index plus CHANNEL_NUMBER_OFFSET. So - * we can use the search234 system to identify the length of that - * initial sequence, in a single log-time pass down the channels - * tree. - */ - search234_start(&ss, channels); - while (ss.element) { - unsigned localid = *(unsigned *)((char *)ss.element + localid_offset); - if (localid == ss.index + CHANNEL_NUMBER_OFFSET) - search234_step(&ss, +1); - else - search234_step(&ss, -1); - } - - /* - * Now ss.index gives exactly the number of channels in that - * initial sequence. So adding CHANNEL_NUMBER_OFFSET to it must - * give precisely the lowest unused channel number. - */ - return ss.index + CHANNEL_NUMBER_OFFSET; -} - -/* ---------------------------------------------------------------------- - * Functions for handling the comma-separated strings used to store - * lists of protocol identifiers in SSH-2. - */ - -void add_to_commasep(strbuf *buf, const char *data) -{ - if (buf->len > 0) - put_byte(buf, ','); - put_data(buf, data, strlen(data)); -} - -bool get_commasep_word(ptrlen *list, ptrlen *word) -{ - const char *comma; - - /* - * Discard empty list elements, should there be any, because we - * never want to return one as if it was a real string. (This - * introduces a mild tolerance of badly formatted data in lists we - * receive, but I think that's acceptable.) - */ - while (list->len > 0 && *(const char *)list->ptr == ',') { - list->ptr = (const char *)list->ptr + 1; - list->len--; - } - - if (!list->len) - return false; - - comma = memchr(list->ptr, ',', list->len); - if (!comma) { - *word = *list; - list->len = 0; - } else { - size_t wordlen = comma - (const char *)list->ptr; - word->ptr = list->ptr; - word->len = wordlen; - list->ptr = (const char *)list->ptr + wordlen + 1; - list->len -= wordlen + 1; - } - return true; -} - -/* ---------------------------------------------------------------------- - * Functions for translating SSH packet type codes into their symbolic - * string names. - */ - -#define TRANSLATE_UNIVERSAL(y, name, value) \ - if (type == value) return #name; -#define TRANSLATE_KEX(y, name, value, ctx) \ - if (type == value && pkt_kctx == ctx) return #name; -#define TRANSLATE_AUTH(y, name, value, ctx) \ - if (type == value && pkt_actx == ctx) return #name; - -const char *ssh1_pkt_type(int type) -{ - SSH1_MESSAGE_TYPES(TRANSLATE_UNIVERSAL, y); - return "unknown"; -} -const char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type) -{ - SSH2_MESSAGE_TYPES(TRANSLATE_UNIVERSAL, TRANSLATE_KEX, TRANSLATE_AUTH, y); - return "unknown"; -} - -#undef TRANSLATE_UNIVERSAL -#undef TRANSLATE_KEX -#undef TRANSLATE_AUTH - -/* ---------------------------------------------------------------------- - * Common helper function for clients and implementations of - * PacketProtocolLayer. - */ - -void ssh_ppl_replace(PacketProtocolLayer *old, PacketProtocolLayer *new) -{ - new->bpp = old->bpp; - ssh_ppl_setup_queues(new, old->in_pq, old->out_pq); - new->selfptr = old->selfptr; - new->user_input = old->user_input; - new->seat = old->seat; - new->ssh = old->ssh; - - *new->selfptr = new; - ssh_ppl_free(old); - - /* The new layer might need to be the first one that sends a - * packet, so trigger a call to its main coroutine immediately. If - * it doesn't need to go first, the worst that will do is return - * straight away. */ - queue_idempotent_callback(&new->ic_process_queue); -} - -void ssh_ppl_free(PacketProtocolLayer *ppl) -{ - delete_callbacks_for_context(ppl); - ppl->vt->free(ppl); -} - -static void ssh_ppl_ic_process_queue_callback(void *context) -{ - PacketProtocolLayer *ppl = (PacketProtocolLayer *)context; - ssh_ppl_process_queue(ppl); -} - -void ssh_ppl_setup_queues(PacketProtocolLayer *ppl, - PktInQueue *inq, PktOutQueue *outq) -{ - ppl->in_pq = inq; - ppl->out_pq = outq; - ppl->in_pq->pqb.ic = &ppl->ic_process_queue; - ppl->ic_process_queue.fn = ssh_ppl_ic_process_queue_callback; - ppl->ic_process_queue.ctx = ppl; - - /* If there's already something on the input queue, it will want - * handling immediately. */ - if (pq_peek(ppl->in_pq)) - queue_idempotent_callback(&ppl->ic_process_queue); -} - -void ssh_ppl_user_output_string_and_free(PacketProtocolLayer *ppl, char *text) -{ - /* Messages sent via this function are from the SSH layer, not - * from the server-side process, so they always have the stderr - * flag set. */ - seat_stderr_pl(ppl->seat, ptrlen_from_asciz(text)); - sfree(text); -} - -/* ---------------------------------------------------------------------- - * Common helper functions for clients and implementations of - * BinaryPacketProtocol. - */ - -static void ssh_bpp_input_raw_data_callback(void *context) -{ - BinaryPacketProtocol *bpp = (BinaryPacketProtocol *)context; - Ssh *ssh = bpp->ssh; /* in case bpp is about to get freed */ - ssh_bpp_handle_input(bpp); - /* If we've now cleared enough backlog on the input connection, we - * may need to unfreeze it. */ - ssh_conn_processed_data(ssh); -} - -static void ssh_bpp_output_packet_callback(void *context) -{ - BinaryPacketProtocol *bpp = (BinaryPacketProtocol *)context; - ssh_bpp_handle_output(bpp); -} - -void ssh_bpp_common_setup(BinaryPacketProtocol *bpp) -{ - pq_in_init(&bpp->in_pq); - pq_out_init(&bpp->out_pq); - bpp->input_eof = false; - bpp->ic_in_raw.fn = ssh_bpp_input_raw_data_callback; - bpp->ic_in_raw.ctx = bpp; - bpp->ic_out_pq.fn = ssh_bpp_output_packet_callback; - bpp->ic_out_pq.ctx = bpp; - bpp->out_pq.pqb.ic = &bpp->ic_out_pq; -} - -void ssh_bpp_free(BinaryPacketProtocol *bpp) -{ - delete_callbacks_for_context(bpp); - bpp->vt->free(bpp); -} - -void ssh2_bpp_queue_disconnect(BinaryPacketProtocol *bpp, - const char *msg, int category) -{ - PktOut *pkt = ssh_bpp_new_pktout(bpp, SSH2_MSG_DISCONNECT); - put_uint32(pkt, category); - put_stringz(pkt, msg); - put_stringz(pkt, "en"); /* language tag */ - pq_push(&bpp->out_pq, pkt); -} - -#define BITMAP_UNIVERSAL(y, name, value) \ - | (value >= y && value < y+32 ? 1UL << (value-y) : 0) -#define BITMAP_CONDITIONAL(y, name, value, ctx) \ - BITMAP_UNIVERSAL(y, name, value) -#define SSH2_BITMAP_WORD(y) \ - (0 SSH2_MESSAGE_TYPES(BITMAP_UNIVERSAL, BITMAP_CONDITIONAL, \ - BITMAP_CONDITIONAL, (32*y))) - -bool ssh2_bpp_check_unimplemented(BinaryPacketProtocol *bpp, PktIn *pktin) -{ - static const unsigned valid_bitmap[] = { - SSH2_BITMAP_WORD(0), - SSH2_BITMAP_WORD(1), - SSH2_BITMAP_WORD(2), - SSH2_BITMAP_WORD(3), - SSH2_BITMAP_WORD(4), - SSH2_BITMAP_WORD(5), - SSH2_BITMAP_WORD(6), - SSH2_BITMAP_WORD(7), - }; - - if (pktin->type < 0x100 && - !((valid_bitmap[pktin->type >> 5] >> (pktin->type & 0x1F)) & 1)) { - PktOut *pkt = ssh_bpp_new_pktout(bpp, SSH2_MSG_UNIMPLEMENTED); - put_uint32(pkt, pktin->sequence); - pq_push(&bpp->out_pq, pkt); - return true; - } - - return false; -} - -#undef BITMAP_UNIVERSAL -#undef BITMAP_CONDITIONAL -#undef SSH1_BITMAP_WORD - -/* ---------------------------------------------------------------------- - * Function to check a host key against any manually configured in Conf. - */ - -int verify_ssh_manual_host_key( - Conf *conf, const char *fingerprint, ssh_key *key) -{ - if (!conf_get_str_nthstrkey(conf, CONF_ssh_manual_hostkeys, 0)) - return -1; /* no manual keys configured */ - - if (fingerprint) { - /* - * The fingerprint string we've been given will have things - * like 'ssh-rsa 2048' at the front of it. Strip those off and - * narrow down to just the colon-separated hex block at the - * end of the string. - */ - const char *p = strrchr(fingerprint, ' '); - fingerprint = p ? p+1 : fingerprint; - /* Quick sanity checks, including making sure it's in lowercase */ - assert(strlen(fingerprint) == 16*3 - 1); - assert(fingerprint[2] == ':'); - assert(fingerprint[strspn(fingerprint, "0123456789abcdef:")] == 0); - - if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys, fingerprint)) - return 1; /* success */ - } - - if (key) { - /* - * Construct the base64-encoded public key blob and see if - * that's listed. - */ - strbuf *binblob; - char *base64blob; - int atoms, i; - binblob = strbuf_new(); - ssh_key_public_blob(key, BinarySink_UPCAST(binblob)); - atoms = (binblob->len + 2) / 3; - base64blob = snewn(atoms * 4 + 1, char); - for (i = 0; i < atoms; i++) - base64_encode_atom(binblob->u + 3*i, - binblob->len - 3*i, base64blob + 4*i); - base64blob[atoms * 4] = '\0'; - strbuf_free(binblob); - if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys, base64blob)) { - sfree(base64blob); - return 1; /* success */ - } - sfree(base64blob); - } - - return 0; -} - -/* ---------------------------------------------------------------------- - * Common functions shared between SSH-1 layers. - */ - -bool ssh1_common_get_specials( - PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx) -{ - /* - * Don't bother offering IGNORE if we've decided the remote - * won't cope with it, since we wouldn't bother sending it if - * asked anyway. - */ - if (!(ppl->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) { - add_special(ctx, "IGNORE message", SS_NOP, 0); - return true; - } - - return false; -} - -bool ssh1_common_filter_queue(PacketProtocolLayer *ppl) -{ - PktIn *pktin; - ptrlen msg; - - while ((pktin = pq_peek(ppl->in_pq)) != NULL) { - switch (pktin->type) { - case SSH1_MSG_DISCONNECT: - msg = get_string(pktin); - ssh_remote_error(ppl->ssh, - "Remote side sent disconnect message:\n\"%.*s\"", - PTRLEN_PRINTF(msg)); - /* don't try to pop the queue, because we've been freed! */ - return true; /* indicate that we've been freed */ - - case SSH1_MSG_DEBUG: - msg = get_string(pktin); - ppl_logevent("Remote debug message: %.*s", PTRLEN_PRINTF(msg)); - pq_pop(ppl->in_pq); - break; - - case SSH1_MSG_IGNORE: - /* Do nothing, because we're ignoring it! Duhh. */ - pq_pop(ppl->in_pq); - break; - - default: - return false; - } - } - - return false; -} - -void ssh1_compute_session_id( - unsigned char *session_id, const unsigned char *cookie, - RSAKey *hostkey, RSAKey *servkey) -{ - ssh_hash *hash = ssh_hash_new(&ssh_md5); - - for (size_t i = (mp_get_nbits(hostkey->modulus) + 7) / 8; i-- ;) - put_byte(hash, mp_get_byte(hostkey->modulus, i)); - for (size_t i = (mp_get_nbits(servkey->modulus) + 7) / 8; i-- ;) - put_byte(hash, mp_get_byte(servkey->modulus, i)); - put_data(hash, cookie, 8); - ssh_hash_final(hash, session_id); -} - -/* ---------------------------------------------------------------------- - * Other miscellaneous utility functions. - */ - -void free_rportfwd(struct ssh_rportfwd *rpf) -{ - if (rpf) { - sfree(rpf->log_description); - sfree(rpf->shost); - sfree(rpf->dhost); - sfree(rpf); - } -} +/* + * Supporting routines used in common by all the various components of + * the SSH system. + */ + +#include +#include + +#include "putty.h" +#include "mpint.h" +#include "ssh.h" +#include "sshbpp.h" +#include "sshppl.h" +#include "sshchan.h" + +/* ---------------------------------------------------------------------- + * Implementation of PacketQueue. + */ + +static void pq_ensure_unlinked(PacketQueueNode *node) +{ + if (node->on_free_queue) { + node->next->prev = node->prev; + node->prev->next = node->next; + } else { + assert(!node->next); + assert(!node->prev); + } +} + +void pq_base_push(PacketQueueBase *pqb, PacketQueueNode *node) +{ + pq_ensure_unlinked(node); + node->next = &pqb->end; + node->prev = pqb->end.prev; + node->next->prev = node; + node->prev->next = node; + pqb->total_size += node->formal_size; + + if (pqb->ic) + queue_idempotent_callback(pqb->ic); +} + +void pq_base_push_front(PacketQueueBase *pqb, PacketQueueNode *node) +{ + pq_ensure_unlinked(node); + node->prev = &pqb->end; + node->next = pqb->end.next; + node->next->prev = node; + node->prev->next = node; + pqb->total_size += node->formal_size; + + if (pqb->ic) + queue_idempotent_callback(pqb->ic); +} + +static PacketQueueNode pktin_freeq_head = { + &pktin_freeq_head, &pktin_freeq_head, true +}; + +static void pktin_free_queue_callback(void *vctx) +{ + while (pktin_freeq_head.next != &pktin_freeq_head) { + PacketQueueNode *node = pktin_freeq_head.next; + PktIn *pktin = container_of(node, PktIn, qnode); + pktin_freeq_head.next = node->next; + sfree(pktin); + } + + pktin_freeq_head.prev = &pktin_freeq_head; +} + +static IdempotentCallback ic_pktin_free = { + pktin_free_queue_callback, NULL, false +}; + +static inline void pq_unlink_common(PacketQueueBase *pqb, + PacketQueueNode *node) +{ + node->next->prev = node->prev; + node->prev->next = node->next; + + /* Check total_size doesn't drift out of sync downwards, by + * ensuring it doesn't underflow when we do this subtraction */ + assert(pqb->total_size >= node->formal_size); + pqb->total_size -= node->formal_size; + + /* Check total_size doesn't drift out of sync upwards, by checking + * that it's returned to exactly zero whenever a queue is + * emptied */ + assert(pqb->end.next != &pqb->end || pqb->total_size == 0); +} + +static PktIn *pq_in_after(PacketQueueBase *pqb, + PacketQueueNode *prev, bool pop) +{ + PacketQueueNode *node = prev->next; + if (node == &pqb->end) + return NULL; + + if (pop) { + pq_unlink_common(pqb, node); + + node->prev = pktin_freeq_head.prev; + node->next = &pktin_freeq_head; + node->next->prev = node; + node->prev->next = node; + node->on_free_queue = true; + + queue_idempotent_callback(&ic_pktin_free); + } + + return container_of(node, PktIn, qnode); +} + +static PktOut *pq_out_after(PacketQueueBase *pqb, + PacketQueueNode *prev, bool pop) +{ + PacketQueueNode *node = prev->next; + if (node == &pqb->end) + return NULL; + + if (pop) { + pq_unlink_common(pqb, node); + + node->prev = node->next = NULL; + } + + return container_of(node, PktOut, qnode); +} + +void pq_in_init(PktInQueue *pq) +{ + pq->pqb.ic = NULL; + pq->pqb.end.next = pq->pqb.end.prev = &pq->pqb.end; + pq->after = pq_in_after; + pq->pqb.total_size = 0; +} + +void pq_out_init(PktOutQueue *pq) +{ + pq->pqb.ic = NULL; + pq->pqb.end.next = pq->pqb.end.prev = &pq->pqb.end; + pq->after = pq_out_after; + pq->pqb.total_size = 0; +} + +void pq_in_clear(PktInQueue *pq) +{ + PktIn *pkt; + pq->pqb.ic = NULL; + while ((pkt = pq_pop(pq)) != NULL) { + /* No need to actually free these packets: pq_pop on a + * PktInQueue will automatically move them to the free + * queue. */ + } +} + +void pq_out_clear(PktOutQueue *pq) +{ + PktOut *pkt; + pq->pqb.ic = NULL; + while ((pkt = pq_pop(pq)) != NULL) + ssh_free_pktout(pkt); +} + +/* + * Concatenate the contents of the two queues q1 and q2, and leave the + * result in qdest. qdest must be either empty, or one of the input + * queues. + */ +void pq_base_concatenate(PacketQueueBase *qdest, + PacketQueueBase *q1, PacketQueueBase *q2) +{ + struct PacketQueueNode *head1, *tail1, *head2, *tail2; + + size_t total_size = q1->total_size + q2->total_size; + + /* + * Extract the contents from both input queues, and empty them. + */ + + head1 = (q1->end.next == &q1->end ? NULL : q1->end.next); + tail1 = (q1->end.prev == &q1->end ? NULL : q1->end.prev); + head2 = (q2->end.next == &q2->end ? NULL : q2->end.next); + tail2 = (q2->end.prev == &q2->end ? NULL : q2->end.prev); + + q1->end.next = q1->end.prev = &q1->end; + q2->end.next = q2->end.prev = &q2->end; + q1->total_size = q2->total_size = 0; + + /* + * Link the two lists together, handling the case where one or + * both is empty. + */ + + if (tail1) + tail1->next = head2; + else + head1 = head2; + + if (head2) + head2->prev = tail1; + else + tail2 = tail1; + + /* + * Check the destination queue is currently empty. (If it was one + * of the input queues, then it will be, because we emptied both + * of those just a moment ago.) + */ + + assert(qdest->end.next == &qdest->end); + assert(qdest->end.prev == &qdest->end); + + /* + * If our concatenated list has anything in it, then put it in + * dest. + */ + + if (!head1) { + assert(!tail2); + } else { + assert(tail2); + qdest->end.next = head1; + qdest->end.prev = tail2; + head1->prev = &qdest->end; + tail2->next = &qdest->end; + + if (qdest->ic) + queue_idempotent_callback(qdest->ic); + } + + qdest->total_size = total_size; +} + +/* ---------------------------------------------------------------------- + * Low-level functions for the packet structures themselves. + */ + +static void ssh_pkt_BinarySink_write(BinarySink *bs, + const void *data, size_t len); +PktOut *ssh_new_packet(void) +{ + PktOut *pkt = snew(PktOut); + + BinarySink_INIT(pkt, ssh_pkt_BinarySink_write); + pkt->data = NULL; + pkt->length = 0; + pkt->maxlen = 0; + pkt->downstream_id = 0; + pkt->additional_log_text = NULL; + pkt->qnode.next = pkt->qnode.prev = NULL; + pkt->qnode.on_free_queue = false; + + return pkt; +} + +static void ssh_pkt_adddata(PktOut *pkt, const void *data, int len) +{ + sgrowarrayn_nm(pkt->data, pkt->maxlen, pkt->length, len); + memcpy(pkt->data + pkt->length, data, len); + pkt->length += len; + pkt->qnode.formal_size = pkt->length; +} + +static void ssh_pkt_BinarySink_write(BinarySink *bs, + const void *data, size_t len) +{ + PktOut *pkt = BinarySink_DOWNCAST(bs, PktOut); + ssh_pkt_adddata(pkt, data, len); +} + +void ssh_free_pktout(PktOut *pkt) +{ + sfree(pkt->data); + sfree(pkt); +} + +/* ---------------------------------------------------------------------- + * Implement zombiechan_new() and its trivial vtable. + */ + +static void zombiechan_free(Channel *chan); +static size_t zombiechan_send( + Channel *chan, bool is_stderr, const void *, size_t); +static void zombiechan_set_input_wanted(Channel *chan, bool wanted); +static void zombiechan_do_nothing(Channel *chan); +static void zombiechan_open_failure(Channel *chan, const char *); +static bool zombiechan_want_close(Channel *chan, bool sent_eof, bool rcvd_eof); +static char *zombiechan_log_close_msg(Channel *chan) { return NULL; } + +static const struct ChannelVtable zombiechan_channelvt = { + zombiechan_free, + zombiechan_do_nothing, /* open_confirmation */ + zombiechan_open_failure, + zombiechan_send, + zombiechan_do_nothing, /* send_eof */ + zombiechan_set_input_wanted, + zombiechan_log_close_msg, + zombiechan_want_close, + chan_no_exit_status, + chan_no_exit_signal, + chan_no_exit_signal_numeric, + chan_no_run_shell, + chan_no_run_command, + chan_no_run_subsystem, + chan_no_enable_x11_forwarding, + chan_no_enable_agent_forwarding, + chan_no_allocate_pty, + chan_no_set_env, + chan_no_send_break, + chan_no_send_signal, + chan_no_change_window_size, + chan_no_request_response, +}; + +Channel *zombiechan_new(void) +{ + Channel *chan = snew(Channel); + chan->vt = &zombiechan_channelvt; + chan->initial_fixed_window_size = 0; + return chan; +} + +static void zombiechan_free(Channel *chan) +{ + assert(chan->vt == &zombiechan_channelvt); + sfree(chan); +} + +static void zombiechan_do_nothing(Channel *chan) +{ + assert(chan->vt == &zombiechan_channelvt); +} + +static void zombiechan_open_failure(Channel *chan, const char *errtext) +{ + assert(chan->vt == &zombiechan_channelvt); +} + +static size_t zombiechan_send(Channel *chan, bool is_stderr, + const void *data, size_t length) +{ + assert(chan->vt == &zombiechan_channelvt); + return 0; +} + +static void zombiechan_set_input_wanted(Channel *chan, bool enable) +{ + assert(chan->vt == &zombiechan_channelvt); +} + +static bool zombiechan_want_close(Channel *chan, bool sent_eof, bool rcvd_eof) +{ + return true; +} + +/* ---------------------------------------------------------------------- + * Centralised standard methods for other channel implementations to + * borrow. + */ + +void chan_remotely_opened_confirmation(Channel *chan) +{ + unreachable("this channel type should never receive OPEN_CONFIRMATION"); +} + +void chan_remotely_opened_failure(Channel *chan, const char *errtext) +{ + unreachable("this channel type should never receive OPEN_FAILURE"); +} + +bool chan_default_want_close( + Channel *chan, bool sent_local_eof, bool rcvd_remote_eof) +{ + /* + * Default close policy: we start initiating the CHANNEL_CLOSE + * procedure as soon as both sides of the channel have seen EOF. + */ + return sent_local_eof && rcvd_remote_eof; +} + +bool chan_no_exit_status(Channel *chan, int status) +{ + return false; +} + +bool chan_no_exit_signal( + Channel *chan, ptrlen signame, bool core_dumped, ptrlen msg) +{ + return false; +} + +bool chan_no_exit_signal_numeric( + Channel *chan, int signum, bool core_dumped, ptrlen msg) +{ + return false; +} + +bool chan_no_run_shell(Channel *chan) +{ + return false; +} + +bool chan_no_run_command(Channel *chan, ptrlen command) +{ + return false; +} + +bool chan_no_run_subsystem(Channel *chan, ptrlen subsys) +{ + return false; +} + +bool chan_no_enable_x11_forwarding( + Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata, + unsigned screen_number) +{ + return false; +} + +bool chan_no_enable_agent_forwarding(Channel *chan) +{ + return false; +} + +bool chan_no_allocate_pty( + Channel *chan, ptrlen termtype, unsigned width, unsigned height, + unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes) +{ + return false; +} + +bool chan_no_set_env(Channel *chan, ptrlen var, ptrlen value) +{ + return false; +} + +bool chan_no_send_break(Channel *chan, unsigned length) +{ + return false; +} + +bool chan_no_send_signal(Channel *chan, ptrlen signame) +{ + return false; +} + +bool chan_no_change_window_size( + Channel *chan, unsigned width, unsigned height, + unsigned pixwidth, unsigned pixheight) +{ + return false; +} + +void chan_no_request_response(Channel *chan, bool success) +{ + unreachable("this channel type should never send a want-reply request"); +} + +/* ---------------------------------------------------------------------- + * Common routines for handling SSH tty modes. + */ + +static unsigned real_ttymode_opcode(unsigned our_opcode, int ssh_version) +{ + switch (our_opcode) { + case TTYMODE_ISPEED: + return ssh_version == 1 ? TTYMODE_ISPEED_SSH1 : TTYMODE_ISPEED_SSH2; + case TTYMODE_OSPEED: + return ssh_version == 1 ? TTYMODE_OSPEED_SSH1 : TTYMODE_OSPEED_SSH2; + default: + return our_opcode; + } +} + +static unsigned our_ttymode_opcode(unsigned real_opcode, int ssh_version) +{ + if (ssh_version == 1) { + switch (real_opcode) { + case TTYMODE_ISPEED_SSH1: + return TTYMODE_ISPEED; + case TTYMODE_OSPEED_SSH1: + return TTYMODE_OSPEED; + default: + return real_opcode; + } + } else { + switch (real_opcode) { + case TTYMODE_ISPEED_SSH2: + return TTYMODE_ISPEED; + case TTYMODE_OSPEED_SSH2: + return TTYMODE_OSPEED; + default: + return real_opcode; + } + } +} + +struct ssh_ttymodes get_ttymodes_from_conf(Seat *seat, Conf *conf) +{ + struct ssh_ttymodes modes; + size_t i; + + static const struct mode_name_type { + const char *mode; + int opcode; + enum { TYPE_CHAR, TYPE_BOOL } type; + } modes_names_types[] = { + #define TTYMODE_CHAR(name, val, index) { #name, val, TYPE_CHAR }, + #define TTYMODE_FLAG(name, val, field, mask) { #name, val, TYPE_BOOL }, + #include "sshttymodes.h" + #undef TTYMODE_CHAR + #undef TTYMODE_FLAG + }; + + memset(&modes, 0, sizeof(modes)); + + for (i = 0; i < lenof(modes_names_types); i++) { + const struct mode_name_type *mode = &modes_names_types[i]; + const char *sval = conf_get_str_str(conf, CONF_ttymodes, mode->mode); + char *to_free = NULL; + + if (!sval) + sval = "N"; /* just in case */ + + /* + * sval[0] can be + * - 'V', indicating that an explicit value follows it; + * - 'A', indicating that we should pass the value through from + * the local environment via get_ttymode; or + * - 'N', indicating that we should explicitly not send this + * mode. + */ + if (sval[0] == 'A') { + sval = to_free = seat_get_ttymode(seat, mode->mode); + } else if (sval[0] == 'V') { + sval++; /* skip the 'V' */ + } else { + /* else 'N', or something from the future we don't understand */ + continue; + } + + if (sval) { + /* + * Parse the string representation of the tty mode + * into the integer value it will take on the wire. + */ + unsigned ival = 0; + + switch (mode->type) { + case TYPE_CHAR: + if (*sval) { + char *next = NULL; + /* We know ctrlparse won't write to the string, so + * casting away const is ugly but allowable. */ + ival = ctrlparse((char *)sval, &next); + if (!next) + ival = sval[0]; + } else { + ival = 255; /* special value meaning "don't set" */ + } + break; + case TYPE_BOOL: + if (stricmp(sval, "yes") == 0 || + stricmp(sval, "on") == 0 || + stricmp(sval, "true") == 0 || + stricmp(sval, "+") == 0) + ival = 1; /* true */ + else if (stricmp(sval, "no") == 0 || + stricmp(sval, "off") == 0 || + stricmp(sval, "false") == 0 || + stricmp(sval, "-") == 0) + ival = 0; /* false */ + else + ival = (atoi(sval) != 0); + break; + default: + unreachable("Bad mode->type"); + } + + modes.have_mode[mode->opcode] = true; + modes.mode_val[mode->opcode] = ival; + } + + sfree(to_free); + } + + { + unsigned ospeed, ispeed; + + /* Unpick the terminal-speed config string. */ + ospeed = ispeed = 38400; /* last-resort defaults */ + sscanf(conf_get_str(conf, CONF_termspeed), "%u,%u", &ospeed, &ispeed); + /* Currently we unconditionally set these */ + modes.have_mode[TTYMODE_ISPEED] = true; + modes.mode_val[TTYMODE_ISPEED] = ispeed; + modes.have_mode[TTYMODE_OSPEED] = true; + modes.mode_val[TTYMODE_OSPEED] = ospeed; + } + + return modes; +} + +struct ssh_ttymodes read_ttymodes_from_packet( + BinarySource *bs, int ssh_version) +{ + struct ssh_ttymodes modes; + memset(&modes, 0, sizeof(modes)); + + while (1) { + unsigned real_opcode, our_opcode; + + real_opcode = get_byte(bs); + if (real_opcode == TTYMODE_END_OF_LIST) + break; + if (real_opcode >= 160) { + /* + * RFC 4254 (and the SSH 1.5 spec): "Opcodes 160 to 255 + * are not yet defined, and cause parsing to stop (they + * should only be used after any other data)." + * + * My interpretation of this is that if one of these + * opcodes appears, it's not a parse _error_, but it is + * something that we don't know how to parse even well + * enough to step over it to find the next opcode, so we + * stop parsing now and assume that the rest of the string + * is composed entirely of things we don't understand and + * (as usual for unsupported terminal modes) silently + * ignore. + */ + return modes; + } + + our_opcode = our_ttymode_opcode(real_opcode, ssh_version); + assert(our_opcode < TTYMODE_LIMIT); + modes.have_mode[our_opcode] = true; + + if (ssh_version == 1 && real_opcode >= 1 && real_opcode <= 127) + modes.mode_val[our_opcode] = get_byte(bs); + else + modes.mode_val[our_opcode] = get_uint32(bs); + } + + return modes; +} + +void write_ttymodes_to_packet(BinarySink *bs, int ssh_version, + struct ssh_ttymodes modes) +{ + unsigned i; + + for (i = 0; i < TTYMODE_LIMIT; i++) { + if (modes.have_mode[i]) { + unsigned val = modes.mode_val[i]; + unsigned opcode = real_ttymode_opcode(i, ssh_version); + + put_byte(bs, opcode); + if (ssh_version == 1 && opcode >= 1 && opcode <= 127) + put_byte(bs, val); + else + put_uint32(bs, val); + } + } + + put_byte(bs, TTYMODE_END_OF_LIST); +} + +/* ---------------------------------------------------------------------- + * Routine for allocating a new channel ID, given a means of finding + * the index field in a given channel structure. + */ + +unsigned alloc_channel_id_general(tree234 *channels, size_t localid_offset) +{ + const unsigned CHANNEL_NUMBER_OFFSET = 256; + search234_state ss; + + /* + * First-fit allocation of channel numbers: we always pick the + * lowest unused one. + * + * Every channel before that, and no channel after it, has an ID + * exactly equal to its tree index plus CHANNEL_NUMBER_OFFSET. So + * we can use the search234 system to identify the length of that + * initial sequence, in a single log-time pass down the channels + * tree. + */ + search234_start(&ss, channels); + while (ss.element) { + unsigned localid = *(unsigned *)((char *)ss.element + localid_offset); + if (localid == ss.index + CHANNEL_NUMBER_OFFSET) + search234_step(&ss, +1); + else + search234_step(&ss, -1); + } + + /* + * Now ss.index gives exactly the number of channels in that + * initial sequence. So adding CHANNEL_NUMBER_OFFSET to it must + * give precisely the lowest unused channel number. + */ + return ss.index + CHANNEL_NUMBER_OFFSET; +} + +/* ---------------------------------------------------------------------- + * Functions for handling the comma-separated strings used to store + * lists of protocol identifiers in SSH-2. + */ + +void add_to_commasep(strbuf *buf, const char *data) +{ + if (buf->len > 0) + put_byte(buf, ','); + put_data(buf, data, strlen(data)); +} + +bool get_commasep_word(ptrlen *list, ptrlen *word) +{ + const char *comma; + + /* + * Discard empty list elements, should there be any, because we + * never want to return one as if it was a real string. (This + * introduces a mild tolerance of badly formatted data in lists we + * receive, but I think that's acceptable.) + */ + while (list->len > 0 && *(const char *)list->ptr == ',') { + list->ptr = (const char *)list->ptr + 1; + list->len--; + } + + if (!list->len) + return false; + + comma = memchr(list->ptr, ',', list->len); + if (!comma) { + *word = *list; + list->len = 0; + } else { + size_t wordlen = comma - (const char *)list->ptr; + word->ptr = list->ptr; + word->len = wordlen; + list->ptr = (const char *)list->ptr + wordlen + 1; + list->len -= wordlen + 1; + } + return true; +} + +/* ---------------------------------------------------------------------- + * Functions for translating SSH packet type codes into their symbolic + * string names. + */ + +#define TRANSLATE_UNIVERSAL(y, name, value) \ + if (type == value) return #name; +#define TRANSLATE_KEX(y, name, value, ctx) \ + if (type == value && pkt_kctx == ctx) return #name; +#define TRANSLATE_AUTH(y, name, value, ctx) \ + if (type == value && pkt_actx == ctx) return #name; + +const char *ssh1_pkt_type(int type) +{ + SSH1_MESSAGE_TYPES(TRANSLATE_UNIVERSAL, y); + return "unknown"; +} +const char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type) +{ + SSH2_MESSAGE_TYPES(TRANSLATE_UNIVERSAL, TRANSLATE_KEX, TRANSLATE_AUTH, y); + return "unknown"; +} + +#undef TRANSLATE_UNIVERSAL +#undef TRANSLATE_KEX +#undef TRANSLATE_AUTH + +/* ---------------------------------------------------------------------- + * Common helper function for clients and implementations of + * PacketProtocolLayer. + */ + +void ssh_ppl_replace(PacketProtocolLayer *old, PacketProtocolLayer *new) +{ + new->bpp = old->bpp; + ssh_ppl_setup_queues(new, old->in_pq, old->out_pq); + new->selfptr = old->selfptr; + new->user_input = old->user_input; + new->seat = old->seat; + new->ssh = old->ssh; + + *new->selfptr = new; + ssh_ppl_free(old); + + /* The new layer might need to be the first one that sends a + * packet, so trigger a call to its main coroutine immediately. If + * it doesn't need to go first, the worst that will do is return + * straight away. */ + queue_idempotent_callback(&new->ic_process_queue); +} + +void ssh_ppl_free(PacketProtocolLayer *ppl) +{ + delete_callbacks_for_context(ppl); + ppl->vt->free(ppl); +} + +static void ssh_ppl_ic_process_queue_callback(void *context) +{ + PacketProtocolLayer *ppl = (PacketProtocolLayer *)context; + ssh_ppl_process_queue(ppl); +} + +void ssh_ppl_setup_queues(PacketProtocolLayer *ppl, + PktInQueue *inq, PktOutQueue *outq) +{ + ppl->in_pq = inq; + ppl->out_pq = outq; + ppl->in_pq->pqb.ic = &ppl->ic_process_queue; + ppl->ic_process_queue.fn = ssh_ppl_ic_process_queue_callback; + ppl->ic_process_queue.ctx = ppl; + + /* If there's already something on the input queue, it will want + * handling immediately. */ + if (pq_peek(ppl->in_pq)) + queue_idempotent_callback(&ppl->ic_process_queue); +} + +void ssh_ppl_user_output_string_and_free(PacketProtocolLayer *ppl, char *text) +{ + /* Messages sent via this function are from the SSH layer, not + * from the server-side process, so they always have the stderr + * flag set. */ + seat_stderr_pl(ppl->seat, ptrlen_from_asciz(text)); + sfree(text); +} + +size_t ssh_ppl_default_queued_data_size(PacketProtocolLayer *ppl) +{ + return ppl->out_pq->pqb.total_size; +} + +/* ---------------------------------------------------------------------- + * Common helper functions for clients and implementations of + * BinaryPacketProtocol. + */ + +static void ssh_bpp_input_raw_data_callback(void *context) +{ + BinaryPacketProtocol *bpp = (BinaryPacketProtocol *)context; + Ssh *ssh = bpp->ssh; /* in case bpp is about to get freed */ + ssh_bpp_handle_input(bpp); + /* If we've now cleared enough backlog on the input connection, we + * may need to unfreeze it. */ + ssh_conn_processed_data(ssh); +} + +static void ssh_bpp_output_packet_callback(void *context) +{ + BinaryPacketProtocol *bpp = (BinaryPacketProtocol *)context; + ssh_bpp_handle_output(bpp); +} + +void ssh_bpp_common_setup(BinaryPacketProtocol *bpp) +{ + pq_in_init(&bpp->in_pq); + pq_out_init(&bpp->out_pq); + bpp->input_eof = false; + bpp->ic_in_raw.fn = ssh_bpp_input_raw_data_callback; + bpp->ic_in_raw.ctx = bpp; + bpp->ic_out_pq.fn = ssh_bpp_output_packet_callback; + bpp->ic_out_pq.ctx = bpp; + bpp->out_pq.pqb.ic = &bpp->ic_out_pq; +} + +void ssh_bpp_free(BinaryPacketProtocol *bpp) +{ + delete_callbacks_for_context(bpp); + bpp->vt->free(bpp); +} + +void ssh2_bpp_queue_disconnect(BinaryPacketProtocol *bpp, + const char *msg, int category) +{ + PktOut *pkt = ssh_bpp_new_pktout(bpp, SSH2_MSG_DISCONNECT); + put_uint32(pkt, category); + put_stringz(pkt, msg); + put_stringz(pkt, "en"); /* language tag */ + pq_push(&bpp->out_pq, pkt); +} + +#define BITMAP_UNIVERSAL(y, name, value) \ + | (value >= y && value < y+32 ? 1UL << (value-y) : 0) +#define BITMAP_CONDITIONAL(y, name, value, ctx) \ + BITMAP_UNIVERSAL(y, name, value) +#define SSH2_BITMAP_WORD(y) \ + (0 SSH2_MESSAGE_TYPES(BITMAP_UNIVERSAL, BITMAP_CONDITIONAL, \ + BITMAP_CONDITIONAL, (32*y))) + +bool ssh2_bpp_check_unimplemented(BinaryPacketProtocol *bpp, PktIn *pktin) +{ + static const unsigned valid_bitmap[] = { + SSH2_BITMAP_WORD(0), + SSH2_BITMAP_WORD(1), + SSH2_BITMAP_WORD(2), + SSH2_BITMAP_WORD(3), + SSH2_BITMAP_WORD(4), + SSH2_BITMAP_WORD(5), + SSH2_BITMAP_WORD(6), + SSH2_BITMAP_WORD(7), + }; + + if (pktin->type < 0x100 && + !((valid_bitmap[pktin->type >> 5] >> (pktin->type & 0x1F)) & 1)) { + PktOut *pkt = ssh_bpp_new_pktout(bpp, SSH2_MSG_UNIMPLEMENTED); + put_uint32(pkt, pktin->sequence); + pq_push(&bpp->out_pq, pkt); + return true; + } + + return false; +} + +#undef BITMAP_UNIVERSAL +#undef BITMAP_CONDITIONAL +#undef SSH1_BITMAP_WORD + +/* ---------------------------------------------------------------------- + * Function to check a host key against any manually configured in Conf. + */ + +int verify_ssh_manual_host_key( + Conf *conf, const char *fingerprint, ssh_key *key) +{ + if (!conf_get_str_nthstrkey(conf, CONF_ssh_manual_hostkeys, 0)) + return -1; /* no manual keys configured */ + + if (fingerprint) { + /* + * The fingerprint string we've been given will have things + * like 'ssh-rsa 2048' at the front of it. Strip those off and + * narrow down to just the colon-separated hex block at the + * end of the string. + */ + const char *p = strrchr(fingerprint, ' '); + fingerprint = p ? p+1 : fingerprint; + /* Quick sanity checks, including making sure it's in lowercase */ + assert(strlen(fingerprint) == 16*3 - 1); + assert(fingerprint[2] == ':'); + assert(fingerprint[strspn(fingerprint, "0123456789abcdef:")] == 0); + + if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys, fingerprint)) + return 1; /* success */ + } + + if (key) { + /* + * Construct the base64-encoded public key blob and see if + * that's listed. + */ + strbuf *binblob; + char *base64blob; + int atoms, i; + binblob = strbuf_new(); + ssh_key_public_blob(key, BinarySink_UPCAST(binblob)); + atoms = (binblob->len + 2) / 3; + base64blob = snewn(atoms * 4 + 1, char); + for (i = 0; i < atoms; i++) + base64_encode_atom(binblob->u + 3*i, + binblob->len - 3*i, base64blob + 4*i); + base64blob[atoms * 4] = '\0'; + strbuf_free(binblob); + if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys, base64blob)) { + sfree(base64blob); + return 1; /* success */ + } + sfree(base64blob); + } + + return 0; +} + +/* ---------------------------------------------------------------------- + * Common functions shared between SSH-1 layers. + */ + +bool ssh1_common_get_specials( + PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx) +{ + /* + * Don't bother offering IGNORE if we've decided the remote + * won't cope with it, since we wouldn't bother sending it if + * asked anyway. + */ + if (!(ppl->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) { + add_special(ctx, "IGNORE message", SS_NOP, 0); + return true; + } + + return false; +} + +bool ssh1_common_filter_queue(PacketProtocolLayer *ppl) +{ + PktIn *pktin; + ptrlen msg; + + while ((pktin = pq_peek(ppl->in_pq)) != NULL) { + switch (pktin->type) { + case SSH1_MSG_DISCONNECT: + msg = get_string(pktin); + ssh_remote_error(ppl->ssh, + "Remote side sent disconnect message:\n\"%.*s\"", + PTRLEN_PRINTF(msg)); + /* don't try to pop the queue, because we've been freed! */ + return true; /* indicate that we've been freed */ + + case SSH1_MSG_DEBUG: + msg = get_string(pktin); + ppl_logevent("Remote debug message: %.*s", PTRLEN_PRINTF(msg)); + pq_pop(ppl->in_pq); + break; + + case SSH1_MSG_IGNORE: + /* Do nothing, because we're ignoring it! Duhh. */ + pq_pop(ppl->in_pq); + break; + + default: + return false; + } + } + + return false; +} + +void ssh1_compute_session_id( + unsigned char *session_id, const unsigned char *cookie, + RSAKey *hostkey, RSAKey *servkey) +{ + ssh_hash *hash = ssh_hash_new(&ssh_md5); + + for (size_t i = (mp_get_nbits(hostkey->modulus) + 7) / 8; i-- ;) + put_byte(hash, mp_get_byte(hostkey->modulus, i)); + for (size_t i = (mp_get_nbits(servkey->modulus) + 7) / 8; i-- ;) + put_byte(hash, mp_get_byte(servkey->modulus, i)); + put_data(hash, cookie, 8); + ssh_hash_final(hash, session_id); +} + +/* ---------------------------------------------------------------------- + * Other miscellaneous utility functions. + */ + +void free_rportfwd(struct ssh_rportfwd *rpf) +{ + if (rpf) { + sfree(rpf->log_description); + sfree(rpf->shost); + sfree(rpf->dhost); + sfree(rpf); + } +} diff --git a/0.73_My_PuTTY/sshcr.h b/0.74_My_PuTTY/sshcr.h similarity index 100% rename from 0.73_My_PuTTY/sshcr.h rename to 0.74_My_PuTTY/sshcr.h diff --git a/0.73_My_PuTTY/sshcrc.c b/0.74_My_PuTTY/sshcrc.c similarity index 100% rename from 0.73_My_PuTTY/sshcrc.c rename to 0.74_My_PuTTY/sshcrc.c diff --git a/0.73_My_PuTTY/sshcrcda.c b/0.74_My_PuTTY/sshcrcda.c similarity index 100% rename from 0.73_My_PuTTY/sshcrcda.c rename to 0.74_My_PuTTY/sshcrcda.c diff --git a/0.73_My_PuTTY/sshdes.c b/0.74_My_PuTTY/sshdes.c similarity index 100% rename from 0.73_My_PuTTY/sshdes.c rename to 0.74_My_PuTTY/sshdes.c diff --git a/0.73_My_PuTTY/sshdh.c b/0.74_My_PuTTY/sshdh.c similarity index 100% rename from 0.73_My_PuTTY/sshdh.c rename to 0.74_My_PuTTY/sshdh.c diff --git a/0.73_My_PuTTY/sshdss.c b/0.74_My_PuTTY/sshdss.c similarity index 91% rename from 0.73_My_PuTTY/sshdss.c rename to 0.74_My_PuTTY/sshdss.c index 6af3c25..ba9589b 100644 --- a/0.73_My_PuTTY/sshdss.c +++ b/0.74_My_PuTTY/sshdss.c @@ -1,488 +1,490 @@ -/* - * Digital Signature Standard implementation for PuTTY. - */ - -#include -#include -#include - -#include "ssh.h" -#include "mpint.h" -#include "misc.h" - -static void dss_freekey(ssh_key *key); /* forward reference */ - -static ssh_key *dss_new_pub(const ssh_keyalg *self, ptrlen data) -{ - BinarySource src[1]; - struct dss_key *dss; - - BinarySource_BARE_INIT_PL(src, data); - if (!ptrlen_eq_string(get_string(src), "ssh-dss")) - return NULL; - - dss = snew(struct dss_key); - dss->sshk.vt = &ssh_dss; - dss->p = get_mp_ssh2(src); - dss->q = get_mp_ssh2(src); - dss->g = get_mp_ssh2(src); - dss->y = get_mp_ssh2(src); - dss->x = NULL; - - if (get_err(src) || - mp_eq_integer(dss->p, 0) || mp_eq_integer(dss->q, 0)) { - /* Invalid key. */ - dss_freekey(&dss->sshk); - return NULL; - } - - return &dss->sshk; -} - -static void dss_freekey(ssh_key *key) -{ - struct dss_key *dss = container_of(key, struct dss_key, sshk); - if (dss->p) - mp_free(dss->p); - if (dss->q) - mp_free(dss->q); - if (dss->g) - mp_free(dss->g); - if (dss->y) - mp_free(dss->y); - if (dss->x) - mp_free(dss->x); - sfree(dss); -} - -static void append_hex_to_strbuf(strbuf *sb, mp_int *x) -{ - if (sb->len > 0) - put_byte(sb, ','); - put_data(sb, "0x", 2); - char *hex = mp_get_hex(x); - size_t hexlen = strlen(hex); - put_data(sb, hex, hexlen); - smemclr(hex, hexlen); - sfree(hex); -} - -static char *dss_cache_str(ssh_key *key) -{ - struct dss_key *dss = container_of(key, struct dss_key, sshk); - strbuf *sb = strbuf_new(); - - if (!dss->p) - return NULL; - - append_hex_to_strbuf(sb, dss->p); - append_hex_to_strbuf(sb, dss->q); - append_hex_to_strbuf(sb, dss->g); - append_hex_to_strbuf(sb, dss->y); - - return strbuf_to_str(sb); -} - -static char *dss_invalid(ssh_key *key, unsigned flags) -{ - /* No validity criterion will stop us from using a DSA key at all */ - return NULL; -} - -static bool dss_verify(ssh_key *key, ptrlen sig, ptrlen data) -{ - struct dss_key *dss = container_of(key, struct dss_key, sshk); - BinarySource src[1]; - unsigned char hash[20]; - bool toret; - - if (!dss->p) - return false; - - BinarySource_BARE_INIT_PL(src, sig); - - /* - * Commercial SSH (2.0.13) and OpenSSH disagree over the format - * of a DSA signature. OpenSSH is in line with RFC 4253: - * it uses a string "ssh-dss", followed by a 40-byte string - * containing two 160-bit integers end-to-end. Commercial SSH - * can't be bothered with the header bit, and considers a DSA - * signature blob to be _just_ the 40-byte string containing - * the two 160-bit integers. We tell them apart by measuring - * the length: length 40 means the commercial-SSH bug, anything - * else is assumed to be RFC-compliant. - */ - if (sig.len != 40) { /* bug not present; read admin fields */ - ptrlen type = get_string(src); - sig = get_string(src); - - if (get_err(src) || !ptrlen_eq_string(type, "ssh-dss") || - sig.len != 40) - return false; - } - - /* Now we're sitting on a 40-byte string for sure. */ - mp_int *r = mp_from_bytes_be(make_ptrlen(sig.ptr, 20)); - mp_int *s = mp_from_bytes_be(make_ptrlen((const char *)sig.ptr + 20, 20)); - if (!r || !s) { - if (r) - mp_free(r); - if (s) - mp_free(s); - return false; - } - - /* Basic sanity checks: 0 < r,s < q */ - unsigned invalid = 0; - invalid |= mp_eq_integer(r, 0); - invalid |= mp_eq_integer(s, 0); - invalid |= mp_cmp_hs(r, dss->q); - invalid |= mp_cmp_hs(s, dss->q); - if (invalid) { - mp_free(r); - mp_free(s); - return false; - } - - /* - * Step 1. w <- s^-1 mod q. - */ - mp_int *w = mp_invert(s, dss->q); - if (!w) { - mp_free(r); - mp_free(s); - return false; - } - - /* - * Step 2. u1 <- SHA(message) * w mod q. - */ - hash_simple(&ssh_sha1, data, hash); - mp_int *sha = mp_from_bytes_be(make_ptrlen(hash, 20)); - mp_int *u1 = mp_modmul(sha, w, dss->q); - - /* - * Step 3. u2 <- r * w mod q. - */ - mp_int *u2 = mp_modmul(r, w, dss->q); - - /* - * Step 4. v <- (g^u1 * y^u2 mod p) mod q. - */ - mp_int *gu1p = mp_modpow(dss->g, u1, dss->p); - mp_int *yu2p = mp_modpow(dss->y, u2, dss->p); - mp_int *gu1yu2p = mp_modmul(gu1p, yu2p, dss->p); - mp_int *v = mp_mod(gu1yu2p, dss->q); - - /* - * Step 5. v should now be equal to r. - */ - - toret = mp_cmp_eq(v, r); - - mp_free(w); - mp_free(sha); - mp_free(u1); - mp_free(u2); - mp_free(gu1p); - mp_free(yu2p); - mp_free(gu1yu2p); - mp_free(v); - mp_free(r); - mp_free(s); - - return toret; -} - -static void dss_public_blob(ssh_key *key, BinarySink *bs) -{ - struct dss_key *dss = container_of(key, struct dss_key, sshk); - - put_stringz(bs, "ssh-dss"); - put_mp_ssh2(bs, dss->p); - put_mp_ssh2(bs, dss->q); - put_mp_ssh2(bs, dss->g); - put_mp_ssh2(bs, dss->y); -} - -static void dss_private_blob(ssh_key *key, BinarySink *bs) -{ - struct dss_key *dss = container_of(key, struct dss_key, sshk); - - put_mp_ssh2(bs, dss->x); -} - -static ssh_key *dss_new_priv(const ssh_keyalg *self, ptrlen pub, ptrlen priv) -{ - BinarySource src[1]; - ssh_key *sshk; - struct dss_key *dss; - ptrlen hash; - unsigned char digest[20]; - mp_int *ytest; - - sshk = dss_new_pub(self, pub); - if (!sshk) - return NULL; - - dss = container_of(sshk, struct dss_key, sshk); - BinarySource_BARE_INIT_PL(src, priv); - dss->x = get_mp_ssh2(src); - if (get_err(src)) { - dss_freekey(&dss->sshk); - return NULL; - } - - /* - * Check the obsolete hash in the old DSS key format. - */ - hash = get_string(src); - if (hash.len == 20) { - ssh_hash *h = ssh_hash_new(&ssh_sha1); - put_mp_ssh2(h, dss->p); - put_mp_ssh2(h, dss->q); - put_mp_ssh2(h, dss->g); - ssh_hash_final(h, digest); - if (!smemeq(hash.ptr, digest, 20)) { - dss_freekey(&dss->sshk); - return NULL; - } - } - - /* - * Now ensure g^x mod p really is y. - */ - ytest = mp_modpow(dss->g, dss->x, dss->p); - if (!mp_cmp_eq(ytest, dss->y)) { - mp_free(ytest); - dss_freekey(&dss->sshk); - return NULL; - } - mp_free(ytest); - - return &dss->sshk; -} - -static ssh_key *dss_new_priv_openssh(const ssh_keyalg *self, - BinarySource *src) -{ - struct dss_key *dss; - - dss = snew(struct dss_key); - dss->sshk.vt = &ssh_dss; - - dss->p = get_mp_ssh2(src); - dss->q = get_mp_ssh2(src); - dss->g = get_mp_ssh2(src); - dss->y = get_mp_ssh2(src); - dss->x = get_mp_ssh2(src); - - if (get_err(src) || - mp_eq_integer(dss->q, 0) || mp_eq_integer(dss->p, 0)) { - /* Invalid key. */ - dss_freekey(&dss->sshk); - return NULL; - } - - return &dss->sshk; -} - -static void dss_openssh_blob(ssh_key *key, BinarySink *bs) -{ - struct dss_key *dss = container_of(key, struct dss_key, sshk); - - put_mp_ssh2(bs, dss->p); - put_mp_ssh2(bs, dss->q); - put_mp_ssh2(bs, dss->g); - put_mp_ssh2(bs, dss->y); - put_mp_ssh2(bs, dss->x); -} - -static int dss_pubkey_bits(const ssh_keyalg *self, ptrlen pub) -{ - ssh_key *sshk; - struct dss_key *dss; - int ret; - - sshk = dss_new_pub(self, pub); - if (!sshk) - return -1; - - dss = container_of(sshk, struct dss_key, sshk); - ret = mp_get_nbits(dss->p); - dss_freekey(&dss->sshk); - - return ret; -} - -mp_int *dss_gen_k(const char *id_string, mp_int *modulus, - mp_int *private_key, - unsigned char *digest, int digest_len) -{ - /* - * The basic DSS signing algorithm is: - * - * - invent a random k between 1 and q-1 (exclusive). - * - Compute r = (g^k mod p) mod q. - * - Compute s = k^-1 * (hash + x*r) mod q. - * - * This has the dangerous properties that: - * - * - if an attacker in possession of the public key _and_ the - * signature (for example, the host you just authenticated - * to) can guess your k, he can reverse the computation of s - * and work out x = r^-1 * (s*k - hash) mod q. That is, he - * can deduce the private half of your key, and masquerade - * as you for as long as the key is still valid. - * - * - since r is a function purely of k and the public key, if - * the attacker only has a _range of possibilities_ for k - * it's easy for him to work through them all and check each - * one against r; he'll never be unsure of whether he's got - * the right one. - * - * - if you ever sign two different hashes with the same k, it - * will be immediately obvious because the two signatures - * will have the same r, and moreover an attacker in - * possession of both signatures (and the public key of - * course) can compute k = (hash1-hash2) * (s1-s2)^-1 mod q, - * and from there deduce x as before. - * - * - the Bleichenbacher attack on DSA makes use of methods of - * generating k which are significantly non-uniformly - * distributed; in particular, generating a 160-bit random - * number and reducing it mod q is right out. - * - * For this reason we must be pretty careful about how we - * generate our k. Since this code runs on Windows, with no - * particularly good system entropy sources, we can't trust our - * RNG itself to produce properly unpredictable data. Hence, we - * use a totally different scheme instead. - * - * What we do is to take a SHA-512 (_big_) hash of the private - * key x, and then feed this into another SHA-512 hash that - * also includes the message hash being signed. That is: - * - * proto_k = SHA512 ( SHA512(x) || SHA160(message) ) - * - * This number is 512 bits long, so reducing it mod q won't be - * noticeably non-uniform. So - * - * k = proto_k mod q - * - * This has the interesting property that it's _deterministic_: - * signing the same hash twice with the same key yields the - * same signature. - * - * Despite this determinism, it's still not predictable to an - * attacker, because in order to repeat the SHA-512 - * construction that created it, the attacker would have to - * know the private key value x - and by assumption he doesn't, - * because if he knew that he wouldn't be attacking k! - * - * (This trick doesn't, _per se_, protect against reuse of k. - * Reuse of k is left to chance; all it does is prevent - * _excessively high_ chances of reuse of k due to entropy - * problems.) - * - * Thanks to Colin Plumb for the general idea of using x to - * ensure k is hard to guess, and to the Cambridge University - * Computer Security Group for helping to argue out all the - * fine details. - */ - ssh_hash *h; - unsigned char digest512[64]; - - /* - * Hash some identifying text plus x. - */ - h = ssh_hash_new(&ssh_sha512); - put_asciz(h, id_string); - put_mp_ssh2(h, private_key); - ssh_hash_final(h, digest512); - - /* - * Now hash that digest plus the message hash. - */ - h = ssh_hash_new(&ssh_sha512); - put_data(h, digest512, sizeof(digest512)); - put_data(h, digest, digest_len); - ssh_hash_final(h, digest512); - - /* - * Now convert the result into a bignum, and coerce it to the - * range [2,q), which we do by reducing it mod q-2 and adding 2. - */ - mp_int *modminus2 = mp_copy(modulus); - mp_sub_integer_into(modminus2, modminus2, 2); - mp_int *proto_k = mp_from_bytes_be(make_ptrlen(digest512, 64)); - mp_int *k = mp_mod(proto_k, modminus2); - mp_free(proto_k); - mp_free(modminus2); - mp_add_integer_into(k, k, 2); - - smemclr(digest512, sizeof(digest512)); - - return k; -} - -static void dss_sign(ssh_key *key, ptrlen data, unsigned flags, BinarySink *bs) -{ - struct dss_key *dss = container_of(key, struct dss_key, sshk); - unsigned char digest[20]; - int i; - - hash_simple(&ssh_sha1, data, digest); - - mp_int *k = dss_gen_k("DSA deterministic k generator", dss->q, dss->x, - digest, sizeof(digest)); - mp_int *kinv = mp_invert(k, dss->q); /* k^-1 mod q */ - - /* - * Now we have k, so just go ahead and compute the signature. - */ - mp_int *gkp = mp_modpow(dss->g, k, dss->p); /* g^k mod p */ - mp_int *r = mp_mod(gkp, dss->q); /* r = (g^k mod p) mod q */ - mp_free(gkp); - - mp_int *hash = mp_from_bytes_be(make_ptrlen(digest, 20)); - mp_int *xr = mp_mul(dss->x, r); - mp_int *hxr = mp_add(xr, hash); /* hash + x*r */ - mp_int *s = mp_modmul(kinv, hxr, dss->q); /* s = k^-1 * (hash+x*r) mod q */ - mp_free(hxr); - mp_free(xr); - mp_free(kinv); - mp_free(k); - mp_free(hash); - - put_stringz(bs, "ssh-dss"); - put_uint32(bs, 40); - for (i = 0; i < 20; i++) - put_byte(bs, mp_get_byte(r, 19 - i)); - for (i = 0; i < 20; i++) - put_byte(bs, mp_get_byte(s, 19 - i)); - mp_free(r); - mp_free(s); -} - -const ssh_keyalg ssh_dss = { - dss_new_pub, - dss_new_priv, - dss_new_priv_openssh, - - dss_freekey, - dss_invalid, - dss_sign, - dss_verify, - dss_public_blob, - dss_private_blob, - dss_openssh_blob, - dss_cache_str, - - dss_pubkey_bits, - - "ssh-dss", - "dss", - NULL, - 0, /* no supported flags */ -}; +/* + * Digital Signature Standard implementation for PuTTY. + */ + +#include +#include +#include + +#include "ssh.h" +#include "mpint.h" +#include "misc.h" + +static void dss_freekey(ssh_key *key); /* forward reference */ + +static ssh_key *dss_new_pub(const ssh_keyalg *self, ptrlen data) +{ + BinarySource src[1]; + struct dss_key *dss; + + BinarySource_BARE_INIT_PL(src, data); + if (!ptrlen_eq_string(get_string(src), "ssh-dss")) + return NULL; + + dss = snew(struct dss_key); + dss->sshk.vt = &ssh_dss; + dss->p = get_mp_ssh2(src); + dss->q = get_mp_ssh2(src); + dss->g = get_mp_ssh2(src); + dss->y = get_mp_ssh2(src); + dss->x = NULL; + + if (get_err(src) || + mp_eq_integer(dss->p, 0) || mp_eq_integer(dss->q, 0)) { + /* Invalid key. */ + dss_freekey(&dss->sshk); + return NULL; + } + + return &dss->sshk; +} + +static void dss_freekey(ssh_key *key) +{ + struct dss_key *dss = container_of(key, struct dss_key, sshk); + if (dss->p) + mp_free(dss->p); + if (dss->q) + mp_free(dss->q); + if (dss->g) + mp_free(dss->g); + if (dss->y) + mp_free(dss->y); + if (dss->x) + mp_free(dss->x); + sfree(dss); +} + +static void append_hex_to_strbuf(strbuf *sb, mp_int *x) +{ + if (sb->len > 0) + put_byte(sb, ','); + put_data(sb, "0x", 2); + char *hex = mp_get_hex(x); + size_t hexlen = strlen(hex); + put_data(sb, hex, hexlen); + smemclr(hex, hexlen); + sfree(hex); +} + +static char *dss_cache_str(ssh_key *key) +{ + struct dss_key *dss = container_of(key, struct dss_key, sshk); + strbuf *sb = strbuf_new(); + + if (!dss->p) { + strbuf_free(sb); + return NULL; + } + + append_hex_to_strbuf(sb, dss->p); + append_hex_to_strbuf(sb, dss->q); + append_hex_to_strbuf(sb, dss->g); + append_hex_to_strbuf(sb, dss->y); + + return strbuf_to_str(sb); +} + +static char *dss_invalid(ssh_key *key, unsigned flags) +{ + /* No validity criterion will stop us from using a DSA key at all */ + return NULL; +} + +static bool dss_verify(ssh_key *key, ptrlen sig, ptrlen data) +{ + struct dss_key *dss = container_of(key, struct dss_key, sshk); + BinarySource src[1]; + unsigned char hash[20]; + bool toret; + + if (!dss->p) + return false; + + BinarySource_BARE_INIT_PL(src, sig); + + /* + * Commercial SSH (2.0.13) and OpenSSH disagree over the format + * of a DSA signature. OpenSSH is in line with RFC 4253: + * it uses a string "ssh-dss", followed by a 40-byte string + * containing two 160-bit integers end-to-end. Commercial SSH + * can't be bothered with the header bit, and considers a DSA + * signature blob to be _just_ the 40-byte string containing + * the two 160-bit integers. We tell them apart by measuring + * the length: length 40 means the commercial-SSH bug, anything + * else is assumed to be RFC-compliant. + */ + if (sig.len != 40) { /* bug not present; read admin fields */ + ptrlen type = get_string(src); + sig = get_string(src); + + if (get_err(src) || !ptrlen_eq_string(type, "ssh-dss") || + sig.len != 40) + return false; + } + + /* Now we're sitting on a 40-byte string for sure. */ + mp_int *r = mp_from_bytes_be(make_ptrlen(sig.ptr, 20)); + mp_int *s = mp_from_bytes_be(make_ptrlen((const char *)sig.ptr + 20, 20)); + if (!r || !s) { + if (r) + mp_free(r); + if (s) + mp_free(s); + return false; + } + + /* Basic sanity checks: 0 < r,s < q */ + unsigned invalid = 0; + invalid |= mp_eq_integer(r, 0); + invalid |= mp_eq_integer(s, 0); + invalid |= mp_cmp_hs(r, dss->q); + invalid |= mp_cmp_hs(s, dss->q); + if (invalid) { + mp_free(r); + mp_free(s); + return false; + } + + /* + * Step 1. w <- s^-1 mod q. + */ + mp_int *w = mp_invert(s, dss->q); + if (!w) { + mp_free(r); + mp_free(s); + return false; + } + + /* + * Step 2. u1 <- SHA(message) * w mod q. + */ + hash_simple(&ssh_sha1, data, hash); + mp_int *sha = mp_from_bytes_be(make_ptrlen(hash, 20)); + mp_int *u1 = mp_modmul(sha, w, dss->q); + + /* + * Step 3. u2 <- r * w mod q. + */ + mp_int *u2 = mp_modmul(r, w, dss->q); + + /* + * Step 4. v <- (g^u1 * y^u2 mod p) mod q. + */ + mp_int *gu1p = mp_modpow(dss->g, u1, dss->p); + mp_int *yu2p = mp_modpow(dss->y, u2, dss->p); + mp_int *gu1yu2p = mp_modmul(gu1p, yu2p, dss->p); + mp_int *v = mp_mod(gu1yu2p, dss->q); + + /* + * Step 5. v should now be equal to r. + */ + + toret = mp_cmp_eq(v, r); + + mp_free(w); + mp_free(sha); + mp_free(u1); + mp_free(u2); + mp_free(gu1p); + mp_free(yu2p); + mp_free(gu1yu2p); + mp_free(v); + mp_free(r); + mp_free(s); + + return toret; +} + +static void dss_public_blob(ssh_key *key, BinarySink *bs) +{ + struct dss_key *dss = container_of(key, struct dss_key, sshk); + + put_stringz(bs, "ssh-dss"); + put_mp_ssh2(bs, dss->p); + put_mp_ssh2(bs, dss->q); + put_mp_ssh2(bs, dss->g); + put_mp_ssh2(bs, dss->y); +} + +static void dss_private_blob(ssh_key *key, BinarySink *bs) +{ + struct dss_key *dss = container_of(key, struct dss_key, sshk); + + put_mp_ssh2(bs, dss->x); +} + +static ssh_key *dss_new_priv(const ssh_keyalg *self, ptrlen pub, ptrlen priv) +{ + BinarySource src[1]; + ssh_key *sshk; + struct dss_key *dss; + ptrlen hash; + unsigned char digest[20]; + mp_int *ytest; + + sshk = dss_new_pub(self, pub); + if (!sshk) + return NULL; + + dss = container_of(sshk, struct dss_key, sshk); + BinarySource_BARE_INIT_PL(src, priv); + dss->x = get_mp_ssh2(src); + if (get_err(src)) { + dss_freekey(&dss->sshk); + return NULL; + } + + /* + * Check the obsolete hash in the old DSS key format. + */ + hash = get_string(src); + if (hash.len == 20) { + ssh_hash *h = ssh_hash_new(&ssh_sha1); + put_mp_ssh2(h, dss->p); + put_mp_ssh2(h, dss->q); + put_mp_ssh2(h, dss->g); + ssh_hash_final(h, digest); + if (!smemeq(hash.ptr, digest, 20)) { + dss_freekey(&dss->sshk); + return NULL; + } + } + + /* + * Now ensure g^x mod p really is y. + */ + ytest = mp_modpow(dss->g, dss->x, dss->p); + if (!mp_cmp_eq(ytest, dss->y)) { + mp_free(ytest); + dss_freekey(&dss->sshk); + return NULL; + } + mp_free(ytest); + + return &dss->sshk; +} + +static ssh_key *dss_new_priv_openssh(const ssh_keyalg *self, + BinarySource *src) +{ + struct dss_key *dss; + + dss = snew(struct dss_key); + dss->sshk.vt = &ssh_dss; + + dss->p = get_mp_ssh2(src); + dss->q = get_mp_ssh2(src); + dss->g = get_mp_ssh2(src); + dss->y = get_mp_ssh2(src); + dss->x = get_mp_ssh2(src); + + if (get_err(src) || + mp_eq_integer(dss->q, 0) || mp_eq_integer(dss->p, 0)) { + /* Invalid key. */ + dss_freekey(&dss->sshk); + return NULL; + } + + return &dss->sshk; +} + +static void dss_openssh_blob(ssh_key *key, BinarySink *bs) +{ + struct dss_key *dss = container_of(key, struct dss_key, sshk); + + put_mp_ssh2(bs, dss->p); + put_mp_ssh2(bs, dss->q); + put_mp_ssh2(bs, dss->g); + put_mp_ssh2(bs, dss->y); + put_mp_ssh2(bs, dss->x); +} + +static int dss_pubkey_bits(const ssh_keyalg *self, ptrlen pub) +{ + ssh_key *sshk; + struct dss_key *dss; + int ret; + + sshk = dss_new_pub(self, pub); + if (!sshk) + return -1; + + dss = container_of(sshk, struct dss_key, sshk); + ret = mp_get_nbits(dss->p); + dss_freekey(&dss->sshk); + + return ret; +} + +mp_int *dss_gen_k(const char *id_string, mp_int *modulus, + mp_int *private_key, + unsigned char *digest, int digest_len) +{ + /* + * The basic DSS signing algorithm is: + * + * - invent a random k between 1 and q-1 (exclusive). + * - Compute r = (g^k mod p) mod q. + * - Compute s = k^-1 * (hash + x*r) mod q. + * + * This has the dangerous properties that: + * + * - if an attacker in possession of the public key _and_ the + * signature (for example, the host you just authenticated + * to) can guess your k, he can reverse the computation of s + * and work out x = r^-1 * (s*k - hash) mod q. That is, he + * can deduce the private half of your key, and masquerade + * as you for as long as the key is still valid. + * + * - since r is a function purely of k and the public key, if + * the attacker only has a _range of possibilities_ for k + * it's easy for him to work through them all and check each + * one against r; he'll never be unsure of whether he's got + * the right one. + * + * - if you ever sign two different hashes with the same k, it + * will be immediately obvious because the two signatures + * will have the same r, and moreover an attacker in + * possession of both signatures (and the public key of + * course) can compute k = (hash1-hash2) * (s1-s2)^-1 mod q, + * and from there deduce x as before. + * + * - the Bleichenbacher attack on DSA makes use of methods of + * generating k which are significantly non-uniformly + * distributed; in particular, generating a 160-bit random + * number and reducing it mod q is right out. + * + * For this reason we must be pretty careful about how we + * generate our k. Since this code runs on Windows, with no + * particularly good system entropy sources, we can't trust our + * RNG itself to produce properly unpredictable data. Hence, we + * use a totally different scheme instead. + * + * What we do is to take a SHA-512 (_big_) hash of the private + * key x, and then feed this into another SHA-512 hash that + * also includes the message hash being signed. That is: + * + * proto_k = SHA512 ( SHA512(x) || SHA160(message) ) + * + * This number is 512 bits long, so reducing it mod q won't be + * noticeably non-uniform. So + * + * k = proto_k mod q + * + * This has the interesting property that it's _deterministic_: + * signing the same hash twice with the same key yields the + * same signature. + * + * Despite this determinism, it's still not predictable to an + * attacker, because in order to repeat the SHA-512 + * construction that created it, the attacker would have to + * know the private key value x - and by assumption he doesn't, + * because if he knew that he wouldn't be attacking k! + * + * (This trick doesn't, _per se_, protect against reuse of k. + * Reuse of k is left to chance; all it does is prevent + * _excessively high_ chances of reuse of k due to entropy + * problems.) + * + * Thanks to Colin Plumb for the general idea of using x to + * ensure k is hard to guess, and to the Cambridge University + * Computer Security Group for helping to argue out all the + * fine details. + */ + ssh_hash *h; + unsigned char digest512[64]; + + /* + * Hash some identifying text plus x. + */ + h = ssh_hash_new(&ssh_sha512); + put_asciz(h, id_string); + put_mp_ssh2(h, private_key); + ssh_hash_final(h, digest512); + + /* + * Now hash that digest plus the message hash. + */ + h = ssh_hash_new(&ssh_sha512); + put_data(h, digest512, sizeof(digest512)); + put_data(h, digest, digest_len); + ssh_hash_final(h, digest512); + + /* + * Now convert the result into a bignum, and coerce it to the + * range [2,q), which we do by reducing it mod q-2 and adding 2. + */ + mp_int *modminus2 = mp_copy(modulus); + mp_sub_integer_into(modminus2, modminus2, 2); + mp_int *proto_k = mp_from_bytes_be(make_ptrlen(digest512, 64)); + mp_int *k = mp_mod(proto_k, modminus2); + mp_free(proto_k); + mp_free(modminus2); + mp_add_integer_into(k, k, 2); + + smemclr(digest512, sizeof(digest512)); + + return k; +} + +static void dss_sign(ssh_key *key, ptrlen data, unsigned flags, BinarySink *bs) +{ + struct dss_key *dss = container_of(key, struct dss_key, sshk); + unsigned char digest[20]; + int i; + + hash_simple(&ssh_sha1, data, digest); + + mp_int *k = dss_gen_k("DSA deterministic k generator", dss->q, dss->x, + digest, sizeof(digest)); + mp_int *kinv = mp_invert(k, dss->q); /* k^-1 mod q */ + + /* + * Now we have k, so just go ahead and compute the signature. + */ + mp_int *gkp = mp_modpow(dss->g, k, dss->p); /* g^k mod p */ + mp_int *r = mp_mod(gkp, dss->q); /* r = (g^k mod p) mod q */ + mp_free(gkp); + + mp_int *hash = mp_from_bytes_be(make_ptrlen(digest, 20)); + mp_int *xr = mp_mul(dss->x, r); + mp_int *hxr = mp_add(xr, hash); /* hash + x*r */ + mp_int *s = mp_modmul(kinv, hxr, dss->q); /* s = k^-1 * (hash+x*r) mod q */ + mp_free(hxr); + mp_free(xr); + mp_free(kinv); + mp_free(k); + mp_free(hash); + + put_stringz(bs, "ssh-dss"); + put_uint32(bs, 40); + for (i = 0; i < 20; i++) + put_byte(bs, mp_get_byte(r, 19 - i)); + for (i = 0; i < 20; i++) + put_byte(bs, mp_get_byte(s, 19 - i)); + mp_free(r); + mp_free(s); +} + +const ssh_keyalg ssh_dss = { + dss_new_pub, + dss_new_priv, + dss_new_priv_openssh, + + dss_freekey, + dss_invalid, + dss_sign, + dss_verify, + dss_public_blob, + dss_private_blob, + dss_openssh_blob, + dss_cache_str, + + dss_pubkey_bits, + + "ssh-dss", + "dss", + NULL, + 0, /* no supported flags */ +}; diff --git a/0.73_My_PuTTY/sshdssg.c b/0.74_My_PuTTY/sshdssg.c similarity index 100% rename from 0.73_My_PuTTY/sshdssg.c rename to 0.74_My_PuTTY/sshdssg.c diff --git a/0.73_My_PuTTY/sshecc.c b/0.74_My_PuTTY/sshecc.c similarity index 96% rename from 0.73_My_PuTTY/sshecc.c rename to 0.74_My_PuTTY/sshecc.c index 59782e5..97d7e50 100644 --- a/0.73_My_PuTTY/sshecc.c +++ b/0.74_My_PuTTY/sshecc.c @@ -1549,7 +1549,7 @@ bool ec_ed_alg_and_curve_by_bits( int bits, const struct ec_curve **curve, const ssh_keyalg **alg) { switch (bits) { - case 256: *alg = &ssh_ecdsa_ed25519; break; + case 255: case 256: *alg = &ssh_ecdsa_ed25519; break; default: return false; } *curve = ((struct ecsign_extra *)(*alg)->extra)->curve(); diff --git a/0.73_My_PuTTY/sshecdsag.c b/0.74_My_PuTTY/sshecdsag.c similarity index 100% rename from 0.73_My_PuTTY/sshecdsag.c rename to 0.74_My_PuTTY/sshecdsag.c diff --git a/0.73_My_PuTTY/sshgss.h b/0.74_My_PuTTY/sshgss.h similarity index 100% rename from 0.73_My_PuTTY/sshgss.h rename to 0.74_My_PuTTY/sshgss.h diff --git a/0.73_My_PuTTY/sshgssc.c b/0.74_My_PuTTY/sshgssc.c similarity index 79% rename from 0.73_My_PuTTY/sshgssc.c rename to 0.74_My_PuTTY/sshgssc.c index 26d301b..a1aca28 100644 --- a/0.73_My_PuTTY/sshgssc.c +++ b/0.74_My_PuTTY/sshgssc.c @@ -1,288 +1,288 @@ -#include "putty.h" - -#include -#include -#include "sshgssc.h" -#include "misc.h" - -#ifndef NO_GSSAPI - -static Ssh_gss_stat ssh_gssapi_indicate_mech(struct ssh_gss_library *lib, - Ssh_gss_buf *mech) -{ - /* Copy constant into mech */ - mech->length = GSS_MECH_KRB5->length; - mech->value = GSS_MECH_KRB5->elements; - return SSH_GSS_OK; -} - -static Ssh_gss_stat ssh_gssapi_import_name(struct ssh_gss_library *lib, - char *host, - Ssh_gss_name *srv_name) -{ - struct gssapi_functions *gss = &lib->u.gssapi; - OM_uint32 min_stat,maj_stat; - gss_buffer_desc host_buf; - char *pStr; - - pStr = dupcat("host@", host, NULL); - - host_buf.value = pStr; - host_buf.length = strlen(pStr); - - maj_stat = gss->import_name(&min_stat, &host_buf, - GSS_C_NT_HOSTBASED_SERVICE, srv_name); - /* Release buffer */ - sfree(pStr); - if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK; - return SSH_GSS_FAILURE; -} - -static Ssh_gss_stat ssh_gssapi_acquire_cred(struct ssh_gss_library *lib, - Ssh_gss_ctx *ctx, - time_t *expiry) -{ - struct gssapi_functions *gss = &lib->u.gssapi; - gss_OID_set_desc k5only = { 1, GSS_MECH_KRB5 }; - gss_cred_id_t cred; - OM_uint32 dummy; - OM_uint32 time_rec; - gssapi_ssh_gss_ctx *gssctx = snew(gssapi_ssh_gss_ctx); - - gssctx->ctx = GSS_C_NO_CONTEXT; - gssctx->expiry = 0; - - gssctx->maj_stat = - gss->acquire_cred(&gssctx->min_stat, GSS_C_NO_NAME, GSS_C_INDEFINITE, - &k5only, GSS_C_INITIATE, &cred, - (gss_OID_set *)0, &time_rec); - - if (gssctx->maj_stat != GSS_S_COMPLETE) { - sfree(gssctx); - return SSH_GSS_FAILURE; - } - - /* - * When the credential lifetime is not yet available due to deferred - * processing, gss_acquire_cred should return a 0 lifetime which is - * distinct from GSS_C_INDEFINITE which signals a crential that never - * expires. However, not all implementations get this right, and with - * Kerberos, initiator credentials always expire at some point. So when - * lifetime is 0 or GSS_C_INDEFINITE we call gss_inquire_cred_by_mech() to - * complete deferred processing. - */ - if (time_rec == GSS_C_INDEFINITE || time_rec == 0) { - gssctx->maj_stat = - gss->inquire_cred_by_mech(&gssctx->min_stat, cred, - (gss_OID) GSS_MECH_KRB5, - GSS_C_NO_NAME, - &time_rec, - NULL, - NULL); - } - (void) gss->release_cred(&dummy, &cred); - - if (gssctx->maj_stat != GSS_S_COMPLETE) { - sfree(gssctx); - return SSH_GSS_FAILURE; - } - - if (time_rec != GSS_C_INDEFINITE) - gssctx->expiry = time(NULL) + time_rec; - else - gssctx->expiry = GSS_NO_EXPIRATION; - - if (expiry) { - *expiry = gssctx->expiry; - } - - *ctx = (Ssh_gss_ctx) gssctx; - return SSH_GSS_OK; -} - -static Ssh_gss_stat ssh_gssapi_init_sec_context(struct ssh_gss_library *lib, - Ssh_gss_ctx *ctx, - Ssh_gss_name srv_name, - int to_deleg, - Ssh_gss_buf *recv_tok, - Ssh_gss_buf *send_tok, - time_t *expiry, - unsigned long *lifetime) -{ - struct gssapi_functions *gss = &lib->u.gssapi; - gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx*) *ctx; - OM_uint32 ret_flags; - OM_uint32 lifetime_rec; - - if (to_deleg) to_deleg = GSS_C_DELEG_FLAG; - gssctx->maj_stat = gss->init_sec_context(&gssctx->min_stat, - GSS_C_NO_CREDENTIAL, - &gssctx->ctx, - srv_name, - (gss_OID) GSS_MECH_KRB5, - GSS_C_MUTUAL_FLAG | - GSS_C_INTEG_FLAG | to_deleg, - 0, - GSS_C_NO_CHANNEL_BINDINGS, - recv_tok, - NULL, /* ignore mech type */ - send_tok, - &ret_flags, - &lifetime_rec); - - if (lifetime) { - if (lifetime_rec == GSS_C_INDEFINITE) - *lifetime = ULONG_MAX; - else - *lifetime = lifetime_rec; - } - if (expiry) { - if (lifetime_rec == GSS_C_INDEFINITE) - *expiry = GSS_NO_EXPIRATION; - else - *expiry = time(NULL) + lifetime_rec; - } - - if (gssctx->maj_stat == GSS_S_COMPLETE) return SSH_GSS_S_COMPLETE; - if (gssctx->maj_stat == GSS_S_CONTINUE_NEEDED) return SSH_GSS_S_CONTINUE_NEEDED; - return SSH_GSS_FAILURE; -} - -static Ssh_gss_stat ssh_gssapi_display_status(struct ssh_gss_library *lib, - Ssh_gss_ctx ctx, - Ssh_gss_buf *buf) -{ - struct gssapi_functions *gss = &lib->u.gssapi; - gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx *) ctx; - OM_uint32 lmin,lmax; - OM_uint32 ccc; - gss_buffer_desc msg_maj=GSS_C_EMPTY_BUFFER; - gss_buffer_desc msg_min=GSS_C_EMPTY_BUFFER; - - /* Return empty buffer in case of failure */ - SSH_GSS_CLEAR_BUF(buf); - - /* get first mesg from GSS */ - ccc=0; - lmax=gss->display_status(&lmin,gssctx->maj_stat,GSS_C_GSS_CODE,(gss_OID) GSS_MECH_KRB5,&ccc,&msg_maj); - - if (lmax != GSS_S_COMPLETE) return SSH_GSS_FAILURE; - - /* get first mesg from Kerberos */ - ccc=0; - lmax=gss->display_status(&lmin,gssctx->min_stat,GSS_C_MECH_CODE,(gss_OID) GSS_MECH_KRB5,&ccc,&msg_min); - - if (lmax != GSS_S_COMPLETE) { - gss->release_buffer(&lmin, &msg_maj); - return SSH_GSS_FAILURE; - } - - /* copy data into buffer */ - buf->length = msg_maj.length + msg_min.length + 1; - buf->value = snewn(buf->length + 1, char); - - /* copy mem */ - memcpy((char *)buf->value, msg_maj.value, msg_maj.length); - ((char *)buf->value)[msg_maj.length] = ' '; - memcpy((char *)buf->value + msg_maj.length + 1, msg_min.value, msg_min.length); - ((char *)buf->value)[buf->length] = 0; - /* free mem & exit */ - gss->release_buffer(&lmin, &msg_maj); - gss->release_buffer(&lmin, &msg_min); - return SSH_GSS_OK; -} - -static Ssh_gss_stat ssh_gssapi_free_tok(struct ssh_gss_library *lib, - Ssh_gss_buf *send_tok) -{ - struct gssapi_functions *gss = &lib->u.gssapi; - OM_uint32 min_stat,maj_stat; - maj_stat = gss->release_buffer(&min_stat, send_tok); - - if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK; - return SSH_GSS_FAILURE; -} - -static Ssh_gss_stat ssh_gssapi_release_cred(struct ssh_gss_library *lib, - Ssh_gss_ctx *ctx) -{ - struct gssapi_functions *gss = &lib->u.gssapi; - gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx *) *ctx; - OM_uint32 min_stat; - OM_uint32 maj_stat=GSS_S_COMPLETE; - - if (gssctx == NULL) return SSH_GSS_FAILURE; - if (gssctx->ctx != GSS_C_NO_CONTEXT) - maj_stat = gss->delete_sec_context(&min_stat,&gssctx->ctx,GSS_C_NO_BUFFER); - sfree(gssctx); - *ctx = NULL; - - if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK; - return SSH_GSS_FAILURE; -} - - -static Ssh_gss_stat ssh_gssapi_release_name(struct ssh_gss_library *lib, - Ssh_gss_name *srv_name) -{ - struct gssapi_functions *gss = &lib->u.gssapi; - OM_uint32 min_stat,maj_stat; - maj_stat = gss->release_name(&min_stat, srv_name); - - if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK; - return SSH_GSS_FAILURE; -} - -static Ssh_gss_stat ssh_gssapi_get_mic(struct ssh_gss_library *lib, - Ssh_gss_ctx ctx, Ssh_gss_buf *buf, - Ssh_gss_buf *hash) -{ - struct gssapi_functions *gss = &lib->u.gssapi; - gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx *) ctx; - if (gssctx == NULL) return SSH_GSS_FAILURE; - return gss->get_mic(&(gssctx->min_stat), gssctx->ctx, 0, buf, hash); -} - -static Ssh_gss_stat ssh_gssapi_verify_mic(struct ssh_gss_library *lib, - Ssh_gss_ctx ctx, Ssh_gss_buf *buf, - Ssh_gss_buf *hash) -{ - struct gssapi_functions *gss = &lib->u.gssapi; - gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx *) ctx; - if (gssctx == NULL) return SSH_GSS_FAILURE; - return gss->verify_mic(&(gssctx->min_stat), gssctx->ctx, buf, hash, NULL); -} - -static Ssh_gss_stat ssh_gssapi_free_mic(struct ssh_gss_library *lib, - Ssh_gss_buf *hash) -{ - /* On Unix this is the same freeing process as ssh_gssapi_free_tok. */ - return ssh_gssapi_free_tok(lib, hash); -} - -void ssh_gssapi_bind_fns(struct ssh_gss_library *lib) -{ - lib->indicate_mech = ssh_gssapi_indicate_mech; - lib->import_name = ssh_gssapi_import_name; - lib->release_name = ssh_gssapi_release_name; - lib->init_sec_context = ssh_gssapi_init_sec_context; - lib->free_tok = ssh_gssapi_free_tok; - lib->acquire_cred = ssh_gssapi_acquire_cred; - lib->release_cred = ssh_gssapi_release_cred; - lib->get_mic = ssh_gssapi_get_mic; - lib->verify_mic = ssh_gssapi_verify_mic; - lib->free_mic = ssh_gssapi_free_mic; - lib->display_status = ssh_gssapi_display_status; -} - -#else - -/* Dummy function so this source file defines something if NO_GSSAPI - is defined. */ - -int ssh_gssapi_init(void) -{ - return 0; -} - -#endif +#include "putty.h" + +#include +#include +#include "sshgssc.h" +#include "misc.h" + +#ifndef NO_GSSAPI + +static Ssh_gss_stat ssh_gssapi_indicate_mech(struct ssh_gss_library *lib, + Ssh_gss_buf *mech) +{ + /* Copy constant into mech */ + mech->length = GSS_MECH_KRB5->length; + mech->value = GSS_MECH_KRB5->elements; + return SSH_GSS_OK; +} + +static Ssh_gss_stat ssh_gssapi_import_name(struct ssh_gss_library *lib, + char *host, + Ssh_gss_name *srv_name) +{ + struct gssapi_functions *gss = &lib->u.gssapi; + OM_uint32 min_stat,maj_stat; + gss_buffer_desc host_buf; + char *pStr; + + pStr = dupcat("host@", host); + + host_buf.value = pStr; + host_buf.length = strlen(pStr); + + maj_stat = gss->import_name(&min_stat, &host_buf, + GSS_C_NT_HOSTBASED_SERVICE, srv_name); + /* Release buffer */ + sfree(pStr); + if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK; + return SSH_GSS_FAILURE; +} + +static Ssh_gss_stat ssh_gssapi_acquire_cred(struct ssh_gss_library *lib, + Ssh_gss_ctx *ctx, + time_t *expiry) +{ + struct gssapi_functions *gss = &lib->u.gssapi; + gss_OID_set_desc k5only = { 1, GSS_MECH_KRB5 }; + gss_cred_id_t cred; + OM_uint32 dummy; + OM_uint32 time_rec; + gssapi_ssh_gss_ctx *gssctx = snew(gssapi_ssh_gss_ctx); + + gssctx->ctx = GSS_C_NO_CONTEXT; + gssctx->expiry = 0; + + gssctx->maj_stat = + gss->acquire_cred(&gssctx->min_stat, GSS_C_NO_NAME, GSS_C_INDEFINITE, + &k5only, GSS_C_INITIATE, &cred, + (gss_OID_set *)0, &time_rec); + + if (gssctx->maj_stat != GSS_S_COMPLETE) { + sfree(gssctx); + return SSH_GSS_FAILURE; + } + + /* + * When the credential lifetime is not yet available due to deferred + * processing, gss_acquire_cred should return a 0 lifetime which is + * distinct from GSS_C_INDEFINITE which signals a crential that never + * expires. However, not all implementations get this right, and with + * Kerberos, initiator credentials always expire at some point. So when + * lifetime is 0 or GSS_C_INDEFINITE we call gss_inquire_cred_by_mech() to + * complete deferred processing. + */ + if (time_rec == GSS_C_INDEFINITE || time_rec == 0) { + gssctx->maj_stat = + gss->inquire_cred_by_mech(&gssctx->min_stat, cred, + (gss_OID) GSS_MECH_KRB5, + GSS_C_NO_NAME, + &time_rec, + NULL, + NULL); + } + (void) gss->release_cred(&dummy, &cred); + + if (gssctx->maj_stat != GSS_S_COMPLETE) { + sfree(gssctx); + return SSH_GSS_FAILURE; + } + + if (time_rec != GSS_C_INDEFINITE) + gssctx->expiry = time(NULL) + time_rec; + else + gssctx->expiry = GSS_NO_EXPIRATION; + + if (expiry) { + *expiry = gssctx->expiry; + } + + *ctx = (Ssh_gss_ctx) gssctx; + return SSH_GSS_OK; +} + +static Ssh_gss_stat ssh_gssapi_init_sec_context(struct ssh_gss_library *lib, + Ssh_gss_ctx *ctx, + Ssh_gss_name srv_name, + int to_deleg, + Ssh_gss_buf *recv_tok, + Ssh_gss_buf *send_tok, + time_t *expiry, + unsigned long *lifetime) +{ + struct gssapi_functions *gss = &lib->u.gssapi; + gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx*) *ctx; + OM_uint32 ret_flags; + OM_uint32 lifetime_rec; + + if (to_deleg) to_deleg = GSS_C_DELEG_FLAG; + gssctx->maj_stat = gss->init_sec_context(&gssctx->min_stat, + GSS_C_NO_CREDENTIAL, + &gssctx->ctx, + srv_name, + (gss_OID) GSS_MECH_KRB5, + GSS_C_MUTUAL_FLAG | + GSS_C_INTEG_FLAG | to_deleg, + 0, + GSS_C_NO_CHANNEL_BINDINGS, + recv_tok, + NULL, /* ignore mech type */ + send_tok, + &ret_flags, + &lifetime_rec); + + if (lifetime) { + if (lifetime_rec == GSS_C_INDEFINITE) + *lifetime = ULONG_MAX; + else + *lifetime = lifetime_rec; + } + if (expiry) { + if (lifetime_rec == GSS_C_INDEFINITE) + *expiry = GSS_NO_EXPIRATION; + else + *expiry = time(NULL) + lifetime_rec; + } + + if (gssctx->maj_stat == GSS_S_COMPLETE) return SSH_GSS_S_COMPLETE; + if (gssctx->maj_stat == GSS_S_CONTINUE_NEEDED) return SSH_GSS_S_CONTINUE_NEEDED; + return SSH_GSS_FAILURE; +} + +static Ssh_gss_stat ssh_gssapi_display_status(struct ssh_gss_library *lib, + Ssh_gss_ctx ctx, + Ssh_gss_buf *buf) +{ + struct gssapi_functions *gss = &lib->u.gssapi; + gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx *) ctx; + OM_uint32 lmin,lmax; + OM_uint32 ccc; + gss_buffer_desc msg_maj=GSS_C_EMPTY_BUFFER; + gss_buffer_desc msg_min=GSS_C_EMPTY_BUFFER; + + /* Return empty buffer in case of failure */ + SSH_GSS_CLEAR_BUF(buf); + + /* get first mesg from GSS */ + ccc=0; + lmax=gss->display_status(&lmin,gssctx->maj_stat,GSS_C_GSS_CODE,(gss_OID) GSS_MECH_KRB5,&ccc,&msg_maj); + + if (lmax != GSS_S_COMPLETE) return SSH_GSS_FAILURE; + + /* get first mesg from Kerberos */ + ccc=0; + lmax=gss->display_status(&lmin,gssctx->min_stat,GSS_C_MECH_CODE,(gss_OID) GSS_MECH_KRB5,&ccc,&msg_min); + + if (lmax != GSS_S_COMPLETE) { + gss->release_buffer(&lmin, &msg_maj); + return SSH_GSS_FAILURE; + } + + /* copy data into buffer */ + buf->length = msg_maj.length + msg_min.length + 1; + buf->value = snewn(buf->length + 1, char); + + /* copy mem */ + memcpy((char *)buf->value, msg_maj.value, msg_maj.length); + ((char *)buf->value)[msg_maj.length] = ' '; + memcpy((char *)buf->value + msg_maj.length + 1, msg_min.value, msg_min.length); + ((char *)buf->value)[buf->length] = 0; + /* free mem & exit */ + gss->release_buffer(&lmin, &msg_maj); + gss->release_buffer(&lmin, &msg_min); + return SSH_GSS_OK; +} + +static Ssh_gss_stat ssh_gssapi_free_tok(struct ssh_gss_library *lib, + Ssh_gss_buf *send_tok) +{ + struct gssapi_functions *gss = &lib->u.gssapi; + OM_uint32 min_stat,maj_stat; + maj_stat = gss->release_buffer(&min_stat, send_tok); + + if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK; + return SSH_GSS_FAILURE; +} + +static Ssh_gss_stat ssh_gssapi_release_cred(struct ssh_gss_library *lib, + Ssh_gss_ctx *ctx) +{ + struct gssapi_functions *gss = &lib->u.gssapi; + gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx *) *ctx; + OM_uint32 min_stat; + OM_uint32 maj_stat=GSS_S_COMPLETE; + + if (gssctx == NULL) return SSH_GSS_FAILURE; + if (gssctx->ctx != GSS_C_NO_CONTEXT) + maj_stat = gss->delete_sec_context(&min_stat,&gssctx->ctx,GSS_C_NO_BUFFER); + sfree(gssctx); + *ctx = NULL; + + if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK; + return SSH_GSS_FAILURE; +} + + +static Ssh_gss_stat ssh_gssapi_release_name(struct ssh_gss_library *lib, + Ssh_gss_name *srv_name) +{ + struct gssapi_functions *gss = &lib->u.gssapi; + OM_uint32 min_stat,maj_stat; + maj_stat = gss->release_name(&min_stat, srv_name); + + if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK; + return SSH_GSS_FAILURE; +} + +static Ssh_gss_stat ssh_gssapi_get_mic(struct ssh_gss_library *lib, + Ssh_gss_ctx ctx, Ssh_gss_buf *buf, + Ssh_gss_buf *hash) +{ + struct gssapi_functions *gss = &lib->u.gssapi; + gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx *) ctx; + if (gssctx == NULL) return SSH_GSS_FAILURE; + return gss->get_mic(&(gssctx->min_stat), gssctx->ctx, 0, buf, hash); +} + +static Ssh_gss_stat ssh_gssapi_verify_mic(struct ssh_gss_library *lib, + Ssh_gss_ctx ctx, Ssh_gss_buf *buf, + Ssh_gss_buf *hash) +{ + struct gssapi_functions *gss = &lib->u.gssapi; + gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx *) ctx; + if (gssctx == NULL) return SSH_GSS_FAILURE; + return gss->verify_mic(&(gssctx->min_stat), gssctx->ctx, buf, hash, NULL); +} + +static Ssh_gss_stat ssh_gssapi_free_mic(struct ssh_gss_library *lib, + Ssh_gss_buf *hash) +{ + /* On Unix this is the same freeing process as ssh_gssapi_free_tok. */ + return ssh_gssapi_free_tok(lib, hash); +} + +void ssh_gssapi_bind_fns(struct ssh_gss_library *lib) +{ + lib->indicate_mech = ssh_gssapi_indicate_mech; + lib->import_name = ssh_gssapi_import_name; + lib->release_name = ssh_gssapi_release_name; + lib->init_sec_context = ssh_gssapi_init_sec_context; + lib->free_tok = ssh_gssapi_free_tok; + lib->acquire_cred = ssh_gssapi_acquire_cred; + lib->release_cred = ssh_gssapi_release_cred; + lib->get_mic = ssh_gssapi_get_mic; + lib->verify_mic = ssh_gssapi_verify_mic; + lib->free_mic = ssh_gssapi_free_mic; + lib->display_status = ssh_gssapi_display_status; +} + +#else + +/* Dummy function so this source file defines something if NO_GSSAPI + is defined. */ + +int ssh_gssapi_init(void) +{ + return 0; +} + +#endif diff --git a/0.73_My_PuTTY/sshgssc.h b/0.74_My_PuTTY/sshgssc.h similarity index 100% rename from 0.73_My_PuTTY/sshgssc.h rename to 0.74_My_PuTTY/sshgssc.h diff --git a/0.73_My_PuTTY/sshhmac.c b/0.74_My_PuTTY/sshhmac.c similarity index 95% rename from 0.73_My_PuTTY/sshhmac.c rename to 0.74_My_PuTTY/sshhmac.c index 232d768..75d9daf 100644 --- a/0.73_My_PuTTY/sshhmac.c +++ b/0.74_My_PuTTY/sshhmac.c @@ -1,244 +1,244 @@ -/* - * Implementation of HMAC (RFC 2104) for PuTTY, in a general form that - * can wrap any underlying hash function. - */ - -#include "ssh.h" - -struct hmac { - const ssh_hashalg *hashalg; - ssh_hash *h_outer, *h_inner, *h_live; - bool keyed; - uint8_t *digest; - strbuf *text_name; - ssh2_mac mac; -}; - -struct hmac_extra { - const ssh_hashalg *hashalg_base; - const char *suffix, *annotation; -}; - -static ssh2_mac *hmac_new(const ssh2_macalg *alg, ssh_cipher *cipher) -{ - struct hmac *ctx = snew(struct hmac); - const struct hmac_extra *extra = (const struct hmac_extra *)alg->extra; - - ctx->h_outer = ssh_hash_new(extra->hashalg_base); - /* In case that hashalg was a selector vtable, we'll now switch to - * using whatever real one it selected, for all future purposes. */ - ctx->hashalg = ssh_hash_alg(ctx->h_outer); - ctx->h_inner = ssh_hash_new(ctx->hashalg); - ctx->h_live = ssh_hash_new(ctx->hashalg); - ctx->keyed = false; - - /* - * HMAC is not well defined as a wrapper on an absolutely general - * hash function; it expects that the function it's wrapping will - * consume data in fixed-size blocks, and it's partially defined - * in terms of that block size. So we insist that the hash we're - * given must have defined a meaningful block size. - */ - assert(ctx->hashalg->blocklen); - - ctx->digest = snewn(ctx->hashalg->hlen, uint8_t); - - ctx->text_name = strbuf_new(); - strbuf_catf(ctx->text_name, "HMAC-%s", - ctx->hashalg->text_basename, extra->suffix); - if (extra->annotation || ctx->hashalg->annotation) { - strbuf_catf(ctx->text_name, " ("); - const char *sep = ""; - if (extra->annotation) { - strbuf_catf(ctx->text_name, "%s%s", sep, extra->annotation); - sep = ", "; - } - if (ctx->hashalg->annotation) { - strbuf_catf(ctx->text_name, "%s%s", sep, ctx->hashalg->annotation); - sep = ", "; - } - strbuf_catf(ctx->text_name, ")"); - } - - ctx->mac.vt = alg; - BinarySink_DELEGATE_INIT(&ctx->mac, ctx->h_live); - - return &ctx->mac; -} - -static void hmac_free(ssh2_mac *mac) -{ - struct hmac *ctx = container_of(mac, struct hmac, mac); - - ssh_hash_free(ctx->h_outer); - ssh_hash_free(ctx->h_inner); - ssh_hash_free(ctx->h_live); - smemclr(ctx->digest, ctx->hashalg->hlen); - sfree(ctx->digest); - strbuf_free(ctx->text_name); - - smemclr(ctx, sizeof(*ctx)); - sfree(ctx); -} - -#define PAD_OUTER 0x5C -#define PAD_INNER 0x36 - -static void hmac_key(ssh2_mac *mac, ptrlen key) -{ - struct hmac *ctx = container_of(mac, struct hmac, mac); - - const uint8_t *kp; - size_t klen; - strbuf *sb = NULL; - - if (ctx->keyed) { - /* - * If we've already been keyed, throw away the existing hash - * objects and make a fresh pair to put the new key in. - */ - ssh_hash_free(ctx->h_outer); - ssh_hash_free(ctx->h_inner); - ctx->h_outer = ssh_hash_new(ctx->hashalg); - ctx->h_inner = ssh_hash_new(ctx->hashalg); - } - ctx->keyed = true; - - if (key.len > ctx->hashalg->blocklen) { - /* - * RFC 2104 section 2: if the key exceeds the block length of - * the underlying hash, then we start by hashing the key, and - * use that hash as the 'true' key for the HMAC construction. - */ - sb = strbuf_new_nm(); - strbuf_append(sb, ctx->hashalg->hlen); - - ssh_hash *htmp = ssh_hash_new(ctx->hashalg); - put_datapl(htmp, key); - ssh_hash_final(htmp, sb->u); - - kp = sb->u; - klen = sb->len; - } else { - /* - * A short enough key is used as is. - */ - kp = (const uint8_t *)key.ptr; - klen = key.len; - } - - if (ctx->h_outer) - ssh_hash_free(ctx->h_outer); - if (ctx->h_inner) - ssh_hash_free(ctx->h_inner); - - ctx->h_outer = ssh_hash_new(ctx->hashalg); - for (size_t i = 0; i < klen; i++) - put_byte(ctx->h_outer, PAD_OUTER ^ kp[i]); - for (size_t i = klen; i < ctx->hashalg->blocklen; i++) - put_byte(ctx->h_outer, PAD_OUTER); - - ctx->h_inner = ssh_hash_new(ctx->hashalg); - for (size_t i = 0; i < klen; i++) - put_byte(ctx->h_inner, PAD_INNER ^ kp[i]); - for (size_t i = klen; i < ctx->hashalg->blocklen; i++) - put_byte(ctx->h_inner, PAD_INNER); - - if (sb) - strbuf_free(sb); -} - -static void hmac_start(ssh2_mac *mac) -{ - struct hmac *ctx = container_of(mac, struct hmac, mac); - - ssh_hash_free(ctx->h_live); - ctx->h_live = ssh_hash_copy(ctx->h_inner); - BinarySink_DELEGATE_INIT(&ctx->mac, ctx->h_live); -} - -static void hmac_genresult(ssh2_mac *mac, unsigned char *output) -{ - struct hmac *ctx = container_of(mac, struct hmac, mac); - ssh_hash *htmp; - - /* Leave h_live in place, so that the SSH-2 BPP can continue - * regenerating test results from different-length prefixes of the - * packet */ - htmp = ssh_hash_copy(ctx->h_live); - ssh_hash_final(htmp, ctx->digest); - - htmp = ssh_hash_copy(ctx->h_outer); - put_data(htmp, ctx->digest, ctx->hashalg->hlen); - ssh_hash_final(htmp, ctx->digest); - - /* - * Some instances of HMAC truncate the output hash, so instead of - * writing it directly to 'output' we wrote it to our own - * full-length buffer, and now we copy the required amount. - */ - memcpy(output, ctx->digest, mac->vt->len); - smemclr(ctx->digest, ctx->hashalg->hlen); -} - -static const char *hmac_text_name(ssh2_mac *mac) -{ - struct hmac *ctx = container_of(mac, struct hmac, mac); - return ctx->text_name->s; -} - -const struct hmac_extra ssh_hmac_sha256_extra = { &ssh_sha256, "" }; -const ssh2_macalg ssh_hmac_sha256 = { - hmac_new, hmac_free, hmac_key, - hmac_start, hmac_genresult, hmac_text_name, - "hmac-sha2-256", "hmac-sha2-256-etm@openssh.com", - 32, 32, &ssh_hmac_sha256_extra, -}; - -const struct hmac_extra ssh_hmac_md5_extra = { &ssh_md5, "" }; -const ssh2_macalg ssh_hmac_md5 = { - hmac_new, hmac_free, hmac_key, - hmac_start, hmac_genresult, hmac_text_name, - "hmac-md5", "hmac-md5-etm@openssh.com", - 16, 16, &ssh_hmac_md5_extra, -}; - -const struct hmac_extra ssh_hmac_sha1_extra = { &ssh_sha1, "" }; - -const ssh2_macalg ssh_hmac_sha1 = { - hmac_new, hmac_free, hmac_key, - hmac_start, hmac_genresult, hmac_text_name, - "hmac-sha1", "hmac-sha1-etm@openssh.com", - 20, 20, &ssh_hmac_sha1_extra, -}; - -const struct hmac_extra ssh_hmac_sha1_96_extra = { &ssh_sha1, "-96" }; - -const ssh2_macalg ssh_hmac_sha1_96 = { - hmac_new, hmac_free, hmac_key, - hmac_start, hmac_genresult, hmac_text_name, - "hmac-sha1-96", "hmac-sha1-96-etm@openssh.com", - 12, 20, &ssh_hmac_sha1_96_extra, -}; - -const struct hmac_extra ssh_hmac_sha1_buggy_extra = { - &ssh_sha1, " (bug-compatible)" -}; - -const ssh2_macalg ssh_hmac_sha1_buggy = { - hmac_new, hmac_free, hmac_key, - hmac_start, hmac_genresult, hmac_text_name, - "hmac-sha1", NULL, - 20, 16, &ssh_hmac_sha1_buggy_extra, -}; - -const struct hmac_extra ssh_hmac_sha1_96_buggy_extra = { - &ssh_sha1, "-96 (bug-compatible)" -}; - -const ssh2_macalg ssh_hmac_sha1_96_buggy = { - hmac_new, hmac_free, hmac_key, - hmac_start, hmac_genresult, hmac_text_name, - "hmac-sha1-96", NULL, - 12, 16, &ssh_hmac_sha1_96_buggy_extra, -}; +/* + * Implementation of HMAC (RFC 2104) for PuTTY, in a general form that + * can wrap any underlying hash function. + */ + +#include "ssh.h" + +struct hmac { + const ssh_hashalg *hashalg; + ssh_hash *h_outer, *h_inner, *h_live; + bool keyed; + uint8_t *digest; + strbuf *text_name; + ssh2_mac mac; +}; + +struct hmac_extra { + const ssh_hashalg *hashalg_base; + const char *suffix, *annotation; +}; + +static ssh2_mac *hmac_new(const ssh2_macalg *alg, ssh_cipher *cipher) +{ + struct hmac *ctx = snew(struct hmac); + const struct hmac_extra *extra = (const struct hmac_extra *)alg->extra; + + ctx->h_outer = ssh_hash_new(extra->hashalg_base); + /* In case that hashalg was a selector vtable, we'll now switch to + * using whatever real one it selected, for all future purposes. */ + ctx->hashalg = ssh_hash_alg(ctx->h_outer); + ctx->h_inner = ssh_hash_new(ctx->hashalg); + ctx->h_live = ssh_hash_new(ctx->hashalg); + ctx->keyed = false; + + /* + * HMAC is not well defined as a wrapper on an absolutely general + * hash function; it expects that the function it's wrapping will + * consume data in fixed-size blocks, and it's partially defined + * in terms of that block size. So we insist that the hash we're + * given must have defined a meaningful block size. + */ + assert(ctx->hashalg->blocklen); + + ctx->digest = snewn(ctx->hashalg->hlen, uint8_t); + + ctx->text_name = strbuf_new(); + strbuf_catf(ctx->text_name, "HMAC-%s%s", + ctx->hashalg->text_basename, extra->suffix); + if (extra->annotation || ctx->hashalg->annotation) { + strbuf_catf(ctx->text_name, " ("); + const char *sep = ""; + if (extra->annotation) { + strbuf_catf(ctx->text_name, "%s%s", sep, extra->annotation); + sep = ", "; + } + if (ctx->hashalg->annotation) { + strbuf_catf(ctx->text_name, "%s%s", sep, ctx->hashalg->annotation); + sep = ", "; + } + strbuf_catf(ctx->text_name, ")"); + } + + ctx->mac.vt = alg; + BinarySink_DELEGATE_INIT(&ctx->mac, ctx->h_live); + + return &ctx->mac; +} + +static void hmac_free(ssh2_mac *mac) +{ + struct hmac *ctx = container_of(mac, struct hmac, mac); + + ssh_hash_free(ctx->h_outer); + ssh_hash_free(ctx->h_inner); + ssh_hash_free(ctx->h_live); + smemclr(ctx->digest, ctx->hashalg->hlen); + sfree(ctx->digest); + strbuf_free(ctx->text_name); + + smemclr(ctx, sizeof(*ctx)); + sfree(ctx); +} + +#define PAD_OUTER 0x5C +#define PAD_INNER 0x36 + +static void hmac_key(ssh2_mac *mac, ptrlen key) +{ + struct hmac *ctx = container_of(mac, struct hmac, mac); + + const uint8_t *kp; + size_t klen; + strbuf *sb = NULL; + + if (ctx->keyed) { + /* + * If we've already been keyed, throw away the existing hash + * objects and make a fresh pair to put the new key in. + */ + ssh_hash_free(ctx->h_outer); + ssh_hash_free(ctx->h_inner); + ctx->h_outer = ssh_hash_new(ctx->hashalg); + ctx->h_inner = ssh_hash_new(ctx->hashalg); + } + ctx->keyed = true; + + if (key.len > ctx->hashalg->blocklen) { + /* + * RFC 2104 section 2: if the key exceeds the block length of + * the underlying hash, then we start by hashing the key, and + * use that hash as the 'true' key for the HMAC construction. + */ + sb = strbuf_new_nm(); + strbuf_append(sb, ctx->hashalg->hlen); + + ssh_hash *htmp = ssh_hash_new(ctx->hashalg); + put_datapl(htmp, key); + ssh_hash_final(htmp, sb->u); + + kp = sb->u; + klen = sb->len; + } else { + /* + * A short enough key is used as is. + */ + kp = (const uint8_t *)key.ptr; + klen = key.len; + } + + if (ctx->h_outer) + ssh_hash_free(ctx->h_outer); + if (ctx->h_inner) + ssh_hash_free(ctx->h_inner); + + ctx->h_outer = ssh_hash_new(ctx->hashalg); + for (size_t i = 0; i < klen; i++) + put_byte(ctx->h_outer, PAD_OUTER ^ kp[i]); + for (size_t i = klen; i < ctx->hashalg->blocklen; i++) + put_byte(ctx->h_outer, PAD_OUTER); + + ctx->h_inner = ssh_hash_new(ctx->hashalg); + for (size_t i = 0; i < klen; i++) + put_byte(ctx->h_inner, PAD_INNER ^ kp[i]); + for (size_t i = klen; i < ctx->hashalg->blocklen; i++) + put_byte(ctx->h_inner, PAD_INNER); + + if (sb) + strbuf_free(sb); +} + +static void hmac_start(ssh2_mac *mac) +{ + struct hmac *ctx = container_of(mac, struct hmac, mac); + + ssh_hash_free(ctx->h_live); + ctx->h_live = ssh_hash_copy(ctx->h_inner); + BinarySink_DELEGATE_INIT(&ctx->mac, ctx->h_live); +} + +static void hmac_genresult(ssh2_mac *mac, unsigned char *output) +{ + struct hmac *ctx = container_of(mac, struct hmac, mac); + ssh_hash *htmp; + + /* Leave h_live in place, so that the SSH-2 BPP can continue + * regenerating test results from different-length prefixes of the + * packet */ + htmp = ssh_hash_copy(ctx->h_live); + ssh_hash_final(htmp, ctx->digest); + + htmp = ssh_hash_copy(ctx->h_outer); + put_data(htmp, ctx->digest, ctx->hashalg->hlen); + ssh_hash_final(htmp, ctx->digest); + + /* + * Some instances of HMAC truncate the output hash, so instead of + * writing it directly to 'output' we wrote it to our own + * full-length buffer, and now we copy the required amount. + */ + memcpy(output, ctx->digest, mac->vt->len); + smemclr(ctx->digest, ctx->hashalg->hlen); +} + +static const char *hmac_text_name(ssh2_mac *mac) +{ + struct hmac *ctx = container_of(mac, struct hmac, mac); + return ctx->text_name->s; +} + +const struct hmac_extra ssh_hmac_sha256_extra = { &ssh_sha256, "" }; +const ssh2_macalg ssh_hmac_sha256 = { + hmac_new, hmac_free, hmac_key, + hmac_start, hmac_genresult, hmac_text_name, + "hmac-sha2-256", "hmac-sha2-256-etm@openssh.com", + 32, 32, &ssh_hmac_sha256_extra, +}; + +const struct hmac_extra ssh_hmac_md5_extra = { &ssh_md5, "" }; +const ssh2_macalg ssh_hmac_md5 = { + hmac_new, hmac_free, hmac_key, + hmac_start, hmac_genresult, hmac_text_name, + "hmac-md5", "hmac-md5-etm@openssh.com", + 16, 16, &ssh_hmac_md5_extra, +}; + +const struct hmac_extra ssh_hmac_sha1_extra = { &ssh_sha1, "" }; + +const ssh2_macalg ssh_hmac_sha1 = { + hmac_new, hmac_free, hmac_key, + hmac_start, hmac_genresult, hmac_text_name, + "hmac-sha1", "hmac-sha1-etm@openssh.com", + 20, 20, &ssh_hmac_sha1_extra, +}; + +const struct hmac_extra ssh_hmac_sha1_96_extra = { &ssh_sha1, "-96" }; + +const ssh2_macalg ssh_hmac_sha1_96 = { + hmac_new, hmac_free, hmac_key, + hmac_start, hmac_genresult, hmac_text_name, + "hmac-sha1-96", "hmac-sha1-96-etm@openssh.com", + 12, 20, &ssh_hmac_sha1_96_extra, +}; + +const struct hmac_extra ssh_hmac_sha1_buggy_extra = { + &ssh_sha1, "", "bug-compatible" +}; + +const ssh2_macalg ssh_hmac_sha1_buggy = { + hmac_new, hmac_free, hmac_key, + hmac_start, hmac_genresult, hmac_text_name, + "hmac-sha1", NULL, + 20, 16, &ssh_hmac_sha1_buggy_extra, +}; + +const struct hmac_extra ssh_hmac_sha1_96_buggy_extra = { + &ssh_sha1, "-96", "bug-compatible" +}; + +const ssh2_macalg ssh_hmac_sha1_96_buggy = { + hmac_new, hmac_free, hmac_key, + hmac_start, hmac_genresult, hmac_text_name, + "hmac-sha1-96", NULL, + 12, 16, &ssh_hmac_sha1_96_buggy_extra, +}; diff --git a/0.73_My_PuTTY/sshmac.c b/0.74_My_PuTTY/sshmac.c similarity index 100% rename from 0.73_My_PuTTY/sshmac.c rename to 0.74_My_PuTTY/sshmac.c diff --git a/0.73_My_PuTTY/sshmd5.c b/0.74_My_PuTTY/sshmd5.c similarity index 100% rename from 0.73_My_PuTTY/sshmd5.c rename to 0.74_My_PuTTY/sshmd5.c diff --git a/0.73_My_PuTTY/sshnogss.c b/0.74_My_PuTTY/sshnogss.c similarity index 100% rename from 0.73_My_PuTTY/sshnogss.c rename to 0.74_My_PuTTY/sshnogss.c diff --git a/0.73_My_PuTTY/sshppl.h b/0.74_My_PuTTY/sshppl.h similarity index 91% rename from 0.73_My_PuTTY/sshppl.h rename to 0.74_My_PuTTY/sshppl.h index 7a53747..cb90c77 100644 --- a/0.73_My_PuTTY/sshppl.h +++ b/0.74_My_PuTTY/sshppl.h @@ -10,7 +10,7 @@ typedef void (*packet_handler_fn_t)(PacketProtocolLayer *ppl, PktIn *pktin); struct PacketProtocolLayerVtable { - void (*free)(PacketProtocolLayer *); + void (*free)(PacketProtocolLayer *); void (*process_queue)(PacketProtocolLayer *ppl); bool (*get_specials)( PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx); @@ -19,6 +19,7 @@ struct PacketProtocolLayerVtable { bool (*want_user_input)(PacketProtocolLayer *ppl); void (*got_user_input)(PacketProtocolLayer *ppl); void (*reconfigure)(PacketProtocolLayer *ppl, Conf *conf); + size_t (*queued_data_size)(PacketProtocolLayer *ppl); /* Protocol-level name of this layer. */ const char *name; @@ -73,6 +74,8 @@ static inline void ssh_ppl_got_user_input(PacketProtocolLayer *ppl) { ppl->vt->got_user_input(ppl); } static inline void ssh_ppl_reconfigure(PacketProtocolLayer *ppl, Conf *conf) { ppl->vt->reconfigure(ppl, conf); } +static inline size_t ssh_ppl_queued_data_size(PacketProtocolLayer *ppl) +{ return ppl->vt->queued_data_size(ppl); } /* ssh_ppl_free is more than just a macro wrapper on the vtable; it * does centralised parts of the freeing too. */ @@ -90,6 +93,11 @@ void ssh_ppl_setup_queues(PacketProtocolLayer *ppl, * avoid dereferencing itself on return from this function! */ void ssh_ppl_replace(PacketProtocolLayer *old, PacketProtocolLayer *new); +/* Default implementation of queued_data_size, which just adds up the + * sizes of all the packets in pq_out. A layer can override this if it + * has other things to take into account as well. */ +size_t ssh_ppl_default_queued_data_size(PacketProtocolLayer *ppl); + PacketProtocolLayer *ssh1_login_new( Conf *conf, const char *host, int port, PacketProtocolLayer *successor_layer); diff --git a/0.73_My_PuTTY/sshprime.c b/0.74_My_PuTTY/sshprime.c similarity index 100% rename from 0.73_My_PuTTY/sshprime.c rename to 0.74_My_PuTTY/sshprime.c diff --git a/0.73_My_PuTTY/sshprng.c b/0.74_My_PuTTY/sshprng.c similarity index 94% rename from 0.73_My_PuTTY/sshprng.c rename to 0.74_My_PuTTY/sshprng.c index ea40c99..aeba936 100644 --- a/0.73_My_PuTTY/sshprng.c +++ b/0.74_My_PuTTY/sshprng.c @@ -1,292 +1,292 @@ -/* - * sshprng.c: PuTTY's cryptographic pseudorandom number generator. - * - * This module just defines the PRNG object type and its methods. The - * usual global instance of it is managed by sshrand.c. - */ - -#include "putty.h" -#include "ssh.h" -#include "mpint.h" - -#ifdef PRNG_DIAGNOSTICS -#define prngdebug debug -#else -#define prngdebug(...) ((void)0) -#endif - -/* - * This random number generator is based on the 'Fortuna' design by - * Niels Ferguson and Bruce Schneier. The biggest difference is that I - * use SHA-256 in place of a block cipher: the generator side of the - * system works by computing HASH(key || counter) instead of - * ENCRYPT(counter, key). - * - * Rationale: the Fortuna description itself suggests that using - * SHA-256 would be nice but people wouldn't accept it because it's - * too slow - but PuTTY isn't a heavy enough user of random numbers to - * make that a serious worry. In fact even with SHA-256 this generator - * is faster than the one we previously used. Also the Fortuna - * description worries about periodic rekeying to avoid the barely - * detectable pattern of never repeating a cipher block - but with - * SHA-256, even that shouldn't be a worry, because the output - * 'blocks' are twice the size, and also SHA-256 has no guarantee of - * bijectivity, so it surely _could_ be possible to generate the same - * block from two counter values. Thirdly, Fortuna has to have a hash - * function anyway, for reseeding and entropy collection, so reusing - * the same one means it only depends on one underlying primitive and - * can be easily reinstantiated with a larger hash function if you - * decide you'd like to do that on a particular occasion. - */ - -#define NCOLLECTORS 32 -#define RESEED_DATA_SIZE 64 - -typedef struct prng_impl prng_impl; -struct prng_impl { - prng Prng; - - const ssh_hashalg *hashalg; - - /* - * Generation side: - * - * 'generator' is a hash object with the current key preloaded - * into it. The counter-mode generation is achieved by copying - * that hash object, appending the counter value to the copy, and - * calling ssh_hash_final. - * - * pending_output is a buffer of size equal to the hash length, - * which receives each of those hashes as it's generated. The - * bytes of the hash are returned in reverse order, just because - * that made it marginally easier to deal with the - * pending_output_remaining field. - */ - ssh_hash *generator; - mp_int *counter; - uint8_t *pending_output; - size_t pending_output_remaining; - - /* - * When re-seeding the generator, you call prng_seed_begin(), - * which sets up a hash object in 'keymaker'. You write your new - * seed data into it (which you can do by calling put_data on the - * PRNG object itself) and then call prng_seed_finish(), which - * finalises this hash and uses the output to set up the new - * generator. - * - * The keymaker hash preimage includes the previous key, so if you - * just want to change keys for the sake of not keeping the same - * one for too long, you don't have to put any extra seed data in - * at all. - */ - ssh_hash *keymaker; - - /* - * Collection side: - * - * There are NCOLLECTORS hash objects collecting entropy. Each - * separately numbered entropy source puts its output into those - * hash objects in the order 0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,..., - * that is to say, each entropy source has a separate counter - * which is incremented every time that source generates an event, - * and the event data is added to the collector corresponding to - * the index of the lowest set bit in the current counter value. - * - * Whenever collector #0 has at least RESEED_DATA_SIZE bytes (and - * it's not at least 100ms since the last reseed), the PRNG is - * reseeded, with seed data on reseed #n taken from the first j - * collectors, where j is one more than the number of factors of 2 - * in n. That is, collector #0 is used in every reseed; #1 in - * every other one, #2 in every fourth, etc. - * - * 'until_reseed' counts the amount of data that still needs to be - * added to collector #0 before a reseed will be triggered. - */ - uint32_t source_counters[NOISE_MAX_SOURCES]; - ssh_hash *collectors[NCOLLECTORS]; - size_t until_reseed; - uint32_t reseeds; - uint64_t last_reseed_time; -}; - -static void prng_seed_BinarySink_write( - BinarySink *bs, const void *data, size_t len); - -prng *prng_new(const ssh_hashalg *hashalg) -{ - prng_impl *pi = snew(prng_impl); - - memset(pi, 0, sizeof(prng_impl)); - pi->hashalg = hashalg; - pi->keymaker = NULL; - pi->generator = NULL; - pi->pending_output = snewn(pi->hashalg->hlen, uint8_t); - pi->pending_output_remaining = 0; - pi->counter = mp_new(128); - for (size_t i = 0; i < NCOLLECTORS; i++) - pi->collectors[i] = ssh_hash_new(pi->hashalg); - pi->until_reseed = 0; - BinarySink_INIT(&pi->Prng, prng_seed_BinarySink_write); - - pi->Prng.savesize = pi->hashalg->hlen * 4; - - return &pi->Prng; -} - -void prng_free(prng *pr) -{ - prng_impl *pi = container_of(pr, prng_impl, Prng); - - sfree(pi->pending_output); - mp_free(pi->counter); - for (size_t i = 0; i < NCOLLECTORS; i++) - ssh_hash_free(pi->collectors[i]); - if (pi->generator) - ssh_hash_free(pi->generator); - if (pi->keymaker) - ssh_hash_free(pi->keymaker); - smemclr(pi, sizeof(*pi)); - sfree(pi); -} - -void prng_seed_begin(prng *pr) -{ - prng_impl *pi = container_of(pr, prng_impl, Prng); - - assert(!pi->keymaker); - - prngdebug("prng: reseed begin\n"); - - /* - * Make a hash instance that will generate the key for the new one. - */ - if (pi->generator) { - pi->keymaker = pi->generator; - pi->generator = NULL; - } else { - pi->keymaker = ssh_hash_new(pi->hashalg); - } - - put_byte(pi->keymaker, 'R'); -} - -static void prng_seed_BinarySink_write( - BinarySink *bs, const void *data, size_t len) -{ - prng *pr = BinarySink_DOWNCAST(bs, prng); - prng_impl *pi = container_of(pr, prng_impl, Prng); - assert(pi->keymaker); - prngdebug("prng: got %zu bytes of seed\n", len); - put_data(pi->keymaker, data, len); -} - -void prng_seed_finish(prng *pr) -{ - prng_impl *pi = container_of(pr, prng_impl, Prng); - - assert(pi->keymaker); - - prngdebug("prng: reseed finish\n"); - - /* - * Actually generate the key. - */ - ssh_hash_final(pi->keymaker, pi->pending_output); - pi->keymaker = NULL; - - /* - * Load that key into a fresh hash instance, which will become the - * new generator. - */ - assert(!pi->generator); - pi->generator = ssh_hash_new(pi->hashalg); - put_data(pi->generator, pi->pending_output, pi->hashalg->hlen); - smemclr(pi->pending_output, pi->hashalg->hlen); - - pi->until_reseed = RESEED_DATA_SIZE; - pi->last_reseed_time = prng_reseed_time_ms(); - pi->pending_output_remaining = 0; -} - -static inline void prng_generate(prng_impl *pi) -{ - ssh_hash *h = ssh_hash_copy(pi->generator); - - prngdebug("prng_generate\n"); - - put_byte(h, 'G'); - put_mp_ssh2(h, pi->counter); - mp_add_integer_into(pi->counter, pi->counter, 1); - ssh_hash_final(h, pi->pending_output); - pi->pending_output_remaining = pi->hashalg->hlen; -} - -void prng_read(prng *pr, void *vout, size_t size) -{ - prng_impl *pi = container_of(pr, prng_impl, Prng); - - assert(!pi->keymaker); - - prngdebug("prng_read %zu\n", size); - - uint8_t *out = (uint8_t *)vout; - for (; size > 0; size--) { - if (pi->pending_output_remaining == 0) - prng_generate(pi); - pi->pending_output_remaining--; - *out++ = pi->pending_output[pi->pending_output_remaining]; - pi->pending_output[pi->pending_output_remaining] = 0; - } - - prng_seed_begin(&pi->Prng); - prng_seed_finish(&pi->Prng); -} - -void prng_add_entropy(prng *pr, unsigned source_id, ptrlen data) -{ - prng_impl *pi = container_of(pr, prng_impl, Prng); - - assert(source_id < NOISE_MAX_SOURCES); - uint32_t counter = ++pi->source_counters[source_id]; - - size_t index = 0; - while (index+1 < NCOLLECTORS && !(counter & 1)) { - counter >>= 1; - index++; - } - - prngdebug("prng_add_entropy source=%u size=%zu -> collector %zi\n", - source_id, data.len, index); - - put_datapl(pi->collectors[index], data); - - if (index == 0) - pi->until_reseed = (pi->until_reseed < data.len ? 0 : - pi->until_reseed - data.len); - - if (pi->until_reseed == 0 && - prng_reseed_time_ms() - pi->last_reseed_time >= 100) { - prng_seed_begin(&pi->Prng); - - uint32_t reseed_index = ++pi->reseeds; - prngdebug("prng entropy reseed #%"PRIu32"\n", reseed_index); - for (size_t i = 0; i < NCOLLECTORS; i++) { - prngdebug("emptying collector %zu\n", i); - ssh_hash_final(pi->collectors[i], pi->pending_output); - put_data(&pi->Prng, pi->pending_output, pi->hashalg->hlen); - pi->collectors[i] = ssh_hash_new(pi->hashalg); - if (reseed_index & 1) - break; - reseed_index >>= 1; - } - - prng_seed_finish(&pi->Prng); - } -} - -size_t prng_seed_bits(prng *pr) -{ - prng_impl *pi = container_of(pr, prng_impl, Prng); - return pi->hashalg->hlen * 8; -} +/* + * sshprng.c: PuTTY's cryptographic pseudorandom number generator. + * + * This module just defines the PRNG object type and its methods. The + * usual global instance of it is managed by sshrand.c. + */ + +#include "putty.h" +#include "ssh.h" +#include "mpint.h" + +#ifdef PRNG_DIAGNOSTICS +#define prngdebug debug +#else +#define prngdebug(...) ((void)0) +#endif + +/* + * This random number generator is based on the 'Fortuna' design by + * Niels Ferguson and Bruce Schneier. The biggest difference is that I + * use SHA-256 in place of a block cipher: the generator side of the + * system works by computing HASH(key || counter) instead of + * ENCRYPT(counter, key). + * + * Rationale: the Fortuna description itself suggests that using + * SHA-256 would be nice but people wouldn't accept it because it's + * too slow - but PuTTY isn't a heavy enough user of random numbers to + * make that a serious worry. In fact even with SHA-256 this generator + * is faster than the one we previously used. Also the Fortuna + * description worries about periodic rekeying to avoid the barely + * detectable pattern of never repeating a cipher block - but with + * SHA-256, even that shouldn't be a worry, because the output + * 'blocks' are twice the size, and also SHA-256 has no guarantee of + * bijectivity, so it surely _could_ be possible to generate the same + * block from two counter values. Thirdly, Fortuna has to have a hash + * function anyway, for reseeding and entropy collection, so reusing + * the same one means it only depends on one underlying primitive and + * can be easily reinstantiated with a larger hash function if you + * decide you'd like to do that on a particular occasion. + */ + +#define NCOLLECTORS 32 +#define RESEED_DATA_SIZE 64 + +typedef struct prng_impl prng_impl; +struct prng_impl { + prng Prng; + + const ssh_hashalg *hashalg; + + /* + * Generation side: + * + * 'generator' is a hash object with the current key preloaded + * into it. The counter-mode generation is achieved by copying + * that hash object, appending the counter value to the copy, and + * calling ssh_hash_final. + * + * pending_output is a buffer of size equal to the hash length, + * which receives each of those hashes as it's generated. The + * bytes of the hash are returned in reverse order, just because + * that made it marginally easier to deal with the + * pending_output_remaining field. + */ + ssh_hash *generator; + mp_int *counter; + uint8_t *pending_output; + size_t pending_output_remaining; + + /* + * When re-seeding the generator, you call prng_seed_begin(), + * which sets up a hash object in 'keymaker'. You write your new + * seed data into it (which you can do by calling put_data on the + * PRNG object itself) and then call prng_seed_finish(), which + * finalises this hash and uses the output to set up the new + * generator. + * + * The keymaker hash preimage includes the previous key, so if you + * just want to change keys for the sake of not keeping the same + * one for too long, you don't have to put any extra seed data in + * at all. + */ + ssh_hash *keymaker; + + /* + * Collection side: + * + * There are NCOLLECTORS hash objects collecting entropy. Each + * separately numbered entropy source puts its output into those + * hash objects in the order 0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,..., + * that is to say, each entropy source has a separate counter + * which is incremented every time that source generates an event, + * and the event data is added to the collector corresponding to + * the index of the lowest set bit in the current counter value. + * + * Whenever collector #0 has at least RESEED_DATA_SIZE bytes (and + * it's not at least 100ms since the last reseed), the PRNG is + * reseeded, with seed data on reseed #n taken from the first j + * collectors, where j is one more than the number of factors of 2 + * in n. That is, collector #0 is used in every reseed; #1 in + * every other one, #2 in every fourth, etc. + * + * 'until_reseed' counts the amount of data that still needs to be + * added to collector #0 before a reseed will be triggered. + */ + uint32_t source_counters[NOISE_MAX_SOURCES]; + ssh_hash *collectors[NCOLLECTORS]; + size_t until_reseed; + uint32_t reseeds; + uint64_t last_reseed_time; +}; + +static void prng_seed_BinarySink_write( + BinarySink *bs, const void *data, size_t len); + +prng *prng_new(const ssh_hashalg *hashalg) +{ + prng_impl *pi = snew(prng_impl); + + memset(pi, 0, sizeof(prng_impl)); + pi->hashalg = hashalg; + pi->keymaker = NULL; + pi->generator = NULL; + pi->pending_output = snewn(pi->hashalg->hlen, uint8_t); + pi->pending_output_remaining = 0; + pi->counter = mp_new(128); + for (size_t i = 0; i < NCOLLECTORS; i++) + pi->collectors[i] = ssh_hash_new(pi->hashalg); + pi->until_reseed = 0; + BinarySink_INIT(&pi->Prng, prng_seed_BinarySink_write); + + pi->Prng.savesize = pi->hashalg->hlen * 4; + + return &pi->Prng; +} + +void prng_free(prng *pr) +{ + prng_impl *pi = container_of(pr, prng_impl, Prng); + + sfree(pi->pending_output); + mp_free(pi->counter); + for (size_t i = 0; i < NCOLLECTORS; i++) + ssh_hash_free(pi->collectors[i]); + if (pi->generator) + ssh_hash_free(pi->generator); + if (pi->keymaker) + ssh_hash_free(pi->keymaker); + smemclr(pi, sizeof(*pi)); + sfree(pi); +} + +void prng_seed_begin(prng *pr) +{ + prng_impl *pi = container_of(pr, prng_impl, Prng); + + assert(!pi->keymaker); + + prngdebug("prng: reseed begin\n"); + + /* + * Make a hash instance that will generate the key for the new one. + */ + if (pi->generator) { + pi->keymaker = pi->generator; + pi->generator = NULL; + } else { + pi->keymaker = ssh_hash_new(pi->hashalg); + } + + put_byte(pi->keymaker, 'R'); +} + +static void prng_seed_BinarySink_write( + BinarySink *bs, const void *data, size_t len) +{ + prng *pr = BinarySink_DOWNCAST(bs, prng); + prng_impl *pi = container_of(pr, prng_impl, Prng); + assert(pi->keymaker); + prngdebug("prng: got %"SIZEu" bytes of seed\n", len); + put_data(pi->keymaker, data, len); +} + +void prng_seed_finish(prng *pr) +{ + prng_impl *pi = container_of(pr, prng_impl, Prng); + + assert(pi->keymaker); + + prngdebug("prng: reseed finish\n"); + + /* + * Actually generate the key. + */ + ssh_hash_final(pi->keymaker, pi->pending_output); + pi->keymaker = NULL; + + /* + * Load that key into a fresh hash instance, which will become the + * new generator. + */ + assert(!pi->generator); + pi->generator = ssh_hash_new(pi->hashalg); + put_data(pi->generator, pi->pending_output, pi->hashalg->hlen); + smemclr(pi->pending_output, pi->hashalg->hlen); + + pi->until_reseed = RESEED_DATA_SIZE; + pi->last_reseed_time = prng_reseed_time_ms(); + pi->pending_output_remaining = 0; +} + +static inline void prng_generate(prng_impl *pi) +{ + ssh_hash *h = ssh_hash_copy(pi->generator); + + prngdebug("prng_generate\n"); + + put_byte(h, 'G'); + put_mp_ssh2(h, pi->counter); + mp_add_integer_into(pi->counter, pi->counter, 1); + ssh_hash_final(h, pi->pending_output); + pi->pending_output_remaining = pi->hashalg->hlen; +} + +void prng_read(prng *pr, void *vout, size_t size) +{ + prng_impl *pi = container_of(pr, prng_impl, Prng); + + assert(!pi->keymaker); + + prngdebug("prng_read %"SIZEu"\n", size); + + uint8_t *out = (uint8_t *)vout; + for (; size > 0; size--) { + if (pi->pending_output_remaining == 0) + prng_generate(pi); + pi->pending_output_remaining--; + *out++ = pi->pending_output[pi->pending_output_remaining]; + pi->pending_output[pi->pending_output_remaining] = 0; + } + + prng_seed_begin(&pi->Prng); + prng_seed_finish(&pi->Prng); +} + +void prng_add_entropy(prng *pr, unsigned source_id, ptrlen data) +{ + prng_impl *pi = container_of(pr, prng_impl, Prng); + + assert(source_id < NOISE_MAX_SOURCES); + uint32_t counter = ++pi->source_counters[source_id]; + + size_t index = 0; + while (index+1 < NCOLLECTORS && !(counter & 1)) { + counter >>= 1; + index++; + } + + prngdebug("prng_add_entropy source=%u size=%"SIZEu" -> collector %zi\n", + source_id, data.len, index); + + put_datapl(pi->collectors[index], data); + + if (index == 0) + pi->until_reseed = (pi->until_reseed < data.len ? 0 : + pi->until_reseed - data.len); + + if (pi->until_reseed == 0 && + prng_reseed_time_ms() - pi->last_reseed_time >= 100) { + prng_seed_begin(&pi->Prng); + + uint32_t reseed_index = ++pi->reseeds; + prngdebug("prng entropy reseed #%"PRIu32"\n", reseed_index); + for (size_t i = 0; i < NCOLLECTORS; i++) { + prngdebug("emptying collector %"SIZEu"\n", i); + ssh_hash_final(pi->collectors[i], pi->pending_output); + put_data(&pi->Prng, pi->pending_output, pi->hashalg->hlen); + pi->collectors[i] = ssh_hash_new(pi->hashalg); + if (reseed_index & 1) + break; + reseed_index >>= 1; + } + + prng_seed_finish(&pi->Prng); + } +} + +size_t prng_seed_bits(prng *pr) +{ + prng_impl *pi = container_of(pr, prng_impl, Prng); + return pi->hashalg->hlen * 8; +} diff --git a/0.73_My_PuTTY/sshpubk.c b/0.74_My_PuTTY/sshpubk.c similarity index 96% rename from 0.73_My_PuTTY/sshpubk.c rename to 0.74_My_PuTTY/sshpubk.c index 946c196..8ef9b19 100644 --- a/0.73_My_PuTTY/sshpubk.c +++ b/0.74_My_PuTTY/sshpubk.c @@ -13,9 +13,6 @@ #include "mpint.h" #include "ssh.h" #include "misc.h" -#ifdef MOD_WINCRYPT -#include "wincrypt/wincrypto.h" -#endif #define rsa_signature "SSH PRIVATE KEY FILE FORMAT 1.1\n" @@ -403,8 +400,8 @@ bool rsa_ssh1_savekey(const Filename *filename, RSAKey *key, /* * PuTTY's own format for SSH-2 keys is as follows: * - * The file is text. Lines are terminated by CRLF, although CR-only - * and LF-only are tolerated on input. + * The file is text. Lines are terminated by LF by preference, + * although CRLF and CR-only are tolerated on input. * * The first line says "PuTTY-User-Key-File-2: " plus the name of the * algorithm ("ssh-dss", "ssh-rsa" etc). @@ -629,25 +626,6 @@ ssh2_userkey *ssh2_load_userkey( encryption = comment = mac = NULL; public_blob = private_blob = NULL; -#ifdef MOD_WINCRYPT - Filename *commentPath; -#ifdef HAS_WINX509 - if (0 == strncmp("cert://", filename->path, 7) - || 0 == strncmp("x509://", filename->path, 7)) { - commentPath = filename_copy(filename); - ret = snew(ssh2_userkey); - ret->comment = commentPath->path; - - ret->key = ssh_key_new_priv( - &ssh_rsa_wincrypt, make_ptrlen(commentPath->path, strlen(commentPath->path)), make_ptrlen(NULL, 0)); - - if (errorstr) - *errorstr = NULL; - return ret; - } -#endif /*HAS_WINX509*/ -#endif - fp = f_open(filename, "rb", false); if (!fp) { error = "can't open file"; @@ -1080,11 +1058,7 @@ bool openssh_loadpub(FILE *fp, char **algorithm, return false; } -#ifdef MOD_WINCRYPT -bool ssh2_userkey_loadpub(const Filename **filename, char **algorithm, -#else bool ssh2_userkey_loadpub(const Filename *filename, char **algorithm, -#endif BinarySink *bs, char **commentptr, const char **errorstr) { @@ -1095,34 +1069,7 @@ bool ssh2_userkey_loadpub(const Filename *filename, char **algorithm, const char *error = NULL; char *comment = NULL; -#ifdef MOD_WINCRYPT -#ifdef HAS_WINX509 - alg = NULL; - if (0 == strncmp("cert://", (*filename)->path, 7)) { - alg = &ssh_rsa_wincrypt; - } - if (0 == strncmp("x509://", (*filename)->path, 7)) { - alg = &ssh_x509_wincrypt; - } - if (alg != NULL) { - if (algorithm) - *algorithm = dupstr(alg->ssh_id); - if (capi_load_key(filename, bs)) { - if (commentptr) - (*commentptr) = dupstr((*filename)->path); - return true; - } else { - if (errorstr) - *errorstr = dupstr("User aborted"); - return false; - } - } -#endif /* HAS_WINX509 */ - - fp = f_open((*filename), "rb", false); -#else fp = f_open(filename, "rb", false); -#endif if (!fp) { error = "can't open file"; goto error; @@ -1415,8 +1362,8 @@ char *ssh1_pubkey_str(RSAKey *key) dec1 = mp_get_decimal(key->exponent); dec2 = mp_get_decimal(key->modulus); - buffer = dupprintf("%zd %s %s%s%s", mp_get_nbits(key->modulus), dec1, dec2, - key->comment ? " " : "", + buffer = dupprintf("%"SIZEu" %s %s%s%s", mp_get_nbits(key->modulus), + dec1, dec2, key->comment ? " " : "", key->comment ? key->comment : ""); sfree(dec1); sfree(dec2); @@ -1573,15 +1520,7 @@ char *ssh2_fingerprint_blob(ptrlen blob) * No algorithm available (which means a seriously confused * key blob, but there we go). Return only the hash. */ -#ifdef MOD_WINCRYPT -#ifdef HAS_WINX509 - return dupcat("x509v3-sign-rsa\t", fingerprint_str, NULL); -#else - return dupcat(fingerprint_str, NULL); -#endif -#else return dupstr(fingerprint_str); -#endif } } @@ -1647,15 +1586,6 @@ int key_type(const Filename *filename) FILE *fp; int ret; -#ifdef MOD_WINCRYPT -#ifdef HAS_WINX509 - if (0 == strncmp("cert://", filename->path, 7) - || 0 == strncmp("x509://", filename->path, 7)) { - return SSH_KEYTYPE_SSH2; - } -#endif /* HAS_WINX509 */ -#endif - fp = f_open(filename, "r", false); if (!fp) return SSH_KEYTYPE_UNOPENABLE; diff --git a/0.73_My_PuTTY/sshrand.c b/0.74_My_PuTTY/sshrand.c similarity index 100% rename from 0.73_My_PuTTY/sshrand.c rename to 0.74_My_PuTTY/sshrand.c diff --git a/0.73_My_PuTTY/sshrsa.c b/0.74_My_PuTTY/sshrsa.c similarity index 91% rename from 0.73_My_PuTTY/sshrsa.c rename to 0.74_My_PuTTY/sshrsa.c index 4d26f9f..8595eb5 100644 --- a/0.73_My_PuTTY/sshrsa.c +++ b/0.74_My_PuTTY/sshrsa.c @@ -1,1020 +1,1038 @@ -/* - * RSA implementation for PuTTY. - */ - -#include -#include -#include -#include - -#include "ssh.h" -#include "mpint.h" -#include "misc.h" - -void BinarySource_get_rsa_ssh1_pub( - BinarySource *src, RSAKey *rsa, RsaSsh1Order order) -{ - unsigned bits; - mp_int *e, *m; - - bits = get_uint32(src); - if (order == RSA_SSH1_EXPONENT_FIRST) { - e = get_mp_ssh1(src); - m = get_mp_ssh1(src); - } else { - m = get_mp_ssh1(src); - e = get_mp_ssh1(src); - } - - if (rsa) { - rsa->bits = bits; - rsa->exponent = e; - rsa->modulus = m; - rsa->bytes = (mp_get_nbits(m) + 7) / 8; - } else { - mp_free(e); - mp_free(m); - } -} - -void BinarySource_get_rsa_ssh1_priv( - BinarySource *src, RSAKey *rsa) -{ - rsa->private_exponent = get_mp_ssh1(src); -} - -bool rsa_ssh1_encrypt(unsigned char *data, int length, RSAKey *key) -{ - mp_int *b1, *b2; - int i; - unsigned char *p; - - if (key->bytes < length + 4) - return false; /* RSA key too short! */ - - memmove(data + key->bytes - length, data, length); - data[0] = 0; - data[1] = 2; - - size_t npad = key->bytes - length - 3; - /* - * Generate a sequence of nonzero padding bytes. We do this in a - * reasonably uniform way and without having to loop round - * retrying the random number generation, by first generating an - * integer in [0,2^n) for an appropriately large n; then we - * repeatedly multiply by 255 to give an integer in [0,255*2^n), - * extract the top 8 bits to give an integer in [0,255), and mask - * those bits off before multiplying up again for the next digit. - * This gives us a sequence of numbers in [0,255), and of course - * adding 1 to each of them gives numbers in [1,256) as we wanted. - * - * (You could imagine this being a sort of fixed-point operation: - * given a uniformly random binary _fraction_, multiplying it by k - * and subtracting off the integer part will yield you a sequence - * of integers each in [0,k). I'm just doing that scaled up by a - * power of 2 to avoid the fractions.) - */ - size_t random_bits = (npad + 16) * 8; - mp_int *randval = mp_new(random_bits + 8); - mp_int *tmp = mp_random_bits(random_bits); - mp_copy_into(randval, tmp); - mp_free(tmp); - for (i = 2; i < key->bytes - length - 1; i++) { - mp_mul_integer_into(randval, randval, 255); - uint8_t byte = mp_get_byte(randval, random_bits / 8); - assert(byte != 255); - data[i] = byte + 1; - mp_reduce_mod_2to(randval, random_bits); - } - mp_free(randval); - data[key->bytes - length - 1] = 0; - - b1 = mp_from_bytes_be(make_ptrlen(data, key->bytes)); - - b2 = mp_modpow(b1, key->exponent, key->modulus); - - p = data; - for (i = key->bytes; i--;) { - *p++ = mp_get_byte(b2, i); - } - - mp_free(b1); - mp_free(b2); - - return true; -} - -/* - * Compute (base ^ exp) % mod, provided mod == p * q, with p,q - * distinct primes, and iqmp is the multiplicative inverse of q mod p. - * Uses Chinese Remainder Theorem to speed computation up over the - * obvious implementation of a single big modpow. - */ -mp_int *crt_modpow(mp_int *base, mp_int *exp, mp_int *mod, - mp_int *p, mp_int *q, mp_int *iqmp) -{ - mp_int *pm1, *qm1, *pexp, *qexp, *presult, *qresult; - mp_int *diff, *multiplier, *ret0, *ret; - - /* - * Reduce the exponent mod phi(p) and phi(q), to save time when - * exponentiating mod p and mod q respectively. Of course, since p - * and q are prime, phi(p) == p-1 and similarly for q. - */ - pm1 = mp_copy(p); - mp_sub_integer_into(pm1, pm1, 1); - qm1 = mp_copy(q); - mp_sub_integer_into(qm1, qm1, 1); - pexp = mp_mod(exp, pm1); - qexp = mp_mod(exp, qm1); - - /* - * Do the two modpows. - */ - mp_int *base_mod_p = mp_mod(base, p); - presult = mp_modpow(base_mod_p, pexp, p); - mp_free(base_mod_p); - mp_int *base_mod_q = mp_mod(base, q); - qresult = mp_modpow(base_mod_q, qexp, q); - mp_free(base_mod_q); - - /* - * Recombine the results. We want a value which is congruent to - * qresult mod q, and to presult mod p. - * - * We know that iqmp * q is congruent to 1 * mod p (by definition - * of iqmp) and to 0 mod q (obviously). So we start with qresult - * (which is congruent to qresult mod both primes), and add on - * (presult-qresult) * (iqmp * q) which adjusts it to be congruent - * to presult mod p without affecting its value mod q. - * - * (If presult-qresult < 0, we add p to it to keep it positive.) - */ - unsigned presult_too_small = mp_cmp_hs(qresult, presult); - mp_cond_add_into(presult, presult, p, presult_too_small); - - diff = mp_sub(presult, qresult); - multiplier = mp_mul(iqmp, q); - ret0 = mp_mul(multiplier, diff); - mp_add_into(ret0, ret0, qresult); - - /* - * Finally, reduce the result mod n. - */ - ret = mp_mod(ret0, mod); - - /* - * Free all the intermediate results before returning. - */ - mp_free(pm1); - mp_free(qm1); - mp_free(pexp); - mp_free(qexp); - mp_free(presult); - mp_free(qresult); - mp_free(diff); - mp_free(multiplier); - mp_free(ret0); - - return ret; -} - -/* - * Wrapper on crt_modpow that looks up all the right values from an - * RSAKey. - */ -static mp_int *rsa_privkey_op(mp_int *input, RSAKey *key) -{ - return crt_modpow(input, key->private_exponent, - key->modulus, key->p, key->q, key->iqmp); -} - -mp_int *rsa_ssh1_decrypt(mp_int *input, RSAKey *key) -{ - return rsa_privkey_op(input, key); -} - -bool rsa_ssh1_decrypt_pkcs1(mp_int *input, RSAKey *key, - strbuf *outbuf) -{ - strbuf *data = strbuf_new_nm(); - bool success = false; - BinarySource src[1]; - - { - mp_int *b = rsa_ssh1_decrypt(input, key); - for (size_t i = (mp_get_nbits(key->modulus) + 7) / 8; i-- > 0 ;) { - put_byte(data, mp_get_byte(b, i)); - } - mp_free(b); - } - - BinarySource_BARE_INIT(src, data->u, data->len); - - /* Check PKCS#1 formatting prefix */ - if (get_byte(src) != 0) goto out; - if (get_byte(src) != 2) goto out; - while (1) { - unsigned char byte = get_byte(src); - if (get_err(src)) goto out; - if (byte == 0) - break; - } - - /* Everything else is the payload */ - success = true; - put_data(outbuf, get_ptr(src), get_avail(src)); - - out: - strbuf_free(data); - return success; -} - -static void append_hex_to_strbuf(strbuf *sb, mp_int *x) -{ - if (sb->len > 0) - put_byte(sb, ','); - put_data(sb, "0x", 2); - char *hex = mp_get_hex(x); - size_t hexlen = strlen(hex); - put_data(sb, hex, hexlen); - smemclr(hex, hexlen); - sfree(hex); -} - -char *rsastr_fmt(RSAKey *key) -{ - strbuf *sb = strbuf_new(); - - append_hex_to_strbuf(sb, key->exponent); - append_hex_to_strbuf(sb, key->modulus); - - return strbuf_to_str(sb); -} - -/* - * Generate a fingerprint string for the key. Compatible with the - * OpenSSH fingerprint code. - */ -char *rsa_ssh1_fingerprint(RSAKey *key) -{ - unsigned char digest[16]; - strbuf *out; - int i; - - /* - * The hash preimage for SSH-1 key fingerprinting consists of the - * modulus and exponent _without_ any preceding length field - - * just the minimum number of bytes to represent each integer, - * stored big-endian, concatenated with no marker at the division - * between them. - */ - - ssh_hash *hash = ssh_hash_new(&ssh_md5); - for (size_t i = (mp_get_nbits(key->modulus) + 7) / 8; i-- > 0 ;) - put_byte(hash, mp_get_byte(key->modulus, i)); - for (size_t i = (mp_get_nbits(key->exponent) + 7) / 8; i-- > 0 ;) - put_byte(hash, mp_get_byte(key->exponent, i)); - ssh_hash_final(hash, digest); - - out = strbuf_new(); - strbuf_catf(out, "%d ", mp_get_nbits(key->modulus)); - for (i = 0; i < 16; i++) - strbuf_catf(out, "%s%02x", i ? ":" : "", digest[i]); - if (key->comment) - strbuf_catf(out, " %s", key->comment); - return strbuf_to_str(out); -} - -/* - * Verify that the public data in an RSA key matches the private - * data. We also check the private data itself: we ensure that p > - * q and that iqmp really is the inverse of q mod p. - */ -bool rsa_verify(RSAKey *key) -{ - mp_int *n, *ed, *pm1, *qm1; - unsigned ok = 1; - - /* Preliminary checks: p,q can't be 0 or 1. (Of course no other - * very small value is any good either, but these are the values - * we _must_ check for to avoid assertion failures further down - * this function.) */ - if (!(mp_hs_integer(key->p, 2) & mp_hs_integer(key->q, 2))) - return false; - - /* n must equal pq. */ - n = mp_mul(key->p, key->q); - ok &= mp_cmp_eq(n, key->modulus); - mp_free(n); - - /* e * d must be congruent to 1, modulo (p-1) and modulo (q-1). */ - pm1 = mp_copy(key->p); - mp_sub_integer_into(pm1, pm1, 1); - ed = mp_modmul(key->exponent, key->private_exponent, pm1); - mp_free(pm1); - ok &= mp_eq_integer(ed, 1); - mp_free(ed); - - qm1 = mp_copy(key->q); - mp_sub_integer_into(qm1, qm1, 1); - ed = mp_modmul(key->exponent, key->private_exponent, qm1); - mp_free(qm1); - ok &= mp_eq_integer(ed, 1); - mp_free(ed); - - /* - * Ensure p > q. - * - * I have seen key blobs in the wild which were generated with - * p < q, so instead of rejecting the key in this case we - * should instead flip them round into the canonical order of - * p > q. This also involves regenerating iqmp. - */ - mp_int *p_new = mp_max(key->p, key->q); - mp_int *q_new = mp_min(key->p, key->q); - mp_free(key->p); - mp_free(key->q); - mp_free(key->iqmp); - key->p = p_new; - key->q = q_new; - key->iqmp = mp_invert(key->q, key->p); - - return ok; -} - -void rsa_ssh1_public_blob(BinarySink *bs, RSAKey *key, - RsaSsh1Order order) -{ - put_uint32(bs, mp_get_nbits(key->modulus)); - if (order == RSA_SSH1_EXPONENT_FIRST) { - put_mp_ssh1(bs, key->exponent); - put_mp_ssh1(bs, key->modulus); - } else { - put_mp_ssh1(bs, key->modulus); - put_mp_ssh1(bs, key->exponent); - } -} - -/* Given an SSH-1 public key blob, determine its length. */ -int rsa_ssh1_public_blob_len(ptrlen data) -{ - BinarySource src[1]; - - BinarySource_BARE_INIT_PL(src, data); - - /* Expect a length word, then exponent and modulus. (It doesn't - * even matter which order.) */ - get_uint32(src); - mp_free(get_mp_ssh1(src)); - mp_free(get_mp_ssh1(src)); - - if (get_err(src)) - return -1; - - /* Return the number of bytes consumed. */ - return src->pos; -} - -void freersapriv(RSAKey *key) -{ - if (key->private_exponent) { - mp_free(key->private_exponent); - key->private_exponent = NULL; - } - if (key->p) { - mp_free(key->p); - key->p = NULL; - } - if (key->q) { - mp_free(key->q); - key->q = NULL; - } - if (key->iqmp) { - mp_free(key->iqmp); - key->iqmp = NULL; - } -} - -void freersakey(RSAKey *key) -{ - freersapriv(key); - if (key->modulus) { - mp_free(key->modulus); - key->modulus = NULL; - } - if (key->exponent) { - mp_free(key->exponent); - key->exponent = NULL; - } - if (key->comment) { - sfree(key->comment); - key->comment = NULL; - } -} - -/* ---------------------------------------------------------------------- - * Implementation of the ssh-rsa signing key type. - */ - -static void rsa2_freekey(ssh_key *key); /* forward reference */ - -static ssh_key *rsa2_new_pub(const ssh_keyalg *self, ptrlen data) -{ - BinarySource src[1]; - RSAKey *rsa; - - BinarySource_BARE_INIT_PL(src, data); - if (!ptrlen_eq_string(get_string(src), "ssh-rsa")) - return NULL; - - rsa = snew(RSAKey); - rsa->sshk.vt = &ssh_rsa; - rsa->exponent = get_mp_ssh2(src); - rsa->modulus = get_mp_ssh2(src); - rsa->private_exponent = NULL; - rsa->p = rsa->q = rsa->iqmp = NULL; - rsa->comment = NULL; - - if (get_err(src)) { - rsa2_freekey(&rsa->sshk); - return NULL; - } - - return &rsa->sshk; -} - -static void rsa2_freekey(ssh_key *key) -{ - RSAKey *rsa = container_of(key, RSAKey, sshk); - freersakey(rsa); - sfree(rsa); -} - -static char *rsa2_cache_str(ssh_key *key) -{ - RSAKey *rsa = container_of(key, RSAKey, sshk); - return rsastr_fmt(rsa); -} - -static void rsa2_public_blob(ssh_key *key, BinarySink *bs) -{ - RSAKey *rsa = container_of(key, RSAKey, sshk); - - put_stringz(bs, "ssh-rsa"); - put_mp_ssh2(bs, rsa->exponent); - put_mp_ssh2(bs, rsa->modulus); -} - -static void rsa2_private_blob(ssh_key *key, BinarySink *bs) -{ - RSAKey *rsa = container_of(key, RSAKey, sshk); - - put_mp_ssh2(bs, rsa->private_exponent); - put_mp_ssh2(bs, rsa->p); - put_mp_ssh2(bs, rsa->q); - put_mp_ssh2(bs, rsa->iqmp); -} - -static ssh_key *rsa2_new_priv(const ssh_keyalg *self, - ptrlen pub, ptrlen priv) -{ - BinarySource src[1]; - ssh_key *sshk; - RSAKey *rsa; - - sshk = rsa2_new_pub(self, pub); - if (!sshk) - return NULL; - - rsa = container_of(sshk, RSAKey, sshk); - BinarySource_BARE_INIT_PL(src, priv); - rsa->private_exponent = get_mp_ssh2(src); - rsa->p = get_mp_ssh2(src); - rsa->q = get_mp_ssh2(src); - rsa->iqmp = get_mp_ssh2(src); - - if (get_err(src) || !rsa_verify(rsa)) { - rsa2_freekey(&rsa->sshk); - return NULL; - } - - return &rsa->sshk; -} - -static ssh_key *rsa2_new_priv_openssh(const ssh_keyalg *self, - BinarySource *src) -{ - RSAKey *rsa; - - rsa = snew(RSAKey); - rsa->sshk.vt = &ssh_rsa; - rsa->comment = NULL; - - rsa->modulus = get_mp_ssh2(src); - rsa->exponent = get_mp_ssh2(src); - rsa->private_exponent = get_mp_ssh2(src); - rsa->iqmp = get_mp_ssh2(src); - rsa->p = get_mp_ssh2(src); - rsa->q = get_mp_ssh2(src); - - if (get_err(src) || !rsa_verify(rsa)) { - rsa2_freekey(&rsa->sshk); - return NULL; - } - - return &rsa->sshk; -} - -static void rsa2_openssh_blob(ssh_key *key, BinarySink *bs) -{ - RSAKey *rsa = container_of(key, RSAKey, sshk); - - put_mp_ssh2(bs, rsa->modulus); - put_mp_ssh2(bs, rsa->exponent); - put_mp_ssh2(bs, rsa->private_exponent); - put_mp_ssh2(bs, rsa->iqmp); - put_mp_ssh2(bs, rsa->p); - put_mp_ssh2(bs, rsa->q); -} - -static int rsa2_pubkey_bits(const ssh_keyalg *self, ptrlen pub) -{ - ssh_key *sshk; - RSAKey *rsa; - int ret; - - sshk = rsa2_new_pub(self, pub); - if (!sshk) - return -1; - - rsa = container_of(sshk, RSAKey, sshk); - ret = mp_get_nbits(rsa->modulus); - rsa2_freekey(&rsa->sshk); - - return ret; -} - -static inline const ssh_hashalg *rsa2_hash_alg_for_flags( - unsigned flags, const char **protocol_id_out) -{ - const ssh_hashalg *halg; - const char *protocol_id; - - if (flags & SSH_AGENT_RSA_SHA2_256) { - halg = &ssh_sha256; - protocol_id = "rsa-sha2-256"; - } else if (flags & SSH_AGENT_RSA_SHA2_512) { - halg = &ssh_sha512; - protocol_id = "rsa-sha2-512"; - } else { - halg = &ssh_sha1; - protocol_id = "ssh-rsa"; - } - - if (protocol_id_out) - *protocol_id_out = protocol_id; - - return halg; -} - -static inline ptrlen rsa_pkcs1_prefix_for_hash(const ssh_hashalg *halg) -{ - if (halg == &ssh_sha1) { - /* - * This is the magic ASN.1/DER prefix that goes in the decoded - * signature, between the string of FFs and the actual SHA-1 - * hash value. The meaning of it is: - * - * 00 -- this marks the end of the FFs; not part of the ASN.1 - * bit itself - * - * 30 21 -- a constructed SEQUENCE of length 0x21 - * 30 09 -- a constructed sub-SEQUENCE of length 9 - * 06 05 -- an object identifier, length 5 - * 2B 0E 03 02 1A -- object id { 1 3 14 3 2 26 } - * (the 1,3 comes from 0x2B = 43 = 40*1+3) - * 05 00 -- NULL - * 04 14 -- a primitive OCTET STRING of length 0x14 - * [0x14 bytes of hash data follows] - * - * The object id in the middle there is listed as `id-sha1' in - * ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1d2.asn - * (the ASN module for PKCS #1) and its expanded form is as - * follows: - * - * id-sha1 OBJECT IDENTIFIER ::= { - * iso(1) identified-organization(3) oiw(14) secsig(3) - * algorithms(2) 26 } - */ - static const unsigned char sha1_asn1_prefix[] = { - 0x00, 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, - 0x0E, 0x03, 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14, - }; - return PTRLEN_FROM_CONST_BYTES(sha1_asn1_prefix); - } - - if (halg == &ssh_sha256) { - /* - * A similar piece of ASN.1 used for signatures using SHA-256, - * in the same format but differing only in various length - * fields and OID. - */ - static const unsigned char sha256_asn1_prefix[] = { - 0x00, 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, - 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, - 0x05, 0x00, 0x04, 0x20, - }; - return PTRLEN_FROM_CONST_BYTES(sha256_asn1_prefix); - } - - if (halg == &ssh_sha512) { - /* - * And one more for SHA-512. - */ - static const unsigned char sha512_asn1_prefix[] = { - 0x00, 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, - 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, - 0x05, 0x00, 0x04, 0x40, - }; - return PTRLEN_FROM_CONST_BYTES(sha512_asn1_prefix); - } - - unreachable("bad hash algorithm for RSA PKCS#1"); -} - -static inline size_t rsa_pkcs1_length_of_fixed_parts(const ssh_hashalg *halg) -{ - ptrlen asn1_prefix = rsa_pkcs1_prefix_for_hash(halg); - return halg->hlen + asn1_prefix.len + 2; -} - -static unsigned char *rsa_pkcs1_signature_string( - size_t nbytes, const ssh_hashalg *halg, ptrlen data) -{ - size_t fixed_parts = rsa_pkcs1_length_of_fixed_parts(halg); - assert(nbytes >= fixed_parts); - size_t padding = nbytes - fixed_parts; - - ptrlen asn1_prefix = rsa_pkcs1_prefix_for_hash(halg); - - unsigned char *bytes = snewn(nbytes, unsigned char); - - bytes[0] = 0; - bytes[1] = 1; - - memset(bytes + 2, 0xFF, padding); - - memcpy(bytes + 2 + padding, asn1_prefix.ptr, asn1_prefix.len); - - ssh_hash *h = ssh_hash_new(halg); - put_datapl(h, data); - ssh_hash_final(h, bytes + 2 + padding + asn1_prefix.len); - - return bytes; -} - -static bool rsa2_verify(ssh_key *key, ptrlen sig, ptrlen data) -{ - RSAKey *rsa = container_of(key, RSAKey, sshk); - BinarySource src[1]; - ptrlen type, in_pl; - mp_int *in, *out; - - /* If we need to support variable flags on verify, this is where they go */ - const ssh_hashalg *halg = rsa2_hash_alg_for_flags(0, NULL); - - /* Start by making sure the key is even long enough to encode a - * signature. If not, everything fails to verify. */ - size_t nbytes = (mp_get_nbits(rsa->modulus) + 7) / 8; - if (nbytes < rsa_pkcs1_length_of_fixed_parts(halg)) - return false; - - BinarySource_BARE_INIT_PL(src, sig); - type = get_string(src); - /* - * RFC 4253 section 6.6: the signature integer in an ssh-rsa - * signature is 'without lengths or padding'. That is, we _don't_ - * expect the usual leading zero byte if the topmost bit of the - * first byte is set. (However, because of the possibility of - * BUG_SSH2_RSA_PADDING at the other end, we tolerate it if it's - * there.) So we can't use get_mp_ssh2, which enforces that - * leading-byte scheme; instead we use get_string and - * mp_from_bytes_be, which will tolerate anything. - */ - in_pl = get_string(src); - if (get_err(src) || !ptrlen_eq_string(type, "ssh-rsa")) - return false; - - in = mp_from_bytes_be(in_pl); - out = mp_modpow(in, rsa->exponent, rsa->modulus); - mp_free(in); - - unsigned diff = 0; - - unsigned char *bytes = rsa_pkcs1_signature_string(nbytes, halg, data); - for (size_t i = 0; i < nbytes; i++) - diff |= bytes[nbytes-1 - i] ^ mp_get_byte(out, i); - smemclr(bytes, nbytes); - sfree(bytes); - mp_free(out); - - return diff == 0; -} - -static void rsa2_sign(ssh_key *key, ptrlen data, - unsigned flags, BinarySink *bs) -{ - RSAKey *rsa = container_of(key, RSAKey, sshk); - unsigned char *bytes; - size_t nbytes; - mp_int *in, *out; - const ssh_hashalg *halg; - const char *sign_alg_name; - - halg = rsa2_hash_alg_for_flags(flags, &sign_alg_name); - - nbytes = (mp_get_nbits(rsa->modulus) + 7) / 8; - - bytes = rsa_pkcs1_signature_string(nbytes, halg, data); - in = mp_from_bytes_be(make_ptrlen(bytes, nbytes)); - smemclr(bytes, nbytes); - sfree(bytes); - - out = rsa_privkey_op(in, rsa); - mp_free(in); - - put_stringz(bs, sign_alg_name); - nbytes = (mp_get_nbits(out) + 7) / 8; - put_uint32(bs, nbytes); - for (size_t i = 0; i < nbytes; i++) - put_byte(bs, mp_get_byte(out, nbytes - 1 - i)); - - mp_free(out); -} - -char *rsa2_invalid(ssh_key *key, unsigned flags) -{ - RSAKey *rsa = container_of(key, RSAKey, sshk); - size_t bits = mp_get_nbits(rsa->modulus), nbytes = (bits + 7) / 8; - const char *sign_alg_name; - const ssh_hashalg *halg = rsa2_hash_alg_for_flags(flags, &sign_alg_name); - if (nbytes < rsa_pkcs1_length_of_fixed_parts(halg)) { - return dupprintf( - "%zu-bit RSA key is too short to generate %s signatures", - bits, sign_alg_name); - } - - return NULL; -} - -const ssh_keyalg ssh_rsa = { - rsa2_new_pub, - rsa2_new_priv, - rsa2_new_priv_openssh, - - rsa2_freekey, - rsa2_invalid, - rsa2_sign, - rsa2_verify, - rsa2_public_blob, - rsa2_private_blob, - rsa2_openssh_blob, - rsa2_cache_str, - - rsa2_pubkey_bits, - - "ssh-rsa", - "rsa2", - NULL, - SSH_AGENT_RSA_SHA2_256 | SSH_AGENT_RSA_SHA2_512, -}; - -RSAKey *ssh_rsakex_newkey(ptrlen data) -{ - ssh_key *sshk = rsa2_new_pub(&ssh_rsa, data); - if (!sshk) - return NULL; - return container_of(sshk, RSAKey, sshk); -} - -void ssh_rsakex_freekey(RSAKey *key) -{ - rsa2_freekey(&key->sshk); -} - -int ssh_rsakex_klen(RSAKey *rsa) -{ - return mp_get_nbits(rsa->modulus); -} - -static void oaep_mask(const ssh_hashalg *h, void *seed, int seedlen, - void *vdata, int datalen) -{ - unsigned char *data = (unsigned char *)vdata; - unsigned count = 0; - - while (datalen > 0) { - int i, max = (datalen > h->hlen ? h->hlen : datalen); - ssh_hash *s; - unsigned char hash[MAX_HASH_LEN]; - - assert(h->hlen <= MAX_HASH_LEN); - s = ssh_hash_new(h); - put_data(s, seed, seedlen); - put_uint32(s, count); - ssh_hash_final(s, hash); - count++; - - for (i = 0; i < max; i++) - data[i] ^= hash[i]; - - data += max; - datalen -= max; - } -} - -strbuf *ssh_rsakex_encrypt(RSAKey *rsa, const ssh_hashalg *h, ptrlen in) -{ - mp_int *b1, *b2; - int k, i; - char *p; - const int HLEN = h->hlen; - - /* - * Here we encrypt using RSAES-OAEP. Essentially this means: - * - * - we have a SHA-based `mask generation function' which - * creates a pseudo-random stream of mask data - * deterministically from an input chunk of data. - * - * - we have a random chunk of data called a seed. - * - * - we use the seed to generate a mask which we XOR with our - * plaintext. - * - * - then we use _the masked plaintext_ to generate a mask - * which we XOR with the seed. - * - * - then we concatenate the masked seed and the masked - * plaintext, and RSA-encrypt that lot. - * - * The result is that the data input to the encryption function - * is random-looking and (hopefully) contains no exploitable - * structure such as PKCS1-v1_5 does. - * - * For a precise specification, see RFC 3447, section 7.1.1. - * Some of the variable names below are derived from that, so - * it'd probably help to read it anyway. - */ - - /* k denotes the length in octets of the RSA modulus. */ - k = (7 + mp_get_nbits(rsa->modulus)) / 8; - - /* The length of the input data must be at most k - 2hLen - 2. */ - assert(in.len > 0 && in.len <= k - 2*HLEN - 2); - - /* The length of the output data wants to be precisely k. */ - strbuf *toret = strbuf_new_nm(); - int outlen = k; - unsigned char *out = strbuf_append(toret, outlen); - - /* - * Now perform EME-OAEP encoding. First set up all the unmasked - * output data. - */ - /* Leading byte zero. */ - out[0] = 0; - /* At position 1, the seed: HLEN bytes of random data. */ - random_read(out + 1, HLEN); - /* At position 1+HLEN, the data block DB, consisting of: */ - /* The hash of the label (we only support an empty label here) */ - { - ssh_hash *s = ssh_hash_new(h); - ssh_hash_final(s, out + HLEN + 1); - } - /* A bunch of zero octets */ - memset(out + 2*HLEN + 1, 0, outlen - (2*HLEN + 1)); - /* A single 1 octet, followed by the input message data. */ - out[outlen - in.len - 1] = 1; - memcpy(out + outlen - in.len, in.ptr, in.len); - - /* - * Now use the seed data to mask the block DB. - */ - oaep_mask(h, out+1, HLEN, out+HLEN+1, outlen-HLEN-1); - - /* - * And now use the masked DB to mask the seed itself. - */ - oaep_mask(h, out+HLEN+1, outlen-HLEN-1, out+1, HLEN); - - /* - * Now `out' contains precisely the data we want to - * RSA-encrypt. - */ - b1 = mp_from_bytes_be(make_ptrlen(out, outlen)); - b2 = mp_modpow(b1, rsa->exponent, rsa->modulus); - p = (char *)out; - for (i = outlen; i--;) { - *p++ = mp_get_byte(b2, i); - } - mp_free(b1); - mp_free(b2); - - /* - * And we're done. - */ - return toret; -} - -mp_int *ssh_rsakex_decrypt( - RSAKey *rsa, const ssh_hashalg *h, ptrlen ciphertext) -{ - mp_int *b1, *b2; - int outlen, i; - unsigned char *out; - unsigned char labelhash[64]; - ssh_hash *hash; - BinarySource src[1]; - const int HLEN = h->hlen; - - /* - * Decryption side of the RSA key exchange operation. - */ - - /* The length of the encrypted data should be exactly the length - * in octets of the RSA modulus.. */ - outlen = (7 + mp_get_nbits(rsa->modulus)) / 8; - if (ciphertext.len != outlen) - return NULL; - - /* Do the RSA decryption, and extract the result into a byte array. */ - b1 = mp_from_bytes_be(ciphertext); - b2 = rsa_privkey_op(b1, rsa); - out = snewn(outlen, unsigned char); - for (i = 0; i < outlen; i++) - out[i] = mp_get_byte(b2, outlen-1-i); - mp_free(b1); - mp_free(b2); - - /* Do the OAEP masking operations, in the reverse order from encryption */ - oaep_mask(h, out+HLEN+1, outlen-HLEN-1, out+1, HLEN); - oaep_mask(h, out+1, HLEN, out+HLEN+1, outlen-HLEN-1); - - /* Check the leading byte is zero. */ - if (out[0] != 0) { - sfree(out); - return NULL; - } - /* Check the label hash at position 1+HLEN */ - assert(HLEN <= lenof(labelhash)); - hash = ssh_hash_new(h); - ssh_hash_final(hash, labelhash); - if (memcmp(out + HLEN + 1, labelhash, HLEN)) { - sfree(out); - return NULL; - } - /* Expect zero bytes followed by a 1 byte */ - for (i = 1 + 2 * HLEN; i < outlen; i++) { - if (out[i] == 1) { - i++; /* skip over the 1 byte */ - break; - } else if (out[i] != 1) { - sfree(out); - return NULL; - } - } - /* And what's left is the input message data, which should be - * encoded as an ordinary SSH-2 mpint. */ - BinarySource_BARE_INIT(src, out + i, outlen - i); - b1 = get_mp_ssh2(src); - sfree(out); - if (get_err(src) || get_avail(src) != 0) { - mp_free(b1); - return NULL; - } - - /* Success! */ - return b1; -} - -static const struct ssh_rsa_kex_extra ssh_rsa_kex_extra_sha1 = { 1024 }; -static const struct ssh_rsa_kex_extra ssh_rsa_kex_extra_sha256 = { 2048 }; - -static const ssh_kex ssh_rsa_kex_sha1 = { - "rsa1024-sha1", NULL, KEXTYPE_RSA, - &ssh_sha1, &ssh_rsa_kex_extra_sha1, -}; - -static const ssh_kex ssh_rsa_kex_sha256 = { - "rsa2048-sha256", NULL, KEXTYPE_RSA, - &ssh_sha256, &ssh_rsa_kex_extra_sha256, -}; - -static const ssh_kex *const rsa_kex_list[] = { - &ssh_rsa_kex_sha256, - &ssh_rsa_kex_sha1 -}; - -const ssh_kexes ssh_rsa_kex = { lenof(rsa_kex_list), rsa_kex_list }; +/* + * RSA implementation for PuTTY. + */ + +#include +#include +#include +#include + +#include "ssh.h" +#include "mpint.h" +#include "misc.h" + +void BinarySource_get_rsa_ssh1_pub( + BinarySource *src, RSAKey *rsa, RsaSsh1Order order) +{ + unsigned bits; + mp_int *e, *m; + + bits = get_uint32(src); + if (order == RSA_SSH1_EXPONENT_FIRST) { + e = get_mp_ssh1(src); + m = get_mp_ssh1(src); + } else { + m = get_mp_ssh1(src); + e = get_mp_ssh1(src); + } + + if (rsa) { + rsa->bits = bits; + rsa->exponent = e; + rsa->modulus = m; + rsa->bytes = (mp_get_nbits(m) + 7) / 8; + } else { + mp_free(e); + mp_free(m); + } +} + +void BinarySource_get_rsa_ssh1_priv( + BinarySource *src, RSAKey *rsa) +{ + rsa->private_exponent = get_mp_ssh1(src); +} + +RSAKey *BinarySource_get_rsa_ssh1_priv_agent(BinarySource *src) +{ + RSAKey *rsa = snew(RSAKey); + memset(rsa, 0, sizeof(RSAKey)); + + get_rsa_ssh1_pub(src, rsa, RSA_SSH1_MODULUS_FIRST); + get_rsa_ssh1_priv(src, rsa); + + /* SSH-1 names p and q the other way round, i.e. we have the + * inverse of p mod q and not of q mod p. We swap the names, + * because our internal RSA wants iqmp. */ + rsa->iqmp = get_mp_ssh1(src); + rsa->q = get_mp_ssh1(src); + rsa->p = get_mp_ssh1(src); + + return rsa; +} + +bool rsa_ssh1_encrypt(unsigned char *data, int length, RSAKey *key) +{ + mp_int *b1, *b2; + int i; + unsigned char *p; + + if (key->bytes < length + 4) + return false; /* RSA key too short! */ + + memmove(data + key->bytes - length, data, length); + data[0] = 0; + data[1] = 2; + + size_t npad = key->bytes - length - 3; + /* + * Generate a sequence of nonzero padding bytes. We do this in a + * reasonably uniform way and without having to loop round + * retrying the random number generation, by first generating an + * integer in [0,2^n) for an appropriately large n; then we + * repeatedly multiply by 255 to give an integer in [0,255*2^n), + * extract the top 8 bits to give an integer in [0,255), and mask + * those bits off before multiplying up again for the next digit. + * This gives us a sequence of numbers in [0,255), and of course + * adding 1 to each of them gives numbers in [1,256) as we wanted. + * + * (You could imagine this being a sort of fixed-point operation: + * given a uniformly random binary _fraction_, multiplying it by k + * and subtracting off the integer part will yield you a sequence + * of integers each in [0,k). I'm just doing that scaled up by a + * power of 2 to avoid the fractions.) + */ + size_t random_bits = (npad + 16) * 8; + mp_int *randval = mp_new(random_bits + 8); + mp_int *tmp = mp_random_bits(random_bits); + mp_copy_into(randval, tmp); + mp_free(tmp); + for (i = 2; i < key->bytes - length - 1; i++) { + mp_mul_integer_into(randval, randval, 255); + uint8_t byte = mp_get_byte(randval, random_bits / 8); + assert(byte != 255); + data[i] = byte + 1; + mp_reduce_mod_2to(randval, random_bits); + } + mp_free(randval); + data[key->bytes - length - 1] = 0; + + b1 = mp_from_bytes_be(make_ptrlen(data, key->bytes)); + + b2 = mp_modpow(b1, key->exponent, key->modulus); + + p = data; + for (i = key->bytes; i--;) { + *p++ = mp_get_byte(b2, i); + } + + mp_free(b1); + mp_free(b2); + + return true; +} + +/* + * Compute (base ^ exp) % mod, provided mod == p * q, with p,q + * distinct primes, and iqmp is the multiplicative inverse of q mod p. + * Uses Chinese Remainder Theorem to speed computation up over the + * obvious implementation of a single big modpow. + */ +mp_int *crt_modpow(mp_int *base, mp_int *exp, mp_int *mod, + mp_int *p, mp_int *q, mp_int *iqmp) +{ + mp_int *pm1, *qm1, *pexp, *qexp, *presult, *qresult; + mp_int *diff, *multiplier, *ret0, *ret; + + /* + * Reduce the exponent mod phi(p) and phi(q), to save time when + * exponentiating mod p and mod q respectively. Of course, since p + * and q are prime, phi(p) == p-1 and similarly for q. + */ + pm1 = mp_copy(p); + mp_sub_integer_into(pm1, pm1, 1); + qm1 = mp_copy(q); + mp_sub_integer_into(qm1, qm1, 1); + pexp = mp_mod(exp, pm1); + qexp = mp_mod(exp, qm1); + + /* + * Do the two modpows. + */ + mp_int *base_mod_p = mp_mod(base, p); + presult = mp_modpow(base_mod_p, pexp, p); + mp_free(base_mod_p); + mp_int *base_mod_q = mp_mod(base, q); + qresult = mp_modpow(base_mod_q, qexp, q); + mp_free(base_mod_q); + + /* + * Recombine the results. We want a value which is congruent to + * qresult mod q, and to presult mod p. + * + * We know that iqmp * q is congruent to 1 * mod p (by definition + * of iqmp) and to 0 mod q (obviously). So we start with qresult + * (which is congruent to qresult mod both primes), and add on + * (presult-qresult) * (iqmp * q) which adjusts it to be congruent + * to presult mod p without affecting its value mod q. + * + * (If presult-qresult < 0, we add p to it to keep it positive.) + */ + unsigned presult_too_small = mp_cmp_hs(qresult, presult); + mp_cond_add_into(presult, presult, p, presult_too_small); + + diff = mp_sub(presult, qresult); + multiplier = mp_mul(iqmp, q); + ret0 = mp_mul(multiplier, diff); + mp_add_into(ret0, ret0, qresult); + + /* + * Finally, reduce the result mod n. + */ + ret = mp_mod(ret0, mod); + + /* + * Free all the intermediate results before returning. + */ + mp_free(pm1); + mp_free(qm1); + mp_free(pexp); + mp_free(qexp); + mp_free(presult); + mp_free(qresult); + mp_free(diff); + mp_free(multiplier); + mp_free(ret0); + + return ret; +} + +/* + * Wrapper on crt_modpow that looks up all the right values from an + * RSAKey. + */ +static mp_int *rsa_privkey_op(mp_int *input, RSAKey *key) +{ + return crt_modpow(input, key->private_exponent, + key->modulus, key->p, key->q, key->iqmp); +} + +mp_int *rsa_ssh1_decrypt(mp_int *input, RSAKey *key) +{ + return rsa_privkey_op(input, key); +} + +bool rsa_ssh1_decrypt_pkcs1(mp_int *input, RSAKey *key, + strbuf *outbuf) +{ + strbuf *data = strbuf_new_nm(); + bool success = false; + BinarySource src[1]; + + { + mp_int *b = rsa_ssh1_decrypt(input, key); + for (size_t i = (mp_get_nbits(key->modulus) + 7) / 8; i-- > 0 ;) { + put_byte(data, mp_get_byte(b, i)); + } + mp_free(b); + } + + BinarySource_BARE_INIT(src, data->u, data->len); + + /* Check PKCS#1 formatting prefix */ + if (get_byte(src) != 0) goto out; + if (get_byte(src) != 2) goto out; + while (1) { + unsigned char byte = get_byte(src); + if (get_err(src)) goto out; + if (byte == 0) + break; + } + + /* Everything else is the payload */ + success = true; + put_data(outbuf, get_ptr(src), get_avail(src)); + + out: + strbuf_free(data); + return success; +} + +static void append_hex_to_strbuf(strbuf *sb, mp_int *x) +{ + if (sb->len > 0) + put_byte(sb, ','); + put_data(sb, "0x", 2); + char *hex = mp_get_hex(x); + size_t hexlen = strlen(hex); + put_data(sb, hex, hexlen); + smemclr(hex, hexlen); + sfree(hex); +} + +char *rsastr_fmt(RSAKey *key) +{ + strbuf *sb = strbuf_new(); + + append_hex_to_strbuf(sb, key->exponent); + append_hex_to_strbuf(sb, key->modulus); + + return strbuf_to_str(sb); +} + +/* + * Generate a fingerprint string for the key. Compatible with the + * OpenSSH fingerprint code. + */ +char *rsa_ssh1_fingerprint(RSAKey *key) +{ + unsigned char digest[16]; + strbuf *out; + int i; + + /* + * The hash preimage for SSH-1 key fingerprinting consists of the + * modulus and exponent _without_ any preceding length field - + * just the minimum number of bytes to represent each integer, + * stored big-endian, concatenated with no marker at the division + * between them. + */ + + ssh_hash *hash = ssh_hash_new(&ssh_md5); + for (size_t i = (mp_get_nbits(key->modulus) + 7) / 8; i-- > 0 ;) + put_byte(hash, mp_get_byte(key->modulus, i)); + for (size_t i = (mp_get_nbits(key->exponent) + 7) / 8; i-- > 0 ;) + put_byte(hash, mp_get_byte(key->exponent, i)); + ssh_hash_final(hash, digest); + + out = strbuf_new(); + strbuf_catf(out, "%"SIZEu" ", mp_get_nbits(key->modulus)); + for (i = 0; i < 16; i++) + strbuf_catf(out, "%s%02x", i ? ":" : "", digest[i]); + if (key->comment) + strbuf_catf(out, " %s", key->comment); + return strbuf_to_str(out); +} + +/* + * Verify that the public data in an RSA key matches the private + * data. We also check the private data itself: we ensure that p > + * q and that iqmp really is the inverse of q mod p. + */ +bool rsa_verify(RSAKey *key) +{ + mp_int *n, *ed, *pm1, *qm1; + unsigned ok = 1; + + /* Preliminary checks: p,q can't be 0 or 1. (Of course no other + * very small value is any good either, but these are the values + * we _must_ check for to avoid assertion failures further down + * this function.) */ + if (!(mp_hs_integer(key->p, 2) & mp_hs_integer(key->q, 2))) + return false; + + /* n must equal pq. */ + n = mp_mul(key->p, key->q); + ok &= mp_cmp_eq(n, key->modulus); + mp_free(n); + + /* e * d must be congruent to 1, modulo (p-1) and modulo (q-1). */ + pm1 = mp_copy(key->p); + mp_sub_integer_into(pm1, pm1, 1); + ed = mp_modmul(key->exponent, key->private_exponent, pm1); + mp_free(pm1); + ok &= mp_eq_integer(ed, 1); + mp_free(ed); + + qm1 = mp_copy(key->q); + mp_sub_integer_into(qm1, qm1, 1); + ed = mp_modmul(key->exponent, key->private_exponent, qm1); + mp_free(qm1); + ok &= mp_eq_integer(ed, 1); + mp_free(ed); + + /* + * Ensure p > q. + * + * I have seen key blobs in the wild which were generated with + * p < q, so instead of rejecting the key in this case we + * should instead flip them round into the canonical order of + * p > q. This also involves regenerating iqmp. + */ + mp_int *p_new = mp_max(key->p, key->q); + mp_int *q_new = mp_min(key->p, key->q); + mp_free(key->p); + mp_free(key->q); + mp_free(key->iqmp); + key->p = p_new; + key->q = q_new; + key->iqmp = mp_invert(key->q, key->p); + + return ok; +} + +void rsa_ssh1_public_blob(BinarySink *bs, RSAKey *key, + RsaSsh1Order order) +{ + put_uint32(bs, mp_get_nbits(key->modulus)); + if (order == RSA_SSH1_EXPONENT_FIRST) { + put_mp_ssh1(bs, key->exponent); + put_mp_ssh1(bs, key->modulus); + } else { + put_mp_ssh1(bs, key->modulus); + put_mp_ssh1(bs, key->exponent); + } +} + +/* Given an SSH-1 public key blob, determine its length. */ +int rsa_ssh1_public_blob_len(ptrlen data) +{ + BinarySource src[1]; + + BinarySource_BARE_INIT_PL(src, data); + + /* Expect a length word, then exponent and modulus. (It doesn't + * even matter which order.) */ + get_uint32(src); + mp_free(get_mp_ssh1(src)); + mp_free(get_mp_ssh1(src)); + + if (get_err(src)) + return -1; + + /* Return the number of bytes consumed. */ + return src->pos; +} + +void freersapriv(RSAKey *key) +{ + if (key->private_exponent) { + mp_free(key->private_exponent); + key->private_exponent = NULL; + } + if (key->p) { + mp_free(key->p); + key->p = NULL; + } + if (key->q) { + mp_free(key->q); + key->q = NULL; + } + if (key->iqmp) { + mp_free(key->iqmp); + key->iqmp = NULL; + } +} + +void freersakey(RSAKey *key) +{ + freersapriv(key); + if (key->modulus) { + mp_free(key->modulus); + key->modulus = NULL; + } + if (key->exponent) { + mp_free(key->exponent); + key->exponent = NULL; + } + if (key->comment) { + sfree(key->comment); + key->comment = NULL; + } +} + +/* ---------------------------------------------------------------------- + * Implementation of the ssh-rsa signing key type. + */ + +static void rsa2_freekey(ssh_key *key); /* forward reference */ + +static ssh_key *rsa2_new_pub(const ssh_keyalg *self, ptrlen data) +{ + BinarySource src[1]; + RSAKey *rsa; + + BinarySource_BARE_INIT_PL(src, data); + if (!ptrlen_eq_string(get_string(src), "ssh-rsa")) + return NULL; + + rsa = snew(RSAKey); + rsa->sshk.vt = &ssh_rsa; + rsa->exponent = get_mp_ssh2(src); + rsa->modulus = get_mp_ssh2(src); + rsa->private_exponent = NULL; + rsa->p = rsa->q = rsa->iqmp = NULL; + rsa->comment = NULL; + + if (get_err(src)) { + rsa2_freekey(&rsa->sshk); + return NULL; + } + + return &rsa->sshk; +} + +static void rsa2_freekey(ssh_key *key) +{ + RSAKey *rsa = container_of(key, RSAKey, sshk); + freersakey(rsa); + sfree(rsa); +} + +static char *rsa2_cache_str(ssh_key *key) +{ + RSAKey *rsa = container_of(key, RSAKey, sshk); + return rsastr_fmt(rsa); +} + +static void rsa2_public_blob(ssh_key *key, BinarySink *bs) +{ + RSAKey *rsa = container_of(key, RSAKey, sshk); + + put_stringz(bs, "ssh-rsa"); + put_mp_ssh2(bs, rsa->exponent); + put_mp_ssh2(bs, rsa->modulus); +} + +static void rsa2_private_blob(ssh_key *key, BinarySink *bs) +{ + RSAKey *rsa = container_of(key, RSAKey, sshk); + + put_mp_ssh2(bs, rsa->private_exponent); + put_mp_ssh2(bs, rsa->p); + put_mp_ssh2(bs, rsa->q); + put_mp_ssh2(bs, rsa->iqmp); +} + +static ssh_key *rsa2_new_priv(const ssh_keyalg *self, + ptrlen pub, ptrlen priv) +{ + BinarySource src[1]; + ssh_key *sshk; + RSAKey *rsa; + + sshk = rsa2_new_pub(self, pub); + if (!sshk) + return NULL; + + rsa = container_of(sshk, RSAKey, sshk); + BinarySource_BARE_INIT_PL(src, priv); + rsa->private_exponent = get_mp_ssh2(src); + rsa->p = get_mp_ssh2(src); + rsa->q = get_mp_ssh2(src); + rsa->iqmp = get_mp_ssh2(src); + + if (get_err(src) || !rsa_verify(rsa)) { + rsa2_freekey(&rsa->sshk); + return NULL; + } + + return &rsa->sshk; +} + +static ssh_key *rsa2_new_priv_openssh(const ssh_keyalg *self, + BinarySource *src) +{ + RSAKey *rsa; + + rsa = snew(RSAKey); + rsa->sshk.vt = &ssh_rsa; + rsa->comment = NULL; + + rsa->modulus = get_mp_ssh2(src); + rsa->exponent = get_mp_ssh2(src); + rsa->private_exponent = get_mp_ssh2(src); + rsa->iqmp = get_mp_ssh2(src); + rsa->p = get_mp_ssh2(src); + rsa->q = get_mp_ssh2(src); + + if (get_err(src) || !rsa_verify(rsa)) { + rsa2_freekey(&rsa->sshk); + return NULL; + } + + return &rsa->sshk; +} + +static void rsa2_openssh_blob(ssh_key *key, BinarySink *bs) +{ + RSAKey *rsa = container_of(key, RSAKey, sshk); + + put_mp_ssh2(bs, rsa->modulus); + put_mp_ssh2(bs, rsa->exponent); + put_mp_ssh2(bs, rsa->private_exponent); + put_mp_ssh2(bs, rsa->iqmp); + put_mp_ssh2(bs, rsa->p); + put_mp_ssh2(bs, rsa->q); +} + +static int rsa2_pubkey_bits(const ssh_keyalg *self, ptrlen pub) +{ + ssh_key *sshk; + RSAKey *rsa; + int ret; + + sshk = rsa2_new_pub(self, pub); + if (!sshk) + return -1; + + rsa = container_of(sshk, RSAKey, sshk); + ret = mp_get_nbits(rsa->modulus); + rsa2_freekey(&rsa->sshk); + + return ret; +} + +static inline const ssh_hashalg *rsa2_hash_alg_for_flags( + unsigned flags, const char **protocol_id_out) +{ + const ssh_hashalg *halg; + const char *protocol_id; + + if (flags & SSH_AGENT_RSA_SHA2_256) { + halg = &ssh_sha256; + protocol_id = "rsa-sha2-256"; + } else if (flags & SSH_AGENT_RSA_SHA2_512) { + halg = &ssh_sha512; + protocol_id = "rsa-sha2-512"; + } else { + halg = &ssh_sha1; + protocol_id = "ssh-rsa"; + } + + if (protocol_id_out) + *protocol_id_out = protocol_id; + + return halg; +} + +static inline ptrlen rsa_pkcs1_prefix_for_hash(const ssh_hashalg *halg) +{ + if (halg == &ssh_sha1) { + /* + * This is the magic ASN.1/DER prefix that goes in the decoded + * signature, between the string of FFs and the actual SHA-1 + * hash value. The meaning of it is: + * + * 00 -- this marks the end of the FFs; not part of the ASN.1 + * bit itself + * + * 30 21 -- a constructed SEQUENCE of length 0x21 + * 30 09 -- a constructed sub-SEQUENCE of length 9 + * 06 05 -- an object identifier, length 5 + * 2B 0E 03 02 1A -- object id { 1 3 14 3 2 26 } + * (the 1,3 comes from 0x2B = 43 = 40*1+3) + * 05 00 -- NULL + * 04 14 -- a primitive OCTET STRING of length 0x14 + * [0x14 bytes of hash data follows] + * + * The object id in the middle there is listed as `id-sha1' in + * ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1d2.asn + * (the ASN module for PKCS #1) and its expanded form is as + * follows: + * + * id-sha1 OBJECT IDENTIFIER ::= { + * iso(1) identified-organization(3) oiw(14) secsig(3) + * algorithms(2) 26 } + */ + static const unsigned char sha1_asn1_prefix[] = { + 0x00, 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, + 0x0E, 0x03, 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14, + }; + return PTRLEN_FROM_CONST_BYTES(sha1_asn1_prefix); + } + + if (halg == &ssh_sha256) { + /* + * A similar piece of ASN.1 used for signatures using SHA-256, + * in the same format but differing only in various length + * fields and OID. + */ + static const unsigned char sha256_asn1_prefix[] = { + 0x00, 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, + 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, + 0x05, 0x00, 0x04, 0x20, + }; + return PTRLEN_FROM_CONST_BYTES(sha256_asn1_prefix); + } + + if (halg == &ssh_sha512) { + /* + * And one more for SHA-512. + */ + static const unsigned char sha512_asn1_prefix[] = { + 0x00, 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, + 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, + 0x05, 0x00, 0x04, 0x40, + }; + return PTRLEN_FROM_CONST_BYTES(sha512_asn1_prefix); + } + + unreachable("bad hash algorithm for RSA PKCS#1"); +} + +static inline size_t rsa_pkcs1_length_of_fixed_parts(const ssh_hashalg *halg) +{ + ptrlen asn1_prefix = rsa_pkcs1_prefix_for_hash(halg); + return halg->hlen + asn1_prefix.len + 2; +} + +static unsigned char *rsa_pkcs1_signature_string( + size_t nbytes, const ssh_hashalg *halg, ptrlen data) +{ + size_t fixed_parts = rsa_pkcs1_length_of_fixed_parts(halg); + assert(nbytes >= fixed_parts); + size_t padding = nbytes - fixed_parts; + + ptrlen asn1_prefix = rsa_pkcs1_prefix_for_hash(halg); + + unsigned char *bytes = snewn(nbytes, unsigned char); + + bytes[0] = 0; + bytes[1] = 1; + + memset(bytes + 2, 0xFF, padding); + + memcpy(bytes + 2 + padding, asn1_prefix.ptr, asn1_prefix.len); + + ssh_hash *h = ssh_hash_new(halg); + put_datapl(h, data); + ssh_hash_final(h, bytes + 2 + padding + asn1_prefix.len); + + return bytes; +} + +static bool rsa2_verify(ssh_key *key, ptrlen sig, ptrlen data) +{ + RSAKey *rsa = container_of(key, RSAKey, sshk); + BinarySource src[1]; + ptrlen type, in_pl; + mp_int *in, *out; + + /* If we need to support variable flags on verify, this is where they go */ + const ssh_hashalg *halg = rsa2_hash_alg_for_flags(0, NULL); + + /* Start by making sure the key is even long enough to encode a + * signature. If not, everything fails to verify. */ + size_t nbytes = (mp_get_nbits(rsa->modulus) + 7) / 8; + if (nbytes < rsa_pkcs1_length_of_fixed_parts(halg)) + return false; + + BinarySource_BARE_INIT_PL(src, sig); + type = get_string(src); + /* + * RFC 4253 section 6.6: the signature integer in an ssh-rsa + * signature is 'without lengths or padding'. That is, we _don't_ + * expect the usual leading zero byte if the topmost bit of the + * first byte is set. (However, because of the possibility of + * BUG_SSH2_RSA_PADDING at the other end, we tolerate it if it's + * there.) So we can't use get_mp_ssh2, which enforces that + * leading-byte scheme; instead we use get_string and + * mp_from_bytes_be, which will tolerate anything. + */ + in_pl = get_string(src); + if (get_err(src) || !ptrlen_eq_string(type, "ssh-rsa")) + return false; + + in = mp_from_bytes_be(in_pl); + out = mp_modpow(in, rsa->exponent, rsa->modulus); + mp_free(in); + + unsigned diff = 0; + + unsigned char *bytes = rsa_pkcs1_signature_string(nbytes, halg, data); + for (size_t i = 0; i < nbytes; i++) + diff |= bytes[nbytes-1 - i] ^ mp_get_byte(out, i); + smemclr(bytes, nbytes); + sfree(bytes); + mp_free(out); + + return diff == 0; +} + +static void rsa2_sign(ssh_key *key, ptrlen data, + unsigned flags, BinarySink *bs) +{ + RSAKey *rsa = container_of(key, RSAKey, sshk); + unsigned char *bytes; + size_t nbytes; + mp_int *in, *out; + const ssh_hashalg *halg; + const char *sign_alg_name; + + halg = rsa2_hash_alg_for_flags(flags, &sign_alg_name); + + nbytes = (mp_get_nbits(rsa->modulus) + 7) / 8; + + bytes = rsa_pkcs1_signature_string(nbytes, halg, data); + in = mp_from_bytes_be(make_ptrlen(bytes, nbytes)); + smemclr(bytes, nbytes); + sfree(bytes); + + out = rsa_privkey_op(in, rsa); + mp_free(in); + + put_stringz(bs, sign_alg_name); + nbytes = (mp_get_nbits(out) + 7) / 8; + put_uint32(bs, nbytes); + for (size_t i = 0; i < nbytes; i++) + put_byte(bs, mp_get_byte(out, nbytes - 1 - i)); + + mp_free(out); +} + +char *rsa2_invalid(ssh_key *key, unsigned flags) +{ + RSAKey *rsa = container_of(key, RSAKey, sshk); + size_t bits = mp_get_nbits(rsa->modulus), nbytes = (bits + 7) / 8; + const char *sign_alg_name; + const ssh_hashalg *halg = rsa2_hash_alg_for_flags(flags, &sign_alg_name); + if (nbytes < rsa_pkcs1_length_of_fixed_parts(halg)) { + return dupprintf( + "%"SIZEu"-bit RSA key is too short to generate %s signatures", + bits, sign_alg_name); + } + + return NULL; +} + +const ssh_keyalg ssh_rsa = { + rsa2_new_pub, + rsa2_new_priv, + rsa2_new_priv_openssh, + + rsa2_freekey, + rsa2_invalid, + rsa2_sign, + rsa2_verify, + rsa2_public_blob, + rsa2_private_blob, + rsa2_openssh_blob, + rsa2_cache_str, + + rsa2_pubkey_bits, + + "ssh-rsa", + "rsa2", + NULL, + SSH_AGENT_RSA_SHA2_256 | SSH_AGENT_RSA_SHA2_512, +}; + +RSAKey *ssh_rsakex_newkey(ptrlen data) +{ + ssh_key *sshk = rsa2_new_pub(&ssh_rsa, data); + if (!sshk) + return NULL; + return container_of(sshk, RSAKey, sshk); +} + +void ssh_rsakex_freekey(RSAKey *key) +{ + rsa2_freekey(&key->sshk); +} + +int ssh_rsakex_klen(RSAKey *rsa) +{ + return mp_get_nbits(rsa->modulus); +} + +static void oaep_mask(const ssh_hashalg *h, void *seed, int seedlen, + void *vdata, int datalen) +{ + unsigned char *data = (unsigned char *)vdata; + unsigned count = 0; + + while (datalen > 0) { + int i, max = (datalen > h->hlen ? h->hlen : datalen); + ssh_hash *s; + unsigned char hash[MAX_HASH_LEN]; + + assert(h->hlen <= MAX_HASH_LEN); + s = ssh_hash_new(h); + put_data(s, seed, seedlen); + put_uint32(s, count); + ssh_hash_final(s, hash); + count++; + + for (i = 0; i < max; i++) + data[i] ^= hash[i]; + + data += max; + datalen -= max; + } +} + +strbuf *ssh_rsakex_encrypt(RSAKey *rsa, const ssh_hashalg *h, ptrlen in) +{ + mp_int *b1, *b2; + int k, i; + char *p; + const int HLEN = h->hlen; + + /* + * Here we encrypt using RSAES-OAEP. Essentially this means: + * + * - we have a SHA-based `mask generation function' which + * creates a pseudo-random stream of mask data + * deterministically from an input chunk of data. + * + * - we have a random chunk of data called a seed. + * + * - we use the seed to generate a mask which we XOR with our + * plaintext. + * + * - then we use _the masked plaintext_ to generate a mask + * which we XOR with the seed. + * + * - then we concatenate the masked seed and the masked + * plaintext, and RSA-encrypt that lot. + * + * The result is that the data input to the encryption function + * is random-looking and (hopefully) contains no exploitable + * structure such as PKCS1-v1_5 does. + * + * For a precise specification, see RFC 3447, section 7.1.1. + * Some of the variable names below are derived from that, so + * it'd probably help to read it anyway. + */ + + /* k denotes the length in octets of the RSA modulus. */ + k = (7 + mp_get_nbits(rsa->modulus)) / 8; + + /* The length of the input data must be at most k - 2hLen - 2. */ + assert(in.len > 0 && in.len <= k - 2*HLEN - 2); + + /* The length of the output data wants to be precisely k. */ + strbuf *toret = strbuf_new_nm(); + int outlen = k; + unsigned char *out = strbuf_append(toret, outlen); + + /* + * Now perform EME-OAEP encoding. First set up all the unmasked + * output data. + */ + /* Leading byte zero. */ + out[0] = 0; + /* At position 1, the seed: HLEN bytes of random data. */ + random_read(out + 1, HLEN); + /* At position 1+HLEN, the data block DB, consisting of: */ + /* The hash of the label (we only support an empty label here) */ + { + ssh_hash *s = ssh_hash_new(h); + ssh_hash_final(s, out + HLEN + 1); + } + /* A bunch of zero octets */ + memset(out + 2*HLEN + 1, 0, outlen - (2*HLEN + 1)); + /* A single 1 octet, followed by the input message data. */ + out[outlen - in.len - 1] = 1; + memcpy(out + outlen - in.len, in.ptr, in.len); + + /* + * Now use the seed data to mask the block DB. + */ + oaep_mask(h, out+1, HLEN, out+HLEN+1, outlen-HLEN-1); + + /* + * And now use the masked DB to mask the seed itself. + */ + oaep_mask(h, out+HLEN+1, outlen-HLEN-1, out+1, HLEN); + + /* + * Now `out' contains precisely the data we want to + * RSA-encrypt. + */ + b1 = mp_from_bytes_be(make_ptrlen(out, outlen)); + b2 = mp_modpow(b1, rsa->exponent, rsa->modulus); + p = (char *)out; + for (i = outlen; i--;) { + *p++ = mp_get_byte(b2, i); + } + mp_free(b1); + mp_free(b2); + + /* + * And we're done. + */ + return toret; +} + +mp_int *ssh_rsakex_decrypt( + RSAKey *rsa, const ssh_hashalg *h, ptrlen ciphertext) +{ + mp_int *b1, *b2; + int outlen, i; + unsigned char *out; + unsigned char labelhash[64]; + ssh_hash *hash; + BinarySource src[1]; + const int HLEN = h->hlen; + + /* + * Decryption side of the RSA key exchange operation. + */ + + /* The length of the encrypted data should be exactly the length + * in octets of the RSA modulus.. */ + outlen = (7 + mp_get_nbits(rsa->modulus)) / 8; + if (ciphertext.len != outlen) + return NULL; + + /* Do the RSA decryption, and extract the result into a byte array. */ + b1 = mp_from_bytes_be(ciphertext); + b2 = rsa_privkey_op(b1, rsa); + out = snewn(outlen, unsigned char); + for (i = 0; i < outlen; i++) + out[i] = mp_get_byte(b2, outlen-1-i); + mp_free(b1); + mp_free(b2); + + /* Do the OAEP masking operations, in the reverse order from encryption */ + oaep_mask(h, out+HLEN+1, outlen-HLEN-1, out+1, HLEN); + oaep_mask(h, out+1, HLEN, out+HLEN+1, outlen-HLEN-1); + + /* Check the leading byte is zero. */ + if (out[0] != 0) { + sfree(out); + return NULL; + } + /* Check the label hash at position 1+HLEN */ + assert(HLEN <= lenof(labelhash)); + hash = ssh_hash_new(h); + ssh_hash_final(hash, labelhash); + if (memcmp(out + HLEN + 1, labelhash, HLEN)) { + sfree(out); + return NULL; + } + /* Expect zero bytes followed by a 1 byte */ + for (i = 1 + 2 * HLEN; i < outlen; i++) { + if (out[i] == 1) { + i++; /* skip over the 1 byte */ + break; + } else if (out[i] != 0) { + sfree(out); + return NULL; + } + } + /* And what's left is the input message data, which should be + * encoded as an ordinary SSH-2 mpint. */ + BinarySource_BARE_INIT(src, out + i, outlen - i); + b1 = get_mp_ssh2(src); + sfree(out); + if (get_err(src) || get_avail(src) != 0) { + mp_free(b1); + return NULL; + } + + /* Success! */ + return b1; +} + +static const struct ssh_rsa_kex_extra ssh_rsa_kex_extra_sha1 = { 1024 }; +static const struct ssh_rsa_kex_extra ssh_rsa_kex_extra_sha256 = { 2048 }; + +static const ssh_kex ssh_rsa_kex_sha1 = { + "rsa1024-sha1", NULL, KEXTYPE_RSA, + &ssh_sha1, &ssh_rsa_kex_extra_sha1, +}; + +static const ssh_kex ssh_rsa_kex_sha256 = { + "rsa2048-sha256", NULL, KEXTYPE_RSA, + &ssh_sha256, &ssh_rsa_kex_extra_sha256, +}; + +static const ssh_kex *const rsa_kex_list[] = { + &ssh_rsa_kex_sha256, + &ssh_rsa_kex_sha1 +}; + +const ssh_kexes ssh_rsa_kex = { lenof(rsa_kex_list), rsa_kex_list }; diff --git a/0.73_My_PuTTY/sshrsag.c b/0.74_My_PuTTY/sshrsag.c similarity index 100% rename from 0.73_My_PuTTY/sshrsag.c rename to 0.74_My_PuTTY/sshrsag.c diff --git a/0.73_My_PuTTY/sshserver.c b/0.74_My_PuTTY/sshserver.c similarity index 95% rename from 0.73_My_PuTTY/sshserver.c rename to 0.74_My_PuTTY/sshserver.c index c3b6cb1..d04c766 100644 --- a/0.73_My_PuTTY/sshserver.c +++ b/0.74_My_PuTTY/sshserver.c @@ -9,6 +9,7 @@ #include "ssh.h" #include "sshbpp.h" #include "sshppl.h" +#include "sshchan.h" #include "sshserver.h" #ifndef NO_GSSAPI #include "sshgssc.h" @@ -85,7 +86,7 @@ void ssh_check_frozen(Ssh *ssh) {} mainchan *mainchan_new( PacketProtocolLayer *ppl, ConnectionLayer *cl, Conf *conf, - int term_width, int term_height, int is_simple, SshChannel **sc_out) + int term_width, int term_height, bool is_simple, SshChannel **sc_out) { return NULL; } void mainchan_get_specials( mainchan *mc, add_special_fn_t add_special, void *ctx) {} @@ -148,8 +149,8 @@ static void server_receive( /* Log raw data, if we're in that mode. */ if (srv->logctx) - log_packet(srv->logctx, PKT_INCOMING, -1, NULL, data, len, - 0, NULL, NULL, 0, NULL); + log_packet(srv->logctx, PKT_INCOMING, -1, NULL, data, len, + 0, NULL, NULL, 0, NULL); bufchain_add(&srv->in_raw, data, len); if (!srv->frozen && srv->bpp) @@ -168,7 +169,7 @@ static void server_sent(Plug *plug, size_t bufsize) * some more data off its bufchain. */ if (bufsize < SSH_MAX_BACKLOG) { - srv_throttle_all(srv, 0, bufsize); + srv_throttle_all(srv, 0, bufsize); queue_idempotent_callback(&srv->ic_out_raw); } #endif @@ -497,7 +498,7 @@ static void server_got_ssh_version(struct ssh_version_receiver *rcv, server_connect_bpp(srv); connection_layer = ssh2_connection_new( - &srv->ssh, NULL, false, srv->conf, + &srv->ssh, NULL, false, srv->conf, ssh_verstring_get_local(old_bpp), &srv->cl); ssh2connection_server_configure(connection_layer, srv->sftpserver_vt, srv->ssc); diff --git a/0.73_My_PuTTY/sshserver.h b/0.74_My_PuTTY/sshserver.h similarity index 96% rename from 0.73_My_PuTTY/sshserver.h rename to 0.74_My_PuTTY/sshserver.h index 870dea2..5d78018 100644 --- a/0.73_My_PuTTY/sshserver.h +++ b/0.74_My_PuTTY/sshserver.h @@ -16,6 +16,8 @@ struct SshServerConfig { unsigned long ssh1_cipher_mask; bool ssh1_allow_compression; + + bool stunt_pretend_to_accept_any_pubkey; }; Plug *ssh_server_plug( diff --git a/0.73_My_PuTTY/sshsh256.c b/0.74_My_PuTTY/sshsh256.c similarity index 100% rename from 0.73_My_PuTTY/sshsh256.c rename to 0.74_My_PuTTY/sshsh256.c diff --git a/0.73_My_PuTTY/sshsh512.c b/0.74_My_PuTTY/sshsh512.c similarity index 100% rename from 0.73_My_PuTTY/sshsh512.c rename to 0.74_My_PuTTY/sshsh512.c diff --git a/0.73_My_PuTTY/sshsha.c b/0.74_My_PuTTY/sshsha.c similarity index 100% rename from 0.73_My_PuTTY/sshsha.c rename to 0.74_My_PuTTY/sshsha.c diff --git a/0.73_My_PuTTY/sshshare.c b/0.74_My_PuTTY/sshshare.c similarity index 96% rename from 0.73_My_PuTTY/sshshare.c rename to 0.74_My_PuTTY/sshshare.c index d2553e4..ac51845 100644 --- a/0.73_My_PuTTY/sshshare.c +++ b/0.74_My_PuTTY/sshshare.c @@ -1,2180 +1,2180 @@ -/* - * Support for SSH connection sharing, i.e. permitting one PuTTY to - * open its own channels over the SSH session being run by another. - */ - -/* - * Discussion and technical documentation - * ====================================== - * - * The basic strategy for PuTTY's implementation of SSH connection - * sharing is to have a single 'upstream' PuTTY process, which manages - * the real SSH connection and all the cryptography, and then zero or - * more 'downstream' PuTTYs, which never talk to the real host but - * only talk to the upstream through local IPC (Unix-domain sockets or - * Windows named pipes). - * - * The downstreams communicate with the upstream using a protocol - * derived from SSH itself, which I'll document in detail below. In - * brief, though: the downstream->upstream protocol uses a trivial - * binary packet protocol (just length/type/data) to encapsulate - * unencrypted SSH messages, and downstreams talk to the upstream more - * or less as if it was an SSH server itself. (So downstreams can - * themselves open multiple SSH channels, for example, by sending - * multiple SSH2_MSG_CHANNEL_OPENs; they can send CHANNEL_REQUESTs of - * their choice within each channel, and they handle their own - * WINDOW_ADJUST messages.) - * - * The upstream would ideally handle these downstreams by just putting - * their messages into the queue for proper SSH-2 encapsulation and - * encryption and sending them straight on to the server. However, - * that's not quite feasible as written, because client-side channel - * IDs could easily conflict (between multiple downstreams, or between - * a downstream and the upstream). To protect against that, the - * upstream rewrites the client-side channel IDs in messages it passes - * on to the server, so that it's performing what you might describe - * as 'channel-number NAT'. Then the upstream remembers which of its - * own channel IDs are channels it's managing itself, and which are - * placeholders associated with a particular downstream, so that when - * replies come in from the server they can be sent on to the relevant - * downstream (after un-NATting the channel number, of course). - * - * Global requests from downstreams are only accepted if the upstream - * knows what to do about them; currently the only such requests are - * the ones having to do with remote-to-local port forwarding (in - * which, again, the upstream remembers that some of the forwardings - * it's asked the server to set up were on behalf of particular - * downstreams, and sends the incoming CHANNEL_OPENs to those - * downstreams when connections come in). - * - * Other fiddly pieces of this mechanism are X forwarding and - * (OpenSSH-style) agent forwarding. Both of these have a fundamental - * problem arising from the protocol design: that the CHANNEL_OPEN - * from the server introducing a forwarded connection does not carry - * any indication of which session channel gave rise to it; so if - * session channels from multiple downstreams enable those forwarding - * methods, it's hard for the upstream to know which downstream to - * send the resulting connections back to. - * - * For X forwarding, we can work around this in a really painful way - * by using the fake X11 authorisation data sent to the server as part - * of the forwarding setup: upstream ensures that every X forwarding - * request carries distinguishable fake auth data, and then when X - * connections come in it waits to see the auth data in the X11 setup - * message before it decides which downstream to pass the connection - * on to. - * - * For agent forwarding, that workaround is unavailable. As a result, - * this system (and, as far as I can think of, any other system too) - * has the fundamental constraint that it can only forward one SSH - * agent - it can't forward two agents to different session channels. - * So downstreams can request agent forwarding if they like, but if - * they do, they'll get whatever SSH agent is known to the upstream - * (if any) forwarded to their sessions. - * - * Downstream-to-upstream protocol - * ------------------------------- - * - * Here I document in detail the protocol spoken between PuTTY - * downstreams and upstreams over local IPC. The IPC mechanism can - * vary between host platforms, but the protocol is the same. - * - * The protocol commences with a version exchange which is exactly - * like the SSH-2 one, in that each side sends a single line of text - * of the form - * - * -- [comments] \r\n - * - * The only difference is that in real SSH-2, is the string - * "SSH", whereas in this protocol the string is - * "SSHCONNECTION@putty.projects.tartarus.org". - * - * (The SSH RFCs allow many protocol-level identifier namespaces to be - * extended by implementors without central standardisation as long as - * they suffix "@" and a domain name they control to their new ids. - * RFC 4253 does not define this particular name to be changeable at - * all, but I like to think this is obviously how it would have done - * so if the working group had foreseen the need :-) - * - * Thereafter, all data exchanged consists of a sequence of binary - * packets concatenated end-to-end, each of which is of the form - * - * uint32 length of packet, N - * byte[N] N bytes of packet data - * - * and, since these are SSH-2 messages, the first data byte is taken - * to be the packet type code. - * - * These messages are interpreted as those of an SSH connection, after - * userauth completes, and without any repeat key exchange. - * Specifically, any message from the SSH Connection Protocol is - * permitted, and also SSH_MSG_IGNORE, SSH_MSG_DEBUG, - * SSH_MSG_DISCONNECT and SSH_MSG_UNIMPLEMENTED from the SSH Transport - * Protocol. - * - * This protocol imposes a few additional requirements, over and above - * those of the standard SSH Connection Protocol: - * - * Message sizes are not permitted to exceed 0x4010 (16400) bytes, - * including their length header. - * - * When the server (i.e. really the PuTTY upstream) sends - * SSH_MSG_CHANNEL_OPEN with channel type "x11", and the client - * (downstream) responds with SSH_MSG_CHANNEL_OPEN_CONFIRMATION, that - * confirmation message MUST include an initial window size of at - * least 256. (Rationale: this is a bit of a fudge which makes it - * easier, by eliminating the possibility of nasty edge cases, for an - * upstream to arrange not to pass the CHANNEL_OPEN on to downstream - * until after it's seen the X11 auth data to decide which downstream - * it needs to go to.) - */ - -#include -#include -#include -#include -#include - -#include "putty.h" -#include "tree234.h" -#include "ssh.h" -#include "sshcr.h" - -struct ssh_sharing_state { - char *sockname; /* the socket name, kept for cleanup */ - Socket *listensock; /* the master listening Socket */ - tree234 *connections; /* holds ssh_sharing_connstates */ - unsigned nextid; /* preferred id for next connstate */ - ConnectionLayer *cl; /* instance of the ssh connection layer */ - char *server_verstring; /* server version string after "SSH-" */ - - Plug plug; -}; - -struct share_globreq; - -struct ssh_sharing_connstate { - unsigned id; /* used to identify this downstream in log messages */ - - Socket *sock; /* the Socket for this connection */ - struct ssh_sharing_state *parent; - - int crLine; /* coroutine state for share_receive */ - - bool sent_verstring, got_verstring; - int curr_packetlen; - - unsigned char recvbuf[0x4010]; - size_t recvlen; - - /* - * Assorted state we have to remember about this downstream, so - * that we can clean it up appropriately when the downstream goes - * away. - */ - - /* Channels which don't have a downstream id, i.e. we've passed a - * CHANNEL_OPEN down from the server but not had an - * OPEN_CONFIRMATION or OPEN_FAILURE back. If downstream goes - * away, we respond to all of these with OPEN_FAILURE. */ - tree234 *halfchannels; /* stores 'struct share_halfchannel' */ - - /* Channels which do have a downstream id. We need to index these - * by both server id and upstream id, so we can find a channel - * when handling either an upward or a downward message referring - * to it. */ - tree234 *channels_by_us; /* stores 'struct share_channel' */ - tree234 *channels_by_server; /* stores 'struct share_channel' */ - - /* Another class of channel which doesn't have a downstream id. - * The difference between these and halfchannels is that xchannels - * do have an *upstream* id, because upstream has already accepted - * the channel request from the server. This arises in the case of - * X forwarding, where we have to accept the request and read the - * X authorisation data before we know whether the channel needs - * to be forwarded to a downstream. */ - tree234 *xchannels_by_us; /* stores 'struct share_xchannel' */ - tree234 *xchannels_by_server; /* stores 'struct share_xchannel' */ - - /* Remote port forwarding requests in force. */ - tree234 *forwardings; /* stores 'struct share_forwarding' */ - - /* Global requests we've sent on to the server, pending replies. */ - struct share_globreq *globreq_head, *globreq_tail; - - Plug plug; -}; - -struct share_halfchannel { - unsigned server_id; -}; - -/* States of a share_channel. */ -enum { - OPEN, - SENT_CLOSE, - RCVD_CLOSE, - /* Downstream has sent CHANNEL_OPEN but server hasn't replied yet. - * If downstream goes away when a channel is in this state, we - * must wait for the server's response before starting to send - * CLOSE. Channels in this state are also not held in - * channels_by_server, because their server_id field is - * meaningless. */ - UNACKNOWLEDGED -}; - -struct share_channel { - unsigned downstream_id, upstream_id, server_id; - int downstream_maxpkt; - int state; - /* - * Some channels (specifically, channels on which downstream has - * sent "x11-req") have the additional function of storing a set - * of downstream X authorisation data and a handle to an upstream - * fake set. - */ - struct X11FakeAuth *x11_auth_upstream; - int x11_auth_proto; - char *x11_auth_data; - int x11_auth_datalen; - bool x11_one_shot; -}; - -struct share_forwarding { - char *host; - int port; - bool active; /* has the server sent REQUEST_SUCCESS? */ - struct ssh_rportfwd *rpf; -}; - -struct share_xchannel_message { - struct share_xchannel_message *next; - int type; - unsigned char *data; - int datalen; -}; - -struct share_xchannel { - unsigned upstream_id, server_id; - - /* - * xchannels come in two flavours: live and dead. Live ones are - * waiting for an OPEN_CONFIRMATION or OPEN_FAILURE from - * downstream; dead ones have had an OPEN_FAILURE, so they only - * exist as a means of letting us conveniently respond to further - * channel messages from the server until such time as the server - * sends us CHANNEL_CLOSE. - */ - bool live; - - /* - * When we receive OPEN_CONFIRMATION, we will need to send a - * WINDOW_ADJUST to the server to synchronise the windows. For - * this purpose we need to know what window we have so far offered - * the server. We record this as exactly the value in the - * OPEN_CONFIRMATION that upstream sent us, adjusted by the amount - * by which the two X greetings differed in length. - */ - int window; - - /* - * Linked list of SSH messages from the server relating to this - * channel, which we queue up until downstream sends us an - * OPEN_CONFIRMATION and we can belatedly send them all on. - */ - struct share_xchannel_message *msghead, *msgtail; -}; - -enum { - GLOBREQ_TCPIP_FORWARD, - GLOBREQ_CANCEL_TCPIP_FORWARD -}; - -struct share_globreq { - struct share_globreq *next; - int type; - bool want_reply; - struct share_forwarding *fwd; -}; - -static int share_connstate_cmp(void *av, void *bv) -{ - const struct ssh_sharing_connstate *a = - (const struct ssh_sharing_connstate *)av; - const struct ssh_sharing_connstate *b = - (const struct ssh_sharing_connstate *)bv; - - if (a->id < b->id) - return -1; - else if (a->id > b->id) - return +1; - else - return 0; -} - -static unsigned share_find_unused_id -(struct ssh_sharing_state *sharestate, unsigned first) -{ - int low_orig, low, mid, high, high_orig; - struct ssh_sharing_connstate *cs; - unsigned ret; - - /* - * Find the lowest unused downstream ID greater or equal to - * 'first'. - * - * Begin by seeing if 'first' itself is available. If it is, we'll - * just return it; if it's already in the tree, we'll find the - * tree index where it appears and use that for the next stage. - */ - { - struct ssh_sharing_connstate dummy; - dummy.id = first; - cs = findrelpos234(sharestate->connections, &dummy, NULL, - REL234_GE, &low_orig); - if (!cs) - return first; - } - - /* - * Now binary-search using the counted B-tree, to find the largest - * ID which is in a contiguous sequence from the beginning of that - * range. - */ - low = low_orig; - high = high_orig = count234(sharestate->connections); - while (high - low > 1) { - mid = (high + low) / 2; - cs = index234(sharestate->connections, mid); - if (cs->id == first + (mid - low_orig)) - low = mid; /* this one is still in the sequence */ - else - high = mid; /* this one is past the end */ - } - - /* - * Now low is the tree index of the largest ID in the initial - * sequence. So the return value is one more than low's id, and we - * know low's id is given by the formula in the binary search loop - * above. - * - * (If an SSH connection went on for _enormously_ long, we might - * reach a point where all ids from 'first' to UINT_MAX were in - * use. In that situation the formula below would wrap round by - * one and return zero, which is conveniently the right way to - * signal 'no id available' from this function.) - */ - ret = first + (low - low_orig) + 1; - { - struct ssh_sharing_connstate dummy; - dummy.id = ret; - assert(NULL == find234(sharestate->connections, &dummy, NULL)); - } - return ret; -} - -static int share_halfchannel_cmp(void *av, void *bv) -{ - const struct share_halfchannel *a = (const struct share_halfchannel *)av; - const struct share_halfchannel *b = (const struct share_halfchannel *)bv; - - if (a->server_id < b->server_id) - return -1; - else if (a->server_id > b->server_id) - return +1; - else - return 0; -} - -static int share_channel_us_cmp(void *av, void *bv) -{ - const struct share_channel *a = (const struct share_channel *)av; - const struct share_channel *b = (const struct share_channel *)bv; - - if (a->upstream_id < b->upstream_id) - return -1; - else if (a->upstream_id > b->upstream_id) - return +1; - else - return 0; -} - -static int share_channel_server_cmp(void *av, void *bv) -{ - const struct share_channel *a = (const struct share_channel *)av; - const struct share_channel *b = (const struct share_channel *)bv; - - if (a->server_id < b->server_id) - return -1; - else if (a->server_id > b->server_id) - return +1; - else - return 0; -} - -static int share_xchannel_us_cmp(void *av, void *bv) -{ - const struct share_xchannel *a = (const struct share_xchannel *)av; - const struct share_xchannel *b = (const struct share_xchannel *)bv; - - if (a->upstream_id < b->upstream_id) - return -1; - else if (a->upstream_id > b->upstream_id) - return +1; - else - return 0; -} - -static int share_xchannel_server_cmp(void *av, void *bv) -{ - const struct share_xchannel *a = (const struct share_xchannel *)av; - const struct share_xchannel *b = (const struct share_xchannel *)bv; - - if (a->server_id < b->server_id) - return -1; - else if (a->server_id > b->server_id) - return +1; - else - return 0; -} - -static int share_forwarding_cmp(void *av, void *bv) -{ - const struct share_forwarding *a = (const struct share_forwarding *)av; - const struct share_forwarding *b = (const struct share_forwarding *)bv; - int i; - - if ((i = strcmp(a->host, b->host)) != 0) - return i; - else if (a->port < b->port) - return -1; - else if (a->port > b->port) - return +1; - else - return 0; -} - -static void share_xchannel_free(struct share_xchannel *xc) -{ - while (xc->msghead) { - struct share_xchannel_message *tmp = xc->msghead; - xc->msghead = tmp->next; - sfree(tmp); - } - sfree(xc); -} - -static void share_connstate_free(struct ssh_sharing_connstate *cs) -{ - struct share_halfchannel *hc; - struct share_xchannel *xc; - struct share_channel *chan; - struct share_forwarding *fwd; - - while ((hc = (struct share_halfchannel *) - delpos234(cs->halfchannels, 0)) != NULL) - sfree(hc); - freetree234(cs->halfchannels); - - /* All channels live in 'channels_by_us' but only some in - * 'channels_by_server', so we use the former to find the list of - * ones to free */ - freetree234(cs->channels_by_server); - while ((chan = (struct share_channel *) - delpos234(cs->channels_by_us, 0)) != NULL) - sfree(chan); - freetree234(cs->channels_by_us); - - /* But every xchannel is in both trees, so it doesn't matter which - * we use to free them. */ - while ((xc = (struct share_xchannel *) - delpos234(cs->xchannels_by_us, 0)) != NULL) - share_xchannel_free(xc); - freetree234(cs->xchannels_by_us); - freetree234(cs->xchannels_by_server); - - while ((fwd = (struct share_forwarding *) - delpos234(cs->forwardings, 0)) != NULL) - sfree(fwd); - freetree234(cs->forwardings); - - while (cs->globreq_head) { - struct share_globreq *globreq = cs->globreq_head; - cs->globreq_head = cs->globreq_head->next; - sfree(globreq); - } - - if (cs->sock) - sk_close(cs->sock); - - sfree(cs); -} - -void sharestate_free(ssh_sharing_state *sharestate) -{ - struct ssh_sharing_connstate *cs; - - platform_ssh_share_cleanup(sharestate->sockname); - - while ((cs = (struct ssh_sharing_connstate *) - delpos234(sharestate->connections, 0)) != NULL) { - share_connstate_free(cs); - } - freetree234(sharestate->connections); - if (sharestate->listensock) { - sk_close(sharestate->listensock); - sharestate->listensock = NULL; - } - sfree(sharestate->server_verstring); - sfree(sharestate->sockname); - sfree(sharestate); -} - -static struct share_halfchannel *share_add_halfchannel - (struct ssh_sharing_connstate *cs, unsigned server_id) -{ - struct share_halfchannel *hc = snew(struct share_halfchannel); - hc->server_id = server_id; - if (add234(cs->halfchannels, hc) != hc) { - /* Duplicate?! */ - sfree(hc); - return NULL; - } else { - return hc; - } -} - -static struct share_halfchannel *share_find_halfchannel - (struct ssh_sharing_connstate *cs, unsigned server_id) -{ - struct share_halfchannel dummyhc; - dummyhc.server_id = server_id; - return find234(cs->halfchannels, &dummyhc, NULL); -} - -static void share_remove_halfchannel(struct ssh_sharing_connstate *cs, - struct share_halfchannel *hc) -{ - del234(cs->halfchannels, hc); - sfree(hc); -} - -static struct share_channel *share_add_channel - (struct ssh_sharing_connstate *cs, unsigned downstream_id, - unsigned upstream_id, unsigned server_id, int state, int maxpkt) -{ - struct share_channel *chan = snew(struct share_channel); - chan->downstream_id = downstream_id; - chan->upstream_id = upstream_id; - chan->server_id = server_id; - chan->state = state; - chan->downstream_maxpkt = maxpkt; - chan->x11_auth_upstream = NULL; - chan->x11_auth_data = NULL; - chan->x11_auth_proto = -1; - chan->x11_auth_datalen = 0; - chan->x11_one_shot = false; - if (add234(cs->channels_by_us, chan) != chan) { - sfree(chan); - return NULL; - } - if (chan->state != UNACKNOWLEDGED) { - if (add234(cs->channels_by_server, chan) != chan) { - del234(cs->channels_by_us, chan); - sfree(chan); - return NULL; - } - } - return chan; -} - -static void share_channel_set_server_id(struct ssh_sharing_connstate *cs, - struct share_channel *chan, - unsigned server_id, int newstate) -{ - chan->server_id = server_id; - chan->state = newstate; - assert(newstate != UNACKNOWLEDGED); - add234(cs->channels_by_server, chan); -} - -static struct share_channel *share_find_channel_by_upstream - (struct ssh_sharing_connstate *cs, unsigned upstream_id) -{ - struct share_channel dummychan; - dummychan.upstream_id = upstream_id; - return find234(cs->channels_by_us, &dummychan, NULL); -} - -static struct share_channel *share_find_channel_by_server - (struct ssh_sharing_connstate *cs, unsigned server_id) -{ - struct share_channel dummychan; - dummychan.server_id = server_id; - return find234(cs->channels_by_server, &dummychan, NULL); -} - -static void share_remove_channel(struct ssh_sharing_connstate *cs, - struct share_channel *chan) -{ - del234(cs->channels_by_us, chan); - del234(cs->channels_by_server, chan); - if (chan->x11_auth_upstream) - ssh_remove_sharing_x11_display(cs->parent->cl, - chan->x11_auth_upstream); - sfree(chan->x11_auth_data); - sfree(chan); -} - -static struct share_xchannel *share_add_xchannel - (struct ssh_sharing_connstate *cs, - unsigned upstream_id, unsigned server_id) -{ - struct share_xchannel *xc = snew(struct share_xchannel); - xc->upstream_id = upstream_id; - xc->server_id = server_id; - xc->live = true; - xc->msghead = xc->msgtail = NULL; - if (add234(cs->xchannels_by_us, xc) != xc) { - sfree(xc); - return NULL; - } - if (add234(cs->xchannels_by_server, xc) != xc) { - del234(cs->xchannels_by_us, xc); - sfree(xc); - return NULL; - } - return xc; -} - -static struct share_xchannel *share_find_xchannel_by_upstream - (struct ssh_sharing_connstate *cs, unsigned upstream_id) -{ - struct share_xchannel dummyxc; - dummyxc.upstream_id = upstream_id; - return find234(cs->xchannels_by_us, &dummyxc, NULL); -} - -static struct share_xchannel *share_find_xchannel_by_server - (struct ssh_sharing_connstate *cs, unsigned server_id) -{ - struct share_xchannel dummyxc; - dummyxc.server_id = server_id; - return find234(cs->xchannels_by_server, &dummyxc, NULL); -} - -static void share_remove_xchannel(struct ssh_sharing_connstate *cs, - struct share_xchannel *xc) -{ - del234(cs->xchannels_by_us, xc); - del234(cs->xchannels_by_server, xc); - share_xchannel_free(xc); -} - -static struct share_forwarding *share_add_forwarding - (struct ssh_sharing_connstate *cs, - const char *host, int port) -{ - struct share_forwarding *fwd = snew(struct share_forwarding); - fwd->host = dupstr(host); - fwd->port = port; - fwd->active = false; - if (add234(cs->forwardings, fwd) != fwd) { - /* Duplicate?! */ - sfree(fwd); - return NULL; - } - return fwd; -} - -static struct share_forwarding *share_find_forwarding - (struct ssh_sharing_connstate *cs, const char *host, int port) -{ - struct share_forwarding dummyfwd, *ret; - dummyfwd.host = dupstr(host); - dummyfwd.port = port; - ret = find234(cs->forwardings, &dummyfwd, NULL); - sfree(dummyfwd.host); - return ret; -} - -static void share_remove_forwarding(struct ssh_sharing_connstate *cs, - struct share_forwarding *fwd) -{ - del234(cs->forwardings, fwd); - sfree(fwd); -} - -static void log_downstream(struct ssh_sharing_connstate *cs, - const char *logfmt, ...) -{ - va_list ap; - char *buf; - - va_start(ap, logfmt); - buf = dupvprintf(logfmt, ap); - va_end(ap); - logeventf(cs->parent->cl->logctx, - "Connection sharing downstream #%u: %s", cs->id, buf); - sfree(buf); -} - -static void log_general(struct ssh_sharing_state *sharestate, - const char *logfmt, ...) -{ - va_list ap; - char *buf; - - va_start(ap, logfmt); - buf = dupvprintf(logfmt, ap); - va_end(ap); - logeventf(sharestate->cl->logctx, "Connection sharing: %s", buf); - sfree(buf); -} - -static void send_packet_to_downstream(struct ssh_sharing_connstate *cs, - int type, const void *pkt, int pktlen, - struct share_channel *chan) -{ - strbuf *packet; - - if (!cs->sock) /* throw away all packets destined for a dead downstream */ - return; - - if (type == SSH2_MSG_CHANNEL_DATA) { - /* - * Special case which we take care of at a low level, so as to - * be sure to apply it in all cases. On rare occasions we - * might find that we have a channel for which the - * downstream's maximum packet size exceeds the max packet - * size we presented to the server on its behalf. (This can - * occur in X11 forwarding, where we have to send _our_ - * CHANNEL_OPEN_CONFIRMATION before we discover which if any - * downstream the channel is destined for, so if that - * downstream turns out to present a smaller max packet size - * then we're in this situation.) - * - * If that happens, we just chop up the packet into pieces and - * send them as separate CHANNEL_DATA packets. - */ - BinarySource src[1]; - unsigned channel; - ptrlen data; - - BinarySource_BARE_INIT(src, pkt, pktlen); - channel = get_uint32(src); - data = get_string(src); - - do { - int this_len = (data.len > chan->downstream_maxpkt ? - chan->downstream_maxpkt : data.len); - - packet = strbuf_new_nm(); - put_uint32(packet, 0); /* placeholder for length field */ - put_byte(packet, type); - put_uint32(packet, channel); - put_uint32(packet, this_len); - put_data(packet, data.ptr, this_len); - data.ptr = (const char *)data.ptr + this_len; - data.len -= this_len; - PUT_32BIT_MSB_FIRST(packet->s, packet->len-4); - sk_write(cs->sock, packet->s, packet->len); - strbuf_free(packet); - } while (data.len > 0); - } else { - /* - * Just do the obvious thing. - */ - packet = strbuf_new_nm(); - put_uint32(packet, 0); /* placeholder for length field */ - put_byte(packet, type); - put_data(packet, pkt, pktlen); - PUT_32BIT_MSB_FIRST(packet->s, packet->len-4); - sk_write(cs->sock, packet->s, packet->len); - strbuf_free(packet); - } -} - -static void share_try_cleanup(struct ssh_sharing_connstate *cs) -{ - int i; - struct share_halfchannel *hc; - struct share_channel *chan; - struct share_forwarding *fwd; - - /* - * Any half-open channels, i.e. those for which we'd received - * CHANNEL_OPEN from the server but not passed back a response - * from downstream, should be responded to with OPEN_FAILURE. - */ - while ((hc = (struct share_halfchannel *) - index234(cs->halfchannels, 0)) != NULL) { - static const char reason[] = "PuTTY downstream no longer available"; - static const char lang[] = "en"; - strbuf *packet; - - packet = strbuf_new(); - put_uint32(packet, hc->server_id); - put_uint32(packet, SSH2_OPEN_CONNECT_FAILED); - put_stringz(packet, reason); - put_stringz(packet, lang); - ssh_send_packet_from_downstream( - cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_OPEN_FAILURE, - packet->s, packet->len, - "cleanup after downstream went away"); - strbuf_free(packet); - - share_remove_halfchannel(cs, hc); - } - - /* - * Any actually open channels should have a CHANNEL_CLOSE sent for - * them, unless we've already done so. We won't be able to - * actually clean them up until CHANNEL_CLOSE comes back from the - * server, though (unless the server happens to have sent a CLOSE - * already). - * - * Another annoying exception is UNACKNOWLEDGED channels, i.e. - * we've _sent_ a CHANNEL_OPEN to the server but not received an - * OPEN_CONFIRMATION or OPEN_FAILURE. We must wait for a reply - * before closing the channel, because until we see that reply we - * won't have the server's channel id to put in the close message. - */ - for (i = 0; (chan = (struct share_channel *) - index234(cs->channels_by_us, i)) != NULL; i++) { - strbuf *packet; - - if (chan->state != SENT_CLOSE && chan->state != UNACKNOWLEDGED) { - packet = strbuf_new(); - put_uint32(packet, chan->server_id); - ssh_send_packet_from_downstream( - cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_CLOSE, - packet->s, packet->len, - "cleanup after downstream went away"); - strbuf_free(packet); - - if (chan->state != RCVD_CLOSE) { - chan->state = SENT_CLOSE; - } else { - /* In this case, we _can_ clear up the channel now. */ - ssh_delete_sharing_channel(cs->parent->cl, chan->upstream_id); - share_remove_channel(cs, chan); - i--; /* don't accidentally skip one as a result */ - } - } - } - - /* - * Any remote port forwardings we're managing on behalf of this - * downstream should be cancelled. Again, we must defer those for - * which we haven't yet seen REQUEST_SUCCESS/FAILURE. - * - * We take a fire-and-forget approach during cleanup, not - * bothering to set want_reply. - */ - for (i = 0; (fwd = (struct share_forwarding *) - index234(cs->forwardings, i)) != NULL; i++) { - if (fwd->active) { - strbuf *packet = strbuf_new(); - put_stringz(packet, "cancel-tcpip-forward"); - put_bool(packet, false); /* !want_reply */ - put_stringz(packet, fwd->host); - put_uint32(packet, fwd->port); - ssh_send_packet_from_downstream( - cs->parent->cl, cs->id, SSH2_MSG_GLOBAL_REQUEST, - packet->s, packet->len, - "cleanup after downstream went away"); - strbuf_free(packet); - - ssh_rportfwd_remove(cs->parent->cl, fwd->rpf); - share_remove_forwarding(cs, fwd); - i--; /* don't accidentally skip one as a result */ - } - } - - if (count234(cs->halfchannels) == 0 && - count234(cs->channels_by_us) == 0 && - count234(cs->forwardings) == 0) { - struct ssh_sharing_state *sharestate = cs->parent; - - /* - * Now we're _really_ done, so we can get rid of cs completely. - */ - del234(sharestate->connections, cs); - log_downstream(cs, "disconnected"); - share_connstate_free(cs); - - /* - * And if this was the last downstream, notify the connection - * layer, because it might now be time to wind up the whole - * SSH connection. - */ - if (count234(sharestate->connections) == 0 && sharestate->cl) - ssh_sharing_no_more_downstreams(sharestate->cl); - } -} - -static void share_begin_cleanup(struct ssh_sharing_connstate *cs) -{ - - sk_close(cs->sock); - cs->sock = NULL; - - share_try_cleanup(cs); -} - -static void share_disconnect(struct ssh_sharing_connstate *cs, - const char *message) -{ - strbuf *packet = strbuf_new(); - put_uint32(packet, SSH2_DISCONNECT_PROTOCOL_ERROR); - put_stringz(packet, message); - put_stringz(packet, "en"); /* language */ - send_packet_to_downstream(cs, SSH2_MSG_DISCONNECT, - packet->s, packet->len, NULL); - strbuf_free(packet); - - share_begin_cleanup(cs); -} - -static void share_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) -{ - struct ssh_sharing_connstate *cs = container_of( - plug, struct ssh_sharing_connstate, plug); - - if (error_msg) { -#ifdef BROKEN_PIPE_ERROR_CODE - /* - * Most of the time, we log what went wrong when a downstream - * disappears with a socket error. One exception, though, is - * receiving EPIPE when we haven't received a protocol version - * string from the downstream, because that can happen as a result - * of plink -shareexists (opening the connection and instantly - * closing it again without bothering to read our version string). - * So that one case is not treated as a log-worthy error. - */ - if (error_code == BROKEN_PIPE_ERROR_CODE && !cs->got_verstring) - /* do nothing */; - else -#endif - log_downstream(cs, "Socket error: %s", error_msg); - } - share_begin_cleanup(cs); -} - -/* - * Append a message to the end of an xchannel's queue. - */ -static void share_xchannel_add_message( - struct share_xchannel *xc, int type, const void *data, int len) -{ - struct share_xchannel_message *msg; - - /* - * Allocate the 'struct share_xchannel_message' and the actual - * data in one unit. - */ - msg = snew_plus(struct share_xchannel_message, len); - msg->data = snew_plus_get_aux(msg); - msg->datalen = len; - msg->type = type; - memcpy(msg->data, data, len); - - /* - * Queue it in the xchannel. - */ - if (xc->msgtail) - xc->msgtail->next = msg; - else - xc->msghead = msg; - msg->next = NULL; - xc->msgtail = msg; -} - -void share_dead_xchannel_respond(struct ssh_sharing_connstate *cs, - struct share_xchannel *xc) -{ - /* - * Handle queued incoming messages from the server destined for an - * xchannel which is dead (i.e. downstream sent OPEN_FAILURE). - */ - bool delete = false; - while (xc->msghead) { - struct share_xchannel_message *msg = xc->msghead; - xc->msghead = msg->next; - - if (msg->type == SSH2_MSG_CHANNEL_REQUEST && msg->datalen > 4) { - /* - * A CHANNEL_REQUEST is responded to by sending - * CHANNEL_FAILURE, if it has want_reply set. - */ - BinarySource src[1]; - BinarySource_BARE_INIT(src, msg->data, msg->datalen); - get_uint32(src); /* skip channel id */ - get_string(src); /* skip request type */ - if (get_bool(src)) { - strbuf *packet = strbuf_new(); - put_uint32(packet, xc->server_id); - ssh_send_packet_from_downstream - (cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_FAILURE, - packet->s, packet->len, - "downstream refused X channel open"); - strbuf_free(packet); - } - } else if (msg->type == SSH2_MSG_CHANNEL_CLOSE) { - /* - * On CHANNEL_CLOSE we can discard the channel completely. - */ - delete = true; - } - - sfree(msg); - } - xc->msgtail = NULL; - if (delete) { - ssh_delete_sharing_channel(cs->parent->cl, xc->upstream_id); - share_remove_xchannel(cs, xc); - } -} - -void share_xchannel_confirmation(struct ssh_sharing_connstate *cs, - struct share_xchannel *xc, - struct share_channel *chan, - unsigned downstream_window) -{ - strbuf *packet; - - /* - * Send all the queued messages downstream. - */ - while (xc->msghead) { - struct share_xchannel_message *msg = xc->msghead; - xc->msghead = msg->next; - - if (msg->datalen >= 4) - PUT_32BIT_MSB_FIRST(msg->data, chan->downstream_id); - send_packet_to_downstream(cs, msg->type, - msg->data, msg->datalen, chan); - - sfree(msg); - } - - /* - * Send a WINDOW_ADJUST back upstream, to synchronise the window - * size downstream thinks it's presented with the one we've - * actually presented. - */ - packet = strbuf_new(); - put_uint32(packet, xc->server_id); - put_uint32(packet, downstream_window - xc->window); - ssh_send_packet_from_downstream( - cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_WINDOW_ADJUST, - packet->s, packet->len, - "window adjustment after downstream accepted X channel"); - strbuf_free(packet); -} - -void share_xchannel_failure(struct ssh_sharing_connstate *cs, - struct share_xchannel *xc) -{ - /* - * If downstream refuses to open our X channel at all for some - * reason, we must respond by sending an emergency CLOSE upstream. - */ - strbuf *packet = strbuf_new(); - put_uint32(packet, xc->server_id); - ssh_send_packet_from_downstream( - cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_CLOSE, - packet->s, packet->len, - "downstream refused X channel open"); - strbuf_free(packet); - - /* - * Now mark the xchannel as dead, and respond to anything sent on - * it until we see CLOSE for it in turn. - */ - xc->live = false; - share_dead_xchannel_respond(cs, xc); -} - -void share_setup_x11_channel(ssh_sharing_connstate *cs, share_channel *chan, - unsigned upstream_id, unsigned server_id, - unsigned server_currwin, unsigned server_maxpkt, - unsigned client_adjusted_window, - const char *peer_addr, int peer_port, int endian, - int protomajor, int protominor, - const void *initial_data, int initial_len) -{ - struct share_xchannel *xc; - void *greeting; - int greeting_len; - strbuf *packet; - - /* - * Create an xchannel containing data we've already received from - * the X client, and preload it with a CHANNEL_DATA message - * containing our own made-up authorisation greeting and any - * additional data sent from the server so far. - */ - xc = share_add_xchannel(cs, upstream_id, server_id); - greeting = x11_make_greeting(endian, protomajor, protominor, - chan->x11_auth_proto, - chan->x11_auth_data, chan->x11_auth_datalen, - peer_addr, peer_port, &greeting_len); - packet = strbuf_new_nm(); - put_uint32(packet, 0); /* leave the channel id field unfilled - we - * don't know the downstream id yet */ - put_uint32(packet, greeting_len + initial_len); - put_data(packet, greeting, greeting_len); - put_data(packet, initial_data, initial_len); - sfree(greeting); - share_xchannel_add_message(xc, SSH2_MSG_CHANNEL_DATA, - packet->s, packet->len); - strbuf_free(packet); - - xc->window = client_adjusted_window + greeting_len; - - /* - * Send on a CHANNEL_OPEN to downstream. - */ - packet = strbuf_new(); - put_stringz(packet, "x11"); - put_uint32(packet, server_id); - put_uint32(packet, server_currwin); - put_uint32(packet, server_maxpkt); - put_stringz(packet, peer_addr); - put_uint32(packet, peer_port); - send_packet_to_downstream(cs, SSH2_MSG_CHANNEL_OPEN, - packet->s, packet->len, NULL); - strbuf_free(packet); - - /* - * If this was a once-only X forwarding, clean it up now. - */ - if (chan->x11_one_shot) { - ssh_remove_sharing_x11_display(cs->parent->cl, - chan->x11_auth_upstream); - chan->x11_auth_upstream = NULL; - sfree(chan->x11_auth_data); - chan->x11_auth_proto = -1; - chan->x11_auth_datalen = 0; - chan->x11_one_shot = false; - } -} - -void share_got_pkt_from_server(ssh_sharing_connstate *cs, int type, - const void *vpkt, int pktlen) -{ - const unsigned char *pkt = (const unsigned char *)vpkt; - struct share_globreq *globreq; - size_t id_pos; - unsigned upstream_id, server_id; - struct share_channel *chan; - struct share_xchannel *xc; - BinarySource src[1]; - - BinarySource_BARE_INIT(src, pkt, pktlen); - - switch (type) { - case SSH2_MSG_REQUEST_SUCCESS: - case SSH2_MSG_REQUEST_FAILURE: - globreq = cs->globreq_head; - assert(globreq); /* should match the queue in ssh.c */ - if (globreq->type == GLOBREQ_TCPIP_FORWARD) { - if (type == SSH2_MSG_REQUEST_FAILURE) { - share_remove_forwarding(cs, globreq->fwd); - } else { - globreq->fwd->active = true; - } - } else if (globreq->type == GLOBREQ_CANCEL_TCPIP_FORWARD) { - if (type == SSH2_MSG_REQUEST_SUCCESS) { - share_remove_forwarding(cs, globreq->fwd); - } - } - if (globreq->want_reply) { - send_packet_to_downstream(cs, type, pkt, pktlen, NULL); - } - cs->globreq_head = globreq->next; - sfree(globreq); - if (cs->globreq_head == NULL) - cs->globreq_tail = NULL; - - if (!cs->sock) { - /* Retry cleaning up this connection, in case that reply - * was the last thing we were waiting for. */ - share_try_cleanup(cs); - } - - break; - - case SSH2_MSG_CHANNEL_OPEN: - get_string(src); - server_id = get_uint32(src); - assert(!get_err(src)); - share_add_halfchannel(cs, server_id); - - send_packet_to_downstream(cs, type, pkt, pktlen, NULL); - break; - - case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: - case SSH2_MSG_CHANNEL_OPEN_FAILURE: - case SSH2_MSG_CHANNEL_CLOSE: - case SSH2_MSG_CHANNEL_WINDOW_ADJUST: - case SSH2_MSG_CHANNEL_DATA: - case SSH2_MSG_CHANNEL_EXTENDED_DATA: - case SSH2_MSG_CHANNEL_EOF: - case SSH2_MSG_CHANNEL_REQUEST: - case SSH2_MSG_CHANNEL_SUCCESS: - case SSH2_MSG_CHANNEL_FAILURE: - /* - * All these messages have the recipient channel id as the - * first uint32 field in the packet. Substitute the downstream - * channel id for our one and pass the packet downstream. - */ - id_pos = src->pos; - upstream_id = get_uint32(src); - if ((chan = share_find_channel_by_upstream(cs, upstream_id)) != NULL) { - /* - * The normal case: this id refers to an open channel. - */ - unsigned char *rewritten = snewn(pktlen, unsigned char); - memcpy(rewritten, pkt, pktlen); - PUT_32BIT_MSB_FIRST(rewritten + id_pos, chan->downstream_id); - send_packet_to_downstream(cs, type, rewritten, pktlen, chan); - sfree(rewritten); - - /* - * Update the channel state, for messages that need it. - */ - if (type == SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) { - if (chan->state == UNACKNOWLEDGED && pktlen >= 8) { - share_channel_set_server_id( - cs, chan, GET_32BIT_MSB_FIRST(pkt+4), OPEN); - if (!cs->sock) { - /* Retry cleaning up this connection, so that we - * can send an immediate CLOSE on this channel for - * which we now know the server id. */ - share_try_cleanup(cs); - } - } - } else if (type == SSH2_MSG_CHANNEL_OPEN_FAILURE) { - ssh_delete_sharing_channel(cs->parent->cl, chan->upstream_id); - share_remove_channel(cs, chan); - } else if (type == SSH2_MSG_CHANNEL_CLOSE) { - if (chan->state == SENT_CLOSE) { - ssh_delete_sharing_channel(cs->parent->cl, - chan->upstream_id); - share_remove_channel(cs, chan); - if (!cs->sock) { - /* Retry cleaning up this connection, in case this - * channel closure was the last thing we were - * waiting for. */ - share_try_cleanup(cs); - } - } else { - chan->state = RCVD_CLOSE; - } - } - } else if ((xc = share_find_xchannel_by_upstream(cs, upstream_id)) - != NULL) { - /* - * The unusual case: this id refers to an xchannel. Add it - * to the xchannel's queue. - */ - share_xchannel_add_message(xc, type, pkt, pktlen); - - /* If the xchannel is dead, then also respond to it (which - * may involve deleting the channel). */ - if (!xc->live) - share_dead_xchannel_respond(cs, xc); - } - break; - - default: - assert(!"This packet type should never have come from ssh.c"); - break; - } -} - -static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, - int type, - unsigned char *pkt, int pktlen) -{ - ptrlen request_name; - struct share_forwarding *fwd; - size_t id_pos; - unsigned maxpkt; - unsigned old_id, new_id, server_id; - struct share_globreq *globreq; - struct share_channel *chan; - struct share_halfchannel *hc; - struct share_xchannel *xc; - strbuf *packet; - char *err = NULL; - BinarySource src[1]; - size_t wantreplypos; - bool orig_wantreply; - - BinarySource_BARE_INIT(src, pkt, pktlen); - - switch (type) { - case SSH2_MSG_DISCONNECT: - /* - * This message stops here: if downstream is disconnecting - * from us, that doesn't mean we want to disconnect from the - * SSH server. Close the downstream connection and start - * cleanup. - */ - share_begin_cleanup(cs); - break; - - case SSH2_MSG_GLOBAL_REQUEST: - /* - * The only global requests we understand are "tcpip-forward" - * and "cancel-tcpip-forward". Since those require us to - * maintain state, we must assume that other global requests - * will probably require that too, and so we don't forward on - * any request we don't understand. - */ - request_name = get_string(src); - wantreplypos = src->pos; - orig_wantreply = get_bool(src); - - if (ptrlen_eq_string(request_name, "tcpip-forward")) { - ptrlen hostpl; - char *host; - int port; - struct ssh_rportfwd *rpf; - - /* - * Pick the packet apart to find the want_reply field and - * the host/port we're going to ask to listen on. - */ - hostpl = get_string(src); - port = toint(get_uint32(src)); - if (get_err(src)) { - err = dupprintf("Truncated GLOBAL_REQUEST packet"); - goto confused; - } - host = mkstr(hostpl); - - /* - * See if we can allocate space in ssh.c's tree of remote - * port forwardings. If we can't, it's because another - * client sharing this connection has already allocated - * the identical port forwarding, so we take it on - * ourselves to manufacture a failure packet and send it - * back to downstream. - */ - rpf = ssh_rportfwd_alloc( - cs->parent->cl, host, port, NULL, 0, 0, NULL, NULL, cs); - if (!rpf) { - if (orig_wantreply) { - send_packet_to_downstream(cs, SSH2_MSG_REQUEST_FAILURE, - "", 0, NULL); - } - } else { - /* - * We've managed to make space for this forwarding - * locally. Pass the request on to the SSH server, but - * set want_reply even if it wasn't originally set, so - * that we know whether this forwarding needs to be - * cleaned up if downstream goes away. - */ - pkt[wantreplypos] = 1; - ssh_send_packet_from_downstream - (cs->parent->cl, cs->id, type, pkt, pktlen, - orig_wantreply ? NULL : "upstream added want_reply flag"); - fwd = share_add_forwarding(cs, host, port); - ssh_sharing_queue_global_request(cs->parent->cl, cs); - - if (fwd) { - globreq = snew(struct share_globreq); - globreq->next = NULL; - if (cs->globreq_tail) - cs->globreq_tail->next = globreq; - else - cs->globreq_head = globreq; - globreq->fwd = fwd; - globreq->want_reply = orig_wantreply; - globreq->type = GLOBREQ_TCPIP_FORWARD; - - fwd->rpf = rpf; - } - } - - sfree(host); - } else if (ptrlen_eq_string(request_name, "cancel-tcpip-forward")) { - ptrlen hostpl; - char *host; - int port; - struct share_forwarding *fwd; - - /* - * Pick the packet apart to find the want_reply field and - * the host/port we're going to ask to listen on. - */ - hostpl = get_string(src); - port = toint(get_uint32(src)); - if (get_err(src)) { - err = dupprintf("Truncated GLOBAL_REQUEST packet"); - goto confused; - } - host = mkstr(hostpl); - - /* - * Look up the existing forwarding with these details. - */ - fwd = share_find_forwarding(cs, host, port); - if (!fwd) { - if (orig_wantreply) { - send_packet_to_downstream(cs, SSH2_MSG_REQUEST_FAILURE, - "", 0, NULL); - } - } else { - /* - * Tell ssh.c to stop sending us channel-opens for - * this forwarding. - */ - ssh_rportfwd_remove(cs->parent->cl, fwd->rpf); - - /* - * Pass the cancel request on to the SSH server, but - * set want_reply even if it wasn't originally set, so - * that _we_ know whether the forwarding has been - * deleted even if downstream doesn't want to know. - */ - pkt[wantreplypos] = 1; - ssh_send_packet_from_downstream - (cs->parent->cl, cs->id, type, pkt, pktlen, - orig_wantreply ? NULL : "upstream added want_reply flag"); - ssh_sharing_queue_global_request(cs->parent->cl, cs); - - /* - * And queue a globreq so that when the reply comes - * back we know to cancel it. - */ - globreq = snew(struct share_globreq); - globreq->next = NULL; - if (cs->globreq_tail) - cs->globreq_tail->next = globreq; - else - cs->globreq_head = globreq; - globreq->fwd = fwd; - globreq->want_reply = orig_wantreply; - globreq->type = GLOBREQ_CANCEL_TCPIP_FORWARD; - } - - sfree(host); - } else { - /* - * Request we don't understand. Manufacture a failure - * message if an answer was required. - */ - if (orig_wantreply) - send_packet_to_downstream(cs, SSH2_MSG_REQUEST_FAILURE, - "", 0, NULL); - } - break; - - case SSH2_MSG_CHANNEL_OPEN: - /* Sender channel id comes after the channel type string */ - get_string(src); - id_pos = src->pos; - old_id = get_uint32(src); - new_id = ssh_alloc_sharing_channel(cs->parent->cl, cs); - get_uint32(src); /* skip initial window size */ - maxpkt = get_uint32(src); - if (get_err(src)) { - err = dupprintf("Truncated CHANNEL_OPEN packet"); - goto confused; - } - share_add_channel(cs, old_id, new_id, 0, UNACKNOWLEDGED, maxpkt); - PUT_32BIT_MSB_FIRST(pkt + id_pos, new_id); - ssh_send_packet_from_downstream(cs->parent->cl, cs->id, - type, pkt, pktlen, NULL); - break; - - case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: - if (pktlen < 16) { - err = dupprintf("Truncated CHANNEL_OPEN_CONFIRMATION packet"); - goto confused; - } - - server_id = get_uint32(src); - id_pos = src->pos; - old_id = get_uint32(src); - get_uint32(src); /* skip initial window size */ - maxpkt = get_uint32(src); - if (get_err(src)) { - err = dupprintf("Truncated CHANNEL_OPEN_CONFIRMATION packet"); - goto confused; - } - - /* This server id may refer to either a halfchannel or an xchannel. */ - hc = NULL, xc = NULL; /* placate optimiser */ - if ((hc = share_find_halfchannel(cs, server_id)) != NULL) { - new_id = ssh_alloc_sharing_channel(cs->parent->cl, cs); - } else if ((xc = share_find_xchannel_by_server(cs, server_id)) - != NULL) { - new_id = xc->upstream_id; - } else { - err = dupprintf("CHANNEL_OPEN_CONFIRMATION packet cited unknown channel %u", (unsigned)server_id); - goto confused; - } - - PUT_32BIT_MSB_FIRST(pkt + id_pos, new_id); - - chan = share_add_channel(cs, old_id, new_id, server_id, OPEN, maxpkt); - - if (hc) { - ssh_send_packet_from_downstream(cs->parent->cl, cs->id, - type, pkt, pktlen, NULL); - share_remove_halfchannel(cs, hc); - } else if (xc) { - unsigned downstream_window = GET_32BIT_MSB_FIRST(pkt + 8); - if (downstream_window < 256) { - err = dupprintf("Initial window size for x11 channel must be at least 256 (got %u)", downstream_window); - goto confused; - } - share_xchannel_confirmation(cs, xc, chan, downstream_window); - share_remove_xchannel(cs, xc); - } - - break; - - case SSH2_MSG_CHANNEL_OPEN_FAILURE: - server_id = get_uint32(src); - if (get_err(src)) { - err = dupprintf("Truncated CHANNEL_OPEN_FAILURE packet"); - goto confused; - } - - /* This server id may refer to either a halfchannel or an xchannel. */ - if ((hc = share_find_halfchannel(cs, server_id)) != NULL) { - ssh_send_packet_from_downstream(cs->parent->cl, cs->id, - type, pkt, pktlen, NULL); - share_remove_halfchannel(cs, hc); - } else if ((xc = share_find_xchannel_by_server(cs, server_id)) - != NULL) { - share_xchannel_failure(cs, xc); - } else { - err = dupprintf("CHANNEL_OPEN_FAILURE packet cited unknown channel %u", (unsigned)server_id); - goto confused; - } - - break; - - case SSH2_MSG_CHANNEL_WINDOW_ADJUST: - case SSH2_MSG_CHANNEL_DATA: - case SSH2_MSG_CHANNEL_EXTENDED_DATA: - case SSH2_MSG_CHANNEL_EOF: - case SSH2_MSG_CHANNEL_CLOSE: - case SSH2_MSG_CHANNEL_REQUEST: - case SSH2_MSG_CHANNEL_SUCCESS: - case SSH2_MSG_CHANNEL_FAILURE: - case SSH2_MSG_IGNORE: - case SSH2_MSG_DEBUG: - server_id = get_uint32(src); - - if (type == SSH2_MSG_CHANNEL_REQUEST) { - request_name = get_string(src); - - /* - * Agent forwarding requests from downstream are treated - * specially. Because OpenSSHD doesn't let us enable agent - * forwarding independently per session channel, and in - * particular because the OpenSSH-defined agent forwarding - * protocol does not mark agent-channel requests with the - * id of the session channel they originate from, the only - * way we can implement agent forwarding in a - * connection-shared PuTTY is to forward the _upstream_ - * agent. Hence, we unilaterally deny agent forwarding - * requests from downstreams if we aren't prepared to - * forward an agent ourselves. - * - * (If we are, then we dutifully pass agent forwarding - * requests upstream. OpenSSHD has the curious behaviour - * that all but the first such request will be rejected, - * but all session channels opened after the first request - * get agent forwarding enabled whether they ask for it or - * not; but that's not our concern, since other SSH - * servers supporting the same piece of protocol might in - * principle at least manage to enable agent forwarding on - * precisely the channels that requested it, even if the - * subsequent CHANNEL_OPENs still can't be associated with - * a parent session channel.) - */ - if (ptrlen_eq_string(request_name, "auth-agent-req@openssh.com") && - !ssh_agent_forwarding_permitted(cs->parent->cl)) { - - chan = share_find_channel_by_server(cs, server_id); - if (chan) { - packet = strbuf_new(); - put_uint32(packet, chan->downstream_id); - send_packet_to_downstream( - cs, SSH2_MSG_CHANNEL_FAILURE, - packet->s, packet->len, NULL); - strbuf_free(packet); - } else { - char *buf = dupprintf("Agent forwarding request for " - "unrecognised channel %u", server_id); - share_disconnect(cs, buf); - sfree(buf); - return; - } - break; - } - - /* - * Another thing we treat specially is X11 forwarding - * requests. For these, we have to make up another set of - * X11 auth data, and enter it into our SSH connection's - * list of possible X11 authorisation credentials so that - * when we see an X11 channel open request we can know - * whether it's one to handle locally or one to pass on to - * a downstream, and if the latter, which one. - */ - if (ptrlen_eq_string(request_name, "x11-req")) { - bool want_reply, single_connection; - int screen; - ptrlen auth_data; - int auth_proto; - - chan = share_find_channel_by_server(cs, server_id); - if (!chan) { - char *buf = dupprintf("X11 forwarding request for " - "unrecognised channel %u", server_id); - share_disconnect(cs, buf); - sfree(buf); - return; - } - - /* - * Pick apart the whole message to find the downstream - * auth details. - */ - want_reply = get_bool(src); - single_connection = get_bool(src); - auth_proto = x11_identify_auth_proto(get_string(src)); - auth_data = get_string(src); - screen = toint(get_uint32(src)); - if (get_err(src)) { - err = dupprintf("Truncated CHANNEL_REQUEST(\"x11-req\")" - " packet"); - goto confused; - } - - if (auth_proto < 0) { - /* Reject due to not understanding downstream's - * requested authorisation method. */ - packet = strbuf_new(); - put_uint32(packet, chan->downstream_id); - send_packet_to_downstream( - cs, SSH2_MSG_CHANNEL_FAILURE, - packet->s, packet->len, NULL); - strbuf_free(packet); - break; - } - - chan->x11_auth_proto = auth_proto; - chan->x11_auth_data = x11_dehexify(auth_data, - &chan->x11_auth_datalen); - chan->x11_auth_upstream = - ssh_add_sharing_x11_display(cs->parent->cl, auth_proto, - cs, chan); - chan->x11_one_shot = single_connection; - - /* - * Now construct a replacement X forwarding request, - * containing our own auth data, and send that to the - * server. - */ - packet = strbuf_new_nm(); - put_uint32(packet, server_id); - put_stringz(packet, "x11-req"); - put_bool(packet, want_reply); - put_bool(packet, single_connection); - put_stringz(packet, chan->x11_auth_upstream->protoname); - put_stringz(packet, chan->x11_auth_upstream->datastring); - put_uint32(packet, screen); - ssh_send_packet_from_downstream( - cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_REQUEST, - packet->s, packet->len, NULL); - strbuf_free(packet); - - break; - } - } - - ssh_send_packet_from_downstream(cs->parent->cl, cs->id, - type, pkt, pktlen, NULL); - if (type == SSH2_MSG_CHANNEL_CLOSE && pktlen >= 4) { - chan = share_find_channel_by_server(cs, server_id); - if (chan) { - if (chan->state == RCVD_CLOSE) { - ssh_delete_sharing_channel(cs->parent->cl, - chan->upstream_id); - share_remove_channel(cs, chan); - } else { - chan->state = SENT_CLOSE; - } - } - } - break; - - default: - err = dupprintf("Unexpected packet type %d\n", type); - goto confused; - - /* - * Any other packet type is unexpected. In particular, we - * never pass GLOBAL_REQUESTs downstream, so we never expect - * to see SSH2_MSG_REQUEST_{SUCCESS,FAILURE}. - */ - confused: - assert(err != NULL); - share_disconnect(cs, err); - sfree(err); - break; - } -} - -/* - * An extra coroutine macro, specific to this code which is consuming - * 'const char *data'. - */ -#define crGetChar(c) do \ - { \ - while (len == 0) { \ - *crLine =__LINE__; return; case __LINE__:; \ - } \ - len--; \ - (c) = (unsigned char)*data++; \ - } while (0) - -static void share_receive(Plug *plug, int urgent, const char *data, size_t len) -{ - ssh_sharing_connstate *cs = container_of( - plug, ssh_sharing_connstate, plug); - static const char expected_verstring_prefix[] = - "SSHCONNECTION@putty.projects.tartarus.org-2.0-"; - unsigned char c; - - crBegin(cs->crLine); - - /* - * First read the version string from downstream. - */ - cs->recvlen = 0; - while (1) { - crGetChar(c); - if (c == '\012') - break; - if (cs->recvlen >= sizeof(cs->recvbuf)) { - char *buf = dupprintf("Version string far too long\n"); - share_disconnect(cs, buf); - sfree(buf); - goto dead; - } - cs->recvbuf[cs->recvlen++] = c; - } - - /* - * Now parse the version string to make sure it's at least vaguely - * sensible, and log it. - */ - if (cs->recvlen < sizeof(expected_verstring_prefix)-1 || - memcmp(cs->recvbuf, expected_verstring_prefix, - sizeof(expected_verstring_prefix) - 1)) { - char *buf = dupprintf("Version string did not have expected prefix\n"); - share_disconnect(cs, buf); - sfree(buf); - goto dead; - } - if (cs->recvlen > 0 && cs->recvbuf[cs->recvlen-1] == '\015') - cs->recvlen--; /* trim off \r before \n */ - log_downstream(cs, "Downstream version string: %.*s", - cs->recvlen, cs->recvbuf); - cs->got_verstring = true; - - /* - * Loop round reading packets. - */ - while (1) { - cs->recvlen = 0; - while (cs->recvlen < 4) { - crGetChar(c); - cs->recvbuf[cs->recvlen++] = c; - } - cs->curr_packetlen = toint(GET_32BIT_MSB_FIRST(cs->recvbuf) + 4); - if (cs->curr_packetlen < 5 || - cs->curr_packetlen > sizeof(cs->recvbuf)) { - char *buf = dupprintf("Bad packet length %u\n", - (unsigned)cs->curr_packetlen); - share_disconnect(cs, buf); - sfree(buf); - goto dead; - } - while (cs->recvlen < cs->curr_packetlen) { - crGetChar(c); - cs->recvbuf[cs->recvlen++] = c; - } - - share_got_pkt_from_downstream(cs, cs->recvbuf[4], - cs->recvbuf + 5, cs->recvlen - 5); - } - - dead:; - crFinishV; -} - -static void share_sent(Plug *plug, size_t bufsize) -{ - /* ssh_sharing_connstate *cs = container_of( - plug, ssh_sharing_connstate, plug); */ - - /* - * We do nothing here, because we expect that there won't be a - * need to throttle and unthrottle the connection to a downstream. - * It should automatically throttle itself: if the SSH server - * sends huge amounts of data on all channels then it'll run out - * of window until our downstream sends it back some - * WINDOW_ADJUSTs. - */ -} - -static void share_listen_closing(Plug *plug, const char *error_msg, - int error_code, bool calling_back) -{ - ssh_sharing_state *sharestate = - container_of(plug, ssh_sharing_state, plug); - if (error_msg) - log_general(sharestate, "listening socket: %s", error_msg); - sk_close(sharestate->listensock); - sharestate->listensock = NULL; -} - -static void share_send_verstring(ssh_sharing_connstate *cs) -{ - char *fullstring = dupcat("SSHCONNECTION@putty.projects.tartarus.org-2.0-", - cs->parent->server_verstring, "\015\012", NULL); - sk_write(cs->sock, fullstring, strlen(fullstring)); - sfree(fullstring); - - cs->sent_verstring = true; -} - -int share_ndownstreams(ssh_sharing_state *sharestate) -{ - return count234(sharestate->connections); -} - -void share_activate(ssh_sharing_state *sharestate, - const char *server_verstring) -{ - /* - * Indication from ssh.c that we are now ready to begin serving - * any downstreams that have already connected to us. - */ - struct ssh_sharing_connstate *cs; - int i; - - /* - * Trim the server's version string down to just the software - * version component, removing "SSH-2.0-" or whatever at the - * front. - */ - for (i = 0; i < 2; i++) { - server_verstring += strcspn(server_verstring, "-"); - if (*server_verstring) - server_verstring++; - } - - sharestate->server_verstring = dupstr(server_verstring); - - for (i = 0; (cs = (struct ssh_sharing_connstate *) - index234(sharestate->connections, i)) != NULL; i++) { - assert(!cs->sent_verstring); - share_send_verstring(cs); - } -} - -static const PlugVtable ssh_sharing_conn_plugvt = { - NULL, /* no log function, because that's for outgoing connections */ - share_closing, - share_receive, - share_sent, - NULL /* no accepting function, because we've already done it */ -}; - -static int share_listen_accepting(Plug *plug, - accept_fn_t constructor, accept_ctx_t ctx) -{ - struct ssh_sharing_state *sharestate = container_of( - plug, struct ssh_sharing_state, plug); - struct ssh_sharing_connstate *cs; - const char *err; - SocketPeerInfo *peerinfo; - - /* - * A new downstream has connected to us. - */ - cs = snew(struct ssh_sharing_connstate); - cs->plug.vt = &ssh_sharing_conn_plugvt; - cs->parent = sharestate; - - if ((cs->id = share_find_unused_id(sharestate, sharestate->nextid)) == 0 && - (cs->id = share_find_unused_id(sharestate, 1)) == 0) { - sfree(cs); - return 1; - } - sharestate->nextid = cs->id + 1; - if (sharestate->nextid == 0) - sharestate->nextid++; /* only happens in VERY long-running upstreams */ - - cs->sock = constructor(ctx, &cs->plug); - if ((err = sk_socket_error(cs->sock)) != NULL) { - sfree(cs); - return err != NULL; - } - - sk_set_frozen(cs->sock, 0); - - add234(cs->parent->connections, cs); - - cs->sent_verstring = false; - if (sharestate->server_verstring) - share_send_verstring(cs); - - cs->got_verstring = false; - cs->recvlen = 0; - cs->crLine = 0; - cs->halfchannels = newtree234(share_halfchannel_cmp); - cs->channels_by_us = newtree234(share_channel_us_cmp); - cs->channels_by_server = newtree234(share_channel_server_cmp); - cs->xchannels_by_us = newtree234(share_xchannel_us_cmp); - cs->xchannels_by_server = newtree234(share_xchannel_server_cmp); - cs->forwardings = newtree234(share_forwarding_cmp); - cs->globreq_head = cs->globreq_tail = NULL; - - peerinfo = sk_peer_info(cs->sock); - log_downstream(cs, "connected%s%s", - (peerinfo && peerinfo->log_text ? " from " : ""), - (peerinfo && peerinfo->log_text ? peerinfo->log_text : "")); - sk_free_peer_info(peerinfo); - - return 0; -} - -/* - * Decide on the string used to identify the connection point between - * upstream and downstream (be it a Windows named pipe or a - * Unix-domain socket or whatever else). - * - * I wondered about making this a SHA hash of all sorts of pieces of - * the PuTTY configuration - essentially everything PuTTY uses to know - * where and how to make a connection, including all the proxy details - * (or rather, all the _relevant_ ones - only including settings that - * other settings didn't prevent from having any effect), plus the - * username. However, I think it's better to keep it really simple: - * the connection point identifier is derived from the hostname and - * port used to index the host-key cache (not necessarily where we - * _physically_ connected to, in cases involving proxies or - * CONF_loghost), plus the username if one is specified. - * - * The per-platform code will quite likely hash or obfuscate this name - * in turn, for privacy from other users; failing that, it might - * transform it to avoid dangerous filename characters and so on. But - * that doesn't matter to us: for us, the point is that two session - * configurations which return the same string from this function will - * be treated as potentially shareable with each other. - */ -char *ssh_share_sockname(const char *host, int port, Conf *conf) -{ - char *username = get_remote_username(conf); - char *sockname; - - if (port == 22) { - if (username) - sockname = dupprintf("%s@%s", username, host); - else - sockname = dupprintf("%s", host); - } else { - if (username) - sockname = dupprintf("%s@%s:%d", username, host, port); - else - sockname = dupprintf("%s:%d", host, port); - } - - sfree(username); - return sockname; -} - -bool ssh_share_test_for_upstream(const char *host, int port, Conf *conf) -{ - char *sockname, *logtext, *ds_err, *us_err; - int result; - Socket *sock; - - sockname = ssh_share_sockname(host, port, conf); - - sock = NULL; - logtext = ds_err = us_err = NULL; - result = platform_ssh_share(sockname, conf, nullplug, (Plug *)NULL, &sock, - &logtext, &ds_err, &us_err, false, true); - - sfree(logtext); - sfree(ds_err); - sfree(us_err); - sfree(sockname); - - if (result == SHARE_NONE) { - assert(sock == NULL); - return false; - } else { - assert(result == SHARE_DOWNSTREAM); - sk_close(sock); - return true; - } -} - -static const PlugVtable ssh_sharing_listen_plugvt = { - NULL, /* no log function, because that's for outgoing connections */ - share_listen_closing, - NULL, /* no receive function on a listening socket */ - NULL, /* no sent function on a listening socket */ - share_listen_accepting -}; - -void ssh_connshare_provide_connlayer(ssh_sharing_state *sharestate, - ConnectionLayer *cl) -{ - sharestate->cl = cl; -} - -/* - * Init function for connection sharing. We either open a listening - * socket and become an upstream, or connect to an existing one and - * become a downstream, or do neither. We are responsible for deciding - * which of these to do (including checking the Conf to see if - * connection sharing is even enabled in the first place). If we - * become a downstream, we return the Socket with which we connected - * to the upstream; otherwise (whether or not we have established an - * upstream) we return NULL. - */ -Socket *ssh_connection_sharing_init( - const char *host, int port, Conf *conf, LogContext *logctx, - Plug *sshplug, ssh_sharing_state **state) -{ - int result; - bool can_upstream, can_downstream; - char *logtext, *ds_err, *us_err; - char *sockname; - Socket *sock, *toret = NULL; - struct ssh_sharing_state *sharestate; - - if (!conf_get_bool(conf, CONF_ssh_connection_sharing)) - return NULL; /* do not share anything */ - can_upstream = share_can_be_upstream && - conf_get_bool(conf, CONF_ssh_connection_sharing_upstream); - can_downstream = share_can_be_downstream && - conf_get_bool(conf, CONF_ssh_connection_sharing_downstream); - if (!can_upstream && !can_downstream) - return NULL; - - sockname = ssh_share_sockname(host, port, conf); - - /* - * Create a data structure for the listening plug if we turn out - * to be an upstream. - */ - sharestate = snew(struct ssh_sharing_state); - sharestate->plug.vt = &ssh_sharing_listen_plugvt; - sharestate->listensock = NULL; - sharestate->cl = NULL; - - /* - * Now hand off to a per-platform routine that either connects to - * an existing upstream (using 'ssh' as the plug), establishes our - * own upstream (using 'sharestate' as the plug), or forks off a - * separate upstream and then connects to that. It will return a - * code telling us which kind of socket it put in 'sock'. - */ - sock = NULL; - logtext = ds_err = us_err = NULL; - result = platform_ssh_share( - sockname, conf, sshplug, &sharestate->plug, &sock, &logtext, - &ds_err, &us_err, can_upstream, can_downstream); - switch (result) { - case SHARE_NONE: - /* - * We aren't sharing our connection at all (e.g. something - * went wrong setting the socket up). Free the upstream - * structure and return NULL. - */ - - if (logtext) { - /* For this result, if 'logtext' is not NULL then it is an - * error message indicating a reason why connection sharing - * couldn't be set up _at all_ */ - logeventf(logctx, - "Could not set up connection sharing: %s", logtext); - } else { - /* Failing that, ds_err and us_err indicate why we - * couldn't be a downstream and an upstream respectively */ - if (ds_err) - logeventf(logctx, "Could not set up connection sharing" - " as downstream: %s", ds_err); - if (us_err) - logeventf(logctx, "Could not set up connection sharing" - " as upstream: %s", us_err); - } - - assert(sock == NULL); - *state = NULL; - sfree(sharestate); - sfree(sockname); - break; - - case SHARE_DOWNSTREAM: - /* - * We are downstream, so free sharestate which it turns out we - * don't need after all, and return the downstream socket as a - * replacement for an ordinary SSH connection. - */ - - /* 'logtext' is a local endpoint address */ - logeventf(logctx, "Using existing shared connection at %s", logtext); - - *state = NULL; - sfree(sharestate); - sfree(sockname); - toret = sock; - break; - - case SHARE_UPSTREAM: - /* - * We are upstream. Set up sharestate properly and pass a copy - * to the caller; return NULL, to tell ssh.c that it has to - * make an ordinary connection after all. - */ - - /* 'logtext' is a local endpoint address */ - logeventf(logctx, "Sharing this connection at %s", logtext); - - *state = sharestate; - sharestate->listensock = sock; - sharestate->connections = newtree234(share_connstate_cmp); - sharestate->server_verstring = NULL; - sharestate->sockname = sockname; - sharestate->nextid = 1; - break; - } - - sfree(logtext); - sfree(ds_err); - sfree(us_err); - return toret; -} +/* + * Support for SSH connection sharing, i.e. permitting one PuTTY to + * open its own channels over the SSH session being run by another. + */ + +/* + * Discussion and technical documentation + * ====================================== + * + * The basic strategy for PuTTY's implementation of SSH connection + * sharing is to have a single 'upstream' PuTTY process, which manages + * the real SSH connection and all the cryptography, and then zero or + * more 'downstream' PuTTYs, which never talk to the real host but + * only talk to the upstream through local IPC (Unix-domain sockets or + * Windows named pipes). + * + * The downstreams communicate with the upstream using a protocol + * derived from SSH itself, which I'll document in detail below. In + * brief, though: the downstream->upstream protocol uses a trivial + * binary packet protocol (just length/type/data) to encapsulate + * unencrypted SSH messages, and downstreams talk to the upstream more + * or less as if it was an SSH server itself. (So downstreams can + * themselves open multiple SSH channels, for example, by sending + * multiple SSH2_MSG_CHANNEL_OPENs; they can send CHANNEL_REQUESTs of + * their choice within each channel, and they handle their own + * WINDOW_ADJUST messages.) + * + * The upstream would ideally handle these downstreams by just putting + * their messages into the queue for proper SSH-2 encapsulation and + * encryption and sending them straight on to the server. However, + * that's not quite feasible as written, because client-side channel + * IDs could easily conflict (between multiple downstreams, or between + * a downstream and the upstream). To protect against that, the + * upstream rewrites the client-side channel IDs in messages it passes + * on to the server, so that it's performing what you might describe + * as 'channel-number NAT'. Then the upstream remembers which of its + * own channel IDs are channels it's managing itself, and which are + * placeholders associated with a particular downstream, so that when + * replies come in from the server they can be sent on to the relevant + * downstream (after un-NATting the channel number, of course). + * + * Global requests from downstreams are only accepted if the upstream + * knows what to do about them; currently the only such requests are + * the ones having to do with remote-to-local port forwarding (in + * which, again, the upstream remembers that some of the forwardings + * it's asked the server to set up were on behalf of particular + * downstreams, and sends the incoming CHANNEL_OPENs to those + * downstreams when connections come in). + * + * Other fiddly pieces of this mechanism are X forwarding and + * (OpenSSH-style) agent forwarding. Both of these have a fundamental + * problem arising from the protocol design: that the CHANNEL_OPEN + * from the server introducing a forwarded connection does not carry + * any indication of which session channel gave rise to it; so if + * session channels from multiple downstreams enable those forwarding + * methods, it's hard for the upstream to know which downstream to + * send the resulting connections back to. + * + * For X forwarding, we can work around this in a really painful way + * by using the fake X11 authorisation data sent to the server as part + * of the forwarding setup: upstream ensures that every X forwarding + * request carries distinguishable fake auth data, and then when X + * connections come in it waits to see the auth data in the X11 setup + * message before it decides which downstream to pass the connection + * on to. + * + * For agent forwarding, that workaround is unavailable. As a result, + * this system (and, as far as I can think of, any other system too) + * has the fundamental constraint that it can only forward one SSH + * agent - it can't forward two agents to different session channels. + * So downstreams can request agent forwarding if they like, but if + * they do, they'll get whatever SSH agent is known to the upstream + * (if any) forwarded to their sessions. + * + * Downstream-to-upstream protocol + * ------------------------------- + * + * Here I document in detail the protocol spoken between PuTTY + * downstreams and upstreams over local IPC. The IPC mechanism can + * vary between host platforms, but the protocol is the same. + * + * The protocol commences with a version exchange which is exactly + * like the SSH-2 one, in that each side sends a single line of text + * of the form + * + * -- [comments] \r\n + * + * The only difference is that in real SSH-2, is the string + * "SSH", whereas in this protocol the string is + * "SSHCONNECTION@putty.projects.tartarus.org". + * + * (The SSH RFCs allow many protocol-level identifier namespaces to be + * extended by implementors without central standardisation as long as + * they suffix "@" and a domain name they control to their new ids. + * RFC 4253 does not define this particular name to be changeable at + * all, but I like to think this is obviously how it would have done + * so if the working group had foreseen the need :-) + * + * Thereafter, all data exchanged consists of a sequence of binary + * packets concatenated end-to-end, each of which is of the form + * + * uint32 length of packet, N + * byte[N] N bytes of packet data + * + * and, since these are SSH-2 messages, the first data byte is taken + * to be the packet type code. + * + * These messages are interpreted as those of an SSH connection, after + * userauth completes, and without any repeat key exchange. + * Specifically, any message from the SSH Connection Protocol is + * permitted, and also SSH_MSG_IGNORE, SSH_MSG_DEBUG, + * SSH_MSG_DISCONNECT and SSH_MSG_UNIMPLEMENTED from the SSH Transport + * Protocol. + * + * This protocol imposes a few additional requirements, over and above + * those of the standard SSH Connection Protocol: + * + * Message sizes are not permitted to exceed 0x4010 (16400) bytes, + * including their length header. + * + * When the server (i.e. really the PuTTY upstream) sends + * SSH_MSG_CHANNEL_OPEN with channel type "x11", and the client + * (downstream) responds with SSH_MSG_CHANNEL_OPEN_CONFIRMATION, that + * confirmation message MUST include an initial window size of at + * least 256. (Rationale: this is a bit of a fudge which makes it + * easier, by eliminating the possibility of nasty edge cases, for an + * upstream to arrange not to pass the CHANNEL_OPEN on to downstream + * until after it's seen the X11 auth data to decide which downstream + * it needs to go to.) + */ + +#include +#include +#include +#include +#include + +#include "putty.h" +#include "tree234.h" +#include "ssh.h" +#include "sshcr.h" + +struct ssh_sharing_state { + char *sockname; /* the socket name, kept for cleanup */ + Socket *listensock; /* the master listening Socket */ + tree234 *connections; /* holds ssh_sharing_connstates */ + unsigned nextid; /* preferred id for next connstate */ + ConnectionLayer *cl; /* instance of the ssh connection layer */ + char *server_verstring; /* server version string after "SSH-" */ + + Plug plug; +}; + +struct share_globreq; + +struct ssh_sharing_connstate { + unsigned id; /* used to identify this downstream in log messages */ + + Socket *sock; /* the Socket for this connection */ + struct ssh_sharing_state *parent; + + int crLine; /* coroutine state for share_receive */ + + bool sent_verstring, got_verstring; + int curr_packetlen; + + unsigned char recvbuf[0x4010]; + size_t recvlen; + + /* + * Assorted state we have to remember about this downstream, so + * that we can clean it up appropriately when the downstream goes + * away. + */ + + /* Channels which don't have a downstream id, i.e. we've passed a + * CHANNEL_OPEN down from the server but not had an + * OPEN_CONFIRMATION or OPEN_FAILURE back. If downstream goes + * away, we respond to all of these with OPEN_FAILURE. */ + tree234 *halfchannels; /* stores 'struct share_halfchannel' */ + + /* Channels which do have a downstream id. We need to index these + * by both server id and upstream id, so we can find a channel + * when handling either an upward or a downward message referring + * to it. */ + tree234 *channels_by_us; /* stores 'struct share_channel' */ + tree234 *channels_by_server; /* stores 'struct share_channel' */ + + /* Another class of channel which doesn't have a downstream id. + * The difference between these and halfchannels is that xchannels + * do have an *upstream* id, because upstream has already accepted + * the channel request from the server. This arises in the case of + * X forwarding, where we have to accept the request and read the + * X authorisation data before we know whether the channel needs + * to be forwarded to a downstream. */ + tree234 *xchannels_by_us; /* stores 'struct share_xchannel' */ + tree234 *xchannels_by_server; /* stores 'struct share_xchannel' */ + + /* Remote port forwarding requests in force. */ + tree234 *forwardings; /* stores 'struct share_forwarding' */ + + /* Global requests we've sent on to the server, pending replies. */ + struct share_globreq *globreq_head, *globreq_tail; + + Plug plug; +}; + +struct share_halfchannel { + unsigned server_id; +}; + +/* States of a share_channel. */ +enum { + OPEN, + SENT_CLOSE, + RCVD_CLOSE, + /* Downstream has sent CHANNEL_OPEN but server hasn't replied yet. + * If downstream goes away when a channel is in this state, we + * must wait for the server's response before starting to send + * CLOSE. Channels in this state are also not held in + * channels_by_server, because their server_id field is + * meaningless. */ + UNACKNOWLEDGED +}; + +struct share_channel { + unsigned downstream_id, upstream_id, server_id; + int downstream_maxpkt; + int state; + /* + * Some channels (specifically, channels on which downstream has + * sent "x11-req") have the additional function of storing a set + * of downstream X authorisation data and a handle to an upstream + * fake set. + */ + struct X11FakeAuth *x11_auth_upstream; + int x11_auth_proto; + char *x11_auth_data; + int x11_auth_datalen; + bool x11_one_shot; +}; + +struct share_forwarding { + char *host; + int port; + bool active; /* has the server sent REQUEST_SUCCESS? */ + struct ssh_rportfwd *rpf; +}; + +struct share_xchannel_message { + struct share_xchannel_message *next; + int type; + unsigned char *data; + int datalen; +}; + +struct share_xchannel { + unsigned upstream_id, server_id; + + /* + * xchannels come in two flavours: live and dead. Live ones are + * waiting for an OPEN_CONFIRMATION or OPEN_FAILURE from + * downstream; dead ones have had an OPEN_FAILURE, so they only + * exist as a means of letting us conveniently respond to further + * channel messages from the server until such time as the server + * sends us CHANNEL_CLOSE. + */ + bool live; + + /* + * When we receive OPEN_CONFIRMATION, we will need to send a + * WINDOW_ADJUST to the server to synchronise the windows. For + * this purpose we need to know what window we have so far offered + * the server. We record this as exactly the value in the + * OPEN_CONFIRMATION that upstream sent us, adjusted by the amount + * by which the two X greetings differed in length. + */ + int window; + + /* + * Linked list of SSH messages from the server relating to this + * channel, which we queue up until downstream sends us an + * OPEN_CONFIRMATION and we can belatedly send them all on. + */ + struct share_xchannel_message *msghead, *msgtail; +}; + +enum { + GLOBREQ_TCPIP_FORWARD, + GLOBREQ_CANCEL_TCPIP_FORWARD +}; + +struct share_globreq { + struct share_globreq *next; + int type; + bool want_reply; + struct share_forwarding *fwd; +}; + +static int share_connstate_cmp(void *av, void *bv) +{ + const struct ssh_sharing_connstate *a = + (const struct ssh_sharing_connstate *)av; + const struct ssh_sharing_connstate *b = + (const struct ssh_sharing_connstate *)bv; + + if (a->id < b->id) + return -1; + else if (a->id > b->id) + return +1; + else + return 0; +} + +static unsigned share_find_unused_id +(struct ssh_sharing_state *sharestate, unsigned first) +{ + int low_orig, low, mid, high, high_orig; + struct ssh_sharing_connstate *cs; + unsigned ret; + + /* + * Find the lowest unused downstream ID greater or equal to + * 'first'. + * + * Begin by seeing if 'first' itself is available. If it is, we'll + * just return it; if it's already in the tree, we'll find the + * tree index where it appears and use that for the next stage. + */ + { + struct ssh_sharing_connstate dummy; + dummy.id = first; + cs = findrelpos234(sharestate->connections, &dummy, NULL, + REL234_GE, &low_orig); + if (!cs) + return first; + } + + /* + * Now binary-search using the counted B-tree, to find the largest + * ID which is in a contiguous sequence from the beginning of that + * range. + */ + low = low_orig; + high = high_orig = count234(sharestate->connections); + while (high - low > 1) { + mid = (high + low) / 2; + cs = index234(sharestate->connections, mid); + if (cs->id == first + (mid - low_orig)) + low = mid; /* this one is still in the sequence */ + else + high = mid; /* this one is past the end */ + } + + /* + * Now low is the tree index of the largest ID in the initial + * sequence. So the return value is one more than low's id, and we + * know low's id is given by the formula in the binary search loop + * above. + * + * (If an SSH connection went on for _enormously_ long, we might + * reach a point where all ids from 'first' to UINT_MAX were in + * use. In that situation the formula below would wrap round by + * one and return zero, which is conveniently the right way to + * signal 'no id available' from this function.) + */ + ret = first + (low - low_orig) + 1; + { + struct ssh_sharing_connstate dummy; + dummy.id = ret; + assert(NULL == find234(sharestate->connections, &dummy, NULL)); + } + return ret; +} + +static int share_halfchannel_cmp(void *av, void *bv) +{ + const struct share_halfchannel *a = (const struct share_halfchannel *)av; + const struct share_halfchannel *b = (const struct share_halfchannel *)bv; + + if (a->server_id < b->server_id) + return -1; + else if (a->server_id > b->server_id) + return +1; + else + return 0; +} + +static int share_channel_us_cmp(void *av, void *bv) +{ + const struct share_channel *a = (const struct share_channel *)av; + const struct share_channel *b = (const struct share_channel *)bv; + + if (a->upstream_id < b->upstream_id) + return -1; + else if (a->upstream_id > b->upstream_id) + return +1; + else + return 0; +} + +static int share_channel_server_cmp(void *av, void *bv) +{ + const struct share_channel *a = (const struct share_channel *)av; + const struct share_channel *b = (const struct share_channel *)bv; + + if (a->server_id < b->server_id) + return -1; + else if (a->server_id > b->server_id) + return +1; + else + return 0; +} + +static int share_xchannel_us_cmp(void *av, void *bv) +{ + const struct share_xchannel *a = (const struct share_xchannel *)av; + const struct share_xchannel *b = (const struct share_xchannel *)bv; + + if (a->upstream_id < b->upstream_id) + return -1; + else if (a->upstream_id > b->upstream_id) + return +1; + else + return 0; +} + +static int share_xchannel_server_cmp(void *av, void *bv) +{ + const struct share_xchannel *a = (const struct share_xchannel *)av; + const struct share_xchannel *b = (const struct share_xchannel *)bv; + + if (a->server_id < b->server_id) + return -1; + else if (a->server_id > b->server_id) + return +1; + else + return 0; +} + +static int share_forwarding_cmp(void *av, void *bv) +{ + const struct share_forwarding *a = (const struct share_forwarding *)av; + const struct share_forwarding *b = (const struct share_forwarding *)bv; + int i; + + if ((i = strcmp(a->host, b->host)) != 0) + return i; + else if (a->port < b->port) + return -1; + else if (a->port > b->port) + return +1; + else + return 0; +} + +static void share_xchannel_free(struct share_xchannel *xc) +{ + while (xc->msghead) { + struct share_xchannel_message *tmp = xc->msghead; + xc->msghead = tmp->next; + sfree(tmp); + } + sfree(xc); +} + +static void share_connstate_free(struct ssh_sharing_connstate *cs) +{ + struct share_halfchannel *hc; + struct share_xchannel *xc; + struct share_channel *chan; + struct share_forwarding *fwd; + + while ((hc = (struct share_halfchannel *) + delpos234(cs->halfchannels, 0)) != NULL) + sfree(hc); + freetree234(cs->halfchannels); + + /* All channels live in 'channels_by_us' but only some in + * 'channels_by_server', so we use the former to find the list of + * ones to free */ + freetree234(cs->channels_by_server); + while ((chan = (struct share_channel *) + delpos234(cs->channels_by_us, 0)) != NULL) + sfree(chan); + freetree234(cs->channels_by_us); + + /* But every xchannel is in both trees, so it doesn't matter which + * we use to free them. */ + while ((xc = (struct share_xchannel *) + delpos234(cs->xchannels_by_us, 0)) != NULL) + share_xchannel_free(xc); + freetree234(cs->xchannels_by_us); + freetree234(cs->xchannels_by_server); + + while ((fwd = (struct share_forwarding *) + delpos234(cs->forwardings, 0)) != NULL) + sfree(fwd); + freetree234(cs->forwardings); + + while (cs->globreq_head) { + struct share_globreq *globreq = cs->globreq_head; + cs->globreq_head = cs->globreq_head->next; + sfree(globreq); + } + + if (cs->sock) + sk_close(cs->sock); + + sfree(cs); +} + +void sharestate_free(ssh_sharing_state *sharestate) +{ + struct ssh_sharing_connstate *cs; + + platform_ssh_share_cleanup(sharestate->sockname); + + while ((cs = (struct ssh_sharing_connstate *) + delpos234(sharestate->connections, 0)) != NULL) { + share_connstate_free(cs); + } + freetree234(sharestate->connections); + if (sharestate->listensock) { + sk_close(sharestate->listensock); + sharestate->listensock = NULL; + } + sfree(sharestate->server_verstring); + sfree(sharestate->sockname); + sfree(sharestate); +} + +static struct share_halfchannel *share_add_halfchannel + (struct ssh_sharing_connstate *cs, unsigned server_id) +{ + struct share_halfchannel *hc = snew(struct share_halfchannel); + hc->server_id = server_id; + if (add234(cs->halfchannels, hc) != hc) { + /* Duplicate?! */ + sfree(hc); + return NULL; + } else { + return hc; + } +} + +static struct share_halfchannel *share_find_halfchannel + (struct ssh_sharing_connstate *cs, unsigned server_id) +{ + struct share_halfchannel dummyhc; + dummyhc.server_id = server_id; + return find234(cs->halfchannels, &dummyhc, NULL); +} + +static void share_remove_halfchannel(struct ssh_sharing_connstate *cs, + struct share_halfchannel *hc) +{ + del234(cs->halfchannels, hc); + sfree(hc); +} + +static struct share_channel *share_add_channel + (struct ssh_sharing_connstate *cs, unsigned downstream_id, + unsigned upstream_id, unsigned server_id, int state, int maxpkt) +{ + struct share_channel *chan = snew(struct share_channel); + chan->downstream_id = downstream_id; + chan->upstream_id = upstream_id; + chan->server_id = server_id; + chan->state = state; + chan->downstream_maxpkt = maxpkt; + chan->x11_auth_upstream = NULL; + chan->x11_auth_data = NULL; + chan->x11_auth_proto = -1; + chan->x11_auth_datalen = 0; + chan->x11_one_shot = false; + if (add234(cs->channels_by_us, chan) != chan) { + sfree(chan); + return NULL; + } + if (chan->state != UNACKNOWLEDGED) { + if (add234(cs->channels_by_server, chan) != chan) { + del234(cs->channels_by_us, chan); + sfree(chan); + return NULL; + } + } + return chan; +} + +static void share_channel_set_server_id(struct ssh_sharing_connstate *cs, + struct share_channel *chan, + unsigned server_id, int newstate) +{ + chan->server_id = server_id; + chan->state = newstate; + assert(newstate != UNACKNOWLEDGED); + add234(cs->channels_by_server, chan); +} + +static struct share_channel *share_find_channel_by_upstream + (struct ssh_sharing_connstate *cs, unsigned upstream_id) +{ + struct share_channel dummychan; + dummychan.upstream_id = upstream_id; + return find234(cs->channels_by_us, &dummychan, NULL); +} + +static struct share_channel *share_find_channel_by_server + (struct ssh_sharing_connstate *cs, unsigned server_id) +{ + struct share_channel dummychan; + dummychan.server_id = server_id; + return find234(cs->channels_by_server, &dummychan, NULL); +} + +static void share_remove_channel(struct ssh_sharing_connstate *cs, + struct share_channel *chan) +{ + del234(cs->channels_by_us, chan); + del234(cs->channels_by_server, chan); + if (chan->x11_auth_upstream) + ssh_remove_sharing_x11_display(cs->parent->cl, + chan->x11_auth_upstream); + sfree(chan->x11_auth_data); + sfree(chan); +} + +static struct share_xchannel *share_add_xchannel + (struct ssh_sharing_connstate *cs, + unsigned upstream_id, unsigned server_id) +{ + struct share_xchannel *xc = snew(struct share_xchannel); + xc->upstream_id = upstream_id; + xc->server_id = server_id; + xc->live = true; + xc->msghead = xc->msgtail = NULL; + if (add234(cs->xchannels_by_us, xc) != xc) { + sfree(xc); + return NULL; + } + if (add234(cs->xchannels_by_server, xc) != xc) { + del234(cs->xchannels_by_us, xc); + sfree(xc); + return NULL; + } + return xc; +} + +static struct share_xchannel *share_find_xchannel_by_upstream + (struct ssh_sharing_connstate *cs, unsigned upstream_id) +{ + struct share_xchannel dummyxc; + dummyxc.upstream_id = upstream_id; + return find234(cs->xchannels_by_us, &dummyxc, NULL); +} + +static struct share_xchannel *share_find_xchannel_by_server + (struct ssh_sharing_connstate *cs, unsigned server_id) +{ + struct share_xchannel dummyxc; + dummyxc.server_id = server_id; + return find234(cs->xchannels_by_server, &dummyxc, NULL); +} + +static void share_remove_xchannel(struct ssh_sharing_connstate *cs, + struct share_xchannel *xc) +{ + del234(cs->xchannels_by_us, xc); + del234(cs->xchannels_by_server, xc); + share_xchannel_free(xc); +} + +static struct share_forwarding *share_add_forwarding + (struct ssh_sharing_connstate *cs, + const char *host, int port) +{ + struct share_forwarding *fwd = snew(struct share_forwarding); + fwd->host = dupstr(host); + fwd->port = port; + fwd->active = false; + if (add234(cs->forwardings, fwd) != fwd) { + /* Duplicate?! */ + sfree(fwd); + return NULL; + } + return fwd; +} + +static struct share_forwarding *share_find_forwarding + (struct ssh_sharing_connstate *cs, const char *host, int port) +{ + struct share_forwarding dummyfwd, *ret; + dummyfwd.host = dupstr(host); + dummyfwd.port = port; + ret = find234(cs->forwardings, &dummyfwd, NULL); + sfree(dummyfwd.host); + return ret; +} + +static void share_remove_forwarding(struct ssh_sharing_connstate *cs, + struct share_forwarding *fwd) +{ + del234(cs->forwardings, fwd); + sfree(fwd); +} + +static PRINTF_LIKE(2, 3) void log_downstream(struct ssh_sharing_connstate *cs, + const char *logfmt, ...) +{ + va_list ap; + char *buf; + + va_start(ap, logfmt); + buf = dupvprintf(logfmt, ap); + va_end(ap); + logeventf(cs->parent->cl->logctx, + "Connection sharing downstream #%u: %s", cs->id, buf); + sfree(buf); +} + +static PRINTF_LIKE(2, 3) void log_general(struct ssh_sharing_state *sharestate, + const char *logfmt, ...) +{ + va_list ap; + char *buf; + + va_start(ap, logfmt); + buf = dupvprintf(logfmt, ap); + va_end(ap); + logeventf(sharestate->cl->logctx, "Connection sharing: %s", buf); + sfree(buf); +} + +static void send_packet_to_downstream(struct ssh_sharing_connstate *cs, + int type, const void *pkt, int pktlen, + struct share_channel *chan) +{ + strbuf *packet; + + if (!cs->sock) /* throw away all packets destined for a dead downstream */ + return; + + if (type == SSH2_MSG_CHANNEL_DATA) { + /* + * Special case which we take care of at a low level, so as to + * be sure to apply it in all cases. On rare occasions we + * might find that we have a channel for which the + * downstream's maximum packet size exceeds the max packet + * size we presented to the server on its behalf. (This can + * occur in X11 forwarding, where we have to send _our_ + * CHANNEL_OPEN_CONFIRMATION before we discover which if any + * downstream the channel is destined for, so if that + * downstream turns out to present a smaller max packet size + * then we're in this situation.) + * + * If that happens, we just chop up the packet into pieces and + * send them as separate CHANNEL_DATA packets. + */ + BinarySource src[1]; + unsigned channel; + ptrlen data; + + BinarySource_BARE_INIT(src, pkt, pktlen); + channel = get_uint32(src); + data = get_string(src); + + do { + int this_len = (data.len > chan->downstream_maxpkt ? + chan->downstream_maxpkt : data.len); + + packet = strbuf_new_nm(); + put_uint32(packet, 0); /* placeholder for length field */ + put_byte(packet, type); + put_uint32(packet, channel); + put_uint32(packet, this_len); + put_data(packet, data.ptr, this_len); + data.ptr = (const char *)data.ptr + this_len; + data.len -= this_len; + PUT_32BIT_MSB_FIRST(packet->s, packet->len-4); + sk_write(cs->sock, packet->s, packet->len); + strbuf_free(packet); + } while (data.len > 0); + } else { + /* + * Just do the obvious thing. + */ + packet = strbuf_new_nm(); + put_uint32(packet, 0); /* placeholder for length field */ + put_byte(packet, type); + put_data(packet, pkt, pktlen); + PUT_32BIT_MSB_FIRST(packet->s, packet->len-4); + sk_write(cs->sock, packet->s, packet->len); + strbuf_free(packet); + } +} + +static void share_try_cleanup(struct ssh_sharing_connstate *cs) +{ + int i; + struct share_halfchannel *hc; + struct share_channel *chan; + struct share_forwarding *fwd; + + /* + * Any half-open channels, i.e. those for which we'd received + * CHANNEL_OPEN from the server but not passed back a response + * from downstream, should be responded to with OPEN_FAILURE. + */ + while ((hc = (struct share_halfchannel *) + index234(cs->halfchannels, 0)) != NULL) { + static const char reason[] = "PuTTY downstream no longer available"; + static const char lang[] = "en"; + strbuf *packet; + + packet = strbuf_new(); + put_uint32(packet, hc->server_id); + put_uint32(packet, SSH2_OPEN_CONNECT_FAILED); + put_stringz(packet, reason); + put_stringz(packet, lang); + ssh_send_packet_from_downstream( + cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_OPEN_FAILURE, + packet->s, packet->len, + "cleanup after downstream went away"); + strbuf_free(packet); + + share_remove_halfchannel(cs, hc); + } + + /* + * Any actually open channels should have a CHANNEL_CLOSE sent for + * them, unless we've already done so. We won't be able to + * actually clean them up until CHANNEL_CLOSE comes back from the + * server, though (unless the server happens to have sent a CLOSE + * already). + * + * Another annoying exception is UNACKNOWLEDGED channels, i.e. + * we've _sent_ a CHANNEL_OPEN to the server but not received an + * OPEN_CONFIRMATION or OPEN_FAILURE. We must wait for a reply + * before closing the channel, because until we see that reply we + * won't have the server's channel id to put in the close message. + */ + for (i = 0; (chan = (struct share_channel *) + index234(cs->channels_by_us, i)) != NULL; i++) { + strbuf *packet; + + if (chan->state != SENT_CLOSE && chan->state != UNACKNOWLEDGED) { + packet = strbuf_new(); + put_uint32(packet, chan->server_id); + ssh_send_packet_from_downstream( + cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_CLOSE, + packet->s, packet->len, + "cleanup after downstream went away"); + strbuf_free(packet); + + if (chan->state != RCVD_CLOSE) { + chan->state = SENT_CLOSE; + } else { + /* In this case, we _can_ clear up the channel now. */ + ssh_delete_sharing_channel(cs->parent->cl, chan->upstream_id); + share_remove_channel(cs, chan); + i--; /* don't accidentally skip one as a result */ + } + } + } + + /* + * Any remote port forwardings we're managing on behalf of this + * downstream should be cancelled. Again, we must defer those for + * which we haven't yet seen REQUEST_SUCCESS/FAILURE. + * + * We take a fire-and-forget approach during cleanup, not + * bothering to set want_reply. + */ + for (i = 0; (fwd = (struct share_forwarding *) + index234(cs->forwardings, i)) != NULL; i++) { + if (fwd->active) { + strbuf *packet = strbuf_new(); + put_stringz(packet, "cancel-tcpip-forward"); + put_bool(packet, false); /* !want_reply */ + put_stringz(packet, fwd->host); + put_uint32(packet, fwd->port); + ssh_send_packet_from_downstream( + cs->parent->cl, cs->id, SSH2_MSG_GLOBAL_REQUEST, + packet->s, packet->len, + "cleanup after downstream went away"); + strbuf_free(packet); + + ssh_rportfwd_remove(cs->parent->cl, fwd->rpf); + share_remove_forwarding(cs, fwd); + i--; /* don't accidentally skip one as a result */ + } + } + + if (count234(cs->halfchannels) == 0 && + count234(cs->channels_by_us) == 0 && + count234(cs->forwardings) == 0) { + struct ssh_sharing_state *sharestate = cs->parent; + + /* + * Now we're _really_ done, so we can get rid of cs completely. + */ + del234(sharestate->connections, cs); + log_downstream(cs, "disconnected"); + share_connstate_free(cs); + + /* + * And if this was the last downstream, notify the connection + * layer, because it might now be time to wind up the whole + * SSH connection. + */ + if (count234(sharestate->connections) == 0 && sharestate->cl) + ssh_sharing_no_more_downstreams(sharestate->cl); + } +} + +static void share_begin_cleanup(struct ssh_sharing_connstate *cs) +{ + + sk_close(cs->sock); + cs->sock = NULL; + + share_try_cleanup(cs); +} + +static void share_disconnect(struct ssh_sharing_connstate *cs, + const char *message) +{ + strbuf *packet = strbuf_new(); + put_uint32(packet, SSH2_DISCONNECT_PROTOCOL_ERROR); + put_stringz(packet, message); + put_stringz(packet, "en"); /* language */ + send_packet_to_downstream(cs, SSH2_MSG_DISCONNECT, + packet->s, packet->len, NULL); + strbuf_free(packet); + + share_begin_cleanup(cs); +} + +static void share_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back) +{ + struct ssh_sharing_connstate *cs = container_of( + plug, struct ssh_sharing_connstate, plug); + + if (error_msg) { +#ifdef BROKEN_PIPE_ERROR_CODE + /* + * Most of the time, we log what went wrong when a downstream + * disappears with a socket error. One exception, though, is + * receiving EPIPE when we haven't received a protocol version + * string from the downstream, because that can happen as a result + * of plink -shareexists (opening the connection and instantly + * closing it again without bothering to read our version string). + * So that one case is not treated as a log-worthy error. + */ + if (error_code == BROKEN_PIPE_ERROR_CODE && !cs->got_verstring) + /* do nothing */; + else +#endif + log_downstream(cs, "Socket error: %s", error_msg); + } + share_begin_cleanup(cs); +} + +/* + * Append a message to the end of an xchannel's queue. + */ +static void share_xchannel_add_message( + struct share_xchannel *xc, int type, const void *data, int len) +{ + struct share_xchannel_message *msg; + + /* + * Allocate the 'struct share_xchannel_message' and the actual + * data in one unit. + */ + msg = snew_plus(struct share_xchannel_message, len); + msg->data = snew_plus_get_aux(msg); + msg->datalen = len; + msg->type = type; + memcpy(msg->data, data, len); + + /* + * Queue it in the xchannel. + */ + if (xc->msgtail) + xc->msgtail->next = msg; + else + xc->msghead = msg; + msg->next = NULL; + xc->msgtail = msg; +} + +void share_dead_xchannel_respond(struct ssh_sharing_connstate *cs, + struct share_xchannel *xc) +{ + /* + * Handle queued incoming messages from the server destined for an + * xchannel which is dead (i.e. downstream sent OPEN_FAILURE). + */ + bool delete = false; + while (xc->msghead) { + struct share_xchannel_message *msg = xc->msghead; + xc->msghead = msg->next; + + if (msg->type == SSH2_MSG_CHANNEL_REQUEST && msg->datalen > 4) { + /* + * A CHANNEL_REQUEST is responded to by sending + * CHANNEL_FAILURE, if it has want_reply set. + */ + BinarySource src[1]; + BinarySource_BARE_INIT(src, msg->data, msg->datalen); + get_uint32(src); /* skip channel id */ + get_string(src); /* skip request type */ + if (get_bool(src)) { + strbuf *packet = strbuf_new(); + put_uint32(packet, xc->server_id); + ssh_send_packet_from_downstream + (cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_FAILURE, + packet->s, packet->len, + "downstream refused X channel open"); + strbuf_free(packet); + } + } else if (msg->type == SSH2_MSG_CHANNEL_CLOSE) { + /* + * On CHANNEL_CLOSE we can discard the channel completely. + */ + delete = true; + } + + sfree(msg); + } + xc->msgtail = NULL; + if (delete) { + ssh_delete_sharing_channel(cs->parent->cl, xc->upstream_id); + share_remove_xchannel(cs, xc); + } +} + +void share_xchannel_confirmation(struct ssh_sharing_connstate *cs, + struct share_xchannel *xc, + struct share_channel *chan, + unsigned downstream_window) +{ + strbuf *packet; + + /* + * Send all the queued messages downstream. + */ + while (xc->msghead) { + struct share_xchannel_message *msg = xc->msghead; + xc->msghead = msg->next; + + if (msg->datalen >= 4) + PUT_32BIT_MSB_FIRST(msg->data, chan->downstream_id); + send_packet_to_downstream(cs, msg->type, + msg->data, msg->datalen, chan); + + sfree(msg); + } + + /* + * Send a WINDOW_ADJUST back upstream, to synchronise the window + * size downstream thinks it's presented with the one we've + * actually presented. + */ + packet = strbuf_new(); + put_uint32(packet, xc->server_id); + put_uint32(packet, downstream_window - xc->window); + ssh_send_packet_from_downstream( + cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_WINDOW_ADJUST, + packet->s, packet->len, + "window adjustment after downstream accepted X channel"); + strbuf_free(packet); +} + +void share_xchannel_failure(struct ssh_sharing_connstate *cs, + struct share_xchannel *xc) +{ + /* + * If downstream refuses to open our X channel at all for some + * reason, we must respond by sending an emergency CLOSE upstream. + */ + strbuf *packet = strbuf_new(); + put_uint32(packet, xc->server_id); + ssh_send_packet_from_downstream( + cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_CLOSE, + packet->s, packet->len, + "downstream refused X channel open"); + strbuf_free(packet); + + /* + * Now mark the xchannel as dead, and respond to anything sent on + * it until we see CLOSE for it in turn. + */ + xc->live = false; + share_dead_xchannel_respond(cs, xc); +} + +void share_setup_x11_channel(ssh_sharing_connstate *cs, share_channel *chan, + unsigned upstream_id, unsigned server_id, + unsigned server_currwin, unsigned server_maxpkt, + unsigned client_adjusted_window, + const char *peer_addr, int peer_port, int endian, + int protomajor, int protominor, + const void *initial_data, int initial_len) +{ + struct share_xchannel *xc; + void *greeting; + int greeting_len; + strbuf *packet; + + /* + * Create an xchannel containing data we've already received from + * the X client, and preload it with a CHANNEL_DATA message + * containing our own made-up authorisation greeting and any + * additional data sent from the server so far. + */ + xc = share_add_xchannel(cs, upstream_id, server_id); + greeting = x11_make_greeting(endian, protomajor, protominor, + chan->x11_auth_proto, + chan->x11_auth_data, chan->x11_auth_datalen, + peer_addr, peer_port, &greeting_len); + packet = strbuf_new_nm(); + put_uint32(packet, 0); /* leave the channel id field unfilled - we + * don't know the downstream id yet */ + put_uint32(packet, greeting_len + initial_len); + put_data(packet, greeting, greeting_len); + put_data(packet, initial_data, initial_len); + sfree(greeting); + share_xchannel_add_message(xc, SSH2_MSG_CHANNEL_DATA, + packet->s, packet->len); + strbuf_free(packet); + + xc->window = client_adjusted_window + greeting_len; + + /* + * Send on a CHANNEL_OPEN to downstream. + */ + packet = strbuf_new(); + put_stringz(packet, "x11"); + put_uint32(packet, server_id); + put_uint32(packet, server_currwin); + put_uint32(packet, server_maxpkt); + put_stringz(packet, peer_addr); + put_uint32(packet, peer_port); + send_packet_to_downstream(cs, SSH2_MSG_CHANNEL_OPEN, + packet->s, packet->len, NULL); + strbuf_free(packet); + + /* + * If this was a once-only X forwarding, clean it up now. + */ + if (chan->x11_one_shot) { + ssh_remove_sharing_x11_display(cs->parent->cl, + chan->x11_auth_upstream); + chan->x11_auth_upstream = NULL; + sfree(chan->x11_auth_data); + chan->x11_auth_proto = -1; + chan->x11_auth_datalen = 0; + chan->x11_one_shot = false; + } +} + +void share_got_pkt_from_server(ssh_sharing_connstate *cs, int type, + const void *vpkt, int pktlen) +{ + const unsigned char *pkt = (const unsigned char *)vpkt; + struct share_globreq *globreq; + size_t id_pos; + unsigned upstream_id, server_id; + struct share_channel *chan; + struct share_xchannel *xc; + BinarySource src[1]; + + BinarySource_BARE_INIT(src, pkt, pktlen); + + switch (type) { + case SSH2_MSG_REQUEST_SUCCESS: + case SSH2_MSG_REQUEST_FAILURE: + globreq = cs->globreq_head; + assert(globreq); /* should match the queue in ssh.c */ + if (globreq->type == GLOBREQ_TCPIP_FORWARD) { + if (type == SSH2_MSG_REQUEST_FAILURE) { + share_remove_forwarding(cs, globreq->fwd); + } else { + globreq->fwd->active = true; + } + } else if (globreq->type == GLOBREQ_CANCEL_TCPIP_FORWARD) { + if (type == SSH2_MSG_REQUEST_SUCCESS) { + share_remove_forwarding(cs, globreq->fwd); + } + } + if (globreq->want_reply) { + send_packet_to_downstream(cs, type, pkt, pktlen, NULL); + } + cs->globreq_head = globreq->next; + sfree(globreq); + if (cs->globreq_head == NULL) + cs->globreq_tail = NULL; + + if (!cs->sock) { + /* Retry cleaning up this connection, in case that reply + * was the last thing we were waiting for. */ + share_try_cleanup(cs); + } + + break; + + case SSH2_MSG_CHANNEL_OPEN: + get_string(src); + server_id = get_uint32(src); + assert(!get_err(src)); + share_add_halfchannel(cs, server_id); + + send_packet_to_downstream(cs, type, pkt, pktlen, NULL); + break; + + case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: + case SSH2_MSG_CHANNEL_OPEN_FAILURE: + case SSH2_MSG_CHANNEL_CLOSE: + case SSH2_MSG_CHANNEL_WINDOW_ADJUST: + case SSH2_MSG_CHANNEL_DATA: + case SSH2_MSG_CHANNEL_EXTENDED_DATA: + case SSH2_MSG_CHANNEL_EOF: + case SSH2_MSG_CHANNEL_REQUEST: + case SSH2_MSG_CHANNEL_SUCCESS: + case SSH2_MSG_CHANNEL_FAILURE: + /* + * All these messages have the recipient channel id as the + * first uint32 field in the packet. Substitute the downstream + * channel id for our one and pass the packet downstream. + */ + id_pos = src->pos; + upstream_id = get_uint32(src); + if ((chan = share_find_channel_by_upstream(cs, upstream_id)) != NULL) { + /* + * The normal case: this id refers to an open channel. + */ + unsigned char *rewritten = snewn(pktlen, unsigned char); + memcpy(rewritten, pkt, pktlen); + PUT_32BIT_MSB_FIRST(rewritten + id_pos, chan->downstream_id); + send_packet_to_downstream(cs, type, rewritten, pktlen, chan); + sfree(rewritten); + + /* + * Update the channel state, for messages that need it. + */ + if (type == SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) { + if (chan->state == UNACKNOWLEDGED && pktlen >= 8) { + share_channel_set_server_id( + cs, chan, GET_32BIT_MSB_FIRST(pkt+4), OPEN); + if (!cs->sock) { + /* Retry cleaning up this connection, so that we + * can send an immediate CLOSE on this channel for + * which we now know the server id. */ + share_try_cleanup(cs); + } + } + } else if (type == SSH2_MSG_CHANNEL_OPEN_FAILURE) { + ssh_delete_sharing_channel(cs->parent->cl, chan->upstream_id); + share_remove_channel(cs, chan); + } else if (type == SSH2_MSG_CHANNEL_CLOSE) { + if (chan->state == SENT_CLOSE) { + ssh_delete_sharing_channel(cs->parent->cl, + chan->upstream_id); + share_remove_channel(cs, chan); + if (!cs->sock) { + /* Retry cleaning up this connection, in case this + * channel closure was the last thing we were + * waiting for. */ + share_try_cleanup(cs); + } + } else { + chan->state = RCVD_CLOSE; + } + } + } else if ((xc = share_find_xchannel_by_upstream(cs, upstream_id)) + != NULL) { + /* + * The unusual case: this id refers to an xchannel. Add it + * to the xchannel's queue. + */ + share_xchannel_add_message(xc, type, pkt, pktlen); + + /* If the xchannel is dead, then also respond to it (which + * may involve deleting the channel). */ + if (!xc->live) + share_dead_xchannel_respond(cs, xc); + } + break; + + default: + unreachable("This packet type should never have come from ssh.c"); + } +} + +static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, + int type, + unsigned char *pkt, int pktlen) +{ + ptrlen request_name; + struct share_forwarding *fwd; + size_t id_pos; + unsigned maxpkt; + unsigned old_id, new_id, server_id; + struct share_globreq *globreq; + struct share_channel *chan; + struct share_halfchannel *hc; + struct share_xchannel *xc; + strbuf *packet; + char *err = NULL; + BinarySource src[1]; + size_t wantreplypos; + bool orig_wantreply; + + BinarySource_BARE_INIT(src, pkt, pktlen); + + switch (type) { + case SSH2_MSG_DISCONNECT: + /* + * This message stops here: if downstream is disconnecting + * from us, that doesn't mean we want to disconnect from the + * SSH server. Close the downstream connection and start + * cleanup. + */ + share_begin_cleanup(cs); + break; + + case SSH2_MSG_GLOBAL_REQUEST: + /* + * The only global requests we understand are "tcpip-forward" + * and "cancel-tcpip-forward". Since those require us to + * maintain state, we must assume that other global requests + * will probably require that too, and so we don't forward on + * any request we don't understand. + */ + request_name = get_string(src); + wantreplypos = src->pos; + orig_wantreply = get_bool(src); + + if (ptrlen_eq_string(request_name, "tcpip-forward")) { + ptrlen hostpl; + char *host; + int port; + struct ssh_rportfwd *rpf; + + /* + * Pick the packet apart to find the want_reply field and + * the host/port we're going to ask to listen on. + */ + hostpl = get_string(src); + port = toint(get_uint32(src)); + if (get_err(src)) { + err = dupprintf("Truncated GLOBAL_REQUEST packet"); + goto confused; + } + host = mkstr(hostpl); + + /* + * See if we can allocate space in ssh.c's tree of remote + * port forwardings. If we can't, it's because another + * client sharing this connection has already allocated + * the identical port forwarding, so we take it on + * ourselves to manufacture a failure packet and send it + * back to downstream. + */ + rpf = ssh_rportfwd_alloc( + cs->parent->cl, host, port, NULL, 0, 0, NULL, NULL, cs); + if (!rpf) { + if (orig_wantreply) { + send_packet_to_downstream(cs, SSH2_MSG_REQUEST_FAILURE, + "", 0, NULL); + } + } else { + /* + * We've managed to make space for this forwarding + * locally. Pass the request on to the SSH server, but + * set want_reply even if it wasn't originally set, so + * that we know whether this forwarding needs to be + * cleaned up if downstream goes away. + */ + pkt[wantreplypos] = 1; + ssh_send_packet_from_downstream + (cs->parent->cl, cs->id, type, pkt, pktlen, + orig_wantreply ? NULL : "upstream added want_reply flag"); + fwd = share_add_forwarding(cs, host, port); + ssh_sharing_queue_global_request(cs->parent->cl, cs); + + if (fwd) { + globreq = snew(struct share_globreq); + globreq->next = NULL; + if (cs->globreq_tail) + cs->globreq_tail->next = globreq; + else + cs->globreq_head = globreq; + globreq->fwd = fwd; + globreq->want_reply = orig_wantreply; + globreq->type = GLOBREQ_TCPIP_FORWARD; + + fwd->rpf = rpf; + } + } + + sfree(host); + } else if (ptrlen_eq_string(request_name, "cancel-tcpip-forward")) { + ptrlen hostpl; + char *host; + int port; + struct share_forwarding *fwd; + + /* + * Pick the packet apart to find the want_reply field and + * the host/port we're going to ask to listen on. + */ + hostpl = get_string(src); + port = toint(get_uint32(src)); + if (get_err(src)) { + err = dupprintf("Truncated GLOBAL_REQUEST packet"); + goto confused; + } + host = mkstr(hostpl); + + /* + * Look up the existing forwarding with these details. + */ + fwd = share_find_forwarding(cs, host, port); + if (!fwd) { + if (orig_wantreply) { + send_packet_to_downstream(cs, SSH2_MSG_REQUEST_FAILURE, + "", 0, NULL); + } + } else { + /* + * Tell ssh.c to stop sending us channel-opens for + * this forwarding. + */ + ssh_rportfwd_remove(cs->parent->cl, fwd->rpf); + + /* + * Pass the cancel request on to the SSH server, but + * set want_reply even if it wasn't originally set, so + * that _we_ know whether the forwarding has been + * deleted even if downstream doesn't want to know. + */ + pkt[wantreplypos] = 1; + ssh_send_packet_from_downstream + (cs->parent->cl, cs->id, type, pkt, pktlen, + orig_wantreply ? NULL : "upstream added want_reply flag"); + ssh_sharing_queue_global_request(cs->parent->cl, cs); + + /* + * And queue a globreq so that when the reply comes + * back we know to cancel it. + */ + globreq = snew(struct share_globreq); + globreq->next = NULL; + if (cs->globreq_tail) + cs->globreq_tail->next = globreq; + else + cs->globreq_head = globreq; + globreq->fwd = fwd; + globreq->want_reply = orig_wantreply; + globreq->type = GLOBREQ_CANCEL_TCPIP_FORWARD; + } + + sfree(host); + } else { + /* + * Request we don't understand. Manufacture a failure + * message if an answer was required. + */ + if (orig_wantreply) + send_packet_to_downstream(cs, SSH2_MSG_REQUEST_FAILURE, + "", 0, NULL); + } + break; + + case SSH2_MSG_CHANNEL_OPEN: + /* Sender channel id comes after the channel type string */ + get_string(src); + id_pos = src->pos; + old_id = get_uint32(src); + new_id = ssh_alloc_sharing_channel(cs->parent->cl, cs); + get_uint32(src); /* skip initial window size */ + maxpkt = get_uint32(src); + if (get_err(src)) { + err = dupprintf("Truncated CHANNEL_OPEN packet"); + goto confused; + } + share_add_channel(cs, old_id, new_id, 0, UNACKNOWLEDGED, maxpkt); + PUT_32BIT_MSB_FIRST(pkt + id_pos, new_id); + ssh_send_packet_from_downstream(cs->parent->cl, cs->id, + type, pkt, pktlen, NULL); + break; + + case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: + if (pktlen < 16) { + err = dupprintf("Truncated CHANNEL_OPEN_CONFIRMATION packet"); + goto confused; + } + + server_id = get_uint32(src); + id_pos = src->pos; + old_id = get_uint32(src); + get_uint32(src); /* skip initial window size */ + maxpkt = get_uint32(src); + if (get_err(src)) { + err = dupprintf("Truncated CHANNEL_OPEN_CONFIRMATION packet"); + goto confused; + } + + /* This server id may refer to either a halfchannel or an xchannel. */ + hc = NULL, xc = NULL; /* placate optimiser */ + if ((hc = share_find_halfchannel(cs, server_id)) != NULL) { + new_id = ssh_alloc_sharing_channel(cs->parent->cl, cs); + } else if ((xc = share_find_xchannel_by_server(cs, server_id)) + != NULL) { + new_id = xc->upstream_id; + } else { + err = dupprintf("CHANNEL_OPEN_CONFIRMATION packet cited unknown channel %u", (unsigned)server_id); + goto confused; + } + + PUT_32BIT_MSB_FIRST(pkt + id_pos, new_id); + + chan = share_add_channel(cs, old_id, new_id, server_id, OPEN, maxpkt); + + if (hc) { + ssh_send_packet_from_downstream(cs->parent->cl, cs->id, + type, pkt, pktlen, NULL); + share_remove_halfchannel(cs, hc); + } else if (xc) { + unsigned downstream_window = GET_32BIT_MSB_FIRST(pkt + 8); + if (downstream_window < 256) { + err = dupprintf("Initial window size for x11 channel must be at least 256 (got %u)", downstream_window); + goto confused; + } + share_xchannel_confirmation(cs, xc, chan, downstream_window); + share_remove_xchannel(cs, xc); + } + + break; + + case SSH2_MSG_CHANNEL_OPEN_FAILURE: + server_id = get_uint32(src); + if (get_err(src)) { + err = dupprintf("Truncated CHANNEL_OPEN_FAILURE packet"); + goto confused; + } + + /* This server id may refer to either a halfchannel or an xchannel. */ + if ((hc = share_find_halfchannel(cs, server_id)) != NULL) { + ssh_send_packet_from_downstream(cs->parent->cl, cs->id, + type, pkt, pktlen, NULL); + share_remove_halfchannel(cs, hc); + } else if ((xc = share_find_xchannel_by_server(cs, server_id)) + != NULL) { + share_xchannel_failure(cs, xc); + } else { + err = dupprintf("CHANNEL_OPEN_FAILURE packet cited unknown channel %u", (unsigned)server_id); + goto confused; + } + + break; + + case SSH2_MSG_CHANNEL_WINDOW_ADJUST: + case SSH2_MSG_CHANNEL_DATA: + case SSH2_MSG_CHANNEL_EXTENDED_DATA: + case SSH2_MSG_CHANNEL_EOF: + case SSH2_MSG_CHANNEL_CLOSE: + case SSH2_MSG_CHANNEL_REQUEST: + case SSH2_MSG_CHANNEL_SUCCESS: + case SSH2_MSG_CHANNEL_FAILURE: + case SSH2_MSG_IGNORE: + case SSH2_MSG_DEBUG: + server_id = get_uint32(src); + + if (type == SSH2_MSG_CHANNEL_REQUEST) { + request_name = get_string(src); + + /* + * Agent forwarding requests from downstream are treated + * specially. Because OpenSSHD doesn't let us enable agent + * forwarding independently per session channel, and in + * particular because the OpenSSH-defined agent forwarding + * protocol does not mark agent-channel requests with the + * id of the session channel they originate from, the only + * way we can implement agent forwarding in a + * connection-shared PuTTY is to forward the _upstream_ + * agent. Hence, we unilaterally deny agent forwarding + * requests from downstreams if we aren't prepared to + * forward an agent ourselves. + * + * (If we are, then we dutifully pass agent forwarding + * requests upstream. OpenSSHD has the curious behaviour + * that all but the first such request will be rejected, + * but all session channels opened after the first request + * get agent forwarding enabled whether they ask for it or + * not; but that's not our concern, since other SSH + * servers supporting the same piece of protocol might in + * principle at least manage to enable agent forwarding on + * precisely the channels that requested it, even if the + * subsequent CHANNEL_OPENs still can't be associated with + * a parent session channel.) + */ + if (ptrlen_eq_string(request_name, "auth-agent-req@openssh.com") && + !ssh_agent_forwarding_permitted(cs->parent->cl)) { + + chan = share_find_channel_by_server(cs, server_id); + if (chan) { + packet = strbuf_new(); + put_uint32(packet, chan->downstream_id); + send_packet_to_downstream( + cs, SSH2_MSG_CHANNEL_FAILURE, + packet->s, packet->len, NULL); + strbuf_free(packet); + } else { + char *buf = dupprintf("Agent forwarding request for " + "unrecognised channel %u", server_id); + share_disconnect(cs, buf); + sfree(buf); + return; + } + break; + } + + /* + * Another thing we treat specially is X11 forwarding + * requests. For these, we have to make up another set of + * X11 auth data, and enter it into our SSH connection's + * list of possible X11 authorisation credentials so that + * when we see an X11 channel open request we can know + * whether it's one to handle locally or one to pass on to + * a downstream, and if the latter, which one. + */ + if (ptrlen_eq_string(request_name, "x11-req")) { + bool want_reply, single_connection; + int screen; + ptrlen auth_data; + int auth_proto; + + chan = share_find_channel_by_server(cs, server_id); + if (!chan) { + char *buf = dupprintf("X11 forwarding request for " + "unrecognised channel %u", server_id); + share_disconnect(cs, buf); + sfree(buf); + return; + } + + /* + * Pick apart the whole message to find the downstream + * auth details. + */ + want_reply = get_bool(src); + single_connection = get_bool(src); + auth_proto = x11_identify_auth_proto(get_string(src)); + auth_data = get_string(src); + screen = toint(get_uint32(src)); + if (get_err(src)) { + err = dupprintf("Truncated CHANNEL_REQUEST(\"x11-req\")" + " packet"); + goto confused; + } + + if (auth_proto < 0) { + /* Reject due to not understanding downstream's + * requested authorisation method. */ + packet = strbuf_new(); + put_uint32(packet, chan->downstream_id); + send_packet_to_downstream( + cs, SSH2_MSG_CHANNEL_FAILURE, + packet->s, packet->len, NULL); + strbuf_free(packet); + break; + } + + chan->x11_auth_proto = auth_proto; + chan->x11_auth_data = x11_dehexify(auth_data, + &chan->x11_auth_datalen); + chan->x11_auth_upstream = + ssh_add_sharing_x11_display(cs->parent->cl, auth_proto, + cs, chan); + chan->x11_one_shot = single_connection; + + /* + * Now construct a replacement X forwarding request, + * containing our own auth data, and send that to the + * server. + */ + packet = strbuf_new_nm(); + put_uint32(packet, server_id); + put_stringz(packet, "x11-req"); + put_bool(packet, want_reply); + put_bool(packet, single_connection); + put_stringz(packet, chan->x11_auth_upstream->protoname); + put_stringz(packet, chan->x11_auth_upstream->datastring); + put_uint32(packet, screen); + ssh_send_packet_from_downstream( + cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_REQUEST, + packet->s, packet->len, NULL); + strbuf_free(packet); + + break; + } + } + + ssh_send_packet_from_downstream(cs->parent->cl, cs->id, + type, pkt, pktlen, NULL); + if (type == SSH2_MSG_CHANNEL_CLOSE && pktlen >= 4) { + chan = share_find_channel_by_server(cs, server_id); + if (chan) { + if (chan->state == RCVD_CLOSE) { + ssh_delete_sharing_channel(cs->parent->cl, + chan->upstream_id); + share_remove_channel(cs, chan); + } else { + chan->state = SENT_CLOSE; + } + } + } + break; + + default: + err = dupprintf("Unexpected packet type %d\n", type); + goto confused; + + /* + * Any other packet type is unexpected. In particular, we + * never pass GLOBAL_REQUESTs downstream, so we never expect + * to see SSH2_MSG_REQUEST_{SUCCESS,FAILURE}. + */ + confused: + assert(err != NULL); + share_disconnect(cs, err); + sfree(err); + break; + } +} + +/* + * An extra coroutine macro, specific to this code which is consuming + * 'const char *data'. + */ +#define crGetChar(c) do \ + { \ + while (len == 0) { \ + *crLine =__LINE__; return; case __LINE__:; \ + } \ + len--; \ + (c) = (unsigned char)*data++; \ + } while (0) + +static void share_receive(Plug *plug, int urgent, const char *data, size_t len) +{ + ssh_sharing_connstate *cs = container_of( + plug, ssh_sharing_connstate, plug); + static const char expected_verstring_prefix[] = + "SSHCONNECTION@putty.projects.tartarus.org-2.0-"; + unsigned char c; + + crBegin(cs->crLine); + + /* + * First read the version string from downstream. + */ + cs->recvlen = 0; + while (1) { + crGetChar(c); + if (c == '\012') + break; + if (cs->recvlen >= sizeof(cs->recvbuf)) { + char *buf = dupprintf("Version string far too long\n"); + share_disconnect(cs, buf); + sfree(buf); + goto dead; + } + cs->recvbuf[cs->recvlen++] = c; + } + + /* + * Now parse the version string to make sure it's at least vaguely + * sensible, and log it. + */ + if (cs->recvlen < sizeof(expected_verstring_prefix)-1 || + memcmp(cs->recvbuf, expected_verstring_prefix, + sizeof(expected_verstring_prefix) - 1)) { + char *buf = dupprintf("Version string did not have expected prefix\n"); + share_disconnect(cs, buf); + sfree(buf); + goto dead; + } + if (cs->recvlen > 0 && cs->recvbuf[cs->recvlen-1] == '\015') + cs->recvlen--; /* trim off \r before \n */ + ptrlen verstring = make_ptrlen(cs->recvbuf, cs->recvlen); + log_downstream(cs, "Downstream version string: %.*s", + PTRLEN_PRINTF(verstring)); + cs->got_verstring = true; + + /* + * Loop round reading packets. + */ + while (1) { + cs->recvlen = 0; + while (cs->recvlen < 4) { + crGetChar(c); + cs->recvbuf[cs->recvlen++] = c; + } + cs->curr_packetlen = toint(GET_32BIT_MSB_FIRST(cs->recvbuf) + 4); + if (cs->curr_packetlen < 5 || + cs->curr_packetlen > sizeof(cs->recvbuf)) { + char *buf = dupprintf("Bad packet length %u\n", + (unsigned)cs->curr_packetlen); + share_disconnect(cs, buf); + sfree(buf); + goto dead; + } + while (cs->recvlen < cs->curr_packetlen) { + crGetChar(c); + cs->recvbuf[cs->recvlen++] = c; + } + + share_got_pkt_from_downstream(cs, cs->recvbuf[4], + cs->recvbuf + 5, cs->recvlen - 5); + } + + dead:; + crFinishV; +} + +static void share_sent(Plug *plug, size_t bufsize) +{ + /* ssh_sharing_connstate *cs = container_of( + plug, ssh_sharing_connstate, plug); */ + + /* + * We do nothing here, because we expect that there won't be a + * need to throttle and unthrottle the connection to a downstream. + * It should automatically throttle itself: if the SSH server + * sends huge amounts of data on all channels then it'll run out + * of window until our downstream sends it back some + * WINDOW_ADJUSTs. + */ +} + +static void share_listen_closing(Plug *plug, const char *error_msg, + int error_code, bool calling_back) +{ + ssh_sharing_state *sharestate = + container_of(plug, ssh_sharing_state, plug); + if (error_msg) + log_general(sharestate, "listening socket: %s", error_msg); + sk_close(sharestate->listensock); + sharestate->listensock = NULL; +} + +static void share_send_verstring(ssh_sharing_connstate *cs) +{ + char *fullstring = dupcat("SSHCONNECTION@putty.projects.tartarus.org-2.0-", + cs->parent->server_verstring, "\015\012"); + sk_write(cs->sock, fullstring, strlen(fullstring)); + sfree(fullstring); + + cs->sent_verstring = true; +} + +int share_ndownstreams(ssh_sharing_state *sharestate) +{ + return count234(sharestate->connections); +} + +void share_activate(ssh_sharing_state *sharestate, + const char *server_verstring) +{ + /* + * Indication from ssh.c that we are now ready to begin serving + * any downstreams that have already connected to us. + */ + struct ssh_sharing_connstate *cs; + int i; + + /* + * Trim the server's version string down to just the software + * version component, removing "SSH-2.0-" or whatever at the + * front. + */ + for (i = 0; i < 2; i++) { + server_verstring += strcspn(server_verstring, "-"); + if (*server_verstring) + server_verstring++; + } + + sharestate->server_verstring = dupstr(server_verstring); + + for (i = 0; (cs = (struct ssh_sharing_connstate *) + index234(sharestate->connections, i)) != NULL; i++) { + assert(!cs->sent_verstring); + share_send_verstring(cs); + } +} + +static const PlugVtable ssh_sharing_conn_plugvt = { + NULL, /* no log function, because that's for outgoing connections */ + share_closing, + share_receive, + share_sent, + NULL /* no accepting function, because we've already done it */ +}; + +static int share_listen_accepting(Plug *plug, + accept_fn_t constructor, accept_ctx_t ctx) +{ + struct ssh_sharing_state *sharestate = container_of( + plug, struct ssh_sharing_state, plug); + struct ssh_sharing_connstate *cs; + const char *err; + SocketPeerInfo *peerinfo; + + /* + * A new downstream has connected to us. + */ + cs = snew(struct ssh_sharing_connstate); + cs->plug.vt = &ssh_sharing_conn_plugvt; + cs->parent = sharestate; + + if ((cs->id = share_find_unused_id(sharestate, sharestate->nextid)) == 0 && + (cs->id = share_find_unused_id(sharestate, 1)) == 0) { + sfree(cs); + return 1; + } + sharestate->nextid = cs->id + 1; + if (sharestate->nextid == 0) + sharestate->nextid++; /* only happens in VERY long-running upstreams */ + + cs->sock = constructor(ctx, &cs->plug); + if ((err = sk_socket_error(cs->sock)) != NULL) { + sfree(cs); + return err != NULL; + } + + sk_set_frozen(cs->sock, 0); + + add234(cs->parent->connections, cs); + + cs->sent_verstring = false; + if (sharestate->server_verstring) + share_send_verstring(cs); + + cs->got_verstring = false; + cs->recvlen = 0; + cs->crLine = 0; + cs->halfchannels = newtree234(share_halfchannel_cmp); + cs->channels_by_us = newtree234(share_channel_us_cmp); + cs->channels_by_server = newtree234(share_channel_server_cmp); + cs->xchannels_by_us = newtree234(share_xchannel_us_cmp); + cs->xchannels_by_server = newtree234(share_xchannel_server_cmp); + cs->forwardings = newtree234(share_forwarding_cmp); + cs->globreq_head = cs->globreq_tail = NULL; + + peerinfo = sk_peer_info(cs->sock); + log_downstream(cs, "connected%s%s", + (peerinfo && peerinfo->log_text ? " from " : ""), + (peerinfo && peerinfo->log_text ? peerinfo->log_text : "")); + sk_free_peer_info(peerinfo); + + return 0; +} + +/* + * Decide on the string used to identify the connection point between + * upstream and downstream (be it a Windows named pipe or a + * Unix-domain socket or whatever else). + * + * I wondered about making this a SHA hash of all sorts of pieces of + * the PuTTY configuration - essentially everything PuTTY uses to know + * where and how to make a connection, including all the proxy details + * (or rather, all the _relevant_ ones - only including settings that + * other settings didn't prevent from having any effect), plus the + * username. However, I think it's better to keep it really simple: + * the connection point identifier is derived from the hostname and + * port used to index the host-key cache (not necessarily where we + * _physically_ connected to, in cases involving proxies or + * CONF_loghost), plus the username if one is specified. + * + * The per-platform code will quite likely hash or obfuscate this name + * in turn, for privacy from other users; failing that, it might + * transform it to avoid dangerous filename characters and so on. But + * that doesn't matter to us: for us, the point is that two session + * configurations which return the same string from this function will + * be treated as potentially shareable with each other. + */ +char *ssh_share_sockname(const char *host, int port, Conf *conf) +{ + char *username = get_remote_username(conf); + char *sockname; + + if (port == 22) { + if (username) + sockname = dupprintf("%s@%s", username, host); + else + sockname = dupprintf("%s", host); + } else { + if (username) + sockname = dupprintf("%s@%s:%d", username, host, port); + else + sockname = dupprintf("%s:%d", host, port); + } + + sfree(username); + return sockname; +} + +bool ssh_share_test_for_upstream(const char *host, int port, Conf *conf) +{ + char *sockname, *logtext, *ds_err, *us_err; + int result; + Socket *sock; + + sockname = ssh_share_sockname(host, port, conf); + + sock = NULL; + logtext = ds_err = us_err = NULL; + result = platform_ssh_share(sockname, conf, nullplug, (Plug *)NULL, &sock, + &logtext, &ds_err, &us_err, false, true); + + sfree(logtext); + sfree(ds_err); + sfree(us_err); + sfree(sockname); + + if (result == SHARE_NONE) { + assert(sock == NULL); + return false; + } else { + assert(result == SHARE_DOWNSTREAM); + sk_close(sock); + return true; + } +} + +static const PlugVtable ssh_sharing_listen_plugvt = { + NULL, /* no log function, because that's for outgoing connections */ + share_listen_closing, + NULL, /* no receive function on a listening socket */ + NULL, /* no sent function on a listening socket */ + share_listen_accepting +}; + +void ssh_connshare_provide_connlayer(ssh_sharing_state *sharestate, + ConnectionLayer *cl) +{ + sharestate->cl = cl; +} + +/* + * Init function for connection sharing. We either open a listening + * socket and become an upstream, or connect to an existing one and + * become a downstream, or do neither. We are responsible for deciding + * which of these to do (including checking the Conf to see if + * connection sharing is even enabled in the first place). If we + * become a downstream, we return the Socket with which we connected + * to the upstream; otherwise (whether or not we have established an + * upstream) we return NULL. + */ +Socket *ssh_connection_sharing_init( + const char *host, int port, Conf *conf, LogContext *logctx, + Plug *sshplug, ssh_sharing_state **state) +{ + int result; + bool can_upstream, can_downstream; + char *logtext, *ds_err, *us_err; + char *sockname; + Socket *sock, *toret = NULL; + struct ssh_sharing_state *sharestate; + + if (!conf_get_bool(conf, CONF_ssh_connection_sharing)) + return NULL; /* do not share anything */ + can_upstream = share_can_be_upstream && + conf_get_bool(conf, CONF_ssh_connection_sharing_upstream); + can_downstream = share_can_be_downstream && + conf_get_bool(conf, CONF_ssh_connection_sharing_downstream); + if (!can_upstream && !can_downstream) + return NULL; + + sockname = ssh_share_sockname(host, port, conf); + + /* + * Create a data structure for the listening plug if we turn out + * to be an upstream. + */ + sharestate = snew(struct ssh_sharing_state); + sharestate->plug.vt = &ssh_sharing_listen_plugvt; + sharestate->listensock = NULL; + sharestate->cl = NULL; + + /* + * Now hand off to a per-platform routine that either connects to + * an existing upstream (using 'ssh' as the plug), establishes our + * own upstream (using 'sharestate' as the plug), or forks off a + * separate upstream and then connects to that. It will return a + * code telling us which kind of socket it put in 'sock'. + */ + sock = NULL; + logtext = ds_err = us_err = NULL; + result = platform_ssh_share( + sockname, conf, sshplug, &sharestate->plug, &sock, &logtext, + &ds_err, &us_err, can_upstream, can_downstream); + switch (result) { + case SHARE_NONE: + /* + * We aren't sharing our connection at all (e.g. something + * went wrong setting the socket up). Free the upstream + * structure and return NULL. + */ + + if (logtext) { + /* For this result, if 'logtext' is not NULL then it is an + * error message indicating a reason why connection sharing + * couldn't be set up _at all_ */ + logeventf(logctx, + "Could not set up connection sharing: %s", logtext); + } else { + /* Failing that, ds_err and us_err indicate why we + * couldn't be a downstream and an upstream respectively */ + if (ds_err) + logeventf(logctx, "Could not set up connection sharing" + " as downstream: %s", ds_err); + if (us_err) + logeventf(logctx, "Could not set up connection sharing" + " as upstream: %s", us_err); + } + + assert(sock == NULL); + *state = NULL; + sfree(sharestate); + sfree(sockname); + break; + + case SHARE_DOWNSTREAM: + /* + * We are downstream, so free sharestate which it turns out we + * don't need after all, and return the downstream socket as a + * replacement for an ordinary SSH connection. + */ + + /* 'logtext' is a local endpoint address */ + logeventf(logctx, "Using existing shared connection at %s", logtext); + + *state = NULL; + sfree(sharestate); + sfree(sockname); + toret = sock; + break; + + case SHARE_UPSTREAM: + /* + * We are upstream. Set up sharestate properly and pass a copy + * to the caller; return NULL, to tell ssh.c that it has to + * make an ordinary connection after all. + */ + + /* 'logtext' is a local endpoint address */ + logeventf(logctx, "Sharing this connection at %s", logtext); + + *state = sharestate; + sharestate->listensock = sock; + sharestate->connections = newtree234(share_connstate_cmp); + sharestate->server_verstring = NULL; + sharestate->sockname = sockname; + sharestate->nextid = 1; + break; + } + + sfree(logtext); + sfree(ds_err); + sfree(us_err); + return toret; +} diff --git a/0.73_My_PuTTY/sshsignals.h b/0.74_My_PuTTY/sshsignals.h similarity index 100% rename from 0.73_My_PuTTY/sshsignals.h rename to 0.74_My_PuTTY/sshsignals.h diff --git a/0.73_My_PuTTY/sshttymodes.h b/0.74_My_PuTTY/sshttymodes.h similarity index 100% rename from 0.73_My_PuTTY/sshttymodes.h rename to 0.74_My_PuTTY/sshttymodes.h diff --git a/0.73_My_PuTTY/sshverstring.c b/0.74_My_PuTTY/sshverstring.c similarity index 99% rename from 0.73_My_PuTTY/sshverstring.c rename to 0.74_My_PuTTY/sshverstring.c index 638a9ef..0e773c0 100644 --- a/0.73_My_PuTTY/sshverstring.c +++ b/0.74_My_PuTTY/sshverstring.c @@ -310,8 +310,7 @@ void ssh_verstring_handle_input(BinaryPacketProtocol *bpp) while (s->vstring->len > 0 && (s->vstring->s[s->vstring->len-1] == '\r' || s->vstring->s[s->vstring->len-1] == '\n')) - s->vstring->len--; - s->vstring->s[s->vstring->len] = '\0'; + strbuf_shrink_by(s->vstring, 1); bpp_logevent("Remote version: %s", s->vstring->s); diff --git a/0.73_My_PuTTY/sshzlib.c b/0.74_My_PuTTY/sshzlib.c similarity index 100% rename from 0.73_My_PuTTY/sshzlib.c rename to 0.74_My_PuTTY/sshzlib.c diff --git a/0.73_My_PuTTY/storage.h b/0.74_My_PuTTY/storage.h similarity index 100% rename from 0.73_My_PuTTY/storage.h rename to 0.74_My_PuTTY/storage.h diff --git a/0.73_My_PuTTY/stripctrl.c b/0.74_My_PuTTY/stripctrl.c similarity index 100% rename from 0.73_My_PuTTY/stripctrl.c rename to 0.74_My_PuTTY/stripctrl.c diff --git a/0.73_My_PuTTY/telnet.c b/0.74_My_PuTTY/telnet.c similarity index 51% rename from 0.73_My_PuTTY/telnet.c rename to 0.74_My_PuTTY/telnet.c index e599695..e397c4a 100644 --- a/0.73_My_PuTTY/telnet.c +++ b/0.74_My_PuTTY/telnet.c @@ -1,1070 +1,1070 @@ -/* - * Telnet backend. - */ - -#include -#include -#include - -#include "putty.h" - -#define IAC 255 /* interpret as command: */ -#define DONT 254 /* you are not to use option */ -#define DO 253 /* please, you use option */ -#define WONT 252 /* I won't use option */ -#define WILL 251 /* I will use option */ -#define SB 250 /* interpret as subnegotiation */ -#define SE 240 /* end sub negotiation */ - -#define GA 249 /* you may reverse the line */ -#define EL 248 /* erase the current line */ -#define EC 247 /* erase the current character */ -#define AYT 246 /* are you there */ -#define AO 245 /* abort output--but let prog finish */ -#define IP 244 /* interrupt process--permanently */ -#define BREAK 243 /* break */ -#define DM 242 /* data mark--for connect. cleaning */ -#define NOP 241 /* nop */ -#define EOR 239 /* end of record (transparent mode) */ -#define ABORT 238 /* Abort process */ -#define SUSP 237 /* Suspend process */ -#define xEOF 236 /* End of file: EOF is already used... */ - -#define TELOPTS(X) \ - X(BINARY, 0) /* 8-bit data path */ \ - X(ECHO, 1) /* echo */ \ - X(RCP, 2) /* prepare to reconnect */ \ - X(SGA, 3) /* suppress go ahead */ \ - X(NAMS, 4) /* approximate message size */ \ - X(STATUS, 5) /* give status */ \ - X(TM, 6) /* timing mark */ \ - X(RCTE, 7) /* remote controlled transmission and echo */ \ - X(NAOL, 8) /* negotiate about output line width */ \ - X(NAOP, 9) /* negotiate about output page size */ \ - X(NAOCRD, 10) /* negotiate about CR disposition */ \ - X(NAOHTS, 11) /* negotiate about horizontal tabstops */ \ - X(NAOHTD, 12) /* negotiate about horizontal tab disposition */ \ - X(NAOFFD, 13) /* negotiate about formfeed disposition */ \ - X(NAOVTS, 14) /* negotiate about vertical tab stops */ \ - X(NAOVTD, 15) /* negotiate about vertical tab disposition */ \ - X(NAOLFD, 16) /* negotiate about output LF disposition */ \ - X(XASCII, 17) /* extended ascic character set */ \ - X(LOGOUT, 18) /* force logout */ \ - X(BM, 19) /* byte macro */ \ - X(DET, 20) /* data entry terminal */ \ - X(SUPDUP, 21) /* supdup protocol */ \ - X(SUPDUPOUTPUT, 22) /* supdup output */ \ - X(SNDLOC, 23) /* send location */ \ - X(TTYPE, 24) /* terminal type */ \ - X(EOR, 25) /* end or record */ \ - X(TUID, 26) /* TACACS user identification */ \ - X(OUTMRK, 27) /* output marking */ \ - X(TTYLOC, 28) /* terminal location number */ \ - X(3270REGIME, 29) /* 3270 regime */ \ - X(X3PAD, 30) /* X.3 PAD */ \ - X(NAWS, 31) /* window size */ \ - X(TSPEED, 32) /* terminal speed */ \ - X(LFLOW, 33) /* remote flow control */ \ - X(LINEMODE, 34) /* Linemode option */ \ - X(XDISPLOC, 35) /* X Display Location */ \ - X(OLD_ENVIRON, 36) /* Old - Environment variables */ \ - X(AUTHENTICATION, 37) /* Authenticate */ \ - X(ENCRYPT, 38) /* Encryption option */ \ - X(NEW_ENVIRON, 39) /* New - Environment variables */ \ - X(TN3270E, 40) /* TN3270 enhancements */ \ - X(XAUTH, 41) \ - X(CHARSET, 42) /* Character set */ \ - X(RSP, 43) /* Remote serial port */ \ - X(COM_PORT_OPTION, 44) /* Com port control */ \ - X(SLE, 45) /* Suppress local echo */ \ - X(STARTTLS, 46) /* Start TLS */ \ - X(KERMIT, 47) /* Automatic Kermit file transfer */ \ - X(SEND_URL, 48) \ - X(FORWARD_X, 49) \ - X(PRAGMA_LOGON, 138) \ - X(SSPI_LOGON, 139) \ - X(PRAGMA_HEARTBEAT, 140) \ - X(EXOPL, 255) /* extended-options-list */ - -#define telnet_enum(x,y) TELOPT_##x = y, -enum { TELOPTS(telnet_enum) dummy=0 }; -#undef telnet_enum - -#define TELQUAL_IS 0 /* option is... */ -#define TELQUAL_SEND 1 /* send option */ -#define TELQUAL_INFO 2 /* ENVIRON: informational version of IS */ -#define BSD_VAR 1 -#define BSD_VALUE 0 -#define RFC_VAR 0 -#define RFC_VALUE 1 - -#define CR 13 -#define LF 10 -#define NUL 0 - -#define iswritable(x) \ - ( (x) != IAC && \ - (telnet->opt_states[o_we_bin.index] == ACTIVE || (x) != CR)) - -static const char *telopt(int opt) -{ -#define telnet_str(x,y) case TELOPT_##x: return #x; - switch (opt) { - TELOPTS(telnet_str) - default: - return ""; - } -#undef telnet_str -} - -struct Opt { - int send; /* what we initially send */ - int nsend; /* -ve send if requested to stop it */ - int ack, nak; /* +ve and -ve acknowledgements */ - int option; /* the option code */ - int index; /* index into telnet->opt_states[] */ - enum { - REQUESTED, ACTIVE, INACTIVE, REALLY_INACTIVE - } initial_state; -}; - -enum { - OPTINDEX_NAWS, - OPTINDEX_TSPEED, - OPTINDEX_TTYPE, - OPTINDEX_OENV, - OPTINDEX_NENV, - OPTINDEX_ECHO, - OPTINDEX_WE_SGA, - OPTINDEX_THEY_SGA, - OPTINDEX_WE_BIN, - OPTINDEX_THEY_BIN, - NUM_OPTS -}; - -static const struct Opt o_naws = - { WILL, WONT, DO, DONT, TELOPT_NAWS, OPTINDEX_NAWS, REQUESTED }; -static const struct Opt o_tspeed = - { WILL, WONT, DO, DONT, TELOPT_TSPEED, OPTINDEX_TSPEED, REQUESTED }; -static const struct Opt o_ttype = - { WILL, WONT, DO, DONT, TELOPT_TTYPE, OPTINDEX_TTYPE, REQUESTED }; -static const struct Opt o_oenv = - { WILL, WONT, DO, DONT, TELOPT_OLD_ENVIRON, OPTINDEX_OENV, INACTIVE }; -static const struct Opt o_nenv = - { WILL, WONT, DO, DONT, TELOPT_NEW_ENVIRON, OPTINDEX_NENV, REQUESTED }; -static const struct Opt o_echo = - { DO, DONT, WILL, WONT, TELOPT_ECHO, OPTINDEX_ECHO, REQUESTED }; -static const struct Opt o_we_sga = - { WILL, WONT, DO, DONT, TELOPT_SGA, OPTINDEX_WE_SGA, REQUESTED }; -static const struct Opt o_they_sga = - { DO, DONT, WILL, WONT, TELOPT_SGA, OPTINDEX_THEY_SGA, REQUESTED }; -static const struct Opt o_we_bin = - { WILL, WONT, DO, DONT, TELOPT_BINARY, OPTINDEX_WE_BIN, INACTIVE }; -static const struct Opt o_they_bin = - { DO, DONT, WILL, WONT, TELOPT_BINARY, OPTINDEX_THEY_BIN, INACTIVE }; - -static const struct Opt *const opts[] = { - &o_naws, &o_tspeed, &o_ttype, &o_oenv, &o_nenv, &o_echo, - &o_we_sga, &o_they_sga, &o_we_bin, &o_they_bin, NULL -}; - -typedef struct Telnet Telnet; -struct Telnet { - Socket *s; - bool closed_on_socket_error; - - Seat *seat; - LogContext *logctx; - Ldisc *ldisc; - int term_width, term_height; - - int opt_states[NUM_OPTS]; - - bool echoing, editing; - bool activated; - size_t bufsize; - bool in_synch; - int sb_opt; - strbuf *sb_buf; - bool session_started; - - enum { - TOP_LEVEL, SEENIAC, SEENWILL, SEENWONT, SEENDO, SEENDONT, - SEENSB, SUBNEGOT, SUBNEG_IAC, SEENCR - } state; - - Conf *conf; - - Pinger *pinger; - - Plug plug; - Backend backend; -}; - -#define TELNET_MAX_BACKLOG 4096 - -#define SB_DELTA 1024 - -static void c_write(Telnet *telnet, const void *buf, size_t len) -{ - size_t backlog = seat_stdout(telnet->seat, buf, len); - sk_set_frozen(telnet->s, backlog > TELNET_MAX_BACKLOG); -} - -static void log_option(Telnet *telnet, const char *sender, int cmd, int option) -{ - /* - * The strange-looking "" below is there to avoid a - * trigraph - a double question mark followed by > maps to a - * closing brace character! - */ - logeventf(telnet->logctx, "%s:\t%s %s", sender, - (cmd == WILL ? "WILL" : cmd == WONT ? "WONT" : - cmd == DO ? "DO" : cmd == DONT ? "DONT" : ""), - telopt(option)); -} - -static void send_opt(Telnet *telnet, int cmd, int option) -{ - unsigned char b[3]; - - b[0] = IAC; - b[1] = cmd; - b[2] = option; - telnet->bufsize = sk_write(telnet->s, b, 3); - log_option(telnet, "client", cmd, option); -} - -static void deactivate_option(Telnet *telnet, const struct Opt *o) -{ - if (telnet->opt_states[o->index] == REQUESTED || - telnet->opt_states[o->index] == ACTIVE) - send_opt(telnet, o->nsend, o->option); - telnet->opt_states[o->index] = REALLY_INACTIVE; -} - -/* - * Generate side effects of enabling or disabling an option. - */ -static void option_side_effects( - Telnet *telnet, const struct Opt *o, bool enabled) -{ - if (o->option == TELOPT_ECHO && o->send == DO) - telnet->echoing = !enabled; - else if (o->option == TELOPT_SGA && o->send == DO) - telnet->editing = !enabled; - if (telnet->ldisc) /* cause ldisc to notice the change */ - ldisc_echoedit_update(telnet->ldisc); - - /* Ensure we get the minimum options */ - if (!telnet->activated) { - if (telnet->opt_states[o_echo.index] == INACTIVE) { - telnet->opt_states[o_echo.index] = REQUESTED; - send_opt(telnet, o_echo.send, o_echo.option); - } - if (telnet->opt_states[o_we_sga.index] == INACTIVE) { - telnet->opt_states[o_we_sga.index] = REQUESTED; - send_opt(telnet, o_we_sga.send, o_we_sga.option); - } - if (telnet->opt_states[o_they_sga.index] == INACTIVE) { - telnet->opt_states[o_they_sga.index] = REQUESTED; - send_opt(telnet, o_they_sga.send, o_they_sga.option); - } - telnet->activated = true; - } -} - -static void activate_option(Telnet *telnet, const struct Opt *o) -{ - if (o->send == WILL && o->option == TELOPT_NAWS) - backend_size(&telnet->backend, - telnet->term_width, telnet->term_height); - if (o->send == WILL && - (o->option == TELOPT_NEW_ENVIRON || - o->option == TELOPT_OLD_ENVIRON)) { - /* - * We may only have one kind of ENVIRON going at a time. - * This is a hack, but who cares. - */ - deactivate_option(telnet, o->option == - TELOPT_NEW_ENVIRON ? &o_oenv : &o_nenv); - } - option_side_effects(telnet, o, true); -} - -static void refused_option(Telnet *telnet, const struct Opt *o) -{ - if (o->send == WILL && o->option == TELOPT_NEW_ENVIRON && - telnet->opt_states[o_oenv.index] == INACTIVE) { - send_opt(telnet, WILL, TELOPT_OLD_ENVIRON); - telnet->opt_states[o_oenv.index] = REQUESTED; - } - option_side_effects(telnet, o, false); -} - -static void proc_rec_opt(Telnet *telnet, int cmd, int option) -{ - const struct Opt *const *o; - - log_option(telnet, "server", cmd, option); - for (o = opts; *o; o++) { - if ((*o)->option == option && (*o)->ack == cmd) { - switch (telnet->opt_states[(*o)->index]) { - case REQUESTED: - telnet->opt_states[(*o)->index] = ACTIVE; - activate_option(telnet, *o); - break; - case ACTIVE: - break; - case INACTIVE: - telnet->opt_states[(*o)->index] = ACTIVE; - send_opt(telnet, (*o)->send, option); - activate_option(telnet, *o); - break; - case REALLY_INACTIVE: - send_opt(telnet, (*o)->nsend, option); - break; - } - return; - } else if ((*o)->option == option && (*o)->nak == cmd) { - switch (telnet->opt_states[(*o)->index]) { - case REQUESTED: - telnet->opt_states[(*o)->index] = INACTIVE; - refused_option(telnet, *o); - break; - case ACTIVE: - telnet->opt_states[(*o)->index] = INACTIVE; - send_opt(telnet, (*o)->nsend, option); - option_side_effects(telnet, *o, false); - break; - case INACTIVE: - case REALLY_INACTIVE: - break; - } - return; - } - } - /* - * If we reach here, the option was one we weren't prepared to - * cope with. If the request was positive (WILL or DO), we send - * a negative ack to indicate refusal. If the request was - * negative (WONT / DONT), we must do nothing. - */ - if (cmd == WILL || cmd == DO) - send_opt(telnet, (cmd == WILL ? DONT : WONT), option); -} - -static void process_subneg(Telnet *telnet) -{ - unsigned char *b, *p, *q; - int var, value, n, bsize; - char *e, *eval, *ekey, *user; - - switch (telnet->sb_opt) { - case TELOPT_TSPEED: - if (telnet->sb_buf->len == 1 && telnet->sb_buf->u[0] == TELQUAL_SEND) { - char *termspeed = conf_get_str(telnet->conf, CONF_termspeed); - b = snewn(20 + strlen(termspeed), unsigned char); - b[0] = IAC; - b[1] = SB; - b[2] = TELOPT_TSPEED; - b[3] = TELQUAL_IS; - strcpy((char *)(b + 4), termspeed); - n = 4 + strlen(termspeed); - b[n] = IAC; - b[n + 1] = SE; - telnet->bufsize = sk_write(telnet->s, b, n + 2); - logevent(telnet->logctx, "server:\tSB TSPEED SEND"); - logeventf(telnet->logctx, "client:\tSB TSPEED IS %s", termspeed); - sfree(b); - } else - logevent(telnet->logctx, "server:\tSB TSPEED "); - break; - case TELOPT_TTYPE: - if (telnet->sb_buf->len == 1 && telnet->sb_buf->u[0] == TELQUAL_SEND) { - char *termtype = conf_get_str(telnet->conf, CONF_termtype); - b = snewn(20 + strlen(termtype), unsigned char); - b[0] = IAC; - b[1] = SB; - b[2] = TELOPT_TTYPE; - b[3] = TELQUAL_IS; - for (n = 0; termtype[n]; n++) - b[n + 4] = (termtype[n] >= 'a' && termtype[n] <= 'z' ? - termtype[n] + 'A' - 'a' : - termtype[n]); - b[n + 4] = IAC; - b[n + 5] = SE; - telnet->bufsize = sk_write(telnet->s, b, n + 6); - b[n + 4] = 0; - logevent(telnet->logctx, "server:\tSB TTYPE SEND"); - logeventf(telnet->logctx, "client:\tSB TTYPE IS %s", b + 4); - sfree(b); - } else - logevent(telnet->logctx, "server:\tSB TTYPE \r\n"); - break; - case TELOPT_OLD_ENVIRON: - case TELOPT_NEW_ENVIRON: - p = telnet->sb_buf->u; - q = p + telnet->sb_buf->len; - if (p < q && *p == TELQUAL_SEND) { - p++; - logeventf(telnet->logctx, "server:\tSB %s SEND", - telopt(telnet->sb_opt)); - if (telnet->sb_opt == TELOPT_OLD_ENVIRON) { - if (conf_get_bool(telnet->conf, CONF_rfc_environ)) { - value = RFC_VALUE; - var = RFC_VAR; - } else { - value = BSD_VALUE; - var = BSD_VAR; - } - /* - * Try to guess the sense of VAR and VALUE. - */ - while (p < q) { - if (*p == RFC_VAR) { - value = RFC_VALUE; - var = RFC_VAR; - } else if (*p == BSD_VAR) { - value = BSD_VALUE; - var = BSD_VAR; - } - p++; - } - } else { - /* - * With NEW_ENVIRON, the sense of VAR and VALUE - * isn't in doubt. - */ - value = RFC_VALUE; - var = RFC_VAR; - } - bsize = 20; - for (eval = conf_get_str_strs(telnet->conf, CONF_environmt, - NULL, &ekey); - eval != NULL; - eval = conf_get_str_strs(telnet->conf, CONF_environmt, - ekey, &ekey)) - bsize += strlen(ekey) + strlen(eval) + 2; - user = get_remote_username(telnet->conf); - if (user) - bsize += 6 + strlen(user); - - b = snewn(bsize, unsigned char); - b[0] = IAC; - b[1] = SB; - b[2] = telnet->sb_opt; - b[3] = TELQUAL_IS; - n = 4; - for (eval = conf_get_str_strs(telnet->conf, CONF_environmt, - NULL, &ekey); - eval != NULL; - eval = conf_get_str_strs(telnet->conf, CONF_environmt, - ekey, &ekey)) { - b[n++] = var; - for (e = ekey; *e; e++) - b[n++] = *e; - b[n++] = value; - for (e = eval; *e; e++) - b[n++] = *e; - } - if (user) { - b[n++] = var; - b[n++] = 'U'; - b[n++] = 'S'; - b[n++] = 'E'; - b[n++] = 'R'; - b[n++] = value; - for (e = user; *e; e++) - b[n++] = *e; - } - b[n++] = IAC; - b[n++] = SE; - telnet->bufsize = sk_write(telnet->s, b, n); - if (n == 6) { - logeventf(telnet->logctx, "client:\tSB %s IS ", - telopt(telnet->sb_opt)); - } else { - logeventf(telnet->logctx, "client:\tSB %s IS:", - telopt(telnet->sb_opt)); - for (eval = conf_get_str_strs(telnet->conf, CONF_environmt, - NULL, &ekey); - eval != NULL; - eval = conf_get_str_strs(telnet->conf, CONF_environmt, - ekey, &ekey)) { - logeventf(telnet->logctx, "\t%s=%s", ekey, eval); - } - if (user) - logeventf(telnet->logctx, "\tUSER=%s", user); - } - sfree(b); - sfree(user); - } - break; - } -} - -static void do_telnet_read(Telnet *telnet, const char *buf, size_t len) -{ - strbuf *outbuf = strbuf_new_nm(); - - while (len--) { - int c = (unsigned char) *buf++; - - switch (telnet->state) { - case TOP_LEVEL: - case SEENCR: - if (c == NUL && telnet->state == SEENCR) - telnet->state = TOP_LEVEL; - else if (c == IAC) - telnet->state = SEENIAC; - else { - if (!telnet->in_synch) - put_byte(outbuf, c); - -#if 1 - /* I can't get the F***ing winsock to insert the urgent IAC - * into the right position! Even with SO_OOBINLINE it gives - * it to recv too soon. And of course the DM byte (that - * arrives in the same packet!) appears several K later!! - * - * Oh well, we do get the DM in the right place so I'll - * just stop hiding on the next 0xf2 and hope for the best. - */ - else if (c == DM) - telnet->in_synch = false; -#endif - if (c == CR && telnet->opt_states[o_they_bin.index] != ACTIVE) - telnet->state = SEENCR; - else - telnet->state = TOP_LEVEL; - } - break; - case SEENIAC: - if (c == DO) - telnet->state = SEENDO; - else if (c == DONT) - telnet->state = SEENDONT; - else if (c == WILL) - telnet->state = SEENWILL; - else if (c == WONT) - telnet->state = SEENWONT; - else if (c == SB) - telnet->state = SEENSB; - else if (c == DM) { - telnet->in_synch = false; - telnet->state = TOP_LEVEL; - } else { - /* ignore everything else; print it if it's IAC */ - if (c == IAC) { - put_byte(outbuf, c); - } - telnet->state = TOP_LEVEL; - } - break; - case SEENWILL: - proc_rec_opt(telnet, WILL, c); - telnet->state = TOP_LEVEL; - break; - case SEENWONT: - proc_rec_opt(telnet, WONT, c); - telnet->state = TOP_LEVEL; - break; - case SEENDO: - proc_rec_opt(telnet, DO, c); - telnet->state = TOP_LEVEL; - break; - case SEENDONT: - proc_rec_opt(telnet, DONT, c); - telnet->state = TOP_LEVEL; - break; - case SEENSB: - telnet->sb_opt = c; - telnet->sb_buf->len = 0; - telnet->state = SUBNEGOT; - break; - case SUBNEGOT: - if (c == IAC) - telnet->state = SUBNEG_IAC; - else { - subneg_addchar: - put_byte(telnet->sb_buf, c); - telnet->state = SUBNEGOT; /* in case we came here by goto */ - } - break; - case SUBNEG_IAC: - if (c != SE) - goto subneg_addchar; /* yes, it's a hack, I know, but... */ - else { - process_subneg(telnet); - telnet->state = TOP_LEVEL; - } - break; - } - - if (outbuf->len >= 4096) { - c_write(telnet, outbuf->u, outbuf->len); - outbuf->len = 0; - } - } - - if (outbuf->len) - c_write(telnet, outbuf->u, outbuf->len); - strbuf_free(outbuf); -} - -static void telnet_log(Plug *plug, int type, SockAddr *addr, int port, - const char *error_msg, int error_code) -{ - Telnet *telnet = container_of(plug, Telnet, plug); - backend_socket_log(telnet->seat, telnet->logctx, type, addr, port, - error_msg, error_code, telnet->conf, - telnet->session_started); -} - -static void telnet_closing(Plug *plug, const char *error_msg, int error_code, - bool calling_back) -{ - Telnet *telnet = container_of(plug, Telnet, plug); - - /* - * We don't implement independent EOF in each direction for Telnet - * connections; as soon as we get word that the remote side has - * sent us EOF, we wind up the whole connection. - */ - - if (telnet->s) { - sk_close(telnet->s); - telnet->s = NULL; - if (error_msg) - telnet->closed_on_socket_error = true; - seat_notify_remote_exit(telnet->seat); - } - if (error_msg) { - logevent(telnet->logctx, error_msg); - seat_connection_fatal(telnet->seat, "%s", error_msg); - } - /* Otherwise, the remote side closed the connection normally. */ -} - -static void telnet_receive( - Plug *plug, int urgent, const char *data, size_t len) -{ - Telnet *telnet = container_of(plug, Telnet, plug); - if (urgent) - telnet->in_synch = true; - telnet->session_started = true; - do_telnet_read(telnet, data, len); -} - -static void telnet_sent(Plug *plug, size_t bufsize) -{ - Telnet *telnet = container_of(plug, Telnet, plug); - telnet->bufsize = bufsize; -} - -static const PlugVtable Telnet_plugvt = { - telnet_log, - telnet_closing, - telnet_receive, - telnet_sent -}; - -/* - * Called to set up the Telnet connection. - * - * Returns an error message, or NULL on success. - * - * Also places the canonical host name into `realhost'. It must be - * freed by the caller. - */ -static const char *telnet_init(Seat *seat, Backend **backend_handle, - LogContext *logctx, Conf *conf, - const char *host, int port, - char **realhost, bool nodelay, bool keepalive) -{ - SockAddr *addr; - const char *err; - Telnet *telnet; - char *loghost; - int addressfamily; - - /* No local authentication phase in this protocol */ - seat_set_trust_status(seat, false); - - telnet = snew(Telnet); - telnet->plug.vt = &Telnet_plugvt; - telnet->backend.vt = &telnet_backend; - telnet->conf = conf_copy(conf); - telnet->s = NULL; - telnet->closed_on_socket_error = false; - telnet->echoing = true; - telnet->editing = true; - telnet->activated = false; - telnet->sb_buf = strbuf_new(); - telnet->seat = seat; - telnet->logctx = logctx; - telnet->term_width = conf_get_int(telnet->conf, CONF_width); - telnet->term_height = conf_get_int(telnet->conf, CONF_height); - telnet->state = TOP_LEVEL; - telnet->ldisc = NULL; - telnet->pinger = NULL; - telnet->session_started = true; - *backend_handle = &telnet->backend; - - /* - * Try to find host. - */ - addressfamily = conf_get_int(telnet->conf, CONF_addressfamily); - addr = name_lookup(host, port, realhost, telnet->conf, addressfamily, - telnet->logctx, "Telnet connection"); - if ((err = sk_addr_error(addr)) != NULL) { - sk_addr_free(addr); - return err; - } - - if (port < 0) - port = 23; /* default telnet port */ - - /* - * Open socket. - */ - telnet->s = new_connection(addr, *realhost, port, false, true, nodelay, - keepalive, &telnet->plug, telnet->conf); - if ((err = sk_socket_error(telnet->s)) != NULL) - return err; - - telnet->pinger = pinger_new(telnet->conf, &telnet->backend); - - /* - * Initialise option states. - */ - if (conf_get_bool(telnet->conf, CONF_passive_telnet)) { - const struct Opt *const *o; - - for (o = opts; *o; o++) - telnet->opt_states[(*o)->index] = INACTIVE; - } else { - const struct Opt *const *o; - - for (o = opts; *o; o++) { - telnet->opt_states[(*o)->index] = (*o)->initial_state; - if (telnet->opt_states[(*o)->index] == REQUESTED) - send_opt(telnet, (*o)->send, (*o)->option); - } - telnet->activated = true; - } - - /* - * Set up SYNCH state. - */ - telnet->in_synch = false; - - /* - * We can send special commands from the start. - */ - seat_update_specials_menu(telnet->seat); - - /* - * loghost overrides realhost, if specified. - */ - loghost = conf_get_str(telnet->conf, CONF_loghost); - if (*loghost) { - char *colon; - - sfree(*realhost); - *realhost = dupstr(loghost); - - colon = host_strrchr(*realhost, ':'); - if (colon) - *colon++ = '\0'; - } - - return NULL; -} - -static void telnet_free(Backend *be) -{ - Telnet *telnet = container_of(be, Telnet, backend); - - strbuf_free(telnet->sb_buf); - if (telnet->s) - sk_close(telnet->s); - if (telnet->pinger) - pinger_free(telnet->pinger); - conf_free(telnet->conf); - sfree(telnet); -} -/* - * Reconfigure the Telnet backend. There's no immediate action - * necessary, in this backend: we just save the fresh config for - * any subsequent negotiations. - */ -static void telnet_reconfig(Backend *be, Conf *conf) -{ - Telnet *telnet = container_of(be, Telnet, backend); - pinger_reconfig(telnet->pinger, telnet->conf, conf); - conf_free(telnet->conf); - telnet->conf = conf_copy(conf); -} - -/* - * Called to send data down the Telnet connection. - */ -static size_t telnet_send(Backend *be, const char *buf, size_t len) -{ - Telnet *telnet = container_of(be, Telnet, backend); - unsigned char *p, *end; - static const unsigned char iac[2] = { IAC, IAC }; - static const unsigned char cr[2] = { CR, NUL }; -#if 0 - static const unsigned char nl[2] = { CR, LF }; -#endif - - if (telnet->s == NULL) - return 0; - - p = (unsigned char *)buf; - end = (unsigned char *)(buf + len); - while (p < end) { - unsigned char *q = p; - - while (p < end && iswritable(*p)) - p++; - telnet->bufsize = sk_write(telnet->s, q, p - q); - - while (p < end && !iswritable(*p)) { - telnet->bufsize = - sk_write(telnet->s, *p == IAC ? iac : cr, 2); - p++; - } - } - - return telnet->bufsize; -} - -/* - * Called to query the current socket sendability status. - */ -static size_t telnet_sendbuffer(Backend *be) -{ - Telnet *telnet = container_of(be, Telnet, backend); - return telnet->bufsize; -} - -/* - * Called to set the size of the window from Telnet's POV. - */ -static void telnet_size(Backend *be, int width, int height) -{ - Telnet *telnet = container_of(be, Telnet, backend); - unsigned char b[24]; - int n; - - telnet->term_width = width; - telnet->term_height = height; - - if (telnet->s == NULL || telnet->opt_states[o_naws.index] != ACTIVE) - return; - n = 0; - b[n++] = IAC; - b[n++] = SB; - b[n++] = TELOPT_NAWS; - b[n++] = telnet->term_width >> 8; - if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */ - b[n++] = telnet->term_width & 0xFF; - if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */ - b[n++] = telnet->term_height >> 8; - if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */ - b[n++] = telnet->term_height & 0xFF; - if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */ - b[n++] = IAC; - b[n++] = SE; - telnet->bufsize = sk_write(telnet->s, b, n); - logeventf(telnet->logctx, "client:\tSB NAWS %d,%d", - telnet->term_width, telnet->term_height); -} - -/* - * Send Telnet special codes. - */ -static void telnet_special(Backend *be, SessionSpecialCode code, int arg) -{ - Telnet *telnet = container_of(be, Telnet, backend); - unsigned char b[2]; - - if (telnet->s == NULL) - return; - - b[0] = IAC; - switch (code) { - case SS_AYT: - b[1] = AYT; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_BRK: - b[1] = BREAK; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_EC: - b[1] = EC; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_EL: - b[1] = EL; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_GA: - b[1] = GA; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_NOP: - b[1] = NOP; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_ABORT: - b[1] = ABORT; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_AO: - b[1] = AO; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_IP: - b[1] = IP; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_SUSP: - b[1] = SUSP; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_EOR: - b[1] = EOR; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_EOF: - b[1] = xEOF; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_EOL: - /* In BINARY mode, CR-LF becomes just CR - - * and without the NUL suffix too. */ - if (telnet->opt_states[o_we_bin.index] == ACTIVE) - telnet->bufsize = sk_write(telnet->s, "\r", 1); - else - telnet->bufsize = sk_write(telnet->s, "\r\n", 2); - break; - case SS_SYNCH: - b[1] = DM; - telnet->bufsize = sk_write(telnet->s, b, 1); - telnet->bufsize = sk_write_oob(telnet->s, b + 1, 1); - break; - case SS_PING: - if (telnet->opt_states[o_they_sga.index] == ACTIVE) { - b[1] = NOP; - telnet->bufsize = sk_write(telnet->s, b, 2); - } - break; - default: - break; /* never heard of it */ - } -} - -static const SessionSpecial *telnet_get_specials(Backend *be) -{ - static const SessionSpecial specials[] = { - {"Are You There", SS_AYT}, - {"Break", SS_BRK}, - {"Synch", SS_SYNCH}, - {"Erase Character", SS_EC}, - {"Erase Line", SS_EL}, - {"Go Ahead", SS_GA}, - {"No Operation", SS_NOP}, - {NULL, SS_SEP}, - {"Abort Process", SS_ABORT}, - {"Abort Output", SS_AO}, - {"Interrupt Process", SS_IP}, - {"Suspend Process", SS_SUSP}, - {NULL, SS_SEP}, - {"End Of Record", SS_EOR}, - {"End Of File", SS_EOF}, - {NULL, SS_EXITMENU} - }; - return specials; -} - -static bool telnet_connected(Backend *be) -{ - Telnet *telnet = container_of(be, Telnet, backend); - return telnet->s != NULL; -} - -static bool telnet_sendok(Backend *be) -{ - /* Telnet *telnet = container_of(be, Telnet, backend); */ - return true; -} - -static void telnet_unthrottle(Backend *be, size_t backlog) -{ - Telnet *telnet = container_of(be, Telnet, backend); - sk_set_frozen(telnet->s, backlog > TELNET_MAX_BACKLOG); -} - -static bool telnet_ldisc(Backend *be, int option) -{ - Telnet *telnet = container_of(be, Telnet, backend); - if (option == LD_ECHO) - return telnet->echoing; - if (option == LD_EDIT) - return telnet->editing; - return false; -} - -static void telnet_provide_ldisc(Backend *be, Ldisc *ldisc) -{ - Telnet *telnet = container_of(be, Telnet, backend); - telnet->ldisc = ldisc; -} - -static int telnet_exitcode(Backend *be) -{ - Telnet *telnet = container_of(be, Telnet, backend); - if (telnet->s != NULL) - return -1; /* still connected */ - else if (telnet->closed_on_socket_error) - return INT_MAX; /* a socket error counts as an unclean exit */ - else - /* Telnet doesn't transmit exit codes back to the client */ - return 0; -} - -/* - * cfg_info for Telnet does nothing at all. - */ -static int telnet_cfg_info(Backend *be) -{ - return 0; -} - -const struct BackendVtable telnet_backend = { - telnet_init, - telnet_free, - telnet_reconfig, - telnet_send, - telnet_sendbuffer, - telnet_size, - telnet_special, - telnet_get_specials, - telnet_connected, - telnet_exitcode, - telnet_sendok, - telnet_ldisc, - telnet_provide_ldisc, - telnet_unthrottle, - telnet_cfg_info, - NULL /* test_for_upstream */, - "telnet", - PROT_TELNET, - 23 -}; +/* + * Telnet backend. + */ + +#include +#include +#include + +#include "putty.h" + +#define IAC 255 /* interpret as command: */ +#define DONT 254 /* you are not to use option */ +#define DO 253 /* please, you use option */ +#define WONT 252 /* I won't use option */ +#define WILL 251 /* I will use option */ +#define SB 250 /* interpret as subnegotiation */ +#define SE 240 /* end sub negotiation */ + +#define GA 249 /* you may reverse the line */ +#define EL 248 /* erase the current line */ +#define EC 247 /* erase the current character */ +#define AYT 246 /* are you there */ +#define AO 245 /* abort output--but let prog finish */ +#define IP 244 /* interrupt process--permanently */ +#define BREAK 243 /* break */ +#define DM 242 /* data mark--for connect. cleaning */ +#define NOP 241 /* nop */ +#define EOR 239 /* end of record (transparent mode) */ +#define ABORT 238 /* Abort process */ +#define SUSP 237 /* Suspend process */ +#define xEOF 236 /* End of file: EOF is already used... */ + +#define TELOPTS(X) \ + X(BINARY, 0) /* 8-bit data path */ \ + X(ECHO, 1) /* echo */ \ + X(RCP, 2) /* prepare to reconnect */ \ + X(SGA, 3) /* suppress go ahead */ \ + X(NAMS, 4) /* approximate message size */ \ + X(STATUS, 5) /* give status */ \ + X(TM, 6) /* timing mark */ \ + X(RCTE, 7) /* remote controlled transmission and echo */ \ + X(NAOL, 8) /* negotiate about output line width */ \ + X(NAOP, 9) /* negotiate about output page size */ \ + X(NAOCRD, 10) /* negotiate about CR disposition */ \ + X(NAOHTS, 11) /* negotiate about horizontal tabstops */ \ + X(NAOHTD, 12) /* negotiate about horizontal tab disposition */ \ + X(NAOFFD, 13) /* negotiate about formfeed disposition */ \ + X(NAOVTS, 14) /* negotiate about vertical tab stops */ \ + X(NAOVTD, 15) /* negotiate about vertical tab disposition */ \ + X(NAOLFD, 16) /* negotiate about output LF disposition */ \ + X(XASCII, 17) /* extended ascic character set */ \ + X(LOGOUT, 18) /* force logout */ \ + X(BM, 19) /* byte macro */ \ + X(DET, 20) /* data entry terminal */ \ + X(SUPDUP, 21) /* supdup protocol */ \ + X(SUPDUPOUTPUT, 22) /* supdup output */ \ + X(SNDLOC, 23) /* send location */ \ + X(TTYPE, 24) /* terminal type */ \ + X(EOR, 25) /* end or record */ \ + X(TUID, 26) /* TACACS user identification */ \ + X(OUTMRK, 27) /* output marking */ \ + X(TTYLOC, 28) /* terminal location number */ \ + X(3270REGIME, 29) /* 3270 regime */ \ + X(X3PAD, 30) /* X.3 PAD */ \ + X(NAWS, 31) /* window size */ \ + X(TSPEED, 32) /* terminal speed */ \ + X(LFLOW, 33) /* remote flow control */ \ + X(LINEMODE, 34) /* Linemode option */ \ + X(XDISPLOC, 35) /* X Display Location */ \ + X(OLD_ENVIRON, 36) /* Old - Environment variables */ \ + X(AUTHENTICATION, 37) /* Authenticate */ \ + X(ENCRYPT, 38) /* Encryption option */ \ + X(NEW_ENVIRON, 39) /* New - Environment variables */ \ + X(TN3270E, 40) /* TN3270 enhancements */ \ + X(XAUTH, 41) \ + X(CHARSET, 42) /* Character set */ \ + X(RSP, 43) /* Remote serial port */ \ + X(COM_PORT_OPTION, 44) /* Com port control */ \ + X(SLE, 45) /* Suppress local echo */ \ + X(STARTTLS, 46) /* Start TLS */ \ + X(KERMIT, 47) /* Automatic Kermit file transfer */ \ + X(SEND_URL, 48) \ + X(FORWARD_X, 49) \ + X(PRAGMA_LOGON, 138) \ + X(SSPI_LOGON, 139) \ + X(PRAGMA_HEARTBEAT, 140) \ + X(EXOPL, 255) /* extended-options-list */ + +#define telnet_enum(x,y) TELOPT_##x = y, +enum { TELOPTS(telnet_enum) dummy=0 }; +#undef telnet_enum + +#define TELQUAL_IS 0 /* option is... */ +#define TELQUAL_SEND 1 /* send option */ +#define TELQUAL_INFO 2 /* ENVIRON: informational version of IS */ +#define BSD_VAR 1 +#define BSD_VALUE 0 +#define RFC_VAR 0 +#define RFC_VALUE 1 + +#define CR 13 +#define LF 10 +#define NUL 0 + +#define iswritable(x) \ + ( (x) != IAC && \ + (telnet->opt_states[o_we_bin.index] == ACTIVE || (x) != CR)) + +static const char *telopt(int opt) +{ +#define telnet_str(x,y) case TELOPT_##x: return #x; + switch (opt) { + TELOPTS(telnet_str) + default: + return ""; + } +#undef telnet_str +} + +struct Opt { + int send; /* what we initially send */ + int nsend; /* -ve send if requested to stop it */ + int ack, nak; /* +ve and -ve acknowledgements */ + int option; /* the option code */ + int index; /* index into telnet->opt_states[] */ + enum { + REQUESTED, ACTIVE, INACTIVE, REALLY_INACTIVE + } initial_state; +}; + +enum { + OPTINDEX_NAWS, + OPTINDEX_TSPEED, + OPTINDEX_TTYPE, + OPTINDEX_OENV, + OPTINDEX_NENV, + OPTINDEX_ECHO, + OPTINDEX_WE_SGA, + OPTINDEX_THEY_SGA, + OPTINDEX_WE_BIN, + OPTINDEX_THEY_BIN, + NUM_OPTS +}; + +static const struct Opt o_naws = + { WILL, WONT, DO, DONT, TELOPT_NAWS, OPTINDEX_NAWS, REQUESTED }; +static const struct Opt o_tspeed = + { WILL, WONT, DO, DONT, TELOPT_TSPEED, OPTINDEX_TSPEED, REQUESTED }; +static const struct Opt o_ttype = + { WILL, WONT, DO, DONT, TELOPT_TTYPE, OPTINDEX_TTYPE, REQUESTED }; +static const struct Opt o_oenv = + { WILL, WONT, DO, DONT, TELOPT_OLD_ENVIRON, OPTINDEX_OENV, INACTIVE }; +static const struct Opt o_nenv = + { WILL, WONT, DO, DONT, TELOPT_NEW_ENVIRON, OPTINDEX_NENV, REQUESTED }; +static const struct Opt o_echo = + { DO, DONT, WILL, WONT, TELOPT_ECHO, OPTINDEX_ECHO, REQUESTED }; +static const struct Opt o_we_sga = + { WILL, WONT, DO, DONT, TELOPT_SGA, OPTINDEX_WE_SGA, REQUESTED }; +static const struct Opt o_they_sga = + { DO, DONT, WILL, WONT, TELOPT_SGA, OPTINDEX_THEY_SGA, REQUESTED }; +static const struct Opt o_we_bin = + { WILL, WONT, DO, DONT, TELOPT_BINARY, OPTINDEX_WE_BIN, INACTIVE }; +static const struct Opt o_they_bin = + { DO, DONT, WILL, WONT, TELOPT_BINARY, OPTINDEX_THEY_BIN, INACTIVE }; + +static const struct Opt *const opts[] = { + &o_naws, &o_tspeed, &o_ttype, &o_oenv, &o_nenv, &o_echo, + &o_we_sga, &o_they_sga, &o_we_bin, &o_they_bin, NULL +}; + +typedef struct Telnet Telnet; +struct Telnet { + Socket *s; + bool closed_on_socket_error; + + Seat *seat; + LogContext *logctx; + Ldisc *ldisc; + int term_width, term_height; + + int opt_states[NUM_OPTS]; + + bool echoing, editing; + bool activated; + size_t bufsize; + bool in_synch; + int sb_opt; + strbuf *sb_buf; + bool session_started; + + enum { + TOP_LEVEL, SEENIAC, SEENWILL, SEENWONT, SEENDO, SEENDONT, + SEENSB, SUBNEGOT, SUBNEG_IAC, SEENCR + } state; + + Conf *conf; + + Pinger *pinger; + + Plug plug; + Backend backend; +}; + +#define TELNET_MAX_BACKLOG 4096 + +#define SB_DELTA 1024 + +static void c_write(Telnet *telnet, const void *buf, size_t len) +{ + size_t backlog = seat_stdout(telnet->seat, buf, len); + sk_set_frozen(telnet->s, backlog > TELNET_MAX_BACKLOG); +} + +static void log_option(Telnet *telnet, const char *sender, int cmd, int option) +{ + /* + * The strange-looking "" below is there to avoid a + * trigraph - a double question mark followed by > maps to a + * closing brace character! + */ + logeventf(telnet->logctx, "%s:\t%s %s", sender, + (cmd == WILL ? "WILL" : cmd == WONT ? "WONT" : + cmd == DO ? "DO" : cmd == DONT ? "DONT" : ""), + telopt(option)); +} + +static void send_opt(Telnet *telnet, int cmd, int option) +{ + unsigned char b[3]; + + b[0] = IAC; + b[1] = cmd; + b[2] = option; + telnet->bufsize = sk_write(telnet->s, b, 3); + log_option(telnet, "client", cmd, option); +} + +static void deactivate_option(Telnet *telnet, const struct Opt *o) +{ + if (telnet->opt_states[o->index] == REQUESTED || + telnet->opt_states[o->index] == ACTIVE) + send_opt(telnet, o->nsend, o->option); + telnet->opt_states[o->index] = REALLY_INACTIVE; +} + +/* + * Generate side effects of enabling or disabling an option. + */ +static void option_side_effects( + Telnet *telnet, const struct Opt *o, bool enabled) +{ + if (o->option == TELOPT_ECHO && o->send == DO) + telnet->echoing = !enabled; + else if (o->option == TELOPT_SGA && o->send == DO) + telnet->editing = !enabled; + if (telnet->ldisc) /* cause ldisc to notice the change */ + ldisc_echoedit_update(telnet->ldisc); + + /* Ensure we get the minimum options */ + if (!telnet->activated) { + if (telnet->opt_states[o_echo.index] == INACTIVE) { + telnet->opt_states[o_echo.index] = REQUESTED; + send_opt(telnet, o_echo.send, o_echo.option); + } + if (telnet->opt_states[o_we_sga.index] == INACTIVE) { + telnet->opt_states[o_we_sga.index] = REQUESTED; + send_opt(telnet, o_we_sga.send, o_we_sga.option); + } + if (telnet->opt_states[o_they_sga.index] == INACTIVE) { + telnet->opt_states[o_they_sga.index] = REQUESTED; + send_opt(telnet, o_they_sga.send, o_they_sga.option); + } + telnet->activated = true; + } +} + +static void activate_option(Telnet *telnet, const struct Opt *o) +{ + if (o->send == WILL && o->option == TELOPT_NAWS) + backend_size(&telnet->backend, + telnet->term_width, telnet->term_height); + if (o->send == WILL && + (o->option == TELOPT_NEW_ENVIRON || + o->option == TELOPT_OLD_ENVIRON)) { + /* + * We may only have one kind of ENVIRON going at a time. + * This is a hack, but who cares. + */ + deactivate_option(telnet, o->option == + TELOPT_NEW_ENVIRON ? &o_oenv : &o_nenv); + } + option_side_effects(telnet, o, true); +} + +static void refused_option(Telnet *telnet, const struct Opt *o) +{ + if (o->send == WILL && o->option == TELOPT_NEW_ENVIRON && + telnet->opt_states[o_oenv.index] == INACTIVE) { + send_opt(telnet, WILL, TELOPT_OLD_ENVIRON); + telnet->opt_states[o_oenv.index] = REQUESTED; + } + option_side_effects(telnet, o, false); +} + +static void proc_rec_opt(Telnet *telnet, int cmd, int option) +{ + const struct Opt *const *o; + + log_option(telnet, "server", cmd, option); + for (o = opts; *o; o++) { + if ((*o)->option == option && (*o)->ack == cmd) { + switch (telnet->opt_states[(*o)->index]) { + case REQUESTED: + telnet->opt_states[(*o)->index] = ACTIVE; + activate_option(telnet, *o); + break; + case ACTIVE: + break; + case INACTIVE: + telnet->opt_states[(*o)->index] = ACTIVE; + send_opt(telnet, (*o)->send, option); + activate_option(telnet, *o); + break; + case REALLY_INACTIVE: + send_opt(telnet, (*o)->nsend, option); + break; + } + return; + } else if ((*o)->option == option && (*o)->nak == cmd) { + switch (telnet->opt_states[(*o)->index]) { + case REQUESTED: + telnet->opt_states[(*o)->index] = INACTIVE; + refused_option(telnet, *o); + break; + case ACTIVE: + telnet->opt_states[(*o)->index] = INACTIVE; + send_opt(telnet, (*o)->nsend, option); + option_side_effects(telnet, *o, false); + break; + case INACTIVE: + case REALLY_INACTIVE: + break; + } + return; + } + } + /* + * If we reach here, the option was one we weren't prepared to + * cope with. If the request was positive (WILL or DO), we send + * a negative ack to indicate refusal. If the request was + * negative (WONT / DONT), we must do nothing. + */ + if (cmd == WILL || cmd == DO) + send_opt(telnet, (cmd == WILL ? DONT : WONT), option); +} + +static void process_subneg(Telnet *telnet) +{ + unsigned char *b, *p, *q; + int var, value, n, bsize; + char *e, *eval, *ekey, *user; + + switch (telnet->sb_opt) { + case TELOPT_TSPEED: + if (telnet->sb_buf->len == 1 && telnet->sb_buf->u[0] == TELQUAL_SEND) { + char *termspeed = conf_get_str(telnet->conf, CONF_termspeed); + b = snewn(20 + strlen(termspeed), unsigned char); + b[0] = IAC; + b[1] = SB; + b[2] = TELOPT_TSPEED; + b[3] = TELQUAL_IS; + strcpy((char *)(b + 4), termspeed); + n = 4 + strlen(termspeed); + b[n] = IAC; + b[n + 1] = SE; + telnet->bufsize = sk_write(telnet->s, b, n + 2); + logevent(telnet->logctx, "server:\tSB TSPEED SEND"); + logeventf(telnet->logctx, "client:\tSB TSPEED IS %s", termspeed); + sfree(b); + } else + logevent(telnet->logctx, "server:\tSB TSPEED "); + break; + case TELOPT_TTYPE: + if (telnet->sb_buf->len == 1 && telnet->sb_buf->u[0] == TELQUAL_SEND) { + char *termtype = conf_get_str(telnet->conf, CONF_termtype); + b = snewn(20 + strlen(termtype), unsigned char); + b[0] = IAC; + b[1] = SB; + b[2] = TELOPT_TTYPE; + b[3] = TELQUAL_IS; + for (n = 0; termtype[n]; n++) + b[n + 4] = (termtype[n] >= 'a' && termtype[n] <= 'z' ? + termtype[n] + 'A' - 'a' : + termtype[n]); + b[n + 4] = IAC; + b[n + 5] = SE; + telnet->bufsize = sk_write(telnet->s, b, n + 6); + b[n + 4] = 0; + logevent(telnet->logctx, "server:\tSB TTYPE SEND"); + logeventf(telnet->logctx, "client:\tSB TTYPE IS %s", b + 4); + sfree(b); + } else + logevent(telnet->logctx, "server:\tSB TTYPE \r\n"); + break; + case TELOPT_OLD_ENVIRON: + case TELOPT_NEW_ENVIRON: + p = telnet->sb_buf->u; + q = p + telnet->sb_buf->len; + if (p < q && *p == TELQUAL_SEND) { + p++; + logeventf(telnet->logctx, "server:\tSB %s SEND", + telopt(telnet->sb_opt)); + if (telnet->sb_opt == TELOPT_OLD_ENVIRON) { + if (conf_get_bool(telnet->conf, CONF_rfc_environ)) { + value = RFC_VALUE; + var = RFC_VAR; + } else { + value = BSD_VALUE; + var = BSD_VAR; + } + /* + * Try to guess the sense of VAR and VALUE. + */ + while (p < q) { + if (*p == RFC_VAR) { + value = RFC_VALUE; + var = RFC_VAR; + } else if (*p == BSD_VAR) { + value = BSD_VALUE; + var = BSD_VAR; + } + p++; + } + } else { + /* + * With NEW_ENVIRON, the sense of VAR and VALUE + * isn't in doubt. + */ + value = RFC_VALUE; + var = RFC_VAR; + } + bsize = 20; + for (eval = conf_get_str_strs(telnet->conf, CONF_environmt, + NULL, &ekey); + eval != NULL; + eval = conf_get_str_strs(telnet->conf, CONF_environmt, + ekey, &ekey)) + bsize += strlen(ekey) + strlen(eval) + 2; + user = get_remote_username(telnet->conf); + if (user) + bsize += 6 + strlen(user); + + b = snewn(bsize, unsigned char); + b[0] = IAC; + b[1] = SB; + b[2] = telnet->sb_opt; + b[3] = TELQUAL_IS; + n = 4; + for (eval = conf_get_str_strs(telnet->conf, CONF_environmt, + NULL, &ekey); + eval != NULL; + eval = conf_get_str_strs(telnet->conf, CONF_environmt, + ekey, &ekey)) { + b[n++] = var; + for (e = ekey; *e; e++) + b[n++] = *e; + b[n++] = value; + for (e = eval; *e; e++) + b[n++] = *e; + } + if (user) { + b[n++] = var; + b[n++] = 'U'; + b[n++] = 'S'; + b[n++] = 'E'; + b[n++] = 'R'; + b[n++] = value; + for (e = user; *e; e++) + b[n++] = *e; + } + b[n++] = IAC; + b[n++] = SE; + telnet->bufsize = sk_write(telnet->s, b, n); + if (n == 6) { + logeventf(telnet->logctx, "client:\tSB %s IS ", + telopt(telnet->sb_opt)); + } else { + logeventf(telnet->logctx, "client:\tSB %s IS:", + telopt(telnet->sb_opt)); + for (eval = conf_get_str_strs(telnet->conf, CONF_environmt, + NULL, &ekey); + eval != NULL; + eval = conf_get_str_strs(telnet->conf, CONF_environmt, + ekey, &ekey)) { + logeventf(telnet->logctx, "\t%s=%s", ekey, eval); + } + if (user) + logeventf(telnet->logctx, "\tUSER=%s", user); + } + sfree(b); + sfree(user); + } + break; + } +} + +static void do_telnet_read(Telnet *telnet, const char *buf, size_t len) +{ + strbuf *outbuf = strbuf_new_nm(); + + while (len--) { + int c = (unsigned char) *buf++; + + switch (telnet->state) { + case TOP_LEVEL: + case SEENCR: + if (c == NUL && telnet->state == SEENCR) + telnet->state = TOP_LEVEL; + else if (c == IAC) + telnet->state = SEENIAC; + else { + if (!telnet->in_synch) + put_byte(outbuf, c); + +#if 1 + /* I can't get the F***ing winsock to insert the urgent IAC + * into the right position! Even with SO_OOBINLINE it gives + * it to recv too soon. And of course the DM byte (that + * arrives in the same packet!) appears several K later!! + * + * Oh well, we do get the DM in the right place so I'll + * just stop hiding on the next 0xf2 and hope for the best. + */ + else if (c == DM) + telnet->in_synch = false; +#endif + if (c == CR && telnet->opt_states[o_they_bin.index] != ACTIVE) + telnet->state = SEENCR; + else + telnet->state = TOP_LEVEL; + } + break; + case SEENIAC: + if (c == DO) + telnet->state = SEENDO; + else if (c == DONT) + telnet->state = SEENDONT; + else if (c == WILL) + telnet->state = SEENWILL; + else if (c == WONT) + telnet->state = SEENWONT; + else if (c == SB) + telnet->state = SEENSB; + else if (c == DM) { + telnet->in_synch = false; + telnet->state = TOP_LEVEL; + } else { + /* ignore everything else; print it if it's IAC */ + if (c == IAC) { + put_byte(outbuf, c); + } + telnet->state = TOP_LEVEL; + } + break; + case SEENWILL: + proc_rec_opt(telnet, WILL, c); + telnet->state = TOP_LEVEL; + break; + case SEENWONT: + proc_rec_opt(telnet, WONT, c); + telnet->state = TOP_LEVEL; + break; + case SEENDO: + proc_rec_opt(telnet, DO, c); + telnet->state = TOP_LEVEL; + break; + case SEENDONT: + proc_rec_opt(telnet, DONT, c); + telnet->state = TOP_LEVEL; + break; + case SEENSB: + telnet->sb_opt = c; + strbuf_clear(telnet->sb_buf); + telnet->state = SUBNEGOT; + break; + case SUBNEGOT: + if (c == IAC) + telnet->state = SUBNEG_IAC; + else { + subneg_addchar: + put_byte(telnet->sb_buf, c); + telnet->state = SUBNEGOT; /* in case we came here by goto */ + } + break; + case SUBNEG_IAC: + if (c != SE) + goto subneg_addchar; /* yes, it's a hack, I know, but... */ + else { + process_subneg(telnet); + telnet->state = TOP_LEVEL; + } + break; + } + + if (outbuf->len >= 4096) { + c_write(telnet, outbuf->u, outbuf->len); + strbuf_clear(outbuf); + } + } + + if (outbuf->len) + c_write(telnet, outbuf->u, outbuf->len); + strbuf_free(outbuf); +} + +static void telnet_log(Plug *plug, int type, SockAddr *addr, int port, + const char *error_msg, int error_code) +{ + Telnet *telnet = container_of(plug, Telnet, plug); + backend_socket_log(telnet->seat, telnet->logctx, type, addr, port, + error_msg, error_code, telnet->conf, + telnet->session_started); +} + +static void telnet_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back) +{ + Telnet *telnet = container_of(plug, Telnet, plug); + + /* + * We don't implement independent EOF in each direction for Telnet + * connections; as soon as we get word that the remote side has + * sent us EOF, we wind up the whole connection. + */ + + if (telnet->s) { + sk_close(telnet->s); + telnet->s = NULL; + if (error_msg) + telnet->closed_on_socket_error = true; + seat_notify_remote_exit(telnet->seat); + } + if (error_msg) { + logevent(telnet->logctx, error_msg); + seat_connection_fatal(telnet->seat, "%s", error_msg); + } + /* Otherwise, the remote side closed the connection normally. */ +} + +static void telnet_receive( + Plug *plug, int urgent, const char *data, size_t len) +{ + Telnet *telnet = container_of(plug, Telnet, plug); + if (urgent) + telnet->in_synch = true; + telnet->session_started = true; + do_telnet_read(telnet, data, len); +} + +static void telnet_sent(Plug *plug, size_t bufsize) +{ + Telnet *telnet = container_of(plug, Telnet, plug); + telnet->bufsize = bufsize; +} + +static const PlugVtable Telnet_plugvt = { + telnet_log, + telnet_closing, + telnet_receive, + telnet_sent +}; + +/* + * Called to set up the Telnet connection. + * + * Returns an error message, or NULL on success. + * + * Also places the canonical host name into `realhost'. It must be + * freed by the caller. + */ +static const char *telnet_init(Seat *seat, Backend **backend_handle, + LogContext *logctx, Conf *conf, + const char *host, int port, + char **realhost, bool nodelay, bool keepalive) +{ + SockAddr *addr; + const char *err; + Telnet *telnet; + char *loghost; + int addressfamily; + + /* No local authentication phase in this protocol */ + seat_set_trust_status(seat, false); + + telnet = snew(Telnet); + telnet->plug.vt = &Telnet_plugvt; + telnet->backend.vt = &telnet_backend; + telnet->conf = conf_copy(conf); + telnet->s = NULL; + telnet->closed_on_socket_error = false; + telnet->echoing = true; + telnet->editing = true; + telnet->activated = false; + telnet->sb_buf = strbuf_new(); + telnet->seat = seat; + telnet->logctx = logctx; + telnet->term_width = conf_get_int(telnet->conf, CONF_width); + telnet->term_height = conf_get_int(telnet->conf, CONF_height); + telnet->state = TOP_LEVEL; + telnet->ldisc = NULL; + telnet->pinger = NULL; + telnet->session_started = true; + *backend_handle = &telnet->backend; + + /* + * Try to find host. + */ + addressfamily = conf_get_int(telnet->conf, CONF_addressfamily); + addr = name_lookup(host, port, realhost, telnet->conf, addressfamily, + telnet->logctx, "Telnet connection"); + if ((err = sk_addr_error(addr)) != NULL) { + sk_addr_free(addr); + return err; + } + + if (port < 0) + port = 23; /* default telnet port */ + + /* + * Open socket. + */ + telnet->s = new_connection(addr, *realhost, port, false, true, nodelay, + keepalive, &telnet->plug, telnet->conf); + if ((err = sk_socket_error(telnet->s)) != NULL) + return err; + + telnet->pinger = pinger_new(telnet->conf, &telnet->backend); + + /* + * Initialise option states. + */ + if (conf_get_bool(telnet->conf, CONF_passive_telnet)) { + const struct Opt *const *o; + + for (o = opts; *o; o++) + telnet->opt_states[(*o)->index] = INACTIVE; + } else { + const struct Opt *const *o; + + for (o = opts; *o; o++) { + telnet->opt_states[(*o)->index] = (*o)->initial_state; + if (telnet->opt_states[(*o)->index] == REQUESTED) + send_opt(telnet, (*o)->send, (*o)->option); + } + telnet->activated = true; + } + + /* + * Set up SYNCH state. + */ + telnet->in_synch = false; + + /* + * We can send special commands from the start. + */ + seat_update_specials_menu(telnet->seat); + + /* + * loghost overrides realhost, if specified. + */ + loghost = conf_get_str(telnet->conf, CONF_loghost); + if (*loghost) { + char *colon; + + sfree(*realhost); + *realhost = dupstr(loghost); + + colon = host_strrchr(*realhost, ':'); + if (colon) + *colon++ = '\0'; + } + + return NULL; +} + +static void telnet_free(Backend *be) +{ + Telnet *telnet = container_of(be, Telnet, backend); + + strbuf_free(telnet->sb_buf); + if (telnet->s) + sk_close(telnet->s); + if (telnet->pinger) + pinger_free(telnet->pinger); + conf_free(telnet->conf); + sfree(telnet); +} +/* + * Reconfigure the Telnet backend. There's no immediate action + * necessary, in this backend: we just save the fresh config for + * any subsequent negotiations. + */ +static void telnet_reconfig(Backend *be, Conf *conf) +{ + Telnet *telnet = container_of(be, Telnet, backend); + pinger_reconfig(telnet->pinger, telnet->conf, conf); + conf_free(telnet->conf); + telnet->conf = conf_copy(conf); +} + +/* + * Called to send data down the Telnet connection. + */ +static size_t telnet_send(Backend *be, const char *buf, size_t len) +{ + Telnet *telnet = container_of(be, Telnet, backend); + unsigned char *p, *end; + static const unsigned char iac[2] = { IAC, IAC }; + static const unsigned char cr[2] = { CR, NUL }; +#if 0 + static const unsigned char nl[2] = { CR, LF }; +#endif + + if (telnet->s == NULL) + return 0; + + p = (unsigned char *)buf; + end = (unsigned char *)(buf + len); + while (p < end) { + unsigned char *q = p; + + while (p < end && iswritable(*p)) + p++; + telnet->bufsize = sk_write(telnet->s, q, p - q); + + while (p < end && !iswritable(*p)) { + telnet->bufsize = + sk_write(telnet->s, *p == IAC ? iac : cr, 2); + p++; + } + } + + return telnet->bufsize; +} + +/* + * Called to query the current socket sendability status. + */ +static size_t telnet_sendbuffer(Backend *be) +{ + Telnet *telnet = container_of(be, Telnet, backend); + return telnet->bufsize; +} + +/* + * Called to set the size of the window from Telnet's POV. + */ +static void telnet_size(Backend *be, int width, int height) +{ + Telnet *telnet = container_of(be, Telnet, backend); + unsigned char b[24]; + int n; + + telnet->term_width = width; + telnet->term_height = height; + + if (telnet->s == NULL || telnet->opt_states[o_naws.index] != ACTIVE) + return; + n = 0; + b[n++] = IAC; + b[n++] = SB; + b[n++] = TELOPT_NAWS; + b[n++] = telnet->term_width >> 8; + if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */ + b[n++] = telnet->term_width & 0xFF; + if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */ + b[n++] = telnet->term_height >> 8; + if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */ + b[n++] = telnet->term_height & 0xFF; + if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */ + b[n++] = IAC; + b[n++] = SE; + telnet->bufsize = sk_write(telnet->s, b, n); + logeventf(telnet->logctx, "client:\tSB NAWS %d,%d", + telnet->term_width, telnet->term_height); +} + +/* + * Send Telnet special codes. + */ +static void telnet_special(Backend *be, SessionSpecialCode code, int arg) +{ + Telnet *telnet = container_of(be, Telnet, backend); + unsigned char b[2]; + + if (telnet->s == NULL) + return; + + b[0] = IAC; + switch (code) { + case SS_AYT: + b[1] = AYT; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_BRK: + b[1] = BREAK; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_EC: + b[1] = EC; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_EL: + b[1] = EL; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_GA: + b[1] = GA; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_NOP: + b[1] = NOP; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_ABORT: + b[1] = ABORT; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_AO: + b[1] = AO; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_IP: + b[1] = IP; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_SUSP: + b[1] = SUSP; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_EOR: + b[1] = EOR; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_EOF: + b[1] = xEOF; + telnet->bufsize = sk_write(telnet->s, b, 2); + break; + case SS_EOL: + /* In BINARY mode, CR-LF becomes just CR - + * and without the NUL suffix too. */ + if (telnet->opt_states[o_we_bin.index] == ACTIVE) + telnet->bufsize = sk_write(telnet->s, "\r", 1); + else + telnet->bufsize = sk_write(telnet->s, "\r\n", 2); + break; + case SS_SYNCH: + b[1] = DM; + telnet->bufsize = sk_write(telnet->s, b, 1); + telnet->bufsize = sk_write_oob(telnet->s, b + 1, 1); + break; + case SS_PING: + if (telnet->opt_states[o_they_sga.index] == ACTIVE) { + b[1] = NOP; + telnet->bufsize = sk_write(telnet->s, b, 2); + } + break; + default: + break; /* never heard of it */ + } +} + +static const SessionSpecial *telnet_get_specials(Backend *be) +{ + static const SessionSpecial specials[] = { + {"Are You There", SS_AYT}, + {"Break", SS_BRK}, + {"Synch", SS_SYNCH}, + {"Erase Character", SS_EC}, + {"Erase Line", SS_EL}, + {"Go Ahead", SS_GA}, + {"No Operation", SS_NOP}, + {NULL, SS_SEP}, + {"Abort Process", SS_ABORT}, + {"Abort Output", SS_AO}, + {"Interrupt Process", SS_IP}, + {"Suspend Process", SS_SUSP}, + {NULL, SS_SEP}, + {"End Of Record", SS_EOR}, + {"End Of File", SS_EOF}, + {NULL, SS_EXITMENU} + }; + return specials; +} + +static bool telnet_connected(Backend *be) +{ + Telnet *telnet = container_of(be, Telnet, backend); + return telnet->s != NULL; +} + +static bool telnet_sendok(Backend *be) +{ + /* Telnet *telnet = container_of(be, Telnet, backend); */ + return true; +} + +static void telnet_unthrottle(Backend *be, size_t backlog) +{ + Telnet *telnet = container_of(be, Telnet, backend); + sk_set_frozen(telnet->s, backlog > TELNET_MAX_BACKLOG); +} + +static bool telnet_ldisc(Backend *be, int option) +{ + Telnet *telnet = container_of(be, Telnet, backend); + if (option == LD_ECHO) + return telnet->echoing; + if (option == LD_EDIT) + return telnet->editing; + return false; +} + +static void telnet_provide_ldisc(Backend *be, Ldisc *ldisc) +{ + Telnet *telnet = container_of(be, Telnet, backend); + telnet->ldisc = ldisc; +} + +static int telnet_exitcode(Backend *be) +{ + Telnet *telnet = container_of(be, Telnet, backend); + if (telnet->s != NULL) + return -1; /* still connected */ + else if (telnet->closed_on_socket_error) + return INT_MAX; /* a socket error counts as an unclean exit */ + else + /* Telnet doesn't transmit exit codes back to the client */ + return 0; +} + +/* + * cfg_info for Telnet does nothing at all. + */ +static int telnet_cfg_info(Backend *be) +{ + return 0; +} + +const struct BackendVtable telnet_backend = { + telnet_init, + telnet_free, + telnet_reconfig, + telnet_send, + telnet_sendbuffer, + telnet_size, + telnet_special, + telnet_get_specials, + telnet_connected, + telnet_exitcode, + telnet_sendok, + telnet_ldisc, + telnet_provide_ldisc, + telnet_unthrottle, + telnet_cfg_info, + NULL /* test_for_upstream */, + "telnet", + PROT_TELNET, + 23 +}; diff --git a/0.73_My_PuTTY/terminal.c b/0.74_My_PuTTY/terminal.c similarity index 99% rename from 0.73_My_PuTTY/terminal.c rename to 0.74_My_PuTTY/terminal.c index aedd965..9bac172 100644 --- a/0.73_My_PuTTY/terminal.c +++ b/0.74_My_PuTTY/terminal.c @@ -488,11 +488,11 @@ static void makerle(strbuf *b, termline *ldata, if (hdrsize == 0) { assert(prevpos == hdrpos + 1); runpos = hdrpos; - b->len = prevpos+prevlen; + strbuf_shrink_to(b, prevpos+prevlen); } else { memmove(b->u + prevpos+1, b->u + prevpos, prevlen); runpos = prevpos; - b->len = prevpos+prevlen+1; + strbuf_shrink_to(b, prevpos+prevlen+1); /* * Terminate the previous run of ordinary * literals. @@ -509,7 +509,7 @@ static void makerle(strbuf *b, termline *ldata, oldstate = state; makeliteral(b, c, &state); tmplen = b->len - tmppos; - b->len = tmppos; + strbuf_shrink_to(b, tmppos); if (tmplen != thislen || memcmp(b->u + runpos+1, b->u + tmppos, tmplen)) { state = oldstate; @@ -2127,12 +2127,29 @@ static void swap_screen(Terminal *term, int which, reset = false; /* do no weird resetting if which==0 */ if (which != term->alt_which) { + if (term->erase_to_scrollback && term->alt_screen && + term->alt_which && term->disptop < 0) { + /* + * We're swapping away from the alternate screen, so some + * lines are about to vanish from the virtual scrollback. + * Adjust disptop by that much, so that (if we're not + * resetting the scrollback anyway on a display event) the + * current scroll position still ends up pointing at the + * same text. + */ + term->disptop += term->alt_sblines; + if (term->disptop > 0) + term->disptop = 0; + } + term->alt_which = which; ttr = term->alt_screen; term->alt_screen = term->screen; term->screen = ttr; - term->alt_sblines = find_last_nonempty_line(term, term->alt_screen) + 1; + term->alt_sblines = ( + term->alt_screen ? + find_last_nonempty_line(term, term->alt_screen) + 1 : 0); t = term->curs.x; if (!reset && !keep_cur_pos) term->curs.x = term->alt_x; @@ -2170,37 +2187,57 @@ static void swap_screen(Terminal *term, int which, term->alt_sco_acs = t; tp = term->savecurs; - if (!reset && !keep_cur_pos) + if (!reset) term->savecurs = term->alt_savecurs; term->alt_savecurs = tp; t = term->save_cset; - if (!reset && !keep_cur_pos) + if (!reset) term->save_cset = term->alt_save_cset; term->alt_save_cset = t; t = term->save_csattr; - if (!reset && !keep_cur_pos) + if (!reset) term->save_csattr = term->alt_save_csattr; term->alt_save_csattr = t; t = term->save_attr; - if (!reset && !keep_cur_pos) + if (!reset) term->save_attr = term->alt_save_attr; term->alt_save_attr = t; ttc = term->save_truecolour; - if (!reset && !keep_cur_pos) + if (!reset) term->save_truecolour = term->alt_save_truecolour; term->alt_save_truecolour = ttc; bt = term->save_utf; - if (!reset && !keep_cur_pos) + if (!reset) term->save_utf = term->alt_save_utf; term->alt_save_utf = bt; bt = term->save_wnext; - if (!reset && !keep_cur_pos) + if (!reset) term->save_wnext = term->alt_save_wnext; term->alt_save_wnext = bt; t = term->save_sco_acs; - if (!reset && !keep_cur_pos) + if (!reset) term->save_sco_acs = term->alt_save_sco_acs; term->alt_save_sco_acs = t; + + if (term->erase_to_scrollback && term->alt_screen && + term->alt_which && term->disptop < 0) { + /* + * Inverse of the adjustment at the top of this function. + * This time, we're swapping _to_ the alternate screen, so + * some lines are about to _appear_ in the virtual + * scrollback, and we adjust disptop in the other + * direction. + * + * Both these adjustments depend on the value stored in + * term->alt_sblines while the alt screen is selected, + * which is why we had to do one _before_ switching away + * from it and the other _after_ switching to it. + */ + term->disptop -= term->alt_sblines; + int limit = -sblines(term); + if (term->disptop < limit) + term->disptop = limit; + } } if (reset && term->screen) { @@ -3181,7 +3218,7 @@ static strbuf *term_input_data_from_unicode( int rv; rv = wc_to_mb(term->ucsdata->line_codepage, 0, widebuf, len, bufptr, len + 1, NULL, term->ucsdata); - buf->len = rv < 0 ? 0 : rv; + strbuf_shrink_to(buf, rv < 0 ? 0 : rv); } return buf; @@ -3791,7 +3828,7 @@ static void term_out(Terminal *term) break; case 'Z': /* DECID: terminal type query */ compatibility(VT100); - if (term->ldisc && term->id_string[0]) + if (term->ldisc) ldisc_send(term->ldisc, term->id_string, strlen(term->id_string), false); break; @@ -4123,7 +4160,7 @@ static void term_out(Terminal *term) case 'c': /* DA: terminal type query */ compatibility(VT100); /* This is the response for a VT102 */ - if (term->ldisc && term->id_string[0]) + if (term->ldisc) ldisc_send(term->ldisc, term->id_string, strlen(term->id_string), false); break; @@ -4597,7 +4634,6 @@ static void term_out(Terminal *term) len = strlen(p); ldisc_send(term->ldisc, "\033]L", 3, false); - if (len > 0) ldisc_send(term->ldisc, p, len, false); ldisc_send(term->ldisc, "\033\\", 2, false); @@ -4613,7 +4649,6 @@ static void term_out(Terminal *term) len = strlen(p); ldisc_send(term->ldisc, "\033]l", 3, false); - if (len > 0) ldisc_send(term->ldisc, p, len, false); ldisc_send(term->ldisc, "\033\\", 2, false); @@ -7810,7 +7845,6 @@ char *term_get_ttymode(Terminal *term, const char *mode) struct term_userpass_state { size_t curr_prompt; bool done_prompt; /* printed out prompt yet? */ - size_t pos; /* cursor position */ }; /* Tiny wrapper to make it easier to write lots of little strings */ @@ -7865,7 +7899,6 @@ int term_get_userpass_input(Terminal *term, prompts_t *p, bufchain *input) if (!s->done_prompt) { term_write(term, ptrlen_from_asciz(pr->prompt)); s->done_prompt = true; - s->pos = 0; } /* Breaking out here ensures that the prompt is printed even @@ -7880,8 +7913,6 @@ int term_get_userpass_input(Terminal *term, prompts_t *p, bufchain *input) case 10: case 13: term_write(term, PTRLEN_LITERAL("\r\n")); - prompt_ensure_result_size(pr, s->pos + 1); - pr->result[s->pos] = '\0'; /* go to next prompt, if any */ s->curr_prompt++; s->done_prompt = false; @@ -7889,18 +7920,18 @@ int term_get_userpass_input(Terminal *term, prompts_t *p, bufchain *input) break; case 8: case 127: - if (s->pos > 0) { + if (pr->result->len > 0) { if (pr->echo) term_write(term, PTRLEN_LITERAL("\b \b")); - s->pos--; + strbuf_shrink_by(pr->result, 1); } break; case 21: case 27: - while (s->pos > 0) { + while (pr->result->len > 0) { if (pr->echo) term_write(term, PTRLEN_LITERAL("\b \b")); - s->pos--; + strbuf_shrink_by(pr->result, 1); } break; case 3: @@ -7918,8 +7949,7 @@ int term_get_userpass_input(Terminal *term, prompts_t *p, bufchain *input) */ if (!pr->echo || (c >= ' ' && c <= '~') || ((unsigned char) c >= 160)) { - prompt_ensure_result_size(pr, s->pos + 1); - pr->result[s->pos++] = c; + put_byte(pr->result, c); if (pr->echo) term_write(term, make_ptrlen(&c, 1)); } diff --git a/0.73_My_PuTTY/terminal.h b/0.74_My_PuTTY/terminal.h similarity index 100% rename from 0.73_My_PuTTY/terminal.h rename to 0.74_My_PuTTY/terminal.h diff --git a/0.73_My_PuTTY/testback.c b/0.74_My_PuTTY/testback.c similarity index 100% rename from 0.73_My_PuTTY/testback.c rename to 0.74_My_PuTTY/testback.c diff --git a/0.73_My_PuTTY/testcrypt.c b/0.74_My_PuTTY/testcrypt.c similarity index 93% rename from 0.73_My_PuTTY/testcrypt.c rename to 0.74_My_PuTTY/testcrypt.c index c855313..e23a121 100644 --- a/0.73_My_PuTTY/testcrypt.c +++ b/0.74_My_PuTTY/testcrypt.c @@ -35,7 +35,7 @@ #include "mpint.h" #include "ecc.h" -static NORETURN void fatal_error(const char *p, ...) +static NORETURN PRINTF_LIKE(1, 2) void fatal_error(const char *p, ...) { va_list ap; fprintf(stderr, "testcrypt: "); @@ -89,7 +89,7 @@ enum ValueType { #define VALTYPE_ENUM(n,t,f) VT_##n, VALUE_TYPES(VALTYPE_ENUM) #undef VALTYPE_ENUM -}; +}; typedef enum ValueType ValueType; @@ -495,14 +495,19 @@ static void return_boolean(strbuf *out, bool b) strbuf_catf(out, "%s\n", b ? "true" : "false"); } -static void return_val_string_asciz(strbuf *out, char *s) +static void return_val_string_asciz_const(strbuf *out, const char *s) { strbuf *sb = strbuf_new(); put_data(sb, s, strlen(s)); - sfree(s); return_val_string(out, sb); } +static void return_val_string_asciz(strbuf *out, char *s) +{ + return_val_string_asciz_const(out, s); + sfree(s); +} + #define NULLABLE_RETURN_WRAPPER(type_name, c_type) \ static void return_opt_##type_name(strbuf *out, c_type ptr) \ { \ @@ -516,6 +521,7 @@ NULLABLE_RETURN_WRAPPER(val_string_asciz, char *) NULLABLE_RETURN_WRAPPER(val_cipher, ssh_cipher *) NULLABLE_RETURN_WRAPPER(val_hash, ssh_hash *) NULLABLE_RETURN_WRAPPER(val_key, ssh_key *) +NULLABLE_RETURN_WRAPPER(val_mpint, mp_int *) static void handle_hello(BinarySource *in, strbuf *out) { @@ -754,7 +760,7 @@ strbuf *rsa_ssh1_decrypt_pkcs1_wrapper(mp_int *input, RSAKey *key) /* Again, return "" on failure */ strbuf *sb = strbuf_new(); if (!rsa_ssh1_decrypt_pkcs1(input, key, sb)) - sb->len = 0; + strbuf_clear(sb); return sb; } #define rsa_ssh1_decrypt_pkcs1 rsa_ssh1_decrypt_pkcs1_wrapper @@ -995,30 +1001,28 @@ static void process_line(BinarySource *in, strbuf *out) { ptrlen id = get_word(in); -#define DISPATCH_COMMAND(cmd) \ - if (ptrlen_eq_string(id, #cmd)) { \ - handle_##cmd(in, out); \ - return; \ - } +#define DISPATCH_INTERNAL(cmdname, handler) do { \ + if (ptrlen_eq_string(id, cmdname)) { \ + handler(in, out); \ + return; \ + } \ + } while (0) + +#define DISPATCH_COMMAND(cmd) DISPATCH_INTERNAL(#cmd, handle_##cmd) DISPATCH_COMMAND(hello); DISPATCH_COMMAND(free); DISPATCH_COMMAND(newstring); DISPATCH_COMMAND(getstring); DISPATCH_COMMAND(mp_literal); DISPATCH_COMMAND(mp_dump); +#undef DISPATCH_COMMAND -#define FUNC(rettype, function, ...) \ - if (ptrlen_eq_string(id, #function)) { \ - handle_##function(in, out); \ - return; \ - } - -#define FUNC0 FUNC -#define FUNC1 FUNC -#define FUNC2 FUNC -#define FUNC3 FUNC -#define FUNC4 FUNC -#define FUNC5 FUNC +#define FUNC0(ret,func) DISPATCH_INTERNAL(#func, handle_##func); +#define FUNC1(ret,func,x) DISPATCH_INTERNAL(#func, handle_##func); +#define FUNC2(ret,func,x,y) DISPATCH_INTERNAL(#func, handle_##func); +#define FUNC3(ret,func,x,y,z) DISPATCH_INTERNAL(#func, handle_##func); +#define FUNC4(ret,func,x,y,z,v) DISPATCH_INTERNAL(#func, handle_##func); +#define FUNC5(ret,func,x,y,z,v,w) DISPATCH_INTERNAL(#func, handle_##func); #include "testcrypt.h" @@ -1029,6 +1033,8 @@ static void process_line(BinarySource *in, strbuf *out) #undef FUNC1 #undef FUNC0 +#undef DISPATCH_INTERNAL + fatal_error("command '%.*s': unrecognised", PTRLEN_PRINTF(id)); } @@ -1109,7 +1115,7 @@ int main(int argc, char **argv) for (size_t i = 0; i < sb->len; i++) if (sb->s[i] == '\n') lines++; - fprintf(outfp, "%zu\n%s", lines, sb->s); + fprintf(outfp, "%"SIZEu"\n%s", lines, sb->s); fflush(outfp); strbuf_free(sb); sfree(line); diff --git a/0.73_My_PuTTY/testcrypt.h b/0.74_My_PuTTY/testcrypt.h similarity index 95% rename from 0.73_My_PuTTY/testcrypt.h rename to 0.74_My_PuTTY/testcrypt.h index 40b69b0..a54bcfb 100644 --- a/0.73_My_PuTTY/testcrypt.h +++ b/0.74_My_PuTTY/testcrypt.h @@ -136,6 +136,7 @@ FUNC2(void, ssh2_mac_setkey, val_mac, val_string_ptrlen) FUNC1(void, ssh2_mac_start, val_mac) FUNC2(void, ssh2_mac_update, val_mac, val_string_ptrlen) FUNC1(val_string, ssh2_mac_genresult, val_mac) +FUNC1(val_string_asciz_const, ssh2_mac_text_name, val_mac) /* * The ssh_key abstraction. All the uses of BinarySink and @@ -187,12 +188,15 @@ FUNC2(void, ssh_ecdhkex_getpublic, val_ecdh, out_val_string_binarysink) FUNC2(val_mpint, ssh_ecdhkex_getkey, val_ecdh, val_string_ptrlen) /* - * RSA key exchange. + * RSA key exchange, and also the BinarySource get function + * get_ssh1_rsa_priv_agent, which is a convenient way to make an + * RSAKey for RSA kex testing purposes. */ FUNC1(val_rsakex, ssh_rsakex_newkey, val_string_ptrlen) FUNC1(uint, ssh_rsakex_klen, val_rsakex) FUNC3(val_string, ssh_rsakex_encrypt, val_rsakex, hashalg, val_string_ptrlen) -FUNC3(val_mpint, ssh_rsakex_decrypt, val_rsakex, hashalg, val_string_ptrlen) +FUNC3(opt_val_mpint, ssh_rsakex_decrypt, val_rsakex, hashalg, val_string_ptrlen) +FUNC1(val_rsakex, get_rsa_ssh1_priv_agent, val_string_binarysource) /* * Bare RSA keys as used in SSH-1. The construction API functions diff --git a/0.73_My_PuTTY/testsc.c b/0.74_My_PuTTY/testsc.c similarity index 95% rename from 0.73_My_PuTTY/testsc.c rename to 0.74_My_PuTTY/testsc.c index e51f19c..6df3562 100644 --- a/0.73_My_PuTTY/testsc.c +++ b/0.74_My_PuTTY/testsc.c @@ -81,7 +81,7 @@ #include "mpint.h" #include "ecc.h" -static NORETURN void fatal_error(const char *p, ...) +static NORETURN PRINTF_LIKE(1, 2) void fatal_error(const char *p, ...) { va_list ap; fprintf(stderr, "testsc: "); @@ -160,7 +160,7 @@ VOLATILE_WRAPPED_DEFN(, void, log_to_file, (const char *filename)) static const char *outdir = NULL; char *log_filename(const char *basename, size_t index) { - return dupprintf("%s/%s.%04zu", outdir, basename, index); + return dupprintf("%s/%s.%04"SIZEu, outdir, basename, index); } static char *last_filename; @@ -1454,6 +1454,15 @@ int main(int argc, char **argv) if (is_dry_run) { printf("Dry run (DynamoRIO instrumentation not detected)\n"); } else { + /* Print the address of main() in this run. The idea is that + * if this image is compiled to be position-independent, then + * PC values in the logs won't match the ones you get if you + * disassemble the binary, so it'll be harder to match up the + * log messages to the code. But if you know the address of a + * fixed (and not inlined) function in both worlds, you can + * find out the offset between them. */ + printf("Live run, main = %p\n", (void *)main); + if (!outdir) { fprintf(stderr, "expected -O option\n"); return 1; @@ -1565,7 +1574,7 @@ int main(int argc, char **argv) printf("All tests passed\n"); return 0; } else { - printf("%zu tests failed\n", nrun - npass); + printf("%"SIZEu" tests failed\n", nrun - npass); return 1; } } diff --git a/0.73_My_PuTTY/testzlib.c b/0.74_My_PuTTY/testzlib.c similarity index 100% rename from 0.73_My_PuTTY/testzlib.c rename to 0.74_My_PuTTY/testzlib.c diff --git a/0.73_My_PuTTY/time.c b/0.74_My_PuTTY/time.c similarity index 100% rename from 0.73_My_PuTTY/time.c rename to 0.74_My_PuTTY/time.c diff --git a/0.73_My_PuTTY/timing.c b/0.74_My_PuTTY/timing.c similarity index 100% rename from 0.73_My_PuTTY/timing.c rename to 0.74_My_PuTTY/timing.c diff --git a/0.73_My_PuTTY/tree234.c b/0.74_My_PuTTY/tree234.c similarity index 99% rename from 0.73_My_PuTTY/tree234.c rename to 0.74_My_PuTTY/tree234.c index 57866fd..08b8bb3 100644 --- a/0.73_My_PuTTY/tree234.c +++ b/0.74_My_PuTTY/tree234.c @@ -1072,7 +1072,7 @@ int n_errors = 0; /* * Error reporting function. */ -void error(char *fmt, ...) +PRINTF_LIKE(1, 2) void error(char *fmt, ...) { va_list ap; printf("ERROR: "); diff --git a/0.73_My_PuTTY/tree234.h b/0.74_My_PuTTY/tree234.h similarity index 100% rename from 0.73_My_PuTTY/tree234.h rename to 0.74_My_PuTTY/tree234.h diff --git a/0.73_My_PuTTY/unix/unix.h b/0.74_My_PuTTY/unix/unix.h similarity index 94% rename from 0.73_My_PuTTY/unix/unix.h rename to 0.74_My_PuTTY/unix/unix.h index c47ab2d..b44604a 100644 --- a/0.73_My_PuTTY/unix/unix.h +++ b/0.74_My_PuTTY/unix/unix.h @@ -5,10 +5,10 @@ # include "uxconfig.h" /* Space to hide it from mkfiles.pl */ #endif -#include /* for FILENAME_MAX */ -#include /* C99 int types */ +#include /* for FILENAME_MAX */ +#include /* C99 int types */ #ifndef NO_LIBDL -#include /* Dynamic library loading */ +#include /* Dynamic library loading */ #endif /* NO_LIBDL */ #include "charset.h" #include /* for mode_t */ @@ -95,8 +95,8 @@ extern const struct BackendVtable pty_backend; /* Simple wraparound timer function */ unsigned long getticks(void); #define GETTICKCOUNT getticks -#define TICKSPERSEC 1000 /* we choose to use milliseconds */ -#define CURSORBLINK 450 /* no standard way to set this */ +#define TICKSPERSEC 1000 /* we choose to use milliseconds */ +#define CURSORBLINK 450 /* no standard way to set this */ #define WCHAR wchar_t #define BYTE unsigned char @@ -341,7 +341,7 @@ void gtk_setup_config_box( * from the command line or config files is assumed to be encoded). */ #define DEFAULT_CODEPAGE 0xFFFF -#define CP_UTF8 CS_UTF8 /* from libcharset */ +#define CP_UTF8 CS_UTF8 /* from libcharset */ #define strnicmp strncasecmp #define stricmp strcasecmp diff --git a/0.73_My_PuTTY/utils.c b/0.74_My_PuTTY/utils.c similarity index 93% rename from 0.73_My_PuTTY/utils.c rename to 0.74_My_PuTTY/utils.c index 4b06eee..6343c0d 100644 --- a/0.73_My_PuTTY/utils.c +++ b/0.74_My_PuTTY/utils.c @@ -183,9 +183,19 @@ int main(void) return fails != 0 ? 1 : 0; } /* Stubs to stop the rest of this module causing compile failures. */ -void modalfatalbox(const char *fmt, ...) {} -int conf_get_int(Conf *conf, int primary) { return 0; } -char *conf_get_str(Conf *conf, int primary) { return NULL; } +static NORETURN void fatal_error(const char *p, ...) +{ + va_list ap; + fprintf(stderr, "host_string_test: "); + va_start(ap, p); + vfprintf(stderr, p, ap); + va_end(ap); + fputc('\n', stderr); + exit(1); +} + +void out_of_memory(void) { fatal_error("out of memory"); } + #endif /* TEST_HOST_STRFOO */ /* @@ -249,7 +259,7 @@ char *dupstr(const char *s) } /* Allocate the concatenation of N strings. Terminate arg list with NULL. */ -char *dupcat(const char *s1, ...) +char *dupcat_fn(const char *s1, ...) { int len; char *p, *q, *sn; @@ -418,6 +428,29 @@ void *strbuf_append(strbuf *buf_o, size_t len) return toret; } +void strbuf_shrink_to(strbuf *buf, size_t new_len) +{ + assert(new_len <= buf->len); + buf->len = new_len; + buf->s[buf->len] = '\0'; +} + +void strbuf_shrink_by(strbuf *buf, size_t amount_to_remove) +{ + assert(amount_to_remove <= buf->len); + buf->len -= amount_to_remove; + buf->s[buf->len] = '\0'; +} + +bool strbuf_chomp(strbuf *buf, char char_to_remove) +{ + if (buf->len > 0 && buf->s[buf->len-1] == char_to_remove) { + strbuf_shrink_by(buf, 1); + return true; + } + return false; +} + static void strbuf_BinarySink_write( BinarySink *bs, const void *data, size_t len) { diff --git a/0.73_My_PuTTY/version.c b/0.74_My_PuTTY/version.c similarity index 100% rename from 0.73_My_PuTTY/version.c rename to 0.74_My_PuTTY/version.c diff --git a/0.74_My_PuTTY/version.h b/0.74_My_PuTTY/version.h new file mode 100644 index 0000000..c758f5c --- /dev/null +++ b/0.74_My_PuTTY/version.h @@ -0,0 +1,5 @@ +#define RELEASE 0.74 +#define TEXTVER "Release 0.74" +#define SSHVER "-Release-0.74" +#define BINARY_VERSION 0,74,0,1 +#define SOURCE_COMMIT "unavailable" diff --git a/0.73_My_PuTTY/wcwidth.c b/0.74_My_PuTTY/wcwidth.c similarity index 100% rename from 0.73_My_PuTTY/wcwidth.c rename to 0.74_My_PuTTY/wcwidth.c diff --git a/0.73_My_PuTTY/wildcard.c b/0.74_My_PuTTY/wildcard.c similarity index 100% rename from 0.73_My_PuTTY/wildcard.c rename to 0.74_My_PuTTY/wildcard.c diff --git a/0.73_My_PuTTY/windows/MAKEFILE.MINGW b/0.74_My_PuTTY/windows/MAKEFILE.MINGW similarity index 98% rename from 0.73_My_PuTTY/windows/MAKEFILE.MINGW rename to 0.74_My_PuTTY/windows/MAKEFILE.MINGW index 968c62d..16cc0f3 100644 --- a/0.73_My_PuTTY/windows/MAKEFILE.MINGW +++ b/0.74_My_PuTTY/windows/MAKEFILE.MINGW @@ -130,7 +130,7 @@ UTF8MOUSE_OBJS = fromucs.o slookup.o sbcsdat.o sbcs.o utf8.o ################################################################################ CFLAGS += -DWINVER=0x0501 -D_WIN32_WINDOWS=0x0410 -D_WIN32_WINNT=0x0501 -# -DMOD_PERSO -DMOD_BACKGROUNDIMAGE -DMOD_RECONNECT -DMOD_HYPERLINK -DMOD_ZMODEM -DMOD_STARTBUTTON -DMOD_LAUNCHER -DMOD_SAVEDUMP -DMOD_KEYMAPPING -DMOD_WINCRYPT -DMOD_TUTTY -DMOD_PORTKNOCKING -DMOD_PUTTYX -DMOD_WTS -DMOD_PRINTCLIP -DMOD_RUTTY -DMOD_ADB \ +# -DMOD_PERSO -DMOD_BACKGROUNDIMAGE -DMOD_RECONNECT -DMOD_HYPERLINK -DMOD_ZMODEM -DMOD_STARTBUTTON -DMOD_LAUNCHER -DMOD_SAVEDUMP -DMOD_KEYMAPPING -DMOD_TUTTY -DMOD_PORTKNOCKING -DMOD_PUTTYX -DMOD_WTS -DMOD_PRINTCLIP -DMOD_RUTTY -DMOD_ADB \ # Flag special FDJ @@ -171,10 +171,8 @@ CFLAGS += \ -I../macosx \ -I../../base64 \ -I../../bcrypt \ - -I../../md5 -I../../regex -I../../url -I../../wincrypt -I../../rutty + -I../../md5 -I../../regex -I../../url -I../../rutty -# -DHAS_WINX509 -DMOD_WINCRYPT \ - # Autre flag a gérer # Ajouter -DMOD_NOPASSWORD \ pour compiler une version sans possibilité de sauvegarder le mot de passe @@ -242,7 +240,7 @@ plink.exe: agentf.o aqsync.o \ winnpc.o winnps.o winpgntc.o winplink.o winproxy.o \ winsecur.o winser.o winshare.o winstore.o wintime.o winucs.o \ winx11.o x11fwd.o \ - wincrypto.o adb.o kitty_registry.o kitty_commun.o kitty_ssh.o kitty_store.o kitty_tools.o + adb.o kitty_registry.o kitty_commun.o kitty_ssh.o kitty_store.o kitty_tools.o $(CC) $(LDFLAGS) -o $@ -Wl,-Map,plink.map agentf.o aqsync.o \ be_all_s_plink.o \ be_misc.o callback.o cmdline.o conf.o cproxy.o \ @@ -266,7 +264,7 @@ plink.exe: agentf.o aqsync.o \ winmiscs.o winnet.o winnohlp.o winnoise.o winnojmp.o \ winnpc.o winnps.o winpgntc.o winplink.o winproxy.o \ winsecur.o winser.o winshare.o winstore.o wintime.o winucs.o \ - wincrypto.o adb.o kitty_registry.o kitty_commun.o kitty_ssh.o kitty_store.o kitty_tools.o \ + adb.o kitty_registry.o kitty_commun.o kitty_ssh.o kitty_store.o kitty_tools.o \ ../../base64/base64.a ../../bcrypt/bcrypt.a ../../mini/mini.a \ winx11.o x11fwd.o -ladvapi32 -lcomdlg32 -lgdi32 -limm32 \ -lole32 -lshell32 -luser32 \ @@ -292,7 +290,7 @@ pscp.exe: agentf.o aqsync.o be_misc.o be_ssh.o callback.o cmdline.o conf.o \ winmiscs.o winnet.o winnohlp.o winnoise.o winnojmp.o \ winnpc.o winnps.o winpgntc.o winproxy.o winsecur.o winsftp.o \ winshare.o winstore.o wintime.o winucs.o x11fwd.o \ - kitty_commun.o kitty_ssh.o kitty_tools.o wincrypto.o kitty_registry.o kitty_store.o + kitty_commun.o kitty_ssh.o kitty_tools.o kitty_registry.o kitty_store.o $(CC) $(LDFLAGS) -o $@ -Wl,-Map,pscp.map agentf.o aqsync.o be_misc.o \ be_ssh.o callback.o cmdline.o conf.o cproxy.o ecc.o \ errsock.o logging.o mainchan.o marshal.o memory.o misc.o \ @@ -314,7 +312,7 @@ pscp.exe: agentf.o aqsync.o be_misc.o be_ssh.o callback.o cmdline.o conf.o \ winmisc.o winmiscs.o winnet.o winnohlp.o winnoise.o \ winnojmp.o winnpc.o winnps.o winpgntc.o winproxy.o \ winsecur.o winsftp.o winshare.o winstore.o wintime.o \ - kitty_commun.o kitty_ssh.o kitty_tools.o wincrypto.o kitty_registry.o kitty_store.o \ + kitty_commun.o kitty_ssh.o kitty_tools.o kitty_registry.o kitty_store.o \ ../../base64/base64.a ../../bcrypt/bcrypt.a ../../mini/mini.a \ winucs.o x11fwd.o -ladvapi32 -lcomdlg32 -lgdi32 -limm32 \ -lole32 -lshell32 -luser32 \ @@ -339,7 +337,7 @@ psftp.exe: agentf.o aqsync.o be_misc.o be_ssh.o callback.o cmdline.o conf.o \ winmiscs.o winnet.o winnohlp.o winnoise.o winnojmp.o \ winnpc.o winnps.o winpgntc.o winproxy.o winsecur.o winsftp.o \ winshare.o winstore.o wintime.o winucs.o x11fwd.o \ - kitty_commun.o kitty_ssh.o kitty_tools.o wincrypto.o kitty_registry.o kitty_store.o + kitty_commun.o kitty_ssh.o kitty_tools.o kitty_registry.o kitty_store.o $(CC) $(LDFLAGS) -o $@ -Wl,-Map,psftp.map agentf.o aqsync.o \ be_misc.o be_ssh.o callback.o cmdline.o conf.o cproxy.o \ ecc.o errsock.o logging.o mainchan.o marshal.o memory.o \ @@ -361,7 +359,7 @@ psftp.exe: agentf.o aqsync.o be_misc.o be_ssh.o callback.o cmdline.o conf.o \ winnpc.o winnps.o winpgntc.o winproxy.o winsecur.o winsftp.o \ winshare.o winstore.o wintime.o winucs.o x11fwd.o -ladvapi32 \ -lcomdlg32 -lgdi32 -limm32 -lole32 -lshell32 -luser32 \ - kitty_commun.o kitty_ssh.o kitty_tools.o wincrypto.o kitty_registry.o kitty_store.o \ + kitty_commun.o kitty_ssh.o kitty_tools.o kitty_registry.o kitty_store.o \ ../../base64/base64.a ../../bcrypt/bcrypt.a ../../mini/mini.a \ -lwsock32 @@ -389,7 +387,7 @@ putty.exe: agentf.o aqsync.o be_all_s.o be_misc.o callback.o cmdline.o \ adb.o \ kitty.o kitty_commun.o kitty_crypt.o kitty_image.o kitty_registry.o kitty_ssh.o \ kitty_store.o kitty_tools.o kitty_win.o \ - urlhack.o pageant_integrated.o wincrypto.o winpgnt_integrated.o winpgen_integrated.o winpzmodem.o \ + urlhack.o pageant_integrated.o winpgnt_integrated.o winpgen_integrated.o winpzmodem.o \ import.o sshrsag.o sshdssg.o sshprime.o sshecdsag.o sshbcrypt.o \ void.o $(UTF8MOUSE_OBJS) $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,putty.map agentf.o \ @@ -417,7 +415,7 @@ putty.exe: agentf.o aqsync.o be_all_s.o be_misc.o callback.o cmdline.o \ adb.o \ kitty.o kitty_commun.o kitty_crypt.o kitty_image.o kitty_registry.o kitty_ssh.o \ kitty_store.o kitty_tools.o kitty_win.o \ - urlhack.o pageant_integrated.o wincrypto.o winpgnt_integrated.o winpgen_integrated.o winpzmodem.o \ + urlhack.o pageant_integrated.o winpgnt_integrated.o winpgen_integrated.o winpzmodem.o \ import.o sshrsag.o sshdssg.o sshprime.o sshecdsag.o sshbcrypt.o \ void.o $(UTF8MOUSE_OBJS) \ ../../base64/base64.a ../../bcrypt/bcrypt.a ../../blocnote/notepad.a ../../jpeg/libjpeg.a \ @@ -436,7 +434,7 @@ puttygen.exe: conf.o ecc.o import.o marshal.o memory.o misc.o mpint.o \ stripctrl.o tree234.o utils.o version.o wcwidth.o winctrls.o \ winhelp.o winmisc.o winmiscs.o winnoise.o winnojmp.o \ winpgen.o winsecur.o winstore.o wintime.o winutils.o \ - wincrypto.o kitty_commun.o kitty_crypt.o kitty_registry.o kitty_keygen.o kitty_store.o kitty_tools.o + kitty_commun.o kitty_crypt.o kitty_registry.o kitty_keygen.o kitty_store.o kitty_tools.o $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,puttygen.map conf.o ecc.o \ import.o marshal.o memory.o misc.o mpint.o notiming.o \ puttygen.res.o sshaes.o sshauxcrypt.o sshbcrypt.o sshblowf.o \ @@ -446,7 +444,7 @@ puttygen.exe: conf.o ecc.o import.o marshal.o memory.o misc.o mpint.o \ tree234.o utils.o version.o wcwidth.o winctrls.o winhelp.o \ winmisc.o winmiscs.o winnoise.o winnojmp.o winpgen.o \ winsecur.o winstore.o wintime.o winutils.o \ - wincrypto.o kitty_commun.o kitty_crypt.o kitty_registry.o kitty_keygen.o kitty_store.o kitty_tools.o \ + kitty_commun.o kitty_crypt.o kitty_registry.o kitty_keygen.o kitty_store.o kitty_tools.o \ ../../bcrypt/bcrypt.a ../../mini/mini.a \ -ladvapi32 \ -lcomdlg32 -lgdi32 -limm32 -lole32 -lshell32 -luser32 @@ -595,10 +593,11 @@ errsock.o: ../errsock.c ../tree234.h ../putty.h ../network.h ../defs.h \ fromucs.o: ../charset/fromucs.c ../charset/charset.h ../charset/internal.h $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/fromucs.c -fuzzterm.o: ../fuzzterm.c ../putty.h ../terminal.h ../defs.h ../puttyps.h \ - ../network.h ../misc.h ../marshal.h ../sshsignals.h \ - ../tree234.h ../windows/winstuff.h ../unix/unix.h \ - ../puttymem.h ../windows/winhelp.h ../charset/charset.h +fuzzterm.o: ../fuzzterm.c ../putty.h ../dialog.h ../terminal.h ../defs.h \ + ../puttyps.h ../network.h ../misc.h ../marshal.h \ + ../sshsignals.h ../tree234.h ../windows/winstuff.h \ + ../unix/unix.h ../puttymem.h ../windows/winhelp.h \ + ../charset/charset.h $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../fuzzterm.c gtkapp.o: ../unix/gtkapp.c ../putty.h ../unix/gtkmisc.h ../defs.h \ @@ -1236,8 +1235,8 @@ sshrsag.o: ../sshrsag.c ../ssh.h ../mpint.h ../puttymem.h ../tree234.h \ $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshrsag.c sshserver.o: ../sshserver.c ../putty.h ../ssh.h ../sshbpp.h ../sshppl.h \ - ../sshserver.h ../sshgssc.h ../sshgss.h ../defs.h \ - ../puttyps.h ../network.h ../misc.h ../marshal.h \ + ../sshchan.h ../sshserver.h ../sshgssc.h ../sshgss.h \ + ../defs.h ../puttyps.h ../network.h ../misc.h ../marshal.h \ ../sshsignals.h ../puttymem.h ../tree234.h ../sshttymodes.h \ ../pgssapi.h ../windows/winstuff.h ../unix/unix.h \ ../windows/winhelp.h ../charset/charset.h @@ -1449,10 +1448,11 @@ uxpterm.o: ../unix/uxpterm.c ../putty.h ../defs.h ../puttyps.h ../network.h \ ../windows/winhelp.h ../charset/charset.h $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxpterm.c -uxpty.o: ../unix/uxpty.c ../putty.h ../ssh.h ../tree234.h ../sshttymodes.h \ - ../sshsignals.h ../defs.h ../puttyps.h ../network.h \ - ../misc.h ../marshal.h ../puttymem.h ../windows/winstuff.h \ - ../unix/unix.h ../windows/winhelp.h ../charset/charset.h +uxpty.o: ../unix/uxpty.c ../putty.h ../ssh.h ../sshserver.h ../tree234.h \ + ../sshttymodes.h ../sshsignals.h ../defs.h ../puttyps.h \ + ../network.h ../misc.h ../marshal.h ../puttymem.h \ + ../windows/winstuff.h ../unix/unix.h ../windows/winhelp.h \ + ../charset/charset.h $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxpty.c uxputty.o: ../unix/uxputty.c ../putty.h ../storage.h ../unix/gtkcompat.h \ @@ -1928,9 +1928,6 @@ urlhack_nohyperlink.o: ../../url/urlhack.c ../../url/urlhack.h \ void.o: ../../void.c $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) -c ../../void.c -wincrypto.o: ../../wincrypt/wincrypto.c ../../wincrypt/wincrypto.h - $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) -o wincrypto.o -c ../../wincrypt/wincrypto.c -I../../wincrypt - window_notrans.o: ../windows/window.c ../putty.h ../terminal.h ../storage.h \ ../windows/win_res.h ../windows/winsecur.h ../tree234.h \ ../defs.h ../puttyps.h ../network.h ../misc.h ../marshal.h \ diff --git a/0.73_My_PuTTY/windows/kitty.mft b/0.74_My_PuTTY/windows/kitty.mft similarity index 100% rename from 0.73_My_PuTTY/windows/kitty.mft rename to 0.74_My_PuTTY/windows/kitty.mft diff --git a/0.73_My_PuTTY/windows/pageant.ico b/0.74_My_PuTTY/windows/pageant.ico similarity index 100% rename from 0.73_My_PuTTY/windows/pageant.ico rename to 0.74_My_PuTTY/windows/pageant.ico diff --git a/0.73_My_PuTTY/windows/pageant.mft b/0.74_My_PuTTY/windows/pageant.mft similarity index 100% rename from 0.73_My_PuTTY/windows/pageant.mft rename to 0.74_My_PuTTY/windows/pageant.mft diff --git a/0.73_My_PuTTY/windows/pageant.rc b/0.74_My_PuTTY/windows/pageant.rc similarity index 100% rename from 0.73_My_PuTTY/windows/pageant.rc rename to 0.74_My_PuTTY/windows/pageant.rc diff --git a/0.73_My_PuTTY/windows/pageants.ico b/0.74_My_PuTTY/windows/pageants.ico similarity index 100% rename from 0.73_My_PuTTY/windows/pageants.ico rename to 0.74_My_PuTTY/windows/pageants.ico diff --git a/0.73_My_PuTTY/windows/plink.rc b/0.74_My_PuTTY/windows/plink.rc similarity index 100% rename from 0.73_My_PuTTY/windows/plink.rc rename to 0.74_My_PuTTY/windows/plink.rc diff --git a/0.73_My_PuTTY/windows/pscp.ico b/0.74_My_PuTTY/windows/pscp.ico similarity index 100% rename from 0.73_My_PuTTY/windows/pscp.ico rename to 0.74_My_PuTTY/windows/pscp.ico diff --git a/0.73_My_PuTTY/windows/pscp.rc b/0.74_My_PuTTY/windows/pscp.rc similarity index 100% rename from 0.73_My_PuTTY/windows/pscp.rc rename to 0.74_My_PuTTY/windows/pscp.rc diff --git a/0.73_My_PuTTY/windows/psftp.rc b/0.74_My_PuTTY/windows/psftp.rc similarity index 100% rename from 0.73_My_PuTTY/windows/psftp.rc rename to 0.74_My_PuTTY/windows/psftp.rc diff --git a/0.73_My_PuTTY/windows/putty.ico b/0.74_My_PuTTY/windows/putty.ico similarity index 100% rename from 0.73_My_PuTTY/windows/putty.ico rename to 0.74_My_PuTTY/windows/putty.ico diff --git a/0.73_My_PuTTY/windows/putty.mft b/0.74_My_PuTTY/windows/putty.mft similarity index 100% rename from 0.73_My_PuTTY/windows/putty.mft rename to 0.74_My_PuTTY/windows/putty.mft diff --git a/0.73_My_PuTTY/windows/putty.rc b/0.74_My_PuTTY/windows/putty.rc similarity index 100% rename from 0.73_My_PuTTY/windows/putty.rc rename to 0.74_My_PuTTY/windows/putty.rc diff --git a/0.73_My_PuTTY/windows/puttycfg.ico b/0.74_My_PuTTY/windows/puttycfg.ico similarity index 100% rename from 0.73_My_PuTTY/windows/puttycfg.ico rename to 0.74_My_PuTTY/windows/puttycfg.ico diff --git a/0.73_My_PuTTY/windows/puttygen.ico b/0.74_My_PuTTY/windows/puttygen.ico similarity index 100% rename from 0.73_My_PuTTY/windows/puttygen.ico rename to 0.74_My_PuTTY/windows/puttygen.ico diff --git a/0.73_My_PuTTY/windows/puttygen.mft b/0.74_My_PuTTY/windows/puttygen.mft similarity index 100% rename from 0.73_My_PuTTY/windows/puttygen.mft rename to 0.74_My_PuTTY/windows/puttygen.mft diff --git a/0.73_My_PuTTY/windows/puttygen.rc b/0.74_My_PuTTY/windows/puttygen.rc similarity index 100% rename from 0.73_My_PuTTY/windows/puttygen.rc rename to 0.74_My_PuTTY/windows/puttygen.rc diff --git a/0.73_My_PuTTY/windows/puttyins.ico b/0.74_My_PuTTY/windows/puttyins.ico similarity index 100% rename from 0.73_My_PuTTY/windows/puttyins.ico rename to 0.74_My_PuTTY/windows/puttyins.ico diff --git a/0.73_My_PuTTY/windows/puttytel.rc b/0.74_My_PuTTY/windows/puttytel.rc similarity index 100% rename from 0.73_My_PuTTY/windows/puttytel.rc rename to 0.74_My_PuTTY/windows/puttytel.rc diff --git a/0.73_My_PuTTY/windows/rcstuff.h b/0.74_My_PuTTY/windows/rcstuff.h similarity index 100% rename from 0.73_My_PuTTY/windows/rcstuff.h rename to 0.74_My_PuTTY/windows/rcstuff.h diff --git a/0.73_My_PuTTY/windows/sizetip.c b/0.74_My_PuTTY/windows/sizetip.c similarity index 100% rename from 0.73_My_PuTTY/windows/sizetip.c rename to 0.74_My_PuTTY/windows/sizetip.c diff --git a/0.73_My_PuTTY/windows/version.rc2 b/0.74_My_PuTTY/windows/version.rc2 similarity index 100% rename from 0.73_My_PuTTY/windows/version.rc2 rename to 0.74_My_PuTTY/windows/version.rc2 diff --git a/0.74_My_PuTTY/windows/version_major.txt b/0.74_My_PuTTY/windows/version_major.txt new file mode 100644 index 0000000..bdc2d0a --- /dev/null +++ b/0.74_My_PuTTY/windows/version_major.txt @@ -0,0 +1 @@ +"0.74.0" diff --git a/0.74_My_PuTTY/windows/version_minor.txt b/0.74_My_PuTTY/windows/version_minor.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/0.74_My_PuTTY/windows/version_minor.txt @@ -0,0 +1 @@ +1 diff --git a/0.73_My_PuTTY/windows/win_res.h b/0.74_My_PuTTY/windows/win_res.h similarity index 100% rename from 0.73_My_PuTTY/windows/win_res.h rename to 0.74_My_PuTTY/windows/win_res.h diff --git a/0.73_My_PuTTY/windows/win_res.rc2 b/0.74_My_PuTTY/windows/win_res.rc2 similarity index 100% rename from 0.73_My_PuTTY/windows/win_res.rc2 rename to 0.74_My_PuTTY/windows/win_res.rc2 diff --git a/0.73_My_PuTTY/windows/wincapi.c b/0.74_My_PuTTY/windows/wincapi.c similarity index 100% rename from 0.73_My_PuTTY/windows/wincapi.c rename to 0.74_My_PuTTY/windows/wincapi.c diff --git a/0.73_My_PuTTY/windows/wincapi.h b/0.74_My_PuTTY/windows/wincapi.h similarity index 100% rename from 0.73_My_PuTTY/windows/wincapi.h rename to 0.74_My_PuTTY/windows/wincapi.h diff --git a/0.73_My_PuTTY/windows/wincfg.c b/0.74_My_PuTTY/windows/wincfg.c similarity index 100% rename from 0.73_My_PuTTY/windows/wincfg.c rename to 0.74_My_PuTTY/windows/wincfg.c diff --git a/0.73_My_PuTTY/windows/wincons.c b/0.74_My_PuTTY/windows/wincons.c similarity index 92% rename from 0.73_My_PuTTY/windows/wincons.c rename to 0.74_My_PuTTY/windows/wincons.c index c77d4ec..a33d0ae 100644 --- a/0.73_My_PuTTY/windows/wincons.c +++ b/0.74_My_PuTTY/windows/wincons.c @@ -505,7 +505,6 @@ int console_get_userpass_input(prompts_t *p) for (curr_prompt = 0; curr_prompt < p->n_prompts; curr_prompt++) { DWORD savemode, newmode; - size_t len; prompt_t *pr = p->prompts[curr_prompt]; GetConsoleMode(hin, &savemode); @@ -518,22 +517,38 @@ int console_get_userpass_input(prompts_t *p) console_write(hout, ptrlen_from_asciz(pr->prompt)); - len = 0; + bool failed = false; while (1) { + /* + * Amount of data to try to read from the console in one + * go. This isn't completely arbitrary: a user reported + * that trying to read more than 31366 bytes at a time + * would fail with ERROR_NOT_ENOUGH_MEMORY on Windows 7, + * and Ruby's Win32 support module has evidence of a + * similar workaround: + * + * https://github.com/ruby/ruby/blob/0aa5195262d4193d3accf3e6b9bad236238b816b/win32/win32.c#L6842 + * + * To keep things simple, I stick with a nice round power + * of 2 rather than trying to go to the very limit of that + * bug. (We're typically reading user passphrases and the + * like here, so even this much is overkill really.) + */ + DWORD toread = 16384; + + size_t prev_result_len = pr->result->len; + void *ptr = strbuf_append(pr->result, toread); + DWORD ret = 0; - prompt_ensure_result_size(pr, len * 5 / 4 + 512); - - if (!ReadFile(hin, pr->result + len, pr->resultsize - len - 1, - &ret, NULL) || ret == 0) { - len = (size_t)-1; + if (!ReadFile(hin, ptr, toread, &ret, NULL) || ret == 0) { + failed = true; break; } - len += ret; - if (pr->result[len - 1] == '\n') { - len--; - if (pr->result[len - 1] == '\r') - len--; + + strbuf_shrink_to(pr->result, prev_result_len + ret); + if (strbuf_chomp(pr->result, '\n')) { + strbuf_chomp(pr->result, '\r'); break; } } @@ -543,11 +558,10 @@ int console_get_userpass_input(prompts_t *p) if (!pr->echo) console_write(hout, PTRLEN_LITERAL("\r\n")); - if (len == (size_t)-1) { + if (failed) { return 0; /* failure due to read error */ } - pr->result[len] = '\0'; } return 1; /* success */ diff --git a/0.73_My_PuTTY/windows/winctrls.c b/0.74_My_PuTTY/windows/winctrls.c similarity index 100% rename from 0.73_My_PuTTY/windows/winctrls.c rename to 0.74_My_PuTTY/windows/winctrls.c diff --git a/0.73_My_PuTTY/windows/windefs.c b/0.74_My_PuTTY/windows/windefs.c similarity index 100% rename from 0.73_My_PuTTY/windows/windefs.c rename to 0.74_My_PuTTY/windows/windefs.c diff --git a/0.73_My_PuTTY/windows/windlg.c b/0.74_My_PuTTY/windows/windlg.c similarity index 99% rename from 0.73_My_PuTTY/windows/windlg.c rename to 0.74_My_PuTTY/windows/windlg.c index 200d420..ab43795 100644 --- a/0.73_My_PuTTY/windows/windlg.c +++ b/0.74_My_PuTTY/windows/windlg.c @@ -1112,7 +1112,7 @@ static void win_gui_eventlog(LogPolicy *lp, const char *string) if (*location) sfree(*location); - *location = dupcat(timebuf, string, (const char *)NULL); + *location = dupcat(timebuf, string); if (logbox) { int count; SendDlgItemMessage(logbox, IDN_LIST, LB_ADDSTRING, diff --git a/0.73_My_PuTTY/windows/window.c b/0.74_My_PuTTY/windows/window.c similarity index 99% rename from 0.73_My_PuTTY/windows/window.c rename to 0.74_My_PuTTY/windows/window.c index 0f346fc..899a0a6 100644 --- a/0.73_My_PuTTY/windows/window.c +++ b/0.74_My_PuTTY/windows/window.c @@ -93,6 +93,7 @@ #endif static Mouse_Button translate_button(Mouse_Button button); +static void show_mouseptr(bool show); static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, unsigned char *output); @@ -1879,10 +1880,10 @@ void cleanup_exit(int code) /* * Set up, or shut down, an AsyncSelect. Called from winnet.c. */ -char *do_select(SOCKET skt, bool startup) +char *do_select(SOCKET skt, bool enable) { int msg, events; - if (startup) { + if (enable) { msg = WM_NETEVENT; events = (FD_CONNECT | FD_READ | FD_WRITE | FD_OOB | FD_CLOSE | FD_ACCEPT); @@ -2080,6 +2081,7 @@ static void win_seat_connection_fatal(Seat *seat, const char *msg) ReadInitScript(NULL); char *title = dupprintf("%s Fatal Error: %s", appname,msg); + show_mouseptr(true); lp_eventlog(default_logpolicy, title);//MessageBox(hwnd, msg, title, MB_ICONERROR | MB_OK); sfree(title); @@ -2091,6 +2093,7 @@ static void win_seat_connection_fatal(Seat *seat, const char *msg) } } else { char *title = dupprintf("%s Fatal Error", appname); + show_mouseptr(true); MessageBox(hwnd, msg, title, MB_ICONERROR | MB_OK); sfree(title); @@ -2103,6 +2106,7 @@ static void win_seat_connection_fatal(Seat *seat, const char *msg) } #else char *title = dupprintf("%s Fatal Error", appname); + show_mouseptr(true); MessageBox(hwnd, msg, title, MB_ICONERROR | MB_OK); sfree(title); @@ -3182,12 +3186,15 @@ static void win_seat_notify_remote_exit(Seat *seat) queue_toplevel_callback(close_session, NULL); session_closed = true; ReadInitScript(NULL); + show_mouseptr(true); lp_eventlog(default_logpolicy, "Connection closed by remote host"); } else #endif - if (exitcode != INT_MAX) + if (exitcode != INT_MAX) { + show_mouseptr(true); MessageBox(hwnd, "Connection closed by remote host", appname, MB_OK | MB_ICONINFORMATION); + } } } } @@ -3813,8 +3820,8 @@ free(cmd); for (i = 0; i < lenof(popup_menus); i++) EnableMenuItem(popup_menus[i].menu, IDM_FULLSCREEN, MF_BYCOMMAND | - (resize_action == RESIZE_DISABLED) - ? MF_GRAYED : MF_ENABLED); + (resize_action == RESIZE_DISABLED + ? MF_GRAYED : MF_ENABLED)); /* Gracefully unzoom if necessary */ if (IsZoomed(hwnd) && (resize_action == RESIZE_DISABLED)) ShowWindow(hwnd, SW_RESTORE); @@ -7826,7 +7833,7 @@ static void wintw_clip_write( (int)udata[uindex]); alen = 1; strcpy(after, "}"); } else { - blen = sprintf(before, "\\u%d", udata[uindex]); + blen = sprintf(before, "\\u%d", (int)udata[uindex]); alen = 0; after[0] = '\0'; } } @@ -7997,6 +8004,7 @@ void modalfatalbox(const char *fmt, ...) va_start(ap, fmt); message = dupvprintf(fmt, ap); va_end(ap); + show_mouseptr(true); title = dupprintf("%s Fatal Error", appname); MessageBox(hwnd, message, title, MB_SYSTEMMODAL | MB_ICONERROR | MB_OK); sfree(message); @@ -8015,6 +8023,7 @@ void nonfatal(const char *fmt, ...) va_start(ap, fmt); message = dupvprintf(fmt, ap); va_end(ap); + show_mouseptr(true); title = dupprintf("%s Error", appname); MessageBox(hwnd, message, title, MB_ICONERROR | MB_OK); sfree(message); @@ -8146,6 +8155,7 @@ static void wintw_bell(TermWin *tw, int mode) if (!p_PlaySound || !p_PlaySound(bell_wavefile->path, NULL, SND_ASYNC | SND_FILENAME)) { char *buf, *otherbuf; + show_mouseptr(true); buf = dupprintf( "Unable to play sound file\n%s\nUsing default sound instead", bell_wavefile->path); diff --git a/0.73_My_PuTTY/windows/wingss.c b/0.74_My_PuTTY/windows/wingss.c similarity index 69% rename from 0.73_My_PuTTY/windows/wingss.c rename to 0.74_My_PuTTY/windows/wingss.c index d4d5d88..cd62ff3 100644 --- a/0.73_My_PuTTY/windows/wingss.c +++ b/0.74_My_PuTTY/windows/wingss.c @@ -1,660 +1,660 @@ -#ifndef NO_GSSAPI - -#include -#include "putty.h" - -#define SECURITY_WIN32 -#include - -#include "pgssapi.h" -#include "sshgss.h" -#include "sshgssc.h" - -#include "misc.h" - -#define UNIX_EPOCH 11644473600ULL /* Seconds from Windows epoch */ -#define CNS_PERSEC 10000000ULL /* # 100ns per second */ - -/* - * Note, as a special case, 0 relative to the Windows epoch (unspecified) maps - * to 0 relative to the POSIX epoch (unspecified)! - */ -#define TIME_WIN_TO_POSIX(ft, t) do { \ - ULARGE_INTEGER uli; \ - uli.LowPart = (ft).dwLowDateTime; \ - uli.HighPart = (ft).dwHighDateTime; \ - if (uli.QuadPart != 0) \ - uli.QuadPart = uli.QuadPart / CNS_PERSEC - UNIX_EPOCH; \ - (t) = (time_t) uli.QuadPart; \ -} while(0) - -/* Windows code to set up the GSSAPI library list. */ - -#ifdef _WIN64 -#define MIT_KERB_SUFFIX "64" -#else -#define MIT_KERB_SUFFIX "32" -#endif - -const int ngsslibs = 3; -const char *const gsslibnames[3] = { - "MIT Kerberos GSSAPI"MIT_KERB_SUFFIX".DLL", - "Microsoft SSPI SECUR32.DLL", - "User-specified GSSAPI DLL", -}; -const struct keyvalwhere gsslibkeywords[] = { - { "gssapi32", 0, -1, -1 }, - { "sspi", 1, -1, -1 }, - { "custom", 2, -1, -1 }, -}; - -DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, - AcquireCredentialsHandleA, - (SEC_CHAR *, SEC_CHAR *, ULONG, PVOID, - PVOID, SEC_GET_KEY_FN, PVOID, PCredHandle, PTimeStamp)); -DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, - InitializeSecurityContextA, - (PCredHandle, PCtxtHandle, SEC_CHAR *, ULONG, ULONG, - ULONG, PSecBufferDesc, ULONG, PCtxtHandle, - PSecBufferDesc, PULONG, PTimeStamp)); -DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, - FreeContextBuffer, - (PVOID)); -DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, - FreeCredentialsHandle, - (PCredHandle)); -DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, - DeleteSecurityContext, - (PCtxtHandle)); -DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, - QueryContextAttributesA, - (PCtxtHandle, ULONG, PVOID)); -DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, - MakeSignature, - (PCtxtHandle, ULONG, PSecBufferDesc, ULONG)); -DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, - VerifySignature, - (PCtxtHandle, PSecBufferDesc, ULONG, PULONG)); -DECL_WINDOWS_FUNCTION(static, DLL_DIRECTORY_COOKIE, - AddDllDirectory, - (PCWSTR)); - -typedef struct winSsh_gss_ctx { - unsigned long maj_stat; - unsigned long min_stat; - CredHandle cred_handle; - CtxtHandle context; - PCtxtHandle context_handle; - TimeStamp expiry; -} winSsh_gss_ctx; - - -const Ssh_gss_buf gss_mech_krb5={9,"\x2A\x86\x48\x86\xF7\x12\x01\x02\x02"}; - -const char *gsslogmsg = NULL; - -static void ssh_sspi_bind_fns(struct ssh_gss_library *lib); - -struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) -{ - HMODULE module; - HKEY regkey; - struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist); - char *path; - static HMODULE kernel32_module; - if (!kernel32_module) { - kernel32_module = load_system32_dll("kernel32.dll"); - } -#if defined _MSC_VER && _MSC_VER < 1900 - /* Omit the type-check because older MSVCs don't have this function */ - GET_WINDOWS_FUNCTION_NO_TYPECHECK(kernel32_module, AddDllDirectory); -#else - GET_WINDOWS_FUNCTION(kernel32_module, AddDllDirectory); -#endif - - list->libraries = snewn(3, struct ssh_gss_library); - list->nlibraries = 0; - - /* MIT Kerberos GSSAPI implementation */ - module = NULL; - if (RegOpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\MIT\\Kerberos", ®key) - == ERROR_SUCCESS) { - DWORD type, size; - LONG ret; - char *buffer; - - /* Find out the string length */ - ret = RegQueryValueEx(regkey, "InstallDir", NULL, &type, NULL, &size); - - if (ret == ERROR_SUCCESS && type == REG_SZ) { - buffer = snewn(size + 20, char); - ret = RegQueryValueEx(regkey, "InstallDir", NULL, - &type, (LPBYTE)buffer, &size); - if (ret == ERROR_SUCCESS && type == REG_SZ) { - strcat (buffer, "\\bin"); - if(p_AddDllDirectory) { - /* Add MIT Kerberos' path to the DLL search path, - * it loads its own DLLs further down the road */ - wchar_t *dllPath = - dup_mb_to_wc(DEFAULT_CODEPAGE, 0, buffer); - p_AddDllDirectory(dllPath); - sfree(dllPath); - } - strcat (buffer, "\\gssapi"MIT_KERB_SUFFIX".dll"); - module = LoadLibraryEx (buffer, NULL, - LOAD_LIBRARY_SEARCH_SYSTEM32 | - LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | - LOAD_LIBRARY_SEARCH_USER_DIRS); - } - sfree(buffer); - } - RegCloseKey(regkey); - } - if (module) { - struct ssh_gss_library *lib = - &list->libraries[list->nlibraries++]; - - lib->id = 0; - lib->gsslogmsg = "Using GSSAPI from GSSAPI"MIT_KERB_SUFFIX".DLL"; - lib->handle = (void *)module; - -#define BIND_GSS_FN(name) \ - lib->u.gssapi.name = (t_gss_##name) GetProcAddress(module, "gss_" #name) - - BIND_GSS_FN(delete_sec_context); - BIND_GSS_FN(display_status); - BIND_GSS_FN(get_mic); - BIND_GSS_FN(verify_mic); - BIND_GSS_FN(import_name); - BIND_GSS_FN(init_sec_context); - BIND_GSS_FN(release_buffer); - BIND_GSS_FN(release_cred); - BIND_GSS_FN(release_name); - BIND_GSS_FN(acquire_cred); - BIND_GSS_FN(inquire_cred_by_mech); - -#undef BIND_GSS_FN - - ssh_gssapi_bind_fns(lib); - } - - /* Microsoft SSPI Implementation */ - module = load_system32_dll("secur32.dll"); - if (module) { - struct ssh_gss_library *lib = - &list->libraries[list->nlibraries++]; - - lib->id = 1; - lib->gsslogmsg = "Using SSPI from SECUR32.DLL"; - lib->handle = (void *)module; - - GET_WINDOWS_FUNCTION(module, AcquireCredentialsHandleA); - GET_WINDOWS_FUNCTION(module, InitializeSecurityContextA); - GET_WINDOWS_FUNCTION(module, FreeContextBuffer); - GET_WINDOWS_FUNCTION(module, FreeCredentialsHandle); - GET_WINDOWS_FUNCTION(module, DeleteSecurityContext); - GET_WINDOWS_FUNCTION(module, QueryContextAttributesA); - GET_WINDOWS_FUNCTION(module, MakeSignature); - GET_WINDOWS_FUNCTION(module, VerifySignature); - - ssh_sspi_bind_fns(lib); - } - - /* - * Custom GSSAPI DLL. - */ - module = NULL; - path = conf_get_filename(conf, CONF_ssh_gss_custom)->path; - if (*path) { - if(p_AddDllDirectory) { - /* Add the custom directory as well in case it chainloads - * some other DLLs (e.g a non-installed MIT Kerberos - * instance) */ - int pathlen = strlen(path); - - while (pathlen > 0 && path[pathlen-1] != ':' && - path[pathlen-1] != '\\') - pathlen--; - - if (pathlen > 0 && path[pathlen-1] != '\\') - pathlen--; - - if (pathlen > 0) { - char *dirpath = dupprintf("%.*s", pathlen, path); - wchar_t *dllPath = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, dirpath); - p_AddDllDirectory(dllPath); - sfree(dllPath); - sfree(dirpath); - } - } - - module = LoadLibraryEx(path, NULL, - LOAD_LIBRARY_SEARCH_SYSTEM32 | - LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | - LOAD_LIBRARY_SEARCH_USER_DIRS); - } - if (module) { - struct ssh_gss_library *lib = - &list->libraries[list->nlibraries++]; - - lib->id = 2; - lib->gsslogmsg = dupprintf("Using GSSAPI from user-specified" - " library '%s'", path); - lib->handle = (void *)module; - -#define BIND_GSS_FN(name) \ - lib->u.gssapi.name = (t_gss_##name) GetProcAddress(module, "gss_" #name) - - BIND_GSS_FN(delete_sec_context); - BIND_GSS_FN(display_status); - BIND_GSS_FN(get_mic); - BIND_GSS_FN(verify_mic); - BIND_GSS_FN(import_name); - BIND_GSS_FN(init_sec_context); - BIND_GSS_FN(release_buffer); - BIND_GSS_FN(release_cred); - BIND_GSS_FN(release_name); - BIND_GSS_FN(acquire_cred); - BIND_GSS_FN(inquire_cred_by_mech); - -#undef BIND_GSS_FN - - ssh_gssapi_bind_fns(lib); - } - - - return list; -} - -void ssh_gss_cleanup(struct ssh_gss_liblist *list) -{ - int i; - - /* - * LoadLibrary and FreeLibrary are defined to employ reference - * counting in the case where the same library is repeatedly - * loaded, so even in a multiple-sessions-per-process context - * (not that we currently expect ever to have such a thing on - * Windows) it's safe to naively FreeLibrary everything here - * without worrying about destroying it under the feet of - * another SSH instance still using it. - */ - for (i = 0; i < list->nlibraries; i++) { - FreeLibrary((HMODULE)list->libraries[i].handle); - if (list->libraries[i].id == 2) { - /* The 'custom' id involves a dynamically allocated message. - * Note that we must cast away the 'const' to free it. */ - sfree((char *)list->libraries[i].gsslogmsg); - } - } - sfree(list->libraries); - sfree(list); -} - -static Ssh_gss_stat ssh_sspi_indicate_mech(struct ssh_gss_library *lib, - Ssh_gss_buf *mech) -{ - *mech = gss_mech_krb5; - return SSH_GSS_OK; -} - - -static Ssh_gss_stat ssh_sspi_import_name(struct ssh_gss_library *lib, - char *host, Ssh_gss_name *srv_name) -{ - char *pStr; - - /* Check hostname */ - if (host == NULL) return SSH_GSS_FAILURE; - - /* copy it into form host/FQDN */ - pStr = dupcat("host/", host, NULL); - - *srv_name = (Ssh_gss_name) pStr; - - return SSH_GSS_OK; -} - -static Ssh_gss_stat ssh_sspi_acquire_cred(struct ssh_gss_library *lib, - Ssh_gss_ctx *ctx, - time_t *expiry) -{ - winSsh_gss_ctx *winctx = snew(winSsh_gss_ctx); - memset(winctx, 0, sizeof(winSsh_gss_ctx)); - - /* prepare our "wrapper" structure */ - winctx->maj_stat = winctx->min_stat = SEC_E_OK; - winctx->context_handle = NULL; - - /* Specifying no principal name here means use the credentials of - the current logged-in user */ - - winctx->maj_stat = p_AcquireCredentialsHandleA(NULL, - "Kerberos", - SECPKG_CRED_OUTBOUND, - NULL, - NULL, - NULL, - NULL, - &winctx->cred_handle, - NULL); - - if (winctx->maj_stat != SEC_E_OK) { - p_FreeCredentialsHandle(&winctx->cred_handle); - sfree(winctx); - return SSH_GSS_FAILURE; - } - - /* Windows does not return a valid expiration from AcquireCredentials */ - if (expiry) - *expiry = GSS_NO_EXPIRATION; - - *ctx = (Ssh_gss_ctx) winctx; - return SSH_GSS_OK; -} - -static void localexp_to_exp_lifetime(TimeStamp *localexp, - time_t *expiry, unsigned long *lifetime) -{ - FILETIME nowUTC; - FILETIME expUTC; - time_t now; - time_t exp; - time_t delta; - - if (!lifetime && !expiry) - return; - - GetSystemTimeAsFileTime(&nowUTC); - TIME_WIN_TO_POSIX(nowUTC, now); - - if (lifetime) - *lifetime = 0; - if (expiry) - *expiry = GSS_NO_EXPIRATION; - - /* - * Type oddity: localexp is a pointer to 'TimeStamp', whereas - * LocalFileTimeToFileTime expects a pointer to FILETIME. However, - * despite having different formal type names from the compiler's - * point of view, these two structures are specified to be - * isomorphic in the MS documentation, so it's legitimate to copy - * between them: - * - * https://msdn.microsoft.com/en-us/library/windows/desktop/aa380511(v=vs.85).aspx - */ - { - FILETIME localexp_ft; - enum { vorpal_sword = 1 / (sizeof(*localexp) == sizeof(localexp_ft)) }; - memcpy(&localexp_ft, localexp, sizeof(localexp_ft)); - if (!LocalFileTimeToFileTime(&localexp_ft, &expUTC)) - return; - } - - TIME_WIN_TO_POSIX(expUTC, exp); - delta = exp - now; - if (exp == 0 || delta <= 0) - return; - - if (expiry) - *expiry = exp; - if (lifetime) { - if (delta <= ULONG_MAX) - *lifetime = (unsigned long)delta; - else - *lifetime = ULONG_MAX; - } -} - -static Ssh_gss_stat ssh_sspi_init_sec_context(struct ssh_gss_library *lib, - Ssh_gss_ctx *ctx, - Ssh_gss_name srv_name, - int to_deleg, - Ssh_gss_buf *recv_tok, - Ssh_gss_buf *send_tok, - time_t *expiry, - unsigned long *lifetime) -{ - winSsh_gss_ctx *winctx = (winSsh_gss_ctx *) *ctx; - SecBuffer wsend_tok = {send_tok->length,SECBUFFER_TOKEN,send_tok->value}; - SecBuffer wrecv_tok = {recv_tok->length,SECBUFFER_TOKEN,recv_tok->value}; - SecBufferDesc output_desc={SECBUFFER_VERSION,1,&wsend_tok}; - SecBufferDesc input_desc ={SECBUFFER_VERSION,1,&wrecv_tok}; - unsigned long flags=ISC_REQ_MUTUAL_AUTH|ISC_REQ_REPLAY_DETECT| - ISC_REQ_CONFIDENTIALITY|ISC_REQ_ALLOCATE_MEMORY; - unsigned long ret_flags=0; - TimeStamp localexp; - - /* check if we have to delegate ... */ - if (to_deleg) flags |= ISC_REQ_DELEGATE; - winctx->maj_stat = p_InitializeSecurityContextA(&winctx->cred_handle, - winctx->context_handle, - (char*) srv_name, - flags, - 0, /* reserved */ - SECURITY_NATIVE_DREP, - &input_desc, - 0, /* reserved */ - &winctx->context, - &output_desc, - &ret_flags, - &localexp); - - localexp_to_exp_lifetime(&localexp, expiry, lifetime); - - /* prepare for the next round */ - winctx->context_handle = &winctx->context; - send_tok->value = wsend_tok.pvBuffer; - send_tok->length = wsend_tok.cbBuffer; - - /* check & return our status */ - if (winctx->maj_stat==SEC_E_OK) return SSH_GSS_S_COMPLETE; - if (winctx->maj_stat==SEC_I_CONTINUE_NEEDED) return SSH_GSS_S_CONTINUE_NEEDED; - - return SSH_GSS_FAILURE; -} - -static Ssh_gss_stat ssh_sspi_free_tok(struct ssh_gss_library *lib, - Ssh_gss_buf *send_tok) -{ - /* check input */ - if (send_tok == NULL) return SSH_GSS_FAILURE; - - /* free Windows buffer */ - p_FreeContextBuffer(send_tok->value); - SSH_GSS_CLEAR_BUF(send_tok); - - return SSH_GSS_OK; -} - -static Ssh_gss_stat ssh_sspi_release_cred(struct ssh_gss_library *lib, - Ssh_gss_ctx *ctx) -{ - winSsh_gss_ctx *winctx= (winSsh_gss_ctx *) *ctx; - - /* check input */ - if (winctx == NULL) return SSH_GSS_FAILURE; - - /* free Windows data */ - p_FreeCredentialsHandle(&winctx->cred_handle); - p_DeleteSecurityContext(&winctx->context); - - /* delete our "wrapper" structure */ - sfree(winctx); - *ctx = (Ssh_gss_ctx) NULL; - - return SSH_GSS_OK; -} - - -static Ssh_gss_stat ssh_sspi_release_name(struct ssh_gss_library *lib, - Ssh_gss_name *srv_name) -{ - char *pStr= (char *) *srv_name; - - if (pStr == NULL) return SSH_GSS_FAILURE; - sfree(pStr); - *srv_name = (Ssh_gss_name) NULL; - - return SSH_GSS_OK; -} - -static Ssh_gss_stat ssh_sspi_display_status(struct ssh_gss_library *lib, - Ssh_gss_ctx ctx, Ssh_gss_buf *buf) -{ - winSsh_gss_ctx *winctx = (winSsh_gss_ctx *) ctx; - const char *msg; - - if (winctx == NULL) return SSH_GSS_FAILURE; - - /* decode the error code */ - switch (winctx->maj_stat) { - case SEC_E_OK: msg="SSPI status OK"; break; - case SEC_E_INVALID_HANDLE: msg="The handle passed to the function" - " is invalid."; - break; - case SEC_E_TARGET_UNKNOWN: msg="The target was not recognized."; break; - case SEC_E_LOGON_DENIED: msg="The logon failed."; break; - case SEC_E_INTERNAL_ERROR: msg="The Local Security Authority cannot" - " be contacted."; - break; - case SEC_E_NO_CREDENTIALS: msg="No credentials are available in the" - " security package."; - break; - case SEC_E_NO_AUTHENTICATING_AUTHORITY: - msg="No authority could be contacted for authentication." - "The domain name of the authenticating party could be wrong," - " the domain could be unreachable, or there might have been" - " a trust relationship failure."; - break; - case SEC_E_INSUFFICIENT_MEMORY: - msg="One or more of the SecBufferDesc structures passed as" - " an OUT parameter has a buffer that is too small."; - break; - case SEC_E_INVALID_TOKEN: - msg="The error is due to a malformed input token, such as a" - " token corrupted in transit, a token" - " of incorrect size, or a token passed into the wrong" - " security package. Passing a token to" - " the wrong package can happen if client and server did not" - " negotiate the proper security package."; - break; - default: - msg = "Internal SSPI error"; - break; - } - - buf->value = dupstr(msg); - buf->length = strlen(buf->value); - - return SSH_GSS_OK; -} - -static Ssh_gss_stat ssh_sspi_get_mic(struct ssh_gss_library *lib, - Ssh_gss_ctx ctx, Ssh_gss_buf *buf, - Ssh_gss_buf *hash) -{ - winSsh_gss_ctx *winctx= (winSsh_gss_ctx *) ctx; - SecPkgContext_Sizes ContextSizes; - SecBufferDesc InputBufferDescriptor; - SecBuffer InputSecurityToken[2]; - - if (winctx == NULL) return SSH_GSS_FAILURE; - - winctx->maj_stat = 0; - - memset(&ContextSizes, 0, sizeof(ContextSizes)); - - winctx->maj_stat = p_QueryContextAttributesA(&winctx->context, - SECPKG_ATTR_SIZES, - &ContextSizes); - - if (winctx->maj_stat != SEC_E_OK || - ContextSizes.cbMaxSignature == 0) - return winctx->maj_stat; - - InputBufferDescriptor.cBuffers = 2; - InputBufferDescriptor.pBuffers = InputSecurityToken; - InputBufferDescriptor.ulVersion = SECBUFFER_VERSION; - InputSecurityToken[0].BufferType = SECBUFFER_DATA; - InputSecurityToken[0].cbBuffer = buf->length; - InputSecurityToken[0].pvBuffer = buf->value; - InputSecurityToken[1].BufferType = SECBUFFER_TOKEN; - InputSecurityToken[1].cbBuffer = ContextSizes.cbMaxSignature; - InputSecurityToken[1].pvBuffer = snewn(ContextSizes.cbMaxSignature, char); - - winctx->maj_stat = p_MakeSignature(&winctx->context, - 0, - &InputBufferDescriptor, - 0); - - if (winctx->maj_stat == SEC_E_OK) { - hash->length = InputSecurityToken[1].cbBuffer; - hash->value = InputSecurityToken[1].pvBuffer; - } - - return winctx->maj_stat; -} - -static Ssh_gss_stat ssh_sspi_verify_mic(struct ssh_gss_library *lib, - Ssh_gss_ctx ctx, - Ssh_gss_buf *buf, - Ssh_gss_buf *mic) -{ - winSsh_gss_ctx *winctx= (winSsh_gss_ctx *) ctx; - SecBufferDesc InputBufferDescriptor; - SecBuffer InputSecurityToken[2]; - ULONG qop; - - if (winctx == NULL) return SSH_GSS_FAILURE; - - winctx->maj_stat = 0; - - InputBufferDescriptor.cBuffers = 2; - InputBufferDescriptor.pBuffers = InputSecurityToken; - InputBufferDescriptor.ulVersion = SECBUFFER_VERSION; - InputSecurityToken[0].BufferType = SECBUFFER_DATA; - InputSecurityToken[0].cbBuffer = buf->length; - InputSecurityToken[0].pvBuffer = buf->value; - InputSecurityToken[1].BufferType = SECBUFFER_TOKEN; - InputSecurityToken[1].cbBuffer = mic->length; - InputSecurityToken[1].pvBuffer = mic->value; - - winctx->maj_stat = p_VerifySignature(&winctx->context, - &InputBufferDescriptor, - 0, &qop); - return winctx->maj_stat; -} - -static Ssh_gss_stat ssh_sspi_free_mic(struct ssh_gss_library *lib, - Ssh_gss_buf *hash) -{ - sfree(hash->value); - return SSH_GSS_OK; -} - -static void ssh_sspi_bind_fns(struct ssh_gss_library *lib) -{ - lib->indicate_mech = ssh_sspi_indicate_mech; - lib->import_name = ssh_sspi_import_name; - lib->release_name = ssh_sspi_release_name; - lib->init_sec_context = ssh_sspi_init_sec_context; - lib->free_tok = ssh_sspi_free_tok; - lib->acquire_cred = ssh_sspi_acquire_cred; - lib->release_cred = ssh_sspi_release_cred; - lib->get_mic = ssh_sspi_get_mic; - lib->verify_mic = ssh_sspi_verify_mic; - lib->free_mic = ssh_sspi_free_mic; - lib->display_status = ssh_sspi_display_status; -} - -#else - -/* Dummy function so this source file defines something if NO_GSSAPI - is defined. */ - -void ssh_gss_init(void) -{ -} - -#endif +#ifndef NO_GSSAPI + +#include +#include "putty.h" + +#define SECURITY_WIN32 +#include + +#include "pgssapi.h" +#include "sshgss.h" +#include "sshgssc.h" + +#include "misc.h" + +#define UNIX_EPOCH 11644473600ULL /* Seconds from Windows epoch */ +#define CNS_PERSEC 10000000ULL /* # 100ns per second */ + +/* + * Note, as a special case, 0 relative to the Windows epoch (unspecified) maps + * to 0 relative to the POSIX epoch (unspecified)! + */ +#define TIME_WIN_TO_POSIX(ft, t) do { \ + ULARGE_INTEGER uli; \ + uli.LowPart = (ft).dwLowDateTime; \ + uli.HighPart = (ft).dwHighDateTime; \ + if (uli.QuadPart != 0) \ + uli.QuadPart = uli.QuadPart / CNS_PERSEC - UNIX_EPOCH; \ + (t) = (time_t) uli.QuadPart; \ +} while(0) + +/* Windows code to set up the GSSAPI library list. */ + +#ifdef _WIN64 +#define MIT_KERB_SUFFIX "64" +#else +#define MIT_KERB_SUFFIX "32" +#endif + +const int ngsslibs = 3; +const char *const gsslibnames[3] = { + "MIT Kerberos GSSAPI"MIT_KERB_SUFFIX".DLL", + "Microsoft SSPI SECUR32.DLL", + "User-specified GSSAPI DLL", +}; +const struct keyvalwhere gsslibkeywords[] = { + { "gssapi32", 0, -1, -1 }, + { "sspi", 1, -1, -1 }, + { "custom", 2, -1, -1 }, +}; + +DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, + AcquireCredentialsHandleA, + (SEC_CHAR *, SEC_CHAR *, ULONG, PVOID, + PVOID, SEC_GET_KEY_FN, PVOID, PCredHandle, PTimeStamp)); +DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, + InitializeSecurityContextA, + (PCredHandle, PCtxtHandle, SEC_CHAR *, ULONG, ULONG, + ULONG, PSecBufferDesc, ULONG, PCtxtHandle, + PSecBufferDesc, PULONG, PTimeStamp)); +DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, + FreeContextBuffer, + (PVOID)); +DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, + FreeCredentialsHandle, + (PCredHandle)); +DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, + DeleteSecurityContext, + (PCtxtHandle)); +DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, + QueryContextAttributesA, + (PCtxtHandle, ULONG, PVOID)); +DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, + MakeSignature, + (PCtxtHandle, ULONG, PSecBufferDesc, ULONG)); +DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, + VerifySignature, + (PCtxtHandle, PSecBufferDesc, ULONG, PULONG)); +DECL_WINDOWS_FUNCTION(static, DLL_DIRECTORY_COOKIE, + AddDllDirectory, + (PCWSTR)); + +typedef struct winSsh_gss_ctx { + unsigned long maj_stat; + unsigned long min_stat; + CredHandle cred_handle; + CtxtHandle context; + PCtxtHandle context_handle; + TimeStamp expiry; +} winSsh_gss_ctx; + + +const Ssh_gss_buf gss_mech_krb5={9,"\x2A\x86\x48\x86\xF7\x12\x01\x02\x02"}; + +const char *gsslogmsg = NULL; + +static void ssh_sspi_bind_fns(struct ssh_gss_library *lib); + +struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) +{ + HMODULE module; + HKEY regkey; + struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist); + char *path; + static HMODULE kernel32_module; + if (!kernel32_module) { + kernel32_module = load_system32_dll("kernel32.dll"); + } +#if defined _MSC_VER && _MSC_VER < 1900 + /* Omit the type-check because older MSVCs don't have this function */ + GET_WINDOWS_FUNCTION_NO_TYPECHECK(kernel32_module, AddDllDirectory); +#else + GET_WINDOWS_FUNCTION(kernel32_module, AddDllDirectory); +#endif + + list->libraries = snewn(3, struct ssh_gss_library); + list->nlibraries = 0; + + /* MIT Kerberos GSSAPI implementation */ + module = NULL; + if (RegOpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\MIT\\Kerberos", ®key) + == ERROR_SUCCESS) { + DWORD type, size; + LONG ret; + char *buffer; + + /* Find out the string length */ + ret = RegQueryValueEx(regkey, "InstallDir", NULL, &type, NULL, &size); + + if (ret == ERROR_SUCCESS && type == REG_SZ) { + buffer = snewn(size + 20, char); + ret = RegQueryValueEx(regkey, "InstallDir", NULL, + &type, (LPBYTE)buffer, &size); + if (ret == ERROR_SUCCESS && type == REG_SZ) { + strcat (buffer, "\\bin"); + if(p_AddDllDirectory) { + /* Add MIT Kerberos' path to the DLL search path, + * it loads its own DLLs further down the road */ + wchar_t *dllPath = + dup_mb_to_wc(DEFAULT_CODEPAGE, 0, buffer); + p_AddDllDirectory(dllPath); + sfree(dllPath); + } + strcat (buffer, "\\gssapi"MIT_KERB_SUFFIX".dll"); + module = LoadLibraryEx (buffer, NULL, + LOAD_LIBRARY_SEARCH_SYSTEM32 | + LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | + LOAD_LIBRARY_SEARCH_USER_DIRS); + } + sfree(buffer); + } + RegCloseKey(regkey); + } + if (module) { + struct ssh_gss_library *lib = + &list->libraries[list->nlibraries++]; + + lib->id = 0; + lib->gsslogmsg = "Using GSSAPI from GSSAPI"MIT_KERB_SUFFIX".DLL"; + lib->handle = (void *)module; + +#define BIND_GSS_FN(name) \ + lib->u.gssapi.name = (t_gss_##name) GetProcAddress(module, "gss_" #name) + + BIND_GSS_FN(delete_sec_context); + BIND_GSS_FN(display_status); + BIND_GSS_FN(get_mic); + BIND_GSS_FN(verify_mic); + BIND_GSS_FN(import_name); + BIND_GSS_FN(init_sec_context); + BIND_GSS_FN(release_buffer); + BIND_GSS_FN(release_cred); + BIND_GSS_FN(release_name); + BIND_GSS_FN(acquire_cred); + BIND_GSS_FN(inquire_cred_by_mech); + +#undef BIND_GSS_FN + + ssh_gssapi_bind_fns(lib); + } + + /* Microsoft SSPI Implementation */ + module = load_system32_dll("secur32.dll"); + if (module) { + struct ssh_gss_library *lib = + &list->libraries[list->nlibraries++]; + + lib->id = 1; + lib->gsslogmsg = "Using SSPI from SECUR32.DLL"; + lib->handle = (void *)module; + + GET_WINDOWS_FUNCTION(module, AcquireCredentialsHandleA); + GET_WINDOWS_FUNCTION(module, InitializeSecurityContextA); + GET_WINDOWS_FUNCTION(module, FreeContextBuffer); + GET_WINDOWS_FUNCTION(module, FreeCredentialsHandle); + GET_WINDOWS_FUNCTION(module, DeleteSecurityContext); + GET_WINDOWS_FUNCTION(module, QueryContextAttributesA); + GET_WINDOWS_FUNCTION(module, MakeSignature); + GET_WINDOWS_FUNCTION(module, VerifySignature); + + ssh_sspi_bind_fns(lib); + } + + /* + * Custom GSSAPI DLL. + */ + module = NULL; + path = conf_get_filename(conf, CONF_ssh_gss_custom)->path; + if (*path) { + if(p_AddDllDirectory) { + /* Add the custom directory as well in case it chainloads + * some other DLLs (e.g a non-installed MIT Kerberos + * instance) */ + int pathlen = strlen(path); + + while (pathlen > 0 && path[pathlen-1] != ':' && + path[pathlen-1] != '\\') + pathlen--; + + if (pathlen > 0 && path[pathlen-1] != '\\') + pathlen--; + + if (pathlen > 0) { + char *dirpath = dupprintf("%.*s", pathlen, path); + wchar_t *dllPath = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, dirpath); + p_AddDllDirectory(dllPath); + sfree(dllPath); + sfree(dirpath); + } + } + + module = LoadLibraryEx(path, NULL, + LOAD_LIBRARY_SEARCH_SYSTEM32 | + LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | + LOAD_LIBRARY_SEARCH_USER_DIRS); + } + if (module) { + struct ssh_gss_library *lib = + &list->libraries[list->nlibraries++]; + + lib->id = 2; + lib->gsslogmsg = dupprintf("Using GSSAPI from user-specified" + " library '%s'", path); + lib->handle = (void *)module; + +#define BIND_GSS_FN(name) \ + lib->u.gssapi.name = (t_gss_##name) GetProcAddress(module, "gss_" #name) + + BIND_GSS_FN(delete_sec_context); + BIND_GSS_FN(display_status); + BIND_GSS_FN(get_mic); + BIND_GSS_FN(verify_mic); + BIND_GSS_FN(import_name); + BIND_GSS_FN(init_sec_context); + BIND_GSS_FN(release_buffer); + BIND_GSS_FN(release_cred); + BIND_GSS_FN(release_name); + BIND_GSS_FN(acquire_cred); + BIND_GSS_FN(inquire_cred_by_mech); + +#undef BIND_GSS_FN + + ssh_gssapi_bind_fns(lib); + } + + + return list; +} + +void ssh_gss_cleanup(struct ssh_gss_liblist *list) +{ + int i; + + /* + * LoadLibrary and FreeLibrary are defined to employ reference + * counting in the case where the same library is repeatedly + * loaded, so even in a multiple-sessions-per-process context + * (not that we currently expect ever to have such a thing on + * Windows) it's safe to naively FreeLibrary everything here + * without worrying about destroying it under the feet of + * another SSH instance still using it. + */ + for (i = 0; i < list->nlibraries; i++) { + FreeLibrary((HMODULE)list->libraries[i].handle); + if (list->libraries[i].id == 2) { + /* The 'custom' id involves a dynamically allocated message. + * Note that we must cast away the 'const' to free it. */ + sfree((char *)list->libraries[i].gsslogmsg); + } + } + sfree(list->libraries); + sfree(list); +} + +static Ssh_gss_stat ssh_sspi_indicate_mech(struct ssh_gss_library *lib, + Ssh_gss_buf *mech) +{ + *mech = gss_mech_krb5; + return SSH_GSS_OK; +} + + +static Ssh_gss_stat ssh_sspi_import_name(struct ssh_gss_library *lib, + char *host, Ssh_gss_name *srv_name) +{ + char *pStr; + + /* Check hostname */ + if (host == NULL) return SSH_GSS_FAILURE; + + /* copy it into form host/FQDN */ + pStr = dupcat("host/", host); + + *srv_name = (Ssh_gss_name) pStr; + + return SSH_GSS_OK; +} + +static Ssh_gss_stat ssh_sspi_acquire_cred(struct ssh_gss_library *lib, + Ssh_gss_ctx *ctx, + time_t *expiry) +{ + winSsh_gss_ctx *winctx = snew(winSsh_gss_ctx); + memset(winctx, 0, sizeof(winSsh_gss_ctx)); + + /* prepare our "wrapper" structure */ + winctx->maj_stat = winctx->min_stat = SEC_E_OK; + winctx->context_handle = NULL; + + /* Specifying no principal name here means use the credentials of + the current logged-in user */ + + winctx->maj_stat = p_AcquireCredentialsHandleA(NULL, + "Kerberos", + SECPKG_CRED_OUTBOUND, + NULL, + NULL, + NULL, + NULL, + &winctx->cred_handle, + NULL); + + if (winctx->maj_stat != SEC_E_OK) { + p_FreeCredentialsHandle(&winctx->cred_handle); + sfree(winctx); + return SSH_GSS_FAILURE; + } + + /* Windows does not return a valid expiration from AcquireCredentials */ + if (expiry) + *expiry = GSS_NO_EXPIRATION; + + *ctx = (Ssh_gss_ctx) winctx; + return SSH_GSS_OK; +} + +static void localexp_to_exp_lifetime(TimeStamp *localexp, + time_t *expiry, unsigned long *lifetime) +{ + FILETIME nowUTC; + FILETIME expUTC; + time_t now; + time_t exp; + time_t delta; + + if (!lifetime && !expiry) + return; + + GetSystemTimeAsFileTime(&nowUTC); + TIME_WIN_TO_POSIX(nowUTC, now); + + if (lifetime) + *lifetime = 0; + if (expiry) + *expiry = GSS_NO_EXPIRATION; + + /* + * Type oddity: localexp is a pointer to 'TimeStamp', whereas + * LocalFileTimeToFileTime expects a pointer to FILETIME. However, + * despite having different formal type names from the compiler's + * point of view, these two structures are specified to be + * isomorphic in the MS documentation, so it's legitimate to copy + * between them: + * + * https://msdn.microsoft.com/en-us/library/windows/desktop/aa380511(v=vs.85).aspx + */ + { + FILETIME localexp_ft; + enum { vorpal_sword = 1 / (sizeof(*localexp) == sizeof(localexp_ft)) }; + memcpy(&localexp_ft, localexp, sizeof(localexp_ft)); + if (!LocalFileTimeToFileTime(&localexp_ft, &expUTC)) + return; + } + + TIME_WIN_TO_POSIX(expUTC, exp); + delta = exp - now; + if (exp == 0 || delta <= 0) + return; + + if (expiry) + *expiry = exp; + if (lifetime) { + if (delta <= ULONG_MAX) + *lifetime = (unsigned long)delta; + else + *lifetime = ULONG_MAX; + } +} + +static Ssh_gss_stat ssh_sspi_init_sec_context(struct ssh_gss_library *lib, + Ssh_gss_ctx *ctx, + Ssh_gss_name srv_name, + int to_deleg, + Ssh_gss_buf *recv_tok, + Ssh_gss_buf *send_tok, + time_t *expiry, + unsigned long *lifetime) +{ + winSsh_gss_ctx *winctx = (winSsh_gss_ctx *) *ctx; + SecBuffer wsend_tok = {send_tok->length,SECBUFFER_TOKEN,send_tok->value}; + SecBuffer wrecv_tok = {recv_tok->length,SECBUFFER_TOKEN,recv_tok->value}; + SecBufferDesc output_desc={SECBUFFER_VERSION,1,&wsend_tok}; + SecBufferDesc input_desc ={SECBUFFER_VERSION,1,&wrecv_tok}; + unsigned long flags=ISC_REQ_MUTUAL_AUTH|ISC_REQ_REPLAY_DETECT| + ISC_REQ_CONFIDENTIALITY|ISC_REQ_ALLOCATE_MEMORY; + unsigned long ret_flags=0; + TimeStamp localexp; + + /* check if we have to delegate ... */ + if (to_deleg) flags |= ISC_REQ_DELEGATE; + winctx->maj_stat = p_InitializeSecurityContextA(&winctx->cred_handle, + winctx->context_handle, + (char*) srv_name, + flags, + 0, /* reserved */ + SECURITY_NATIVE_DREP, + &input_desc, + 0, /* reserved */ + &winctx->context, + &output_desc, + &ret_flags, + &localexp); + + localexp_to_exp_lifetime(&localexp, expiry, lifetime); + + /* prepare for the next round */ + winctx->context_handle = &winctx->context; + send_tok->value = wsend_tok.pvBuffer; + send_tok->length = wsend_tok.cbBuffer; + + /* check & return our status */ + if (winctx->maj_stat==SEC_E_OK) return SSH_GSS_S_COMPLETE; + if (winctx->maj_stat==SEC_I_CONTINUE_NEEDED) return SSH_GSS_S_CONTINUE_NEEDED; + + return SSH_GSS_FAILURE; +} + +static Ssh_gss_stat ssh_sspi_free_tok(struct ssh_gss_library *lib, + Ssh_gss_buf *send_tok) +{ + /* check input */ + if (send_tok == NULL) return SSH_GSS_FAILURE; + + /* free Windows buffer */ + p_FreeContextBuffer(send_tok->value); + SSH_GSS_CLEAR_BUF(send_tok); + + return SSH_GSS_OK; +} + +static Ssh_gss_stat ssh_sspi_release_cred(struct ssh_gss_library *lib, + Ssh_gss_ctx *ctx) +{ + winSsh_gss_ctx *winctx= (winSsh_gss_ctx *) *ctx; + + /* check input */ + if (winctx == NULL) return SSH_GSS_FAILURE; + + /* free Windows data */ + p_FreeCredentialsHandle(&winctx->cred_handle); + p_DeleteSecurityContext(&winctx->context); + + /* delete our "wrapper" structure */ + sfree(winctx); + *ctx = (Ssh_gss_ctx) NULL; + + return SSH_GSS_OK; +} + + +static Ssh_gss_stat ssh_sspi_release_name(struct ssh_gss_library *lib, + Ssh_gss_name *srv_name) +{ + char *pStr= (char *) *srv_name; + + if (pStr == NULL) return SSH_GSS_FAILURE; + sfree(pStr); + *srv_name = (Ssh_gss_name) NULL; + + return SSH_GSS_OK; +} + +static Ssh_gss_stat ssh_sspi_display_status(struct ssh_gss_library *lib, + Ssh_gss_ctx ctx, Ssh_gss_buf *buf) +{ + winSsh_gss_ctx *winctx = (winSsh_gss_ctx *) ctx; + const char *msg; + + if (winctx == NULL) return SSH_GSS_FAILURE; + + /* decode the error code */ + switch (winctx->maj_stat) { + case SEC_E_OK: msg="SSPI status OK"; break; + case SEC_E_INVALID_HANDLE: msg="The handle passed to the function" + " is invalid."; + break; + case SEC_E_TARGET_UNKNOWN: msg="The target was not recognized."; break; + case SEC_E_LOGON_DENIED: msg="The logon failed."; break; + case SEC_E_INTERNAL_ERROR: msg="The Local Security Authority cannot" + " be contacted."; + break; + case SEC_E_NO_CREDENTIALS: msg="No credentials are available in the" + " security package."; + break; + case SEC_E_NO_AUTHENTICATING_AUTHORITY: + msg="No authority could be contacted for authentication." + "The domain name of the authenticating party could be wrong," + " the domain could be unreachable, or there might have been" + " a trust relationship failure."; + break; + case SEC_E_INSUFFICIENT_MEMORY: + msg="One or more of the SecBufferDesc structures passed as" + " an OUT parameter has a buffer that is too small."; + break; + case SEC_E_INVALID_TOKEN: + msg="The error is due to a malformed input token, such as a" + " token corrupted in transit, a token" + " of incorrect size, or a token passed into the wrong" + " security package. Passing a token to" + " the wrong package can happen if client and server did not" + " negotiate the proper security package."; + break; + default: + msg = "Internal SSPI error"; + break; + } + + buf->value = dupstr(msg); + buf->length = strlen(buf->value); + + return SSH_GSS_OK; +} + +static Ssh_gss_stat ssh_sspi_get_mic(struct ssh_gss_library *lib, + Ssh_gss_ctx ctx, Ssh_gss_buf *buf, + Ssh_gss_buf *hash) +{ + winSsh_gss_ctx *winctx= (winSsh_gss_ctx *) ctx; + SecPkgContext_Sizes ContextSizes; + SecBufferDesc InputBufferDescriptor; + SecBuffer InputSecurityToken[2]; + + if (winctx == NULL) return SSH_GSS_FAILURE; + + winctx->maj_stat = 0; + + memset(&ContextSizes, 0, sizeof(ContextSizes)); + + winctx->maj_stat = p_QueryContextAttributesA(&winctx->context, + SECPKG_ATTR_SIZES, + &ContextSizes); + + if (winctx->maj_stat != SEC_E_OK || + ContextSizes.cbMaxSignature == 0) + return winctx->maj_stat; + + InputBufferDescriptor.cBuffers = 2; + InputBufferDescriptor.pBuffers = InputSecurityToken; + InputBufferDescriptor.ulVersion = SECBUFFER_VERSION; + InputSecurityToken[0].BufferType = SECBUFFER_DATA; + InputSecurityToken[0].cbBuffer = buf->length; + InputSecurityToken[0].pvBuffer = buf->value; + InputSecurityToken[1].BufferType = SECBUFFER_TOKEN; + InputSecurityToken[1].cbBuffer = ContextSizes.cbMaxSignature; + InputSecurityToken[1].pvBuffer = snewn(ContextSizes.cbMaxSignature, char); + + winctx->maj_stat = p_MakeSignature(&winctx->context, + 0, + &InputBufferDescriptor, + 0); + + if (winctx->maj_stat == SEC_E_OK) { + hash->length = InputSecurityToken[1].cbBuffer; + hash->value = InputSecurityToken[1].pvBuffer; + } + + return winctx->maj_stat; +} + +static Ssh_gss_stat ssh_sspi_verify_mic(struct ssh_gss_library *lib, + Ssh_gss_ctx ctx, + Ssh_gss_buf *buf, + Ssh_gss_buf *mic) +{ + winSsh_gss_ctx *winctx= (winSsh_gss_ctx *) ctx; + SecBufferDesc InputBufferDescriptor; + SecBuffer InputSecurityToken[2]; + ULONG qop; + + if (winctx == NULL) return SSH_GSS_FAILURE; + + winctx->maj_stat = 0; + + InputBufferDescriptor.cBuffers = 2; + InputBufferDescriptor.pBuffers = InputSecurityToken; + InputBufferDescriptor.ulVersion = SECBUFFER_VERSION; + InputSecurityToken[0].BufferType = SECBUFFER_DATA; + InputSecurityToken[0].cbBuffer = buf->length; + InputSecurityToken[0].pvBuffer = buf->value; + InputSecurityToken[1].BufferType = SECBUFFER_TOKEN; + InputSecurityToken[1].cbBuffer = mic->length; + InputSecurityToken[1].pvBuffer = mic->value; + + winctx->maj_stat = p_VerifySignature(&winctx->context, + &InputBufferDescriptor, + 0, &qop); + return winctx->maj_stat; +} + +static Ssh_gss_stat ssh_sspi_free_mic(struct ssh_gss_library *lib, + Ssh_gss_buf *hash) +{ + sfree(hash->value); + return SSH_GSS_OK; +} + +static void ssh_sspi_bind_fns(struct ssh_gss_library *lib) +{ + lib->indicate_mech = ssh_sspi_indicate_mech; + lib->import_name = ssh_sspi_import_name; + lib->release_name = ssh_sspi_release_name; + lib->init_sec_context = ssh_sspi_init_sec_context; + lib->free_tok = ssh_sspi_free_tok; + lib->acquire_cred = ssh_sspi_acquire_cred; + lib->release_cred = ssh_sspi_release_cred; + lib->get_mic = ssh_sspi_get_mic; + lib->verify_mic = ssh_sspi_verify_mic; + lib->free_mic = ssh_sspi_free_mic; + lib->display_status = ssh_sspi_display_status; +} + +#else + +/* Dummy function so this source file defines something if NO_GSSAPI + is defined. */ + +void ssh_gss_init(void) +{ +} + +#endif diff --git a/0.73_My_PuTTY/windows/winhandl.c b/0.74_My_PuTTY/windows/winhandl.c similarity index 100% rename from 0.73_My_PuTTY/windows/winhandl.c rename to 0.74_My_PuTTY/windows/winhandl.c diff --git a/0.73_My_PuTTY/windows/winhelp.c b/0.74_My_PuTTY/windows/winhelp.c similarity index 100% rename from 0.73_My_PuTTY/windows/winhelp.c rename to 0.74_My_PuTTY/windows/winhelp.c diff --git a/0.73_My_PuTTY/windows/winhelp.h b/0.74_My_PuTTY/windows/winhelp.h similarity index 97% rename from 0.73_My_PuTTY/windows/winhelp.h rename to 0.74_My_PuTTY/windows/winhelp.h index a257455..dd59fc2 100644 --- a/0.73_My_PuTTY/windows/winhelp.h +++ b/0.74_My_PuTTY/windows/winhelp.h @@ -102,6 +102,7 @@ #define WINHELP_CTX_ssh_share "config-ssh-sharing" #define WINHELP_CTX_ssh_kexlist "config-ssh-kex-order" #define WINHELP_CTX_ssh_hklist "config-ssh-hostkey-order" +#define WINHELP_CTX_ssh_hk_known "config-ssh-prefer-known-hostkeys" #define WINHELP_CTX_ssh_gssapi_kex_delegation "config-ssh-kex-gssapi-delegation" #define WINHELP_CTX_ssh_kex_repeat "config-ssh-kex-rekey" #define WINHELP_CTX_ssh_kex_manual_hostkeys "config-ssh-kex-manual-hostkeys" diff --git a/0.73_My_PuTTY/windows/winhelp.rc2 b/0.74_My_PuTTY/windows/winhelp.rc2 similarity index 100% rename from 0.73_My_PuTTY/windows/winhelp.rc2 rename to 0.74_My_PuTTY/windows/winhelp.rc2 diff --git a/0.73_My_PuTTY/windows/winhsock.c b/0.74_My_PuTTY/windows/winhsock.c similarity index 100% rename from 0.73_My_PuTTY/windows/winhsock.c rename to 0.74_My_PuTTY/windows/winhsock.c diff --git a/0.73_My_PuTTY/windows/winjump.c b/0.74_My_PuTTY/windows/winjump.c similarity index 99% rename from 0.73_My_PuTTY/windows/winjump.c rename to 0.74_My_PuTTY/windows/winjump.c index a6eab7b..7faf33c 100644 --- a/0.73_My_PuTTY/windows/winjump.c +++ b/0.74_My_PuTTY/windows/winjump.c @@ -432,7 +432,7 @@ static IShellLink *make_shell_link(const char *appname, * behaviour change in which an argument string starting with * '@' causes the SetArguments method to silently do the wrong * thing. */ - param_string = dupcat(" @", sessionname, NULL); + param_string = dupcat(" @", sessionname); } else { param_string = dupstr(""); } @@ -440,8 +440,7 @@ static IShellLink *make_shell_link(const char *appname, sfree(param_string); if (sessionname) { - desc_string = dupcat("Connect to PuTTY session '", - sessionname, "'", NULL); + desc_string = dupcat("Connect to PuTTY session '", sessionname, "'"); } else { assert(appname); desc_string = dupprintf("Run %.*s", diff --git a/0.73_My_PuTTY/windows/winmisc.c b/0.74_My_PuTTY/windows/winmisc.c similarity index 99% rename from 0.73_My_PuTTY/windows/winmisc.c rename to 0.74_My_PuTTY/windows/winmisc.c index 00b2fcd..7bb8639 100644 --- a/0.73_My_PuTTY/windows/winmisc.c +++ b/0.74_My_PuTTY/windows/winmisc.c @@ -233,7 +233,7 @@ HMODULE load_system32_dll(const char *libname) sgrowarray(sysdir, sysdirsize, len); } - fullpath = dupcat(sysdir, "\\", libname, NULL); + fullpath = dupcat(sysdir, "\\", libname); ret = LoadLibrary(fullpath); sfree(fullpath); return ret; diff --git a/0.73_My_PuTTY/windows/winmiscs.c b/0.74_My_PuTTY/windows/winmiscs.c similarity index 100% rename from 0.73_My_PuTTY/windows/winmiscs.c rename to 0.74_My_PuTTY/windows/winmiscs.c diff --git a/0.73_My_PuTTY/windows/winnet.c b/0.74_My_PuTTY/windows/winnet.c similarity index 99% rename from 0.73_My_PuTTY/windows/winnet.c rename to 0.74_My_PuTTY/windows/winnet.c index 1e00dd5..6be595b 100644 --- a/0.73_My_PuTTY/windows/winnet.c +++ b/0.74_My_PuTTY/windows/winnet.c @@ -2097,8 +2097,7 @@ char *get_hostname(void) return dupstr(hostbuf); } -SockAddr *platform_get_x11_unix_address(const char *display, int displaynum, - char **canonicalname) +SockAddr *platform_get_x11_unix_address(const char *display, int displaynum) { SockAddr *ret = snew(SockAddr); memset(ret, 0, sizeof(SockAddr)); diff --git a/0.73_My_PuTTY/windows/winnohlp.c b/0.74_My_PuTTY/windows/winnohlp.c similarity index 100% rename from 0.73_My_PuTTY/windows/winnohlp.c rename to 0.74_My_PuTTY/windows/winnohlp.c diff --git a/0.73_My_PuTTY/windows/winnoise.c b/0.74_My_PuTTY/windows/winnoise.c similarity index 100% rename from 0.73_My_PuTTY/windows/winnoise.c rename to 0.74_My_PuTTY/windows/winnoise.c diff --git a/0.73_My_PuTTY/windows/winnojmp.c b/0.74_My_PuTTY/windows/winnojmp.c similarity index 100% rename from 0.73_My_PuTTY/windows/winnojmp.c rename to 0.74_My_PuTTY/windows/winnojmp.c diff --git a/0.73_My_PuTTY/windows/winnpc.c b/0.74_My_PuTTY/windows/winnpc.c similarity index 100% rename from 0.73_My_PuTTY/windows/winnpc.c rename to 0.74_My_PuTTY/windows/winnpc.c diff --git a/0.73_My_PuTTY/windows/winnps.c b/0.74_My_PuTTY/windows/winnps.c similarity index 100% rename from 0.73_My_PuTTY/windows/winnps.c rename to 0.74_My_PuTTY/windows/winnps.c diff --git a/0.73_My_PuTTY/windows/winpgen.c b/0.74_My_PuTTY/windows/winpgen.c similarity index 99% rename from 0.73_My_PuTTY/windows/winpgen.c rename to 0.74_My_PuTTY/windows/winpgen.c index 6cc122b..2b3807a 100644 --- a/0.73_My_PuTTY/windows/winpgen.c +++ b/0.74_My_PuTTY/windows/winpgen.c @@ -371,7 +371,7 @@ static DWORD WINAPI generate_key_thread(void *param) ecdsa_generate(params->eckey, params->curve_bits, progress_update, &prog); else if (params->keytype == ED25519) - eddsa_generate(params->edkey, 256, progress_update, &prog); + eddsa_generate(params->edkey, 255, progress_update, &prog); else rsa_generate(params->key, params->key_bits, progress_update, &prog); diff --git a/0.73_My_PuTTY/windows/winpgnt.c b/0.74_My_PuTTY/windows/winpgnt.c similarity index 91% rename from 0.73_My_PuTTY/windows/winpgnt.c rename to 0.74_My_PuTTY/windows/winpgnt.c index 4e891c2..a3647ee 100644 --- a/0.73_My_PuTTY/windows/winpgnt.c +++ b/0.74_My_PuTTY/windows/winpgnt.c @@ -65,15 +65,6 @@ extern int DirectoryBrowseFlag ; #define IDM_HELP 0x0040 #define IDM_ABOUT 0x0050 -#ifdef MOD_WINCRYPT -#ifdef HAS_WINX509 -#include "wincrypt/wincrypto.h" -#define IDM_ADDCERT 0x0070 -#define IDM_ADDX509 0x0080 -static void key_to_clipboard(HWND hwnd) ; -#endif /* HAS_WINX509 */ -#endif - #ifdef MOD_PERSO #define APPNAME "Pageant" #endif @@ -173,15 +164,6 @@ static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg, aboutbox = NULL; DestroyWindow(hwnd); return 0; -#ifdef MOD_WINCRYPT -#ifdef HAS_WINX509 - case 100: /* key list */ - if (HIWORD(wParam) == LBN_DBLCLK) { - key_to_clipboard(hwnd); - } - return 0; -#endif /* HAS_WINX509 */ -#endif case 101: EnableWindow(hwnd, 0); DialogBox(hinst, MAKEINTRESOURCE(214), hwnd, LicenceProc); @@ -457,107 +439,6 @@ static void win_add_keyfile(Filename *filename) return; } -#ifdef MOD_WINCRYPT -#ifdef HAS_WINX509 -/* - * Add a key from a Windows certificate - */ -static void prompt_add_capikey(PSTR search) -{ - char *err; - Filename *fn = filename_from_str(search); - pageant_add_keyfile(fn, NULL, &err); - if (err == PAGEANT_ACTION_OK) { - keylist_update(); - } else { - message_box(err, APPNAME, MB_OK | MB_ICONERROR, HELPCTXID(errors_cantloadkey)); - } - filename_free(fn); - if (err != NULL) - sfree(err); -} - -/* - * Copy key to clipboard in ssh authorized_keys format - */ -static void key_to_clipboard2(struct ssh2_userkey *key) -{ - //BinarySink* bs; - char *buffer, *p, *psz; - int i, mbReturn; - HGLOBAL hClipBuffer; - Filename* filename; - strbuf *bblob; - bool isX509 = false; - - bblob = strbuf_new(); - filename = filename_from_str(key->comment); - int len = strlen(filename->path); - if ((len < 7) - || !(0 == strncmp("cert://", filename->path, 7) - || (isX509 = (0 == strncmp("x509://", filename->path, 7))))) { - filename_free(filename); - return; - } - - if (isX509) { - MessageBox(0, "Cannot copy the public key in x509v3-sign-rsa mode.", "Invalid operation", MB_ICONEXCLAMATION | MB_OK | MB_TASKMODAL); - filename_free(filename); - return; - } - - mbReturn = MessageBox(0, "Copy certificate public key to clipboard?\n\nHint: Copied in ssh authorized_keys format.", - "Copy public key", MB_ICONASTERISK | MB_YESNO | MB_TASKMODAL); - - if (mbReturn == IDNO) - return; - - capi_load_key((const Filename **)&filename, BinarySink_UPCAST(bblob)); - buffer = snewn(strlen(key->key->vt->ssh_id) + 4 * ((bblob->len + 2) / 3) + strlen(key->comment) + 3, char); - strcpy(buffer, key->key->vt->ssh_id); - p = buffer + strlen(buffer); - *p++ = ' '; - i = 0; - while (i < bblob->len) { - int n = (bblob->len - i < 3 ? bblob->len - i : 3); - base64_encode_atom((const unsigned char *)bblob->s + i, n, p); - i += n; - p += 4; - } - *p++ = ' '; - strcpy(p, key->comment); - if (OpenClipboard(NULL)) { - hClipBuffer = GlobalAlloc(GMEM_MOVEABLE, strlen(buffer) + 1); - if (hClipBuffer) { - psz = (char *)GlobalLock(hClipBuffer); - strcpy(psz, buffer); - GlobalUnlock(hClipBuffer); - EmptyClipboard(); - SetClipboardData(CF_TEXT, hClipBuffer); - } - CloseClipboard(); - MessageBox(0, "Certificate public copied.", "Copy", MB_ICONINFORMATION | MB_OK | MB_TASKMODAL); - } - sfree(buffer); - strbuf_free(bblob); - filename_free(filename); -} - -/* - * Copy 1'st selected key to clipboard in ssh authorized_keys format - */ -static void key_to_clipboard(HWND hwnd) -{ - int numSelected, *selectedArray; - if ((numSelected = SendDlgItemMessage(hwnd, 100, LB_GETSELCOUNT, 0, 0)) > 0) { - selectedArray = snewn(numSelected, int); - SendDlgItemMessage(hwnd, 100, LB_GETSELITEMS, numSelected, (WPARAM)selectedArray); - key_to_clipboard2(pageant_nth_ssh2_key(selectedArray[0])); - sfree(selectedArray); - } -} -#endif /* HAS_WINX509 */ -#endif /* * Prompt for a key file to add, and add it. */ @@ -593,7 +474,7 @@ static void prompt_add_keyfile(void) char *dir = filelist; char *filewalker = filelist + strlen(dir) + 1; while (*filewalker != '\0') { - char *filename = dupcat(dir, "\\", filewalker, NULL); + char *filename = dupcat(dir, "\\", filewalker); Filename *fn = filename_from_str(filename); win_add_keyfile(fn); filename_free(fn); @@ -831,7 +712,7 @@ static void update_sessions(void) sb = strbuf_new(); while(ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) { if(strcmp(buf, PUTTY_DEFAULT) != 0) { - sb->len = 0; + strbuf_clear(sb); unescape_registry_key(buf, sb); memset(&mii, 0, sizeof(mii)); @@ -1038,7 +919,7 @@ static char *answer_filemapping_message(const char *mapname) mapsize = mbi.RegionSize; } #ifdef DEBUG_IPC - debug("region size = %zd\n", mapsize); + debug("region size = %"SIZEu"\n", mapsize); #endif if (mapsize < 5) { err = dupstr("mapping smaller than smallest possible request"); @@ -1184,16 +1065,6 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, } prompt_add_keyfile(); break; -#ifdef MOD_WINCRYPT -#ifdef HAS_WINX509 - case IDM_ADDCERT: - prompt_add_capikey("cert://*"); - break; - case IDM_ADDX509: - prompt_add_capikey("x509://*"); - break; -#endif /* HAS_WINX509 */ -#endif case IDM_ABOUT: if (!aboutbox) { aboutbox = CreateDialog(hinst, MAKEINTRESOURCE(213), @@ -1504,12 +1375,6 @@ int WINAPI Agent_WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show AppendMenu(systray_menu, MF_ENABLED, IDM_VIEWKEYS, "&View Keys"); AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key"); -#ifdef MOD_WINCRYPT -#ifdef HAS_WINX509 - AppendMenu(systray_menu, MF_ENABLED, IDM_ADDCERT, "Add &Certificate"); - AppendMenu(systray_menu, MF_ENABLED, IDM_ADDX509, "Add &X509 Certificate"); -#endif /* HAS_WINX509 */ -#endif AppendMenu(systray_menu, MF_SEPARATOR, 0, 0); if (has_help()) AppendMenu(systray_menu, MF_ENABLED, IDM_HELP, "&Help"); diff --git a/0.73_My_PuTTY/windows/winpgntc.c b/0.74_My_PuTTY/windows/winpgntc.c similarity index 100% rename from 0.73_My_PuTTY/windows/winpgntc.c rename to 0.74_My_PuTTY/windows/winpgntc.c diff --git a/0.73_My_PuTTY/windows/winplink.c b/0.74_My_PuTTY/windows/winplink.c similarity index 99% rename from 0.73_My_PuTTY/windows/winplink.c rename to 0.74_My_PuTTY/windows/winplink.c index f98ac06..e926c33 100644 --- a/0.73_My_PuTTY/windows/winplink.c +++ b/0.74_My_PuTTY/windows/winplink.c @@ -231,10 +231,10 @@ static void version(void) exit(0); } -char *do_select(SOCKET skt, bool startup) +char *do_select(SOCKET skt, bool enable) { int events; - if (startup) { + if (enable) { events = (FD_CONNECT | FD_READ | FD_WRITE | FD_OOB | FD_CLOSE | FD_ACCEPT); } else { diff --git a/0.73_My_PuTTY/windows/winprint.c b/0.74_My_PuTTY/windows/winprint.c similarity index 100% rename from 0.73_My_PuTTY/windows/winprint.c rename to 0.74_My_PuTTY/windows/winprint.c diff --git a/0.73_My_PuTTY/windows/winproxy.c b/0.74_My_PuTTY/windows/winproxy.c similarity index 100% rename from 0.73_My_PuTTY/windows/winproxy.c rename to 0.74_My_PuTTY/windows/winproxy.c diff --git a/0.73_My_PuTTY/windows/winsecur.c b/0.74_My_PuTTY/windows/winsecur.c similarity index 100% rename from 0.73_My_PuTTY/windows/winsecur.c rename to 0.74_My_PuTTY/windows/winsecur.c diff --git a/0.73_My_PuTTY/windows/winsecur.h b/0.74_My_PuTTY/windows/winsecur.h similarity index 100% rename from 0.73_My_PuTTY/windows/winsecur.h rename to 0.74_My_PuTTY/windows/winsecur.h diff --git a/0.74_My_PuTTY/windows/winser.c b/0.74_My_PuTTY/windows/winser.c new file mode 100644 index 0000000..8dc64cc --- /dev/null +++ b/0.74_My_PuTTY/windows/winser.c @@ -0,0 +1,450 @@ +/* + * Serial back end (Windows-specific). + */ + +#include +#include +#include + +#include "putty.h" + +#define SERIAL_MAX_BACKLOG 4096 + +typedef struct Serial Serial; +struct Serial { + HANDLE port; + struct handle *out, *in; + Seat *seat; + LogContext *logctx; + int bufsize; + long clearbreak_time; + bool break_in_progress; + Backend backend; +}; + +static void serial_terminate(Serial *serial) +{ + if (serial->out) { + handle_free(serial->out); + serial->out = NULL; + } + if (serial->in) { + handle_free(serial->in); + serial->in = NULL; + } + if (serial->port != INVALID_HANDLE_VALUE) { + if (serial->break_in_progress) + ClearCommBreak(serial->port); + CloseHandle(serial->port); + serial->port = INVALID_HANDLE_VALUE; + } +} + +static size_t serial_gotdata( + struct handle *h, const void *data, size_t len, int err) +{ + Serial *serial = (Serial *)handle_get_privdata(h); + if (err || len == 0) { + const char *error_msg; + + /* + * Currently, len==0 should never happen because we're + * ignoring EOFs. However, it seems not totally impossible + * that this same back end might be usable to talk to named + * pipes or some other non-serial device, in which case EOF + * may become meaningful here. + */ + if (!err) + error_msg = "End of file reading from serial device"; + else + error_msg = "Error reading from serial device"; + + serial_terminate(serial); + + seat_notify_remote_exit(serial->seat); + + logevent(serial->logctx, error_msg); + + seat_connection_fatal(serial->seat, "%s", error_msg); + + return 0; + } else { + return seat_stdout(serial->seat, data, len); + } +} + +static void serial_sentdata(struct handle *h, size_t new_backlog, int err) +{ + Serial *serial = (Serial *)handle_get_privdata(h); + if (err) { + const char *error_msg = "Error writing to serial device"; + + serial_terminate(serial); + + seat_notify_remote_exit(serial->seat); + + logevent(serial->logctx, error_msg); + + seat_connection_fatal(serial->seat, "%s", error_msg); + } else { + serial->bufsize = new_backlog; + } +} + +static const char *serial_configure(Serial *serial, HANDLE serport, Conf *conf) +{ + DCB dcb; + COMMTIMEOUTS timeouts; + + /* + * Set up the serial port parameters. If we can't even + * GetCommState, we ignore the problem on the grounds that the + * user might have pointed us at some other type of two-way + * device instead of a serial port. + */ + if (GetCommState(serport, &dcb)) { + const char *str; + + /* + * Boilerplate. + */ + dcb.fBinary = true; + dcb.fDtrControl = DTR_CONTROL_ENABLE; + dcb.fDsrSensitivity = false; + dcb.fTXContinueOnXoff = false; + dcb.fOutX = false; + dcb.fInX = false; + dcb.fErrorChar = false; + dcb.fNull = false; + dcb.fRtsControl = RTS_CONTROL_ENABLE; + dcb.fAbortOnError = false; + dcb.fOutxCtsFlow = false; + dcb.fOutxDsrFlow = false; + + /* + * Configurable parameters. + */ + dcb.BaudRate = conf_get_int(conf, CONF_serspeed); + logeventf(serial->logctx, "Configuring baud rate %lu", dcb.BaudRate); + + dcb.ByteSize = conf_get_int(conf, CONF_serdatabits); + logeventf(serial->logctx, "Configuring %u data bits", dcb.ByteSize); + + switch (conf_get_int(conf, CONF_serstopbits)) { + case 2: dcb.StopBits = ONESTOPBIT; str = "1 stop bit"; break; + case 3: dcb.StopBits = ONE5STOPBITS; str = "1.5 stop bits"; break; + case 4: dcb.StopBits = TWOSTOPBITS; str = "2 stop bits"; break; + default: return "Invalid number of stop bits (need 1, 1.5 or 2)"; + } + logeventf(serial->logctx, "Configuring %s", str); + + switch (conf_get_int(conf, CONF_serparity)) { + case SER_PAR_NONE: dcb.Parity = NOPARITY; str = "no"; break; + case SER_PAR_ODD: dcb.Parity = ODDPARITY; str = "odd"; break; + case SER_PAR_EVEN: dcb.Parity = EVENPARITY; str = "even"; break; + case SER_PAR_MARK: dcb.Parity = MARKPARITY; str = "mark"; break; + case SER_PAR_SPACE: dcb.Parity = SPACEPARITY; str = "space"; break; + } + logeventf(serial->logctx, "Configuring %s parity", str); + + switch (conf_get_int(conf, CONF_serflow)) { + case SER_FLOW_NONE: + str = "no"; + break; + case SER_FLOW_XONXOFF: + dcb.fOutX = dcb.fInX = true; + str = "XON/XOFF"; + break; + case SER_FLOW_RTSCTS: + dcb.fRtsControl = RTS_CONTROL_HANDSHAKE; + dcb.fOutxCtsFlow = true; + str = "RTS/CTS"; + break; + case SER_FLOW_DSRDTR: + dcb.fDtrControl = DTR_CONTROL_HANDSHAKE; + dcb.fOutxDsrFlow = true; + str = "DSR/DTR"; + break; + } + logeventf(serial->logctx, "Configuring %s flow control", str); + + if (!SetCommState(serport, &dcb)) + return "Unable to configure serial port"; + + timeouts.ReadIntervalTimeout = 1; + timeouts.ReadTotalTimeoutMultiplier = 0; + timeouts.ReadTotalTimeoutConstant = 0; + timeouts.WriteTotalTimeoutMultiplier = 0; + timeouts.WriteTotalTimeoutConstant = 0; + if (!SetCommTimeouts(serport, &timeouts)) + return "Unable to configure serial timeouts"; + } + + return NULL; +} + +/* + * Called to set up the serial connection. + * + * Returns an error message, or NULL on success. + * + * Also places the canonical host name into `realhost'. It must be + * freed by the caller. + */ +static const char *serial_init(Seat *seat, Backend **backend_handle, + LogContext *logctx, Conf *conf, + const char *host, int port, + char **realhost, bool nodelay, bool keepalive) +{ + Serial *serial; + HANDLE serport; + const char *err; + char *serline; + + /* No local authentication phase in this protocol */ + seat_set_trust_status(seat, false); + + serial = snew(Serial); + serial->port = INVALID_HANDLE_VALUE; + serial->out = serial->in = NULL; + serial->bufsize = 0; + serial->break_in_progress = false; + serial->backend.vt = &serial_backend; + *backend_handle = &serial->backend; + + serial->seat = seat; + serial->logctx = logctx; + + serline = conf_get_str(conf, CONF_serline); + logeventf(serial->logctx, "Opening serial device %s", serline); + + { + /* + * Munge the string supplied by the user into a Windows filename. + * + * Windows supports opening a few "legacy" devices (including + * COM1-9) by specifying their names verbatim as a filename to + * open. (Thus, no files can ever have these names. See + * + * ("Naming a File") for the complete list of reserved names.) + * + * However, this doesn't let you get at devices COM10 and above. + * For that, you need to specify a filename like "\\.\COM10". + * This is also necessary for special serial and serial-like + * devices such as \\.\WCEUSBSH001. It also works for the "legacy" + * names, so you can do \\.\COM1 (verified as far back as Win95). + * See + * (CreateFile() docs). + * + * So, we believe that prepending "\\.\" should always be the + * Right Thing. However, just in case someone finds something to + * talk to that doesn't exist under there, if the serial line + * contains a backslash, we use it verbatim. (This also lets + * existing configurations using \\.\ continue working.) + */ + char *serfilename = + dupprintf("%s%s", strchr(serline, '\\') ? "" : "\\\\.\\", serline); + serport = CreateFile(serfilename, GENERIC_READ | GENERIC_WRITE, 0, NULL, + OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); + sfree(serfilename); + } + + if (serport == INVALID_HANDLE_VALUE) + return "Unable to open serial port"; + + err = serial_configure(serial, serport, conf); + if (err) + return err; + + serial->port = serport; + serial->out = handle_output_new(serport, serial_sentdata, serial, + HANDLE_FLAG_OVERLAPPED); + serial->in = handle_input_new(serport, serial_gotdata, serial, + HANDLE_FLAG_OVERLAPPED | + HANDLE_FLAG_IGNOREEOF | + HANDLE_FLAG_UNITBUFFER); + + *realhost = dupstr(serline); + + /* + * Specials are always available. + */ + seat_update_specials_menu(serial->seat); + + return NULL; +} + +static void serial_free(Backend *be) +{ + Serial *serial = container_of(be, Serial, backend); + + serial_terminate(serial); + expire_timer_context(serial); + sfree(serial); +} + +static void serial_reconfig(Backend *be, Conf *conf) +{ + Serial *serial = container_of(be, Serial, backend); + + serial_configure(serial, serial->port, conf); + + /* + * FIXME: what should we do if that call returned a non-NULL error + * message? + */ +} + +/* + * Called to send data down the serial connection. + */ +static size_t serial_send(Backend *be, const char *buf, size_t len) +{ + Serial *serial = container_of(be, Serial, backend); + + if (serial->out == NULL) + return 0; + + serial->bufsize = handle_write(serial->out, buf, len); + return serial->bufsize; +} + +/* + * Called to query the current sendability status. + */ +static size_t serial_sendbuffer(Backend *be) +{ + Serial *serial = container_of(be, Serial, backend); + return serial->bufsize; +} + +/* + * Called to set the size of the window + */ +static void serial_size(Backend *be, int width, int height) +{ + /* Do nothing! */ + return; +} + +static void serbreak_timer(void *ctx, unsigned long now) +{ + Serial *serial = (Serial *)ctx; + + if (now == serial->clearbreak_time && serial->port) { + ClearCommBreak(serial->port); + serial->break_in_progress = false; + logevent(serial->logctx, "Finished serial break"); + } +} + +/* + * Send serial special codes. + */ +static void serial_special(Backend *be, SessionSpecialCode code, int arg) +{ + Serial *serial = container_of(be, Serial, backend); + + if (serial->port && code == SS_BRK) { + logevent(serial->logctx, "Starting serial break at user request"); + SetCommBreak(serial->port); + /* + * To send a serial break on Windows, we call SetCommBreak + * to begin the break, then wait a bit, and then call + * ClearCommBreak to finish it. Hence, I must use timing.c + * to arrange a callback when it's time to do the latter. + * + * SUS says that a default break length must be between 1/4 + * and 1/2 second. FreeBSD apparently goes with 2/5 second, + * and so will I. + */ + serial->clearbreak_time = + schedule_timer(TICKSPERSEC * 2 / 5, serbreak_timer, serial); + serial->break_in_progress = true; + } + + return; +} + +/* + * Return a list of the special codes that make sense in this + * protocol. + */ +static const SessionSpecial *serial_get_specials(Backend *be) +{ + static const SessionSpecial specials[] = { + {"Break", SS_BRK}, + {NULL, SS_EXITMENU} + }; + return specials; +} + +static bool serial_connected(Backend *be) +{ + return true; /* always connected */ +} + +static bool serial_sendok(Backend *be) +{ + return true; +} + +static void serial_unthrottle(Backend *be, size_t backlog) +{ + Serial *serial = container_of(be, Serial, backend); + if (serial->in) + handle_unthrottle(serial->in, backlog); +} + +static bool serial_ldisc(Backend *be, int option) +{ + /* + * Local editing and local echo are off by default. + */ + return false; +} + +static void serial_provide_ldisc(Backend *be, Ldisc *ldisc) +{ + /* This is a stub. */ +} + +static int serial_exitcode(Backend *be) +{ + Serial *serial = container_of(be, Serial, backend); + if (serial->port != INVALID_HANDLE_VALUE) + return -1; /* still connected */ + else + /* Exit codes are a meaningless concept with serial ports */ + return INT_MAX; +} + +/* + * cfg_info for Serial does nothing at all. + */ +static int serial_cfg_info(Backend *be) +{ + return 0; +} + +const struct BackendVtable serial_backend = { + serial_init, + serial_free, + serial_reconfig, + serial_send, + serial_sendbuffer, + serial_size, + serial_special, + serial_get_specials, + serial_connected, + serial_exitcode, + serial_sendok, + serial_ldisc, + serial_provide_ldisc, + serial_unthrottle, + serial_cfg_info, + NULL /* test_for_upstream */, + "serial", + PROT_SERIAL, + 0 +}; diff --git a/0.73_My_PuTTY/windows/winsftp.c b/0.74_My_PuTTY/windows/winsftp.c similarity index 97% rename from 0.73_My_PuTTY/windows/winsftp.c rename to 0.74_My_PuTTY/windows/winsftp.c index b324c2a..8cc9762 100644 --- a/0.73_My_PuTTY/windows/winsftp.c +++ b/0.74_My_PuTTY/windows/winsftp.c @@ -311,7 +311,7 @@ DirHandle *open_directory(const char *name, const char **errmsg) DirHandle *ret; /* Enumerate files in dir `foo'. */ - findfile = dupcat(name, "/*", NULL); + findfile = dupcat(name, "/*"); h = FindFirstFile(findfile, &fdat); if (h == INVALID_HANDLE_VALUE) { *errmsg = win_strerror(GetLastError()); @@ -432,7 +432,7 @@ WildcardMatcher *begin_wildcard_matching(const char *name) (fdat.cFileName[1] == '.' && fdat.cFileName[2] == '\0'))) ret->name = NULL; else - ret->name = dupcat(ret->srcpath, fdat.cFileName, NULL); + ret->name = dupcat(ret->srcpath, fdat.cFileName); return ret; } @@ -450,7 +450,7 @@ char *wildcard_get_filename(WildcardMatcher *dir) (fdat.cFileName[1] == '.' && fdat.cFileName[2] == '\0'))) dir->name = NULL; else - dir->name = dupcat(dir->srcpath, fdat.cFileName, NULL); + dir->name = dupcat(dir->srcpath, fdat.cFileName); } if (dir->name) { @@ -492,7 +492,7 @@ char *dir_file_cat(const char *dir, const char *file) return dupcat( dir, (ptrlen_endswith(dir_pl, PTRLEN_LITERAL("\\"), NULL) || ptrlen_endswith(dir_pl, PTRLEN_LITERAL("/"), NULL)) ? "" : "\\", - file, NULL); + file); } /* ---------------------------------------------------------------------- @@ -504,19 +504,21 @@ char *dir_file_cat(const char *dir, const char *file) */ static SOCKET sftp_ssh_socket = INVALID_SOCKET; static HANDLE netevent = INVALID_HANDLE_VALUE; -char *do_select(SOCKET skt, bool startup) +char *do_select(SOCKET skt, bool enable) { int events; - if (startup) + if (enable) sftp_ssh_socket = skt; else sftp_ssh_socket = INVALID_SOCKET; + if (netevent == INVALID_HANDLE_VALUE) + netevent = CreateEvent(NULL, false, false, NULL); + if (p_WSAEventSelect) { - if (startup) { + if (enable) { events = (FD_CONNECT | FD_READ | FD_WRITE | FD_OOB | FD_CLOSE | FD_ACCEPT); - netevent = CreateEvent(NULL, false, false, NULL); } else { events = 0; } @@ -769,7 +771,9 @@ char *ssh_sftp_get_cmdline(const char *prompt, bool no_fds_ok) do { ret = do_eventsel_loop(ctx->event); - /* Error return can only occur if netevent==NULL, and it ain't. */ + /* do_eventsel_loop can't return an error (unlike + * ssh_sftp_loop_iteration, which can return -1 if select goes + * wrong or if the socket doesn't exist). */ assert(ret >= 0); } while (ret == 0); diff --git a/0.73_My_PuTTY/windows/winshare.c b/0.74_My_PuTTY/windows/winshare.c similarity index 100% rename from 0.73_My_PuTTY/windows/winshare.c rename to 0.74_My_PuTTY/windows/winshare.c diff --git a/0.73_My_PuTTY/windows/winstore.c b/0.74_My_PuTTY/windows/winstore.c similarity index 98% rename from 0.73_My_PuTTY/windows/winstore.c rename to 0.74_My_PuTTY/windows/winstore.c index 1ea59ba..04b2e47 100644 --- a/0.73_My_PuTTY/windows/winstore.c +++ b/0.74_My_PuTTY/windows/winstore.c @@ -308,7 +308,7 @@ FontSpec *read_setting_fontspec(settings_r *handle, const char *name) if (!fontname) return NULL; - settingname = dupcat(name, "IsBold", NULL); + settingname = dupcat(name, "IsBold"); isbold = read_setting_i(handle, settingname, -1); sfree(settingname); if (isbold == -1) { @@ -316,7 +316,7 @@ FontSpec *read_setting_fontspec(settings_r *handle, const char *name) return NULL; } - settingname = dupcat(name, "CharSet", NULL); + settingname = dupcat(name, "CharSet"); charset = read_setting_i(handle, settingname, -1); sfree(settingname); if (charset == -1) { @@ -324,7 +324,7 @@ FontSpec *read_setting_fontspec(settings_r *handle, const char *name) return NULL; } - settingname = dupcat(name, "Height", NULL); + settingname = dupcat(name, "Height"); height = read_setting_i(handle, settingname, INT_MIN); sfree(settingname); if (height == INT_MIN) { @@ -343,13 +343,13 @@ void write_setting_fontspec(settings_w *handle, char *settingname; write_setting_s(handle, name, font->name); - settingname = dupcat(name, "IsBold", NULL); + settingname = dupcat(name, "IsBold"); write_setting_i(handle, settingname, font->isbold); sfree(settingname); - settingname = dupcat(name, "CharSet", NULL); + settingname = dupcat(name, "CharSet"); write_setting_i(handle, settingname, font->charset); sfree(settingname); - settingname = dupcat(name, "Height", NULL); + settingname = dupcat(name, "Height"); write_setting_i(handle, settingname, font->height); sfree(settingname); } @@ -1071,15 +1071,13 @@ static HANDLE access_random_seed(int action) char profile[MAX_PATH + 1]; if (SUCCEEDED(p_SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT, profile)) && - try_random_seed_and_free(dupcat(profile, "\\PUTTY.RND", - (const char *)NULL), + try_random_seed_and_free(dupcat(profile, "\\PUTTY.RND"), action, &rethandle)) return rethandle; if (SUCCEEDED(p_SHGetFolderPathA(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, profile)) && - try_random_seed_and_free(dupcat(profile, "\\PUTTY.RND", - (const char *)NULL), + try_random_seed_and_free(dupcat(profile, "\\PUTTY.RND"), action, &rethandle)) return rethandle; } @@ -1102,8 +1100,7 @@ static HANDLE access_random_seed(int action) if (drvlen < lenof(drv) && pathlen < lenof(path) && pathlen > 0 && try_random_seed_and_free( - dupcat(drv, path, "\\PUTTY.RND", (const char *)NULL), - action, &rethandle)) + dupcat(drv, path, "\\PUTTY.RND"), action, &rethandle)) return rethandle; } @@ -1115,8 +1112,7 @@ static HANDLE access_random_seed(int action) DWORD len = GetWindowsDirectory(windir, sizeof(windir)); if (len < lenof(windir) && try_random_seed_and_free( - dupcat(windir, "\\PUTTY.RND", (const char *)NULL), - action, &rethandle)) + dupcat(windir, "\\PUTTY.RND"), action, &rethandle)) return rethandle; } diff --git a/0.73_My_PuTTY/windows/winstuff.h b/0.74_My_PuTTY/windows/winstuff.h similarity index 100% rename from 0.73_My_PuTTY/windows/winstuff.h rename to 0.74_My_PuTTY/windows/winstuff.h diff --git a/0.73_My_PuTTY/windows/wintime.c b/0.74_My_PuTTY/windows/wintime.c similarity index 100% rename from 0.73_My_PuTTY/windows/wintime.c rename to 0.74_My_PuTTY/windows/wintime.c diff --git a/0.73_My_PuTTY/windows/winucs.c b/0.74_My_PuTTY/windows/winucs.c similarity index 100% rename from 0.73_My_PuTTY/windows/winucs.c rename to 0.74_My_PuTTY/windows/winucs.c diff --git a/0.73_My_PuTTY/windows/winutils.c b/0.74_My_PuTTY/windows/winutils.c similarity index 100% rename from 0.73_My_PuTTY/windows/winutils.c rename to 0.74_My_PuTTY/windows/winutils.c diff --git a/0.73_My_PuTTY/windows/winx11.c b/0.74_My_PuTTY/windows/winx11.c similarity index 100% rename from 0.73_My_PuTTY/windows/winx11.c rename to 0.74_My_PuTTY/windows/winx11.c diff --git a/0.73_My_PuTTY/x11fwd.c b/0.74_My_PuTTY/x11fwd.c similarity index 100% rename from 0.73_My_PuTTY/x11fwd.c rename to 0.74_My_PuTTY/x11fwd.c diff --git a/kitty_savedump.c b/kitty_savedump.c index 13c1d90..7920133 100644 --- a/kitty_savedump.c +++ b/kitty_savedump.c @@ -279,6 +279,7 @@ void SaveDumpConfig( FILE *fp, Conf * conf ) { fprintf( fp, "compression=%d\n", conf_get_bool(conf,CONF_compression) ) ; //fprintf( fp, "ssh_kexlist=%d\n", conf_get_int(conf,CONF_ssh_kexlist) ) ; //fprintf( fp, "ssh_hklist=%d\n", conf_get_int(conf,CONF_ssh_hklist) ) ; + fprintf( fp, "ssh_prefer_known_hostkeys=%d\n", conf_get_bool(conf,CONF_ssh_prefer_known_hostkeys) ) ; fprintf( fp, "ssh_rekey_time=%d\n", conf_get_int(conf,CONF_ssh_rekey_time) ) ; fprintf( fp, "ssh_rekey_data=%s\n", conf_get_str(conf,CONF_ssh_rekey_data) ) ; fprintf( fp, "tryagent=%d\n", conf_get_bool(conf,CONF_tryagent) ) ; diff --git a/kitty_settings.c b/kitty_settings.c index 156607b..3023e59 100644 --- a/kitty_settings.c +++ b/kitty_settings.c @@ -125,6 +125,7 @@ void save_open_settings_forced(char *filename, Conf *conf) { wprefs_forced(sesskey, "Cipher", ciphernames, CIPHER_MAX, conf, CONF_ssh_cipherlist); wprefs_forced(sesskey, "KEX", kexnames, KEX_MAX, conf, CONF_ssh_kexlist); wprefs_forced(sesskey, "HostKey", hknames, HK_MAX, conf, CONF_ssh_hklist); + write_setting_b_forced(sesskey, "PreferKnownHostKeys", conf_get_bool(conf, CONF_ssh_prefer_known_hostkeys)); write_setting_i_forced(sesskey, "RekeyTime", conf_get_int(conf, CONF_ssh_rekey_time)); #ifndef NO_GSSAPI write_setting_i_forced(sesskey, "GssapiRekey", conf_get_int(conf, CONF_gssapirekey)); @@ -658,6 +659,7 @@ void load_open_settings_forced(char *filename, Conf *conf) { } gprefs_forced(sesskey, "HostKey", "ed25519,ecdsa,rsa,dsa,WARN", hknames, HK_MAX, conf, CONF_ssh_hklist); + gppb_forced(sesskey, "PreferKnownHostKeys", true, conf, CONF_ssh_prefer_known_hostkeys); gppi_forced(sesskey, "RekeyTime", 60, conf, CONF_ssh_rekey_time); #ifndef NO_GSSAPI gppi_forced(sesskey, "GssapiRekey", GSS_DEF_REKEY_MINS, conf, CONF_gssapirekey); diff --git a/wincrypt/sshbn.c b/wincrypt/sshbn.c deleted file mode 100644 index 3ae153d..0000000 --- a/wincrypt/sshbn.c +++ /dev/null @@ -1,2191 +0,0 @@ -/* - * Bignum routines for RSA and DH and stuff. - */ - -#include -#include -#include -#include -#include -#include - -#include "misc.h" - -#include "sshbn.h" - -#define BIGNUM_INTERNAL -typedef BignumInt *Bignum; - -#include "ssh.h" - -Bignum modmul(Bignum p, Bignum q, Bignum mod) ; -Bignum bigmul(Bignum a, Bignum b) ; -Bignum bigadd(Bignum a, Bignum b) ; -Bignum bigsub(Bignum a, Bignum b) ; -Bignum bignum_from_long(unsigned long n) ; -Bignum bigmod(Bignum a, Bignum b) ; -Bignum modinv(Bignum number, Bignum modulus) ; - -BignumInt bnZero[1] = { 0 }; -BignumInt bnOne[2] = { 1, 1 }; -BignumInt bnTen[2] = { 1, 10 }; - -/* - * The Bignum format is an array of `BignumInt'. The first - * element of the array counts the remaining elements. The - * remaining elements express the actual number, base 2^BIGNUM_INT_BITS, _least_ - * significant digit first. (So it's trivial to extract the bit - * with value 2^n for any n.) - * - * All Bignums in this module are positive. Negative numbers must - * be dealt with outside it. - * - * INVARIANT: the most significant word of any Bignum must be - * nonzero. - */ - -Bignum Zero = bnZero, One = bnOne, Ten = bnTen; - -static Bignum newbn(int length) -{ - Bignum b; - - assert(length >= 0 && length < INT_MAX / BIGNUM_INT_BITS); - - b = snewn(length + 1, BignumInt); - memset(b, 0, (length + 1) * sizeof(*b)); - b[0] = length; - return b; -} - -void bn_restore_invariant(Bignum b) -{ - while (b[0] > 1 && b[b[0]] == 0) - b[0]--; -} - -Bignum copybn(Bignum orig) -{ - Bignum b = snewn(orig[0] + 1, BignumInt); - if (!b) - abort(); /* FIXME */ - memcpy(b, orig, (orig[0] + 1) * sizeof(*b)); - return b; -} - -void freebn(Bignum b) -{ - /* - * Burn the evidence, just in case. - */ - smemclr(b, sizeof(b[0]) * (b[0] + 1)); - sfree(b); -} - -Bignum bn_power_2(int n) -{ - Bignum ret; - - assert(n >= 0); - - ret = newbn(n / BIGNUM_INT_BITS + 1); - bignum_set_bit(ret, n, 1); - return ret; -} - -/* - * Internal addition. Sets c = a - b, where 'a', 'b' and 'c' are all - * big-endian arrays of 'len' BignumInts. Returns the carry off the - * top. - */ -static BignumCarry internal_add(const BignumInt *a, const BignumInt *b, - BignumInt *c, int len) -{ - int i; - BignumCarry carry = 0; - - for (i = len-1; i >= 0; i--) - BignumADC(c[i], carry, a[i], b[i], carry); - - return (BignumInt)carry; -} - -/* - * Internal subtraction. Sets c = a - b, where 'a', 'b' and 'c' are - * all big-endian arrays of 'len' BignumInts. Any borrow from the top - * is ignored. - */ -static void internal_sub(const BignumInt *a, const BignumInt *b, - BignumInt *c, int len) -{ - int i; - BignumCarry carry = 1; - - for (i = len-1; i >= 0; i--) - BignumADC(c[i], carry, a[i], ~b[i], carry); -} - -/* - * Compute c = a * b. - * Input is in the first len words of a and b. - * Result is returned in the first 2*len words of c. - * - * 'scratch' must point to an array of BignumInt of size at least - * mul_compute_scratch(len). (This covers the needs of internal_mul - * and all its recursive calls to itself.) - */ -#define KARATSUBA_THRESHOLD 50 -static int mul_compute_scratch(int len) -{ - int ret = 0; - while (len > KARATSUBA_THRESHOLD) { - int toplen = len/2, botlen = len - toplen; /* botlen is the bigger */ - int midlen = botlen + 1; - ret += 4*midlen; - len = midlen; - } - return ret; -} -static void internal_mul(const BignumInt *a, const BignumInt *b, - BignumInt *c, int len, BignumInt *scratch) -{ - if (len > KARATSUBA_THRESHOLD) { - int i; - - /* - * Karatsuba divide-and-conquer algorithm. Cut each input in - * half, so that it's expressed as two big 'digits' in a giant - * base D: - * - * a = a_1 D + a_0 - * b = b_1 D + b_0 - * - * Then the product is of course - * - * ab = a_1 b_1 D^2 + (a_1 b_0 + a_0 b_1) D + a_0 b_0 - * - * and we compute the three coefficients by recursively - * calling ourself to do half-length multiplications. - * - * The clever bit that makes this worth doing is that we only - * need _one_ half-length multiplication for the central - * coefficient rather than the two that it obviouly looks - * like, because we can use a single multiplication to compute - * - * (a_1 + a_0) (b_1 + b_0) = a_1 b_1 + a_1 b_0 + a_0 b_1 + a_0 b_0 - * - * and then we subtract the other two coefficients (a_1 b_1 - * and a_0 b_0) which we were computing anyway. - * - * Hence we get to multiply two numbers of length N in about - * three times as much work as it takes to multiply numbers of - * length N/2, which is obviously better than the four times - * as much work it would take if we just did a long - * conventional multiply. - */ - - int toplen = len/2, botlen = len - toplen; /* botlen is the bigger */ - int midlen = botlen + 1; - BignumCarry carry; -#ifdef KARA_DEBUG - int i; -#endif - - /* - * The coefficients a_1 b_1 and a_0 b_0 just avoid overlapping - * in the output array, so we can compute them immediately in - * place. - */ - -#ifdef KARA_DEBUG - printf("a1,a0 = 0x"); - for (i = 0; i < len; i++) { - if (i == toplen) printf(", 0x"); - printf("%0*x", BIGNUM_INT_BITS/4, a[i]); - } - printf("\n"); - printf("b1,b0 = 0x"); - for (i = 0; i < len; i++) { - if (i == toplen) printf(", 0x"); - printf("%0*x", BIGNUM_INT_BITS/4, b[i]); - } - printf("\n"); -#endif - - /* a_1 b_1 */ - internal_mul(a, b, c, toplen, scratch); -#ifdef KARA_DEBUG - printf("a1b1 = 0x"); - for (i = 0; i < 2*toplen; i++) { - printf("%0*x", BIGNUM_INT_BITS/4, c[i]); - } - printf("\n"); -#endif - - /* a_0 b_0 */ - internal_mul(a + toplen, b + toplen, c + 2*toplen, botlen, scratch); -#ifdef KARA_DEBUG - printf("a0b0 = 0x"); - for (i = 0; i < 2*botlen; i++) { - printf("%0*x", BIGNUM_INT_BITS/4, c[2*toplen+i]); - } - printf("\n"); -#endif - - /* Zero padding. midlen exceeds toplen by at most 2, so just - * zero the first two words of each input and the rest will be - * copied over. */ - scratch[0] = scratch[1] = scratch[midlen] = scratch[midlen+1] = 0; - - for (i = 0; i < toplen; i++) { - scratch[midlen - toplen + i] = a[i]; /* a_1 */ - scratch[2*midlen - toplen + i] = b[i]; /* b_1 */ - } - - /* compute a_1 + a_0 */ - scratch[0] = internal_add(scratch+1, a+toplen, scratch+1, botlen); -#ifdef KARA_DEBUG - printf("a1plusa0 = 0x"); - for (i = 0; i < midlen; i++) { - printf("%0*x", BIGNUM_INT_BITS/4, scratch[i]); - } - printf("\n"); -#endif - /* compute b_1 + b_0 */ - scratch[midlen] = internal_add(scratch+midlen+1, b+toplen, - scratch+midlen+1, botlen); -#ifdef KARA_DEBUG - printf("b1plusb0 = 0x"); - for (i = 0; i < midlen; i++) { - printf("%0*x", BIGNUM_INT_BITS/4, scratch[midlen+i]); - } - printf("\n"); -#endif - - /* - * Now we can do the third multiplication. - */ - internal_mul(scratch, scratch + midlen, scratch + 2*midlen, midlen, - scratch + 4*midlen); -#ifdef KARA_DEBUG - printf("a1plusa0timesb1plusb0 = 0x"); - for (i = 0; i < 2*midlen; i++) { - printf("%0*x", BIGNUM_INT_BITS/4, scratch[2*midlen+i]); - } - printf("\n"); -#endif - - /* - * Now we can reuse the first half of 'scratch' to compute the - * sum of the outer two coefficients, to subtract from that - * product to obtain the middle one. - */ - scratch[0] = scratch[1] = scratch[2] = scratch[3] = 0; - for (i = 0; i < 2*toplen; i++) - scratch[2*midlen - 2*toplen + i] = c[i]; - scratch[1] = internal_add(scratch+2, c + 2*toplen, - scratch+2, 2*botlen); -#ifdef KARA_DEBUG - printf("a1b1plusa0b0 = 0x"); - for (i = 0; i < 2*midlen; i++) { - printf("%0*x", BIGNUM_INT_BITS/4, scratch[i]); - } - printf("\n"); -#endif - - internal_sub(scratch + 2*midlen, scratch, - scratch + 2*midlen, 2*midlen); -#ifdef KARA_DEBUG - printf("a1b0plusa0b1 = 0x"); - for (i = 0; i < 2*midlen; i++) { - printf("%0*x", BIGNUM_INT_BITS/4, scratch[2*midlen+i]); - } - printf("\n"); -#endif - - /* - * And now all we need to do is to add that middle coefficient - * back into the output. We may have to propagate a carry - * further up the output, but we can be sure it won't - * propagate right the way off the top. - */ - carry = internal_add(c + 2*len - botlen - 2*midlen, - scratch + 2*midlen, - c + 2*len - botlen - 2*midlen, 2*midlen); - i = 2*len - botlen - 2*midlen - 1; - while (carry) { - assert(i >= 0); - BignumADC(c[i], carry, c[i], 0, carry); - i--; - } -#ifdef KARA_DEBUG - printf("ab = 0x"); - for (i = 0; i < 2*len; i++) { - printf("%0*x", BIGNUM_INT_BITS/4, c[i]); - } - printf("\n"); -#endif - - } else { - int i; - BignumInt carry; - const BignumInt *ap, *bp; - BignumInt *cp, *cps; - - /* - * Multiply in the ordinary O(N^2) way. - */ - - for (i = 0; i < 2 * len; i++) - c[i] = 0; - - for (cps = c + 2*len, ap = a + len; ap-- > a; cps--) { - carry = 0; - for (cp = cps, bp = b + len; cp--, bp-- > b ;) - BignumMULADD2(carry, *cp, *ap, *bp, *cp, carry); - *cp = carry; - } - } -} - -/* - * Variant form of internal_mul used for the initial step of - * Montgomery reduction. Only bothers outputting 'len' words - * (everything above that is thrown away). - */ -static void internal_mul_low(const BignumInt *a, const BignumInt *b, - BignumInt *c, int len, BignumInt *scratch) -{ - if (len > KARATSUBA_THRESHOLD) { - int i; - - /* - * Karatsuba-aware version of internal_mul_low. As before, we - * express each input value as a shifted combination of two - * halves: - * - * a = a_1 D + a_0 - * b = b_1 D + b_0 - * - * Then the full product is, as before, - * - * ab = a_1 b_1 D^2 + (a_1 b_0 + a_0 b_1) D + a_0 b_0 - * - * Provided we choose D on the large side (so that a_0 and b_0 - * are _at least_ as long as a_1 and b_1), we don't need the - * topmost term at all, and we only need half of the middle - * term. So there's no point in doing the proper Karatsuba - * optimisation which computes the middle term using the top - * one, because we'd take as long computing the top one as - * just computing the middle one directly. - * - * So instead, we do a much more obvious thing: we call the - * fully optimised internal_mul to compute a_0 b_0, and we - * recursively call ourself to compute the _bottom halves_ of - * a_1 b_0 and a_0 b_1, each of which we add into the result - * in the obvious way. - * - * In other words, there's no actual Karatsuba _optimisation_ - * in this function; the only benefit in doing it this way is - * that we call internal_mul proper for a large part of the - * work, and _that_ can optimise its operation. - */ - - int toplen = len/2, botlen = len - toplen; /* botlen is the bigger */ - - /* - * Scratch space for the various bits and pieces we're going - * to be adding together: we need botlen*2 words for a_0 b_0 - * (though we may end up throwing away its topmost word), and - * toplen words for each of a_1 b_0 and a_0 b_1. That adds up - * to exactly 2*len. - */ - - /* a_0 b_0 */ - internal_mul(a + toplen, b + toplen, scratch + 2*toplen, botlen, - scratch + 2*len); - - /* a_1 b_0 */ - internal_mul_low(a, b + len - toplen, scratch + toplen, toplen, - scratch + 2*len); - - /* a_0 b_1 */ - internal_mul_low(a + len - toplen, b, scratch, toplen, - scratch + 2*len); - - /* Copy the bottom half of the big coefficient into place */ - for (i = 0; i < botlen; i++) - c[toplen + i] = scratch[2*toplen + botlen + i]; - - /* Add the two small coefficients, throwing away the returned carry */ - internal_add(scratch, scratch + toplen, scratch, toplen); - - /* And add that to the large coefficient, leaving the result in c. */ - internal_add(scratch, scratch + 2*toplen + botlen - toplen, - c, toplen); - - } else { - int i; - BignumInt carry; - const BignumInt *ap, *bp; - BignumInt *cp, *cps; - - /* - * Multiply in the ordinary O(N^2) way. - */ - - for (i = 0; i < len; i++) - c[i] = 0; - - for (cps = c + len, ap = a + len; ap-- > a; cps--) { - carry = 0; - for (cp = cps, bp = b + len; bp--, cp-- > c ;) - BignumMULADD2(carry, *cp, *ap, *bp, *cp, carry); - } - } -} - -/* - * Montgomery reduction. Expects x to be a big-endian array of 2*len - * BignumInts whose value satisfies 0 <= x < rn (where r = 2^(len * - * BIGNUM_INT_BITS) is the Montgomery base). Returns in the same array - * a value x' which is congruent to xr^{-1} mod n, and satisfies 0 <= - * x' < n. - * - * 'n' and 'mninv' should be big-endian arrays of 'len' BignumInts - * each, containing respectively n and the multiplicative inverse of - * -n mod r. - * - * 'tmp' is an array of BignumInt used as scratch space, of length at - * least 3*len + mul_compute_scratch(len). - */ -static void monty_reduce(BignumInt *x, const BignumInt *n, - const BignumInt *mninv, BignumInt *tmp, int len) -{ - int i; - BignumInt carry; - - /* - * Multiply x by (-n)^{-1} mod r. This gives us a value m such - * that mn is congruent to -x mod r. Hence, mn+x is an exact - * multiple of r, and is also (obviously) congruent to x mod n. - */ - internal_mul_low(x + len, mninv, tmp, len, tmp + 3*len); - - /* - * Compute t = (mn+x)/r in ordinary, non-modular, integer - * arithmetic. By construction this is exact, and is congruent mod - * n to x * r^{-1}, i.e. the answer we want. - * - * The following multiply leaves that answer in the _most_ - * significant half of the 'x' array, so then we must shift it - * down. - */ - internal_mul(tmp, n, tmp+len, len, tmp + 3*len); - carry = internal_add(x, tmp+len, x, 2*len); - for (i = 0; i < len; i++) - x[len + i] = x[i], x[i] = 0; - - /* - * Reduce t mod n. This doesn't require a full-on division by n, - * but merely a test and single optional subtraction, since we can - * show that 0 <= t < 2n. - * - * Proof: - * + we computed m mod r, so 0 <= m < r. - * + so 0 <= mn < rn, obviously - * + hence we only need 0 <= x < rn to guarantee that 0 <= mn+x < 2rn - * + yielding 0 <= (mn+x)/r < 2n as required. - */ - if (!carry) { - for (i = 0; i < len; i++) - if (x[len + i] != n[i]) - break; - } - if (carry || i >= len || x[len + i] > n[i]) - internal_sub(x+len, n, x+len, len); -} - -static void internal_add_shifted(BignumInt *number, - BignumInt n, int shift) -{ - int word = 1 + (shift / BIGNUM_INT_BITS); - int bshift = shift % BIGNUM_INT_BITS; - BignumInt addendh, addendl; - BignumCarry carry; - - addendl = n << bshift; - addendh = (bshift == 0 ? 0 : n >> (BIGNUM_INT_BITS - bshift)); - - assert(word <= number[0]); - BignumADC(number[word], carry, number[word], addendl, 0); - word++; - if (!addendh && !carry) - return; - assert(word <= number[0]); - BignumADC(number[word], carry, number[word], addendh, carry); - word++; - while (carry) { - assert(word <= number[0]); - BignumADC(number[word], carry, number[word], 0, carry); - word++; - } -} - -static int bn_clz(BignumInt x) -{ - /* - * Count the leading zero bits in x. Equivalently, how far left - * would we need to shift x to make its top bit set? - * - * Precondition: x != 0. - */ - - /* FIXME: would be nice to put in some compiler intrinsics under - * ifdef here */ - int i, ret = 0; - for (i = BIGNUM_INT_BITS / 2; i != 0; i >>= 1) { - if ((x >> (BIGNUM_INT_BITS-i)) == 0) { - x <<= i; - ret += i; - } - } - return ret; -} - -static BignumInt reciprocal_word(BignumInt d) -{ - BignumInt dshort, recip, prodh, prodl; - int corrections; - - /* - * Input: a BignumInt value d, with its top bit set. - */ - assert(d >> (BIGNUM_INT_BITS-1) == 1); - - /* - * Output: a value, shifted to fill a BignumInt, which is strictly - * less than 1/(d+1), i.e. is an *under*-estimate (but by as - * little as possible within the constraints) of the reciprocal of - * any number whose first BIGNUM_INT_BITS bits match d. - * - * Ideally we'd like to _totally_ fill BignumInt, i.e. always - * return a value with the top bit set. Unfortunately we can't - * quite guarantee that for all inputs and also return a fixed - * exponent. So instead we take our reciprocal to be - * 2^(BIGNUM_INT_BITS*2-1) / d, so that it has the top bit clear - * only in the exceptional case where d takes exactly the maximum - * value BIGNUM_INT_MASK; in that case, the top bit is clear and - * the next bit down is set. - */ - - /* - * Start by computing a half-length version of the answer, by - * straightforward division within a BignumInt. - */ - dshort = (d >> (BIGNUM_INT_BITS/2)) + 1; - recip = (BIGNUM_TOP_BIT + dshort - 1) / dshort; - recip <<= BIGNUM_INT_BITS - BIGNUM_INT_BITS/2; - - /* - * Newton-Raphson iteration to improve that starting reciprocal - * estimate: take f(x) = d - 1/x, and then the N-R formula gives - * x_new = x - f(x)/f'(x) = x - (d-1/x)/(1/x^2) = x(2-d*x). Or, - * taking our fixed-point representation into account, take f(x) - * to be d - K/x (where K = 2^(BIGNUM_INT_BITS*2-1) as discussed - * above) and then we get (2K - d*x) * x/K. - * - * Newton-Raphson doubles the number of correct bits at every - * iteration, and the initial division above already gave us half - * the output word, so it's only worth doing one iteration. - */ - BignumMULADD(prodh, prodl, recip, d, recip); - prodl = ~prodl; - prodh = ~prodh; - { - BignumCarry c; - BignumADC(prodl, c, prodl, 1, 0); - prodh += c; - } - BignumMUL(prodh, prodl, prodh, recip); - recip = (prodh << 1) | (prodl >> (BIGNUM_INT_BITS-1)); - - /* - * Now make sure we have the best possible reciprocal estimate, - * before we return it. We might have been off by a handful either - * way - not enough to bother with any better-thought-out kind of - * correction loop. - */ - BignumMULADD(prodh, prodl, recip, d, recip); - corrections = 0; - if (prodh >= BIGNUM_TOP_BIT) { - do { - BignumCarry c = 1; - BignumADC(prodl, c, prodl, ~d, c); prodh += BIGNUM_INT_MASK + c; - recip--; - corrections++; - } while (prodh >= ((BignumInt)1 << (BIGNUM_INT_BITS-1))); - } else { - while (1) { - BignumInt newprodh, newprodl; - BignumCarry c = 0; - BignumADC(newprodl, c, prodl, d, c); newprodh = prodh + c; - if (newprodh >= BIGNUM_TOP_BIT) - break; - prodh = newprodh; - prodl = newprodl; - recip++; - corrections++; - } - } - - return recip; -} - -/* - * Compute a = a % m. - * Input in first alen words of a and first mlen words of m. - * Output in first alen words of a - * (of which first alen-mlen words will be zero). - * Quotient is accumulated in the `quotient' array, which is a Bignum - * rather than the internal bigendian format. - * - * 'recip' must be the result of calling reciprocal_word() on the top - * BIGNUM_INT_BITS of the modulus (denoted m0 in comments below), with - * the topmost set bit normalised to the MSB of the input to - * reciprocal_word. 'rshift' is how far left the top nonzero word of - * the modulus had to be shifted to set that top bit. - */ -static void internal_mod(BignumInt *a, int alen, - BignumInt *m, int mlen, - BignumInt *quot, BignumInt recip, int rshift) -{ - int i, k; - -#ifdef DIVISION_DEBUG - { - int d; - printf("start division, m=0x"); - for (d = 0; d < mlen; d++) - printf("%0*llx", BIGNUM_INT_BITS/4, (unsigned long long)m[d]); - printf(", recip=%#0*llx, rshift=%d\n", - BIGNUM_INT_BITS/4, (unsigned long long)recip, rshift); - } -#endif - - /* - * Repeatedly use that reciprocal estimate to get a decent number - * of quotient bits, and subtract off the resulting multiple of m. - * - * Normally we expect to terminate this loop by means of finding - * out q=0 part way through, but one way in which we might not get - * that far in the first place is if the input a is actually zero, - * in which case we'll discard zero words from the front of a - * until we reach the termination condition in the for statement - * here. - */ - for (i = 0; i <= alen - mlen ;) { - BignumInt product; - BignumInt aword, q; - int shift, full_bitoffset, bitoffset, wordoffset; - -#ifdef DIVISION_DEBUG - { - int d; - printf("main loop, a=0x"); - for (d = 0; d < alen; d++) - printf("%0*llx", BIGNUM_INT_BITS/4, (unsigned long long)a[d]); - printf("\n"); - } -#endif - - if (a[i] == 0) { -#ifdef DIVISION_DEBUG - printf("zero word at i=%d\n", i); -#endif - i++; - continue; - } - - aword = a[i]; - shift = bn_clz(aword); - aword <<= shift; - if (shift > 0 && i+1 < alen) - aword |= a[i+1] >> (BIGNUM_INT_BITS - shift); - - { - BignumInt unused; - BignumMUL(q, unused, recip, aword); - (void)unused; - } - -#ifdef DIVISION_DEBUG - printf("i=%d, aword=%#0*llx, shift=%d, q=%#0*llx\n", - i, BIGNUM_INT_BITS/4, (unsigned long long)aword, - shift, BIGNUM_INT_BITS/4, (unsigned long long)q); -#endif - - /* - * Work out the right bit and word offsets to use when - * subtracting q*m from a. - * - * aword was taken from a[i], which means its LSB was at bit - * position (alen-1-i) * BIGNUM_INT_BITS. But then we shifted - * it left by 'shift', so now the low bit of aword corresponds - * to bit position (alen-1-i) * BIGNUM_INT_BITS - shift, i.e. - * aword is approximately equal to a / 2^(that). - * - * m0 comes from the top word of mod, so its LSB is at bit - * position (mlen-1) * BIGNUM_INT_BITS - rshift, i.e. it can - * be considered to be m / 2^(that power). 'recip' is the - * reciprocal of m0, times 2^(BIGNUM_INT_BITS*2-1), i.e. it's - * about 2^((mlen+1) * BIGNUM_INT_BITS - rshift - 1) / m. - * - * Hence, recip * aword is approximately equal to the product - * of those, which simplifies to - * - * a/m * 2^((mlen+2+i-alen)*BIGNUM_INT_BITS + shift - rshift - 1) - * - * But we've also shifted recip*aword down by BIGNUM_INT_BITS - * to form q, so we have - * - * q ~= a/m * 2^((mlen+1+i-alen)*BIGNUM_INT_BITS + shift - rshift - 1) - * - * and hence, when we now compute q*m, it will be about - * a*2^(all that lot), i.e. the negation of that expression is - * how far left we have to shift the product q*m to make it - * approximately equal to a. - */ - full_bitoffset = -((mlen+1+i-alen)*BIGNUM_INT_BITS + shift-rshift-1); -#ifdef DIVISION_DEBUG - printf("full_bitoffset=%d\n", full_bitoffset); -#endif - - if (full_bitoffset < 0) { - /* - * If we find ourselves needing to shift q*m _right_, that - * means we've reached the bottom of the quotient. Clip q - * so that its right shift becomes zero, and if that means - * q becomes _actually_ zero, this loop is done. - */ - if (full_bitoffset <= -BIGNUM_INT_BITS) - break; - q >>= -full_bitoffset; - full_bitoffset = 0; - if (!q) - break; -#ifdef DIVISION_DEBUG - printf("now full_bitoffset=%d, q=%#0*llx\n", - full_bitoffset, BIGNUM_INT_BITS/4, (unsigned long long)q); -#endif - } - - wordoffset = full_bitoffset / BIGNUM_INT_BITS; - bitoffset = full_bitoffset % BIGNUM_INT_BITS; -#ifdef DIVISION_DEBUG - printf("wordoffset=%d, bitoffset=%d\n", wordoffset, bitoffset); -#endif - - /* wordoffset as computed above is the offset between the LSWs - * of m and a. But in fact m and a are stored MSW-first, so we - * need to adjust it to be the offset between the actual array - * indices, and flip the sign too. */ - wordoffset = alen - mlen - wordoffset; - - if (bitoffset == 0) { - BignumCarry c = 1; - BignumInt prev_hi_word = 0; - for (k = mlen - 1; wordoffset+k >= i; k--) { - BignumInt mword = k<0 ? 0 : m[k]; - BignumMULADD(prev_hi_word, product, q, mword, prev_hi_word); -#ifdef DIVISION_DEBUG - printf(" aligned sub: product word for m[%d] = %#0*llx\n", - k, BIGNUM_INT_BITS/4, - (unsigned long long)product); -#endif -#ifdef DIVISION_DEBUG - printf(" aligned sub: subtrahend for a[%d] = %#0*llx\n", - wordoffset+k, BIGNUM_INT_BITS/4, - (unsigned long long)product); -#endif - BignumADC(a[wordoffset+k], c, a[wordoffset+k], ~product, c); - } - } else { - BignumInt add_word = 0; - BignumInt c = 1; - BignumInt prev_hi_word = 0; - for (k = mlen - 1; wordoffset+k >= i; k--) { - BignumInt mword = k<0 ? 0 : m[k]; - BignumMULADD(prev_hi_word, product, q, mword, prev_hi_word); -#ifdef DIVISION_DEBUG - printf(" unaligned sub: product word for m[%d] = %#0*llx\n", - k, BIGNUM_INT_BITS/4, - (unsigned long long)product); -#endif - - add_word |= product << bitoffset; - -#ifdef DIVISION_DEBUG - printf(" unaligned sub: subtrahend for a[%d] = %#0*llx\n", - wordoffset+k, - BIGNUM_INT_BITS/4, (unsigned long long)add_word); -#endif - BignumADC(a[wordoffset+k], c, a[wordoffset+k], ~add_word, c); - - add_word = product >> (BIGNUM_INT_BITS - bitoffset); - } - } - - if (quot) { -#ifdef DIVISION_DEBUG - printf("adding quotient word %#0*llx << %d\n", - BIGNUM_INT_BITS/4, (unsigned long long)q, full_bitoffset); -#endif - internal_add_shifted(quot, q, full_bitoffset); -#ifdef DIVISION_DEBUG - { - int d; - printf("now quot=0x"); - for (d = quot[0]; d > 0; d--) - printf("%0*llx", BIGNUM_INT_BITS/4, - (unsigned long long)quot[d]); - printf("\n"); - } -#endif - } - } - -#ifdef DIVISION_DEBUG - { - int d; - printf("end main loop, a=0x"); - for (d = 0; d < alen; d++) - printf("%0*llx", BIGNUM_INT_BITS/4, (unsigned long long)a[d]); - if (quot) { - printf(", quot=0x"); - for (d = quot[0]; d > 0; d--) - printf("%0*llx", BIGNUM_INT_BITS/4, - (unsigned long long)quot[d]); - } - printf("\n"); - } -#endif - - /* - * The above loop should terminate with the remaining value in a - * being strictly less than 2*m (if a >= 2*m then we should always - * have managed to get a nonzero q word), but we can't guarantee - * that it will be strictly less than m: consider a case where the - * remainder is 1, and another where the remainder is m-1. By the - * time a contains a value that's _about m_, you clearly can't - * distinguish those cases by looking at only the top word of a - - * you have to go all the way down to the bottom before you find - * out whether it's just less or just more than m. - * - * Hence, we now do a final fixup in which we subtract one last - * copy of m, or don't, accordingly. We should never have to - * subtract more than one copy of m here. - */ - for (i = 0; i < alen; i++) { - /* Compare a with m, word by word, from the MSW down. As soon - * as we encounter a difference, we know whether we need the - * fixup. */ - int mindex = mlen-alen+i; - BignumInt mword = mindex < 0 ? 0 : m[mindex]; - if (a[i] < mword) { -#ifdef DIVISION_DEBUG - printf("final fixup not needed, a < m\n"); -#endif - return; - } else if (a[i] > mword) { -#ifdef DIVISION_DEBUG - printf("final fixup is needed, a > m\n"); -#endif - break; - } - /* If neither of those cases happened, the words are the same, - * so keep going and look at the next one. */ - } -#ifdef DIVISION_DEBUG - if (i == mlen) /* if we printed neither of the above diagnostics */ - printf("final fixup is needed, a == m\n"); -#endif - - /* - * If we got here without returning, then a >= m, so we must - * subtract m, and increment the quotient. - */ - { - BignumCarry c = 1; - for (i = alen - 1; i >= 0; i--) { - int mindex = mlen-alen+i; - BignumInt mword = mindex < 0 ? 0 : m[mindex]; - BignumADC(a[i], c, a[i], ~mword, c); - } - } - if (quot) - internal_add_shifted(quot, 1, 0); - -#ifdef DIVISION_DEBUG - { - int d; - printf("after final fixup, a=0x"); - for (d = 0; d < alen; d++) - printf("%0*llx", BIGNUM_INT_BITS/4, (unsigned long long)a[d]); - if (quot) { - printf(", quot=0x"); - for (d = quot[0]; d > 0; d--) - printf("%0*llx", BIGNUM_INT_BITS/4, - (unsigned long long)quot[d]); - } - printf("\n"); - } -#endif -} - -/* - * Compute (base ^ exp) % mod, the pedestrian way. - */ -Bignum modpow_simple(Bignum base_in, Bignum exp, Bignum mod) -{ - BignumInt *a, *b, *n, *m, *scratch; - BignumInt recip; - int rshift; - int mlen, scratchlen, i, j; - Bignum base, result; - - /* - * The most significant word of mod needs to be non-zero. It - * should already be, but let's make sure. - */ - assert(mod[mod[0]] != 0); - - /* - * Make sure the base is smaller than the modulus, by reducing - * it modulo the modulus if not. - */ - base = bigmod(base_in, mod); - - /* Allocate m of size mlen, copy mod to m */ - /* We use big endian internally */ - mlen = mod[0]; - m = snewn(mlen, BignumInt); - for (j = 0; j < mlen; j++) - m[j] = mod[mod[0] - j]; - - /* Allocate n of size mlen, copy base to n */ - n = snewn(mlen, BignumInt); - i = mlen - base[0]; - for (j = 0; j < i; j++) - n[j] = 0; - for (j = 0; j < (int)base[0]; j++) - n[i + j] = base[base[0] - j]; - - /* Allocate a and b of size 2*mlen. Set a = 1 */ - a = snewn(2 * mlen, BignumInt); - b = snewn(2 * mlen, BignumInt); - for (i = 0; i < 2 * mlen; i++) - a[i] = 0; - a[2 * mlen - 1] = 1; - - /* Scratch space for multiplies */ - scratchlen = mul_compute_scratch(mlen); - scratch = snewn(scratchlen, BignumInt); - - /* Skip leading zero bits of exp. */ - i = 0; - j = BIGNUM_INT_BITS-1; - while (i < (int)exp[0] && (exp[exp[0] - i] & ((BignumInt)1 << j)) == 0) { - j--; - if (j < 0) { - i++; - j = BIGNUM_INT_BITS-1; - } - } - - /* Compute reciprocal of the top full word of the modulus */ - { - BignumInt m0 = m[0]; - rshift = bn_clz(m0); - if (rshift) { - m0 <<= rshift; - if (mlen > 1) - m0 |= m[1] >> (BIGNUM_INT_BITS - rshift); - } - recip = reciprocal_word(m0); - } - - /* Main computation */ - while (i < (int)exp[0]) { - while (j >= 0) { - internal_mul(a + mlen, a + mlen, b, mlen, scratch); - internal_mod(b, mlen * 2, m, mlen, NULL, recip, rshift); - if ((exp[exp[0] - i] & ((BignumInt)1 << j)) != 0) { - internal_mul(b + mlen, n, a, mlen, scratch); - internal_mod(a, mlen * 2, m, mlen, NULL, recip, rshift); - } else { - BignumInt *t; - t = a; - a = b; - b = t; - } - j--; - } - i++; - j = BIGNUM_INT_BITS-1; - } - - /* Copy result to buffer */ - result = newbn(mod[0]); - for (i = 0; i < mlen; i++) - result[result[0] - i] = a[i + mlen]; - while (result[0] > 1 && result[result[0]] == 0) - result[0]--; - - /* Free temporary arrays */ - smemclr(a, 2 * mlen * sizeof(*a)); - sfree(a); - smemclr(scratch, scratchlen * sizeof(*scratch)); - sfree(scratch); - smemclr(b, 2 * mlen * sizeof(*b)); - sfree(b); - smemclr(m, mlen * sizeof(*m)); - sfree(m); - smemclr(n, mlen * sizeof(*n)); - sfree(n); - - freebn(base); - - return result; -} - -/* - * Compute (base ^ exp) % mod. Uses the Montgomery multiplication - * technique where possible, falling back to modpow_simple otherwise. - */ -Bignum modpow(Bignum base_in, Bignum exp, Bignum mod) -{ - BignumInt *a, *b, *x, *n, *mninv, *scratch; - int len, scratchlen, i, j; - Bignum base, base2, r, rn, inv, result; - - /* - * The most significant word of mod needs to be non-zero. It - * should already be, but let's make sure. - */ - assert(mod[mod[0]] != 0); - - /* - * mod had better be odd, or we can't do Montgomery multiplication - * using a power of two at all. - */ - if (!(mod[1] & 1)) - return modpow_simple(base_in, exp, mod); - - /* - * Make sure the base is smaller than the modulus, by reducing - * it modulo the modulus if not. - */ - base = bigmod(base_in, mod); - - /* - * Compute the inverse of n mod r, for monty_reduce. (In fact we - * want the inverse of _minus_ n mod r, but we'll sort that out - * below.) - */ - len = mod[0]; - r = bn_power_2(BIGNUM_INT_BITS * len); - inv = modinv(mod, r); - assert(inv); /* cannot fail, since mod is odd and r is a power of 2 */ - - /* - * Multiply the base by r mod n, to get it into Montgomery - * representation. - */ - base2 = modmul(base, r, mod); - freebn(base); - base = base2; - - rn = bigmod(r, mod); /* r mod n, i.e. Montgomerified 1 */ - - freebn(r); /* won't need this any more */ - - /* - * Set up internal arrays of the right lengths, in big-endian - * format, containing the base, the modulus, and the modulus's - * inverse. - */ - n = snewn(len, BignumInt); - for (j = 0; j < len; j++) - n[len - 1 - j] = mod[j + 1]; - - mninv = snewn(len, BignumInt); - for (j = 0; j < len; j++) - mninv[len - 1 - j] = (j < (int)inv[0] ? inv[j + 1] : 0); - freebn(inv); /* we don't need this copy of it any more */ - /* Now negate mninv mod r, so it's the inverse of -n rather than +n. */ - x = snewn(len, BignumInt); - for (j = 0; j < len; j++) - x[j] = 0; - internal_sub(x, mninv, mninv, len); - - /* x = snewn(len, BignumInt); */ /* already done above */ - for (j = 0; j < len; j++) - x[len - 1 - j] = (j < (int)base[0] ? base[j + 1] : 0); - freebn(base); /* we don't need this copy of it any more */ - - a = snewn(2*len, BignumInt); - b = snewn(2*len, BignumInt); - for (j = 0; j < len; j++) - a[2*len - 1 - j] = (j < (int)rn[0] ? rn[j + 1] : 0); - freebn(rn); - - /* Scratch space for multiplies */ - scratchlen = 3*len + mul_compute_scratch(len); - scratch = snewn(scratchlen, BignumInt); - - /* Skip leading zero bits of exp. */ - i = 0; - j = BIGNUM_INT_BITS-1; - while (i < (int)exp[0] && (exp[exp[0] - i] & ((BignumInt)1 << j)) == 0) { - j--; - if (j < 0) { - i++; - j = BIGNUM_INT_BITS-1; - } - } - - /* Main computation */ - while (i < (int)exp[0]) { - while (j >= 0) { - internal_mul(a + len, a + len, b, len, scratch); - monty_reduce(b, n, mninv, scratch, len); - if ((exp[exp[0] - i] & ((BignumInt)1 << j)) != 0) { - internal_mul(b + len, x, a, len, scratch); - monty_reduce(a, n, mninv, scratch, len); - } else { - BignumInt *t; - t = a; - a = b; - b = t; - } - j--; - } - i++; - j = BIGNUM_INT_BITS-1; - } - - /* - * Final monty_reduce to get back from the adjusted Montgomery - * representation. - */ - monty_reduce(a, n, mninv, scratch, len); - - /* Copy result to buffer */ - result = newbn(mod[0]); - for (i = 0; i < len; i++) - result[result[0] - i] = a[i + len]; - while (result[0] > 1 && result[result[0]] == 0) - result[0]--; - - /* Free temporary arrays */ - smemclr(scratch, scratchlen * sizeof(*scratch)); - sfree(scratch); - smemclr(a, 2 * len * sizeof(*a)); - sfree(a); - smemclr(b, 2 * len * sizeof(*b)); - sfree(b); - smemclr(mninv, len * sizeof(*mninv)); - sfree(mninv); - smemclr(n, len * sizeof(*n)); - sfree(n); - smemclr(x, len * sizeof(*x)); - sfree(x); - - return result; -} - -/* - * Compute (p * q) % mod. - * The most significant word of mod MUST be non-zero. - * We assume that the result array is the same size as the mod array. - */ -Bignum modmul(Bignum p, Bignum q, Bignum mod) -{ - BignumInt *a, *n, *m, *o, *scratch; - BignumInt recip; - int rshift, scratchlen; - int pqlen, mlen, rlen, i, j; - Bignum result; - - /* - * The most significant word of mod needs to be non-zero. It - * should already be, but let's make sure. - */ - assert(mod[mod[0]] != 0); - - /* Allocate m of size mlen, copy mod to m */ - /* We use big endian internally */ - mlen = mod[0]; - m = snewn(mlen, BignumInt); - for (j = 0; j < mlen; j++) - m[j] = mod[mod[0] - j]; - - pqlen = (p[0] > q[0] ? p[0] : q[0]); - - /* - * Make sure that we're allowing enough space. The shifting below - * will underflow the vectors we allocate if pqlen is too small. - */ - if (2*pqlen <= mlen) - pqlen = mlen/2 + 1; - - /* Allocate n of size pqlen, copy p to n */ - n = snewn(pqlen, BignumInt); - i = pqlen - p[0]; - for (j = 0; j < i; j++) - n[j] = 0; - for (j = 0; j < (int)p[0]; j++) - n[i + j] = p[p[0] - j]; - - /* Allocate o of size pqlen, copy q to o */ - o = snewn(pqlen, BignumInt); - i = pqlen - q[0]; - for (j = 0; j < i; j++) - o[j] = 0; - for (j = 0; j < (int)q[0]; j++) - o[i + j] = q[q[0] - j]; - - /* Allocate a of size 2*pqlen for result */ - a = snewn(2 * pqlen, BignumInt); - - /* Scratch space for multiplies */ - scratchlen = mul_compute_scratch(pqlen); - scratch = snewn(scratchlen, BignumInt); - - /* Compute reciprocal of the top full word of the modulus */ - { - BignumInt m0 = m[0]; - rshift = bn_clz(m0); - if (rshift) { - m0 <<= rshift; - if (mlen > 1) - m0 |= m[1] >> (BIGNUM_INT_BITS - rshift); - } - recip = reciprocal_word(m0); - } - - /* Main computation */ - internal_mul(n, o, a, pqlen, scratch); - internal_mod(a, pqlen * 2, m, mlen, NULL, recip, rshift); - - /* Copy result to buffer */ - rlen = (mlen < pqlen * 2 ? mlen : pqlen * 2); - result = newbn(rlen); - for (i = 0; i < rlen; i++) - result[result[0] - i] = a[i + 2 * pqlen - rlen]; - while (result[0] > 1 && result[result[0]] == 0) - result[0]--; - - /* Free temporary arrays */ - smemclr(scratch, scratchlen * sizeof(*scratch)); - sfree(scratch); - smemclr(a, 2 * pqlen * sizeof(*a)); - sfree(a); - smemclr(m, mlen * sizeof(*m)); - sfree(m); - smemclr(n, pqlen * sizeof(*n)); - sfree(n); - smemclr(o, pqlen * sizeof(*o)); - sfree(o); - - return result; -} - -Bignum modsub(const Bignum a, const Bignum b, const Bignum n) -{ - Bignum a1, b1, ret; - - if (bignum_cmp(a, n) >= 0) a1 = bigmod(a, n); - else a1 = a; - if (bignum_cmp(b, n) >= 0) b1 = bigmod(b, n); - else b1 = b; - - if (bignum_cmp(a1, b1) >= 0) /* a >= b */ - { - ret = bigsub(a1, b1); - } - else - { - /* Handle going round the corner of the modulus without having - * negative support in Bignum */ - Bignum tmp = bigsub(n, b1); - assert(tmp); - ret = bigadd(tmp, a1); - freebn(tmp); - } - - if (a != a1) freebn(a1); - if (b != b1) freebn(b1); - - return ret; -} - -/* - * Compute p % mod. - * The most significant word of mod MUST be non-zero. - * We assume that the result array is the same size as the mod array. - * We optionally write out a quotient if `quotient' is non-NULL. - * We can avoid writing out the result if `result' is NULL. - */ -static void bigdivmod(Bignum p, Bignum mod, Bignum result, Bignum quotient) -{ - BignumInt *n, *m; - BignumInt recip; - int rshift; - int plen, mlen, i, j; - - /* - * The most significant word of mod needs to be non-zero. It - * should already be, but let's make sure. - */ - assert(mod[mod[0]] != 0); - - /* Allocate m of size mlen, copy mod to m */ - /* We use big endian internally */ - mlen = mod[0]; - m = snewn(mlen, BignumInt); - for (j = 0; j < mlen; j++) - m[j] = mod[mod[0] - j]; - - plen = p[0]; - /* Ensure plen > mlen */ - if (plen <= mlen) - plen = mlen + 1; - - /* Allocate n of size plen, copy p to n */ - n = snewn(plen, BignumInt); - for (j = 0; j < plen; j++) - n[j] = 0; - for (j = 1; j <= (int)p[0]; j++) - n[plen - j] = p[j]; - - /* Compute reciprocal of the top full word of the modulus */ - { - BignumInt m0 = m[0]; - rshift = bn_clz(m0); - if (rshift) { - m0 <<= rshift; - if (mlen > 1) - m0 |= m[1] >> (BIGNUM_INT_BITS - rshift); - } - recip = reciprocal_word(m0); - } - - /* Main computation */ - internal_mod(n, plen, m, mlen, quotient, recip, rshift); - - /* Copy result to buffer */ - if (result) { - for (i = 1; i <= (int)result[0]; i++) { - int j = plen - i; - result[i] = j >= 0 ? n[j] : 0; - } - } - - /* Free temporary arrays */ - smemclr(m, mlen * sizeof(*m)); - sfree(m); - smemclr(n, plen * sizeof(*n)); - sfree(n); -} - -/* - * Decrement a number. - */ -void decbn(Bignum bn) -{ - int i = 1; - while (i < (int)bn[0] && bn[i] == 0) - bn[i++] = BIGNUM_INT_MASK; - bn[i]--; -} - -Bignum bignum_from_bytes(const unsigned char *data, int nbytes) -{ - Bignum result; - int w, i; - - assert(nbytes >= 0 && nbytes < INT_MAX/8); - - w = (nbytes + BIGNUM_INT_BYTES - 1) / BIGNUM_INT_BYTES; /* bytes->words */ - - result = newbn(w); - for (i = 1; i <= w; i++) - result[i] = 0; - for (i = nbytes; i--;) { - unsigned char byte = *data++; - result[1 + i / BIGNUM_INT_BYTES] |= - (BignumInt)byte << (8*i % BIGNUM_INT_BITS); - } - - bn_restore_invariant(result); - return result; -} - -Bignum bignum_from_bytes_le(const unsigned char *data, int nbytes) -{ - Bignum result; - int w, i; - - assert(nbytes >= 0 && nbytes < INT_MAX/8); - - w = (nbytes + BIGNUM_INT_BYTES - 1) / BIGNUM_INT_BYTES; /* bytes->words */ - - result = newbn(w); - for (i = 1; i <= w; i++) - result[i] = 0; - for (i = 0; i < nbytes; ++i) { - unsigned char byte = *data++; - result[1 + i / BIGNUM_INT_BYTES] |= - (BignumInt)byte << (8*i % BIGNUM_INT_BITS); - } - - bn_restore_invariant(result); - return result; -} - -Bignum bignum_from_decimal(const char *decimal) -{ - Bignum result = copybn(Zero); - - while (*decimal) { - Bignum tmp, tmp2; - - if (!isdigit((unsigned char)*decimal)) { - freebn(result); - return 0; - } - - tmp = bigmul(result, Ten); - tmp2 = bignum_from_long(*decimal - '0'); - freebn(result); - result = bigadd(tmp, tmp2); - freebn(tmp); - freebn(tmp2); - - decimal++; - } - - return result; -} - -Bignum bignum_random_in_range(const Bignum lower, const Bignum upper) -{ - Bignum ret = NULL; - unsigned char *bytes; - int upper_len = bignum_bitcount(upper); - int upper_bytes = upper_len / 8; - int upper_bits = upper_len % 8; - if (upper_bits) ++upper_bytes; - - bytes = snewn(upper_bytes, unsigned char); - do { - int i; - - if (ret) freebn(ret); - - for (i = 0; i < upper_bytes; ++i) - { - bytes[i] = (unsigned char)random_byte(); - } - /* Mask the top to reduce failure rate to 50/50 */ - if (upper_bits) - { - bytes[i - 1] &= 0xFF >> (8 - upper_bits); - } - - ret = bignum_from_bytes(bytes, upper_bytes); - } while (bignum_cmp(ret, lower) < 0 || bignum_cmp(ret, upper) > 0); - smemclr(bytes, upper_bytes); - sfree(bytes); - - return ret; -} - -/* - * Read an SSH-1-format bignum from a data buffer. Return the number - * of bytes consumed, or -1 if there wasn't enough data. - */ -int ssh1_read_bignum(const unsigned char *data, int len, Bignum * result) -{ - const unsigned char *p = data; - int i; - int w, b; - - if (len < 2) - return -1; - - w = 0; - for (i = 0; i < 2; i++) - w = (w << 8) + *p++; - b = (w + 7) / 8; /* bits -> bytes */ - - if (len < b+2) - return -1; - - if (!result) /* just return length */ - return b + 2; - - *result = bignum_from_bytes(p, b); - - return p + b - data; -} - -/* - * Return the bit count of a bignum, for SSH-1 encoding. - */ -int bignum_bitcount(Bignum bn) -{ - int bitcount = bn[0] * BIGNUM_INT_BITS - 1; - while (bitcount >= 0 - && (bn[bitcount / BIGNUM_INT_BITS + 1] >> (bitcount % BIGNUM_INT_BITS)) == 0) bitcount--; - return bitcount + 1; -} - -/* - * Return the byte length of a bignum when SSH-1 encoded. - */ -int ssh1_bignum_length(Bignum bn) -{ - return 2 + (bignum_bitcount(bn) + 7) / 8; -} - -/* - * Return the byte length of a bignum when SSH-2 encoded. - */ -int ssh2_bignum_length(Bignum bn) -{ - return 4 + (bignum_bitcount(bn) + 8) / 8; -} - -/* - * Return a byte from a bignum; 0 is least significant, etc. - */ -int bignum_byte(Bignum bn, int i) -{ - if (i < 0 || i >= (int)(BIGNUM_INT_BYTES * bn[0])) - return 0; /* beyond the end */ - else - return (bn[i / BIGNUM_INT_BYTES + 1] >> - ((i % BIGNUM_INT_BYTES)*8)) & 0xFF; -} - -/* - * Return a bit from a bignum; 0 is least significant, etc. - */ -int bignum_bit(Bignum bn, int i) -{ - if (i < 0 || i >= (int)(BIGNUM_INT_BITS * bn[0])) - return 0; /* beyond the end */ - else - return (bn[i / BIGNUM_INT_BITS + 1] >> (i % BIGNUM_INT_BITS)) & 1; -} - -/* - * Set a bit in a bignum; 0 is least significant, etc. - */ -void bignum_set_bit(Bignum bn, int bitnum, int value) -{ - if (bitnum < 0 || bitnum >= (int)(BIGNUM_INT_BITS * bn[0])) { - if (value) abort(); /* beyond the end */ - } else { - int v = bitnum / BIGNUM_INT_BITS + 1; - BignumInt mask = (BignumInt)1 << (bitnum % BIGNUM_INT_BITS); - if (value) - bn[v] |= mask; - else - bn[v] &= ~mask; - } -} - -/* - * Write a SSH-1-format bignum into a buffer. It is assumed the - * buffer is big enough. Returns the number of bytes used. - */ -int ssh1_write_bignum(void *data, Bignum bn) -{ - unsigned char *p = data; - int len = ssh1_bignum_length(bn); - int i; - int bitc = bignum_bitcount(bn); - - *p++ = (bitc >> 8) & 0xFF; - *p++ = (bitc) & 0xFF; - for (i = len - 2; i--;) - *p++ = bignum_byte(bn, i); - return len; -} - -/* - * Compare two bignums. Returns like strcmp. - */ -int bignum_cmp(Bignum a, Bignum b) -{ - int amax = a[0], bmax = b[0]; - int i; - - /* Annoyingly we have two representations of zero */ - if (amax == 1 && a[amax] == 0) - amax = 0; - if (bmax == 1 && b[bmax] == 0) - bmax = 0; - - assert(amax == 0 || a[amax] != 0); - assert(bmax == 0 || b[bmax] != 0); - - i = (amax > bmax ? amax : bmax); - while (i) { - BignumInt aval = (i > amax ? 0 : a[i]); - BignumInt bval = (i > bmax ? 0 : b[i]); - if (aval < bval) - return -1; - if (aval > bval) - return +1; - i--; - } - return 0; -} - -/* - * Right-shift one bignum to form another. - */ -Bignum bignum_rshift(Bignum a, int shift) -{ - Bignum ret; - int i, shiftw, shiftb, shiftbb, bits; - BignumInt ai, ai1; - - assert(shift >= 0); - - bits = bignum_bitcount(a) - shift; - ret = newbn((bits + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS); - - if (ret) { - shiftw = shift / BIGNUM_INT_BITS; - shiftb = shift % BIGNUM_INT_BITS; - shiftbb = BIGNUM_INT_BITS - shiftb; - - ai1 = a[shiftw + 1]; - for (i = 1; i <= (int)ret[0]; i++) { - ai = ai1; - ai1 = (i + shiftw + 1 <= (int)a[0] ? a[i + shiftw + 1] : 0); - ret[i] = ((ai >> shiftb) | (ai1 << shiftbb)) & BIGNUM_INT_MASK; - } - } - - return ret; -} - -/* - * Left-shift one bignum to form another. - */ -Bignum bignum_lshift(Bignum a, int shift) -{ - Bignum ret; - int bits, shiftWords, shiftBits; - - assert(shift >= 0); - - bits = bignum_bitcount(a) + shift; - ret = newbn((bits + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS); - - shiftWords = shift / BIGNUM_INT_BITS; - shiftBits = shift % BIGNUM_INT_BITS; - - if (shiftBits == 0) - { - memcpy(&ret[1 + shiftWords], &a[1], sizeof(BignumInt) * a[0]); - } - else - { - int i; - BignumInt carry = 0; - - /* Remember that Bignum[0] is length, so add 1 */ - for (i = shiftWords + 1; i < ((int)a[0]) + shiftWords + 1; ++i) - { - BignumInt from = a[i - shiftWords]; - ret[i] = (from << shiftBits) | carry; - carry = from >> (BIGNUM_INT_BITS - shiftBits); - } - if (carry) ret[i] = carry; - } - - return ret; -} - -/* - * Non-modular multiplication and addition. - */ -Bignum bigmuladd(Bignum a, Bignum b, Bignum addend) -{ - int alen = a[0], blen = b[0]; - int mlen = (alen > blen ? alen : blen); - int rlen, i, maxspot; - int wslen; - BignumInt *workspace; - Bignum ret; - - /* mlen space for a, mlen space for b, 2*mlen for result, - * plus scratch space for multiplication */ - wslen = mlen * 4 + mul_compute_scratch(mlen); - workspace = snewn(wslen, BignumInt); - for (i = 0; i < mlen; i++) { - workspace[0 * mlen + i] = (mlen - i <= (int)a[0] ? a[mlen - i] : 0); - workspace[1 * mlen + i] = (mlen - i <= (int)b[0] ? b[mlen - i] : 0); - } - - internal_mul(workspace + 0 * mlen, workspace + 1 * mlen, - workspace + 2 * mlen, mlen, workspace + 4 * mlen); - - /* now just copy the result back */ - rlen = alen + blen + 1; - if (addend && rlen <= (int)addend[0]) - rlen = addend[0] + 1; - ret = newbn(rlen); - maxspot = 0; - for (i = 1; i <= (int)ret[0]; i++) { - ret[i] = (i <= 2 * mlen ? workspace[4 * mlen - i] : 0); - if (ret[i] != 0) - maxspot = i; - } - ret[0] = maxspot; - - /* now add in the addend, if any */ - if (addend) { - BignumCarry carry = 0; - for (i = 1; i <= rlen; i++) { - BignumInt retword = (i <= (int)ret[0] ? ret[i] : 0); - BignumInt addword = (i <= (int)addend[0] ? addend[i] : 0); - BignumADC(ret[i], carry, retword, addword, carry); - if (ret[i] != 0 && i > maxspot) - maxspot = i; - } - } - ret[0] = maxspot; - - smemclr(workspace, wslen * sizeof(*workspace)); - sfree(workspace); - return ret; -} - -/* - * Non-modular multiplication. - */ -Bignum bigmul(Bignum a, Bignum b) -{ - return bigmuladd(a, b, NULL); -} - -/* - * Simple addition. - */ -Bignum bigadd(Bignum a, Bignum b) -{ - int alen = a[0], blen = b[0]; - int rlen = (alen > blen ? alen : blen) + 1; - int i, maxspot; - Bignum ret; - BignumCarry carry; - - ret = newbn(rlen); - - carry = 0; - maxspot = 0; - for (i = 1; i <= rlen; i++) { - BignumInt aword = (i <= (int)a[0] ? a[i] : 0); - BignumInt bword = (i <= (int)b[0] ? b[i] : 0); - BignumADC(ret[i], carry, aword, bword, carry); - if (ret[i] != 0 && i > maxspot) - maxspot = i; - } - ret[0] = maxspot; - - return ret; -} - -/* - * Subtraction. Returns a-b, or NULL if the result would come out - * negative (recall that this entire bignum module only handles - * positive numbers). - */ -Bignum bigsub(Bignum a, Bignum b) -{ - int alen = a[0], blen = b[0]; - int rlen = (alen > blen ? alen : blen); - int i, maxspot; - Bignum ret; - BignumCarry carry; - - ret = newbn(rlen); - - carry = 1; - maxspot = 0; - for (i = 1; i <= rlen; i++) { - BignumInt aword = (i <= (int)a[0] ? a[i] : 0); - BignumInt bword = (i <= (int)b[0] ? b[i] : 0); - BignumADC(ret[i], carry, aword, ~bword, carry); - if (ret[i] != 0 && i > maxspot) - maxspot = i; - } - ret[0] = maxspot; - - if (!carry) { - freebn(ret); - return NULL; - } - - return ret; -} - -/* - * Create a bignum which is the bitmask covering another one. That - * is, the smallest integer which is >= N and is also one less than - * a power of two. - */ -Bignum bignum_bitmask(Bignum n) -{ - Bignum ret = copybn(n); - int i; - BignumInt j; - - i = ret[0]; - while (n[i] == 0 && i > 0) - i--; - if (i <= 0) - return ret; /* input was zero */ - j = 1; - while (j < n[i]) - j = 2 * j + 1; - ret[i] = j; - while (--i > 0) - ret[i] = BIGNUM_INT_MASK; - return ret; -} - -/* - * Convert an unsigned long into a bignum. - */ -Bignum bignum_from_long(unsigned long n) -{ - const int maxwords = - (sizeof(unsigned long) + sizeof(BignumInt) - 1) / sizeof(BignumInt); - Bignum ret; - int i; - - ret = newbn(maxwords); - ret[0] = 0; - for (i = 0; i < maxwords; i++) { - ret[i+1] = n >> (i * BIGNUM_INT_BITS); - if (ret[i+1] != 0) - ret[0] = i+1; - } - - return ret; -} - -/* - * Add a long to a bignum. - */ -Bignum bignum_add_long(Bignum number, unsigned long n) -{ - const int maxwords = - (sizeof(unsigned long) + sizeof(BignumInt) - 1) / sizeof(BignumInt); - Bignum ret; - int words, i; - BignumCarry carry; - - words = number[0]; - if (words < maxwords) - words = maxwords; - words++; - ret = newbn(words); - - carry = 0; - ret[0] = 0; - for (i = 0; i < words; i++) { - BignumInt nword = (i < maxwords ? n >> (i * BIGNUM_INT_BITS) : 0); - BignumInt numword = (i < number[0] ? number[i+1] : 0); - BignumADC(ret[i+1], carry, numword, nword, carry); - if (ret[i+1] != 0) - ret[0] = i+1; - } - return ret; -} - -/* - * Compute the residue of a bignum, modulo a (max 16-bit) short. - */ -unsigned short bignum_mod_short(Bignum number, unsigned short modulus) -{ - unsigned long mod = modulus, r = 0; - /* Precompute (BIGNUM_INT_MASK+1) % mod */ - unsigned long base_r = (BIGNUM_INT_MASK - modulus + 1) % mod; - int i; - - for (i = number[0]; i > 0; i--) { - /* - * Conceptually, ((r << BIGNUM_INT_BITS) + number[i]) % mod - */ - r = ((r * base_r) + (number[i] % mod)) % mod; - } - return (unsigned short) r; -} - -#ifdef DEBUG -void diagbn(char *prefix, Bignum md) -{ - int i, nibbles, morenibbles; - static const char hex[] = "0123456789ABCDEF"; - - debug(("%s0x", prefix ? prefix : "")); - - nibbles = (3 + bignum_bitcount(md)) / 4; - if (nibbles < 1) - nibbles = 1; - morenibbles = 4 * md[0] - nibbles; - for (i = 0; i < morenibbles; i++) - debug(("-")); - for (i = nibbles; i--;) - debug(("%c", - hex[(bignum_byte(md, i / 2) >> (4 * (i % 2))) & 0xF])); - - if (prefix) - debug(("\n")); -} -#endif - -/* - * Simple division. - */ -Bignum bigdiv(Bignum a, Bignum b) -{ - Bignum q = newbn(a[0]); - bigdivmod(a, b, NULL, q); - while (q[0] > 1 && q[q[0]] == 0) - q[0]--; - return q; -} - -/* - * Simple remainder. - */ -Bignum bigmod(Bignum a, Bignum b) -{ - Bignum r = newbn(b[0]); - bigdivmod(a, b, r, NULL); - while (r[0] > 1 && r[r[0]] == 0) - r[0]--; - return r; -} - -/* - * Greatest common divisor. - */ -Bignum biggcd(Bignum av, Bignum bv) -{ - Bignum a = copybn(av); - Bignum b = copybn(bv); - - while (bignum_cmp(b, Zero) != 0) { - Bignum t = newbn(b[0]); - bigdivmod(a, b, t, NULL); - while (t[0] > 1 && t[t[0]] == 0) - t[0]--; - freebn(a); - a = b; - b = t; - } - - freebn(b); - return a; -} - -/* - * Modular inverse, using Euclid's extended algorithm. - */ -Bignum modinv(Bignum number, Bignum modulus) -{ - Bignum a = copybn(modulus); - Bignum b = copybn(number); - Bignum xp = copybn(Zero); - Bignum x = copybn(One); - int sign = +1; - - assert(number[number[0]] != 0); - assert(modulus[modulus[0]] != 0); - - while (bignum_cmp(b, One) != 0) { - Bignum t, q; - - if (bignum_cmp(b, Zero) == 0) { - /* - * Found a common factor between the inputs, so we cannot - * return a modular inverse at all. - */ - freebn(b); - freebn(a); - freebn(xp); - freebn(x); - return NULL; - } - - t = newbn(b[0]); - q = newbn(a[0]); - bigdivmod(a, b, t, q); - while (t[0] > 1 && t[t[0]] == 0) - t[0]--; - while (q[0] > 1 && q[q[0]] == 0) - q[0]--; - freebn(a); - a = b; - b = t; - t = xp; - xp = x; - x = bigmuladd(q, xp, t); - sign = -sign; - freebn(t); - freebn(q); - } - - freebn(b); - freebn(a); - freebn(xp); - - /* now we know that sign * x == 1, and that x < modulus */ - if (sign < 0) { - /* set a new x to be modulus - x */ - Bignum newx = newbn(modulus[0]); - BignumInt carry = 0; - int maxspot = 1; - int i; - - for (i = 1; i <= (int)newx[0]; i++) { - BignumInt aword = (i <= (int)modulus[0] ? modulus[i] : 0); - BignumInt bword = (i <= (int)x[0] ? x[i] : 0); - newx[i] = aword - bword - carry; - bword = ~bword; - carry = carry ? (newx[i] >= bword) : (newx[i] > bword); - if (newx[i] != 0) - maxspot = i; - } - newx[0] = maxspot; - freebn(x); - x = newx; - } - - /* and return. */ - return x; -} - -/* - * Render a bignum into decimal. Return a malloced string holding - * the decimal representation. - */ -char *bignum_decimal(Bignum x) -{ - int ndigits, ndigit; - int i, iszero; - BignumInt carry; - char *ret; - BignumInt *workspace; - - /* - * First, estimate the number of digits. Since log(10)/log(2) - * is just greater than 93/28 (the joys of continued fraction - * approximations...) we know that for every 93 bits, we need - * at most 28 digits. This will tell us how much to malloc. - * - * Formally: if x has i bits, that means x is strictly less - * than 2^i. Since 2 is less than 10^(28/93), this is less than - * 10^(28i/93). We need an integer power of ten, so we must - * round up (rounding down might make it less than x again). - * Therefore if we multiply the bit count by 28/93, rounding - * up, we will have enough digits. - * - * i=0 (i.e., x=0) is an irritating special case. - */ - i = bignum_bitcount(x); - if (!i) - ndigits = 1; /* x = 0 */ - else - ndigits = (28 * i + 92) / 93; /* multiply by 28/93 and round up */ - ndigits++; /* allow for trailing \0 */ - ret = snewn(ndigits, char); - - /* - * Now allocate some workspace to hold the binary form as we - * repeatedly divide it by ten. Initialise this to the - * big-endian form of the number. - */ - workspace = snewn(x[0], BignumInt); - for (i = 0; i < (int)x[0]; i++) - workspace[i] = x[x[0] - i]; - - /* - * Next, write the decimal number starting with the last digit. - * We use ordinary short division, dividing 10 into the - * workspace. - */ - ndigit = ndigits - 1; - ret[ndigit] = '\0'; - do { - iszero = 1; - carry = 0; - for (i = 0; i < (int)x[0]; i++) { - /* - * Conceptually, we want to compute - * - * (carry << BIGNUM_INT_BITS) + workspace[i] - * ----------------------------------------- - * 10 - * - * but we don't have an integer type longer than BignumInt - * to work with. So we have to do it in pieces. - */ - - BignumInt q, r; - q = workspace[i] / 10; - r = workspace[i] % 10; - - /* I want (BIGNUM_INT_MASK+1)/10 but can't say so directly! */ - q += carry * ((BIGNUM_INT_MASK-9) / 10 + 1); - r += carry * ((BIGNUM_INT_MASK-9) % 10); - - q += r / 10; - r %= 10; - - workspace[i] = q; - carry = r; - - if (workspace[i]) - iszero = 0; - } - ret[--ndigit] = (char) (carry + '0'); - } while (!iszero); - - /* - * There's a chance we've fallen short of the start of the - * string. Correct if so. - */ - if (ndigit > 0) - memmove(ret, ret + ndigit, ndigits - ndigit); - - /* - * Done. - */ - smemclr(workspace, x[0] * sizeof(*workspace)); - sfree(workspace); - return ret; -} diff --git a/wincrypt/sshbn.h b/wincrypt/sshbn.h deleted file mode 100644 index 6ee97ee..0000000 --- a/wincrypt/sshbn.h +++ /dev/null @@ -1,220 +0,0 @@ -/* - * sshbn.h: the assorted conditional definitions of BignumInt and - * multiply macros used throughout the bignum code to treat numbers as - * arrays of the most conveniently sized word for the target machine. - * Exported so that other code (e.g. poly1305) can use it too. - * - * This file must export, in whatever ifdef branch it ends up in: - * - * - two types: 'BignumInt' and 'BignumCarry'. BignumInt is an - * unsigned integer type which will be used as the base word size - * for all bignum operations. BignumCarry is an unsigned integer - * type used to hold the carry flag taken as input and output by - * the BignumADC macro (see below). - * - * - four constant macros: BIGNUM_INT_BITS, BIGNUM_INT_BYTES, - * BIGNUM_TOP_BIT, BIGNUM_INT_MASK. These should be more or less - * self-explanatory, but just in case, they give the number of bits - * in BignumInt, the number of bytes that works out to, the - * BignumInt value consisting of only the top bit, and the - * BignumInt value with all bits set. - * - * - four statement macros: BignumADC, BignumMUL, BignumMULADD, - * BignumMULADD2. These do various kinds of multi-word arithmetic, - * and all produce two output values. - * * BignumADC(ret,retc,a,b,c) takes input BignumInt values a,b - * and a BignumCarry c, and outputs a BignumInt ret = a+b+c and - * a BignumCarry retc which is the carry off the top of that - * addition. - * * BignumMUL(rh,rl,a,b) returns the two halves of the - * double-width product a*b. - * * BignumMULADD(rh,rl,a,b,addend) returns the two halves of the - * double-width value a*b + addend. - * * BignumMULADD2(rh,rl,a,b,addend1,addend2) returns the two - * halves of the double-width value a*b + addend1 + addend2. - * - * Every branch of the main ifdef below defines the type BignumInt and - * the value BIGNUM_INT_BITS. The other three constant macros are - * filled in by common code further down. - * - * Most branches also define a macro DEFINE_BIGNUMDBLINT containing a - * typedef statement which declares a type _twice_ the length of a - * BignumInt. This causes the common code further down to produce a - * default implementation of the four statement macros in terms of - * that double-width type, and also to defined BignumCarry to be - * BignumInt. - * - * However, if a particular compile target does not have a type twice - * the length of the BignumInt you want to use but it does provide - * some alternative means of doing add-with-carry and double-word - * multiply, then the ifdef branch in question can just define - * BignumCarry and the four statement macros itself, and that's fine - * too. - */ - -#if defined __SIZEOF_INT128__ - - /* - * 64-bit BignumInt using gcc/clang style 128-bit BignumDblInt. - * - * gcc and clang both provide a __uint128_t type on 64-bit targets - * (and, when they do, indicate its presence by the above macro), - * using the same 'two machine registers' kind of code generation - * that 32-bit targets use for 64-bit ints. - */ - - typedef unsigned long long BignumInt; - #define BIGNUM_INT_BITS 64 - #define DEFINE_BIGNUMDBLINT typedef __uint128_t BignumDblInt - -#elif defined _MSC_VER && defined _M_AMD64 - - /* - * 64-bit BignumInt, using Visual Studio x86-64 compiler intrinsics. - * - * 64-bit Visual Studio doesn't provide very much in the way of help - * here: there's no int128 type, and also no inline assembler giving - * us direct access to the x86-64 MUL or ADC instructions. However, - * there are compiler intrinsics giving us that access, so we can - * use those - though it turns out we have to be a little careful, - * since they seem to generate wrong code if their pointer-typed - * output parameters alias their inputs. Hence all the internal temp - * variables inside the macros. - */ - - #include - typedef unsigned char BignumCarry; /* the type _addcarry_u64 likes to use */ - typedef unsigned __int64 BignumInt; - #define BIGNUM_INT_BITS 64 - #define BignumADC(ret, retc, a, b, c) do \ - { \ - BignumInt ADC_tmp; \ - (retc) = _addcarry_u64(c, a, b, &ADC_tmp); \ - (ret) = ADC_tmp; \ - } while (0) - #define BignumMUL(rh, rl, a, b) do \ - { \ - BignumInt MULADD_hi; \ - (rl) = _umul128(a, b, &MULADD_hi); \ - (rh) = MULADD_hi; \ - } while (0) - #define BignumMULADD(rh, rl, a, b, addend) do \ - { \ - BignumInt MULADD_lo, MULADD_hi; \ - MULADD_lo = _umul128(a, b, &MULADD_hi); \ - MULADD_hi += _addcarry_u64(0, MULADD_lo, (addend), &(rl)); \ - (rh) = MULADD_hi; \ - } while (0) - #define BignumMULADD2(rh, rl, a, b, addend1, addend2) do \ - { \ - BignumInt MULADD_lo1, MULADD_lo2, MULADD_hi; \ - MULADD_lo1 = _umul128(a, b, &MULADD_hi); \ - MULADD_hi += _addcarry_u64(0, MULADD_lo1, (addend1), &MULADD_lo2); \ - MULADD_hi += _addcarry_u64(0, MULADD_lo2, (addend2), &(rl)); \ - (rh) = MULADD_hi; \ - } while (0) - -#elif defined __GNUC__ || defined _LLP64 || __STDC__ >= 199901L - - /* 32-bit BignumInt, using C99 unsigned long long as BignumDblInt */ - - typedef unsigned int BignumInt; - #define BIGNUM_INT_BITS 32 - #define DEFINE_BIGNUMDBLINT typedef unsigned long long BignumDblInt - -#elif defined _MSC_VER && defined _M_IX86 - - /* 32-bit BignumInt, using Visual Studio __int64 as BignumDblInt */ - - typedef unsigned int BignumInt; - #define BIGNUM_INT_BITS 32 - #define DEFINE_BIGNUMDBLINT typedef unsigned __int64 BignumDblInt - -#elif defined _LP64 - - /* - * 32-bit BignumInt, using unsigned long itself as BignumDblInt. - * - * Only for platforms where long is 64 bits, of course. - */ - - typedef unsigned int BignumInt; - #define BIGNUM_INT_BITS 32 - #define DEFINE_BIGNUMDBLINT typedef unsigned long BignumDblInt - -#else - - /* - * 16-bit BignumInt, using unsigned long as BignumDblInt. - * - * This is the final fallback for real emergencies: C89 guarantees - * unsigned short/long to be at least the required sizes, so this - * should work on any C implementation at all. But it'll be - * noticeably slow, so if you find yourself in this case you - * probably want to move heaven and earth to find an alternative! - */ - - typedef unsigned short BignumInt; - #define BIGNUM_INT_BITS 16 - #define DEFINE_BIGNUMDBLINT typedef unsigned long BignumDblInt - -#endif - -/* - * Common code across all branches of that ifdef: define the three - * easy constant macros in terms of BIGNUM_INT_BITS. - */ -#define BIGNUM_INT_BYTES (BIGNUM_INT_BITS / 8) -#define BIGNUM_TOP_BIT (((BignumInt)1) << (BIGNUM_INT_BITS-1)) -#define BIGNUM_INT_MASK (BIGNUM_TOP_BIT | (BIGNUM_TOP_BIT-1)) - -/* - * Common code across _most_ branches of the ifdef: define a set of - * statement macros in terms of the BignumDblInt type provided. In - * this case, we also define BignumCarry to be the same thing as - * BignumInt, for simplicity. - */ -#ifdef DEFINE_BIGNUMDBLINT - - typedef BignumInt BignumCarry; - #define BignumADC(ret, retc, a, b, c) do \ - { \ - DEFINE_BIGNUMDBLINT; \ - BignumDblInt ADC_temp = (BignumInt)(a); \ - ADC_temp += (BignumInt)(b); \ - ADC_temp += (c); \ - (ret) = (BignumInt)ADC_temp; \ - (retc) = (BignumCarry)(ADC_temp >> BIGNUM_INT_BITS); \ - } while (0) - - #define BignumMUL(rh, rl, a, b) do \ - { \ - DEFINE_BIGNUMDBLINT; \ - BignumDblInt MUL_temp = (BignumInt)(a); \ - MUL_temp *= (BignumInt)(b); \ - (rh) = (BignumInt)(MUL_temp >> BIGNUM_INT_BITS); \ - (rl) = (BignumInt)(MUL_temp); \ - } while (0) - - #define BignumMULADD(rh, rl, a, b, addend) do \ - { \ - DEFINE_BIGNUMDBLINT; \ - BignumDblInt MUL_temp = (BignumInt)(a); \ - MUL_temp *= (BignumInt)(b); \ - MUL_temp += (BignumInt)(addend); \ - (rh) = (BignumInt)(MUL_temp >> BIGNUM_INT_BITS); \ - (rl) = (BignumInt)(MUL_temp); \ - } while (0) - - #define BignumMULADD2(rh, rl, a, b, addend1, addend2) do \ - { \ - DEFINE_BIGNUMDBLINT; \ - BignumDblInt MUL_temp = (BignumInt)(a); \ - MUL_temp *= (BignumInt)(b); \ - MUL_temp += (BignumInt)(addend1); \ - MUL_temp += (BignumInt)(addend2); \ - (rh) = (BignumInt)(MUL_temp >> BIGNUM_INT_BITS); \ - (rl) = (BignumInt)(MUL_temp); \ - } while (0) - -#endif /* DEFINE_BIGNUMDBLINT */ diff --git a/wincrypt/wincrypto.c b/wincrypt/wincrypto.c deleted file mode 100644 index b7b0838..0000000 --- a/wincrypt/wincrypto.c +++ /dev/null @@ -1,656 +0,0 @@ -#ifdef MOD_WINCRYPT -/* - * PuTTY wincrypt patch main file. - * Author: Ulf Frisk, puttywincrypt@ulffrisk.com - */ -#include "putty.h" -#include "ssh.h" -#include "mpint.h" - -#include -#include -#include "bcrypt.h" -#include "ncrypt.h" -#include "wincrypt/wincrypto.h" - - /* - * Defines and declarations due to missing declarations in mingw32 and BCC55 headers. - */ -#ifndef CERT_SYSTEM_STORE_CURRENT_USER -#define CERT_SYSTEM_STORE_CURRENT_USER (1 << 16) -#endif /* CERT_SYSTEM_STORE_CURRENT_USER */ - -#ifndef CERT_STORE_PROV_MEMORY -#define CERT_STORE_PROV_MEMORY ((LPCSTR) 2) -#endif /* CERT_STORE_PROV_MEMORY */ - -#ifndef CRYPT_FIND_USER_KEYSET_FLAG -#define CRYPT_FIND_USER_KEYSET_FLAG 0x00000001 -#endif /* CRYPT_FIND_USER_KEYSET_FLAG */ - -#ifndef CRYPT_FIND_SILENT_KEYSET_FLAG -#define CRYPT_FIND_SILENT_KEYSET_FLAG 0x00000040 -#endif /* CRYPT_FIND_SILENT_KEYSET_FLAG */ - -#ifndef CERT_CLOSE_STORE_FORCE_FLAG -#define CERT_CLOSE_STORE_FORCE_FLAG 0x00000001 -#endif /* CERT_CLOSE_STORE_FORCE_FLAG */ - -#ifndef CRYPT_ACQUIRE_NO_HEALING -#define CRYPT_ACQUIRE_NO_HEALING 0x00000008 -#endif /* CRYPT_ACQUIRE_NO_HEALING */ - -#ifndef CRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAG -#define CRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAG 0x00010000 -#endif /* CRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAG */ - -#ifndef CERT_NCRYPT_KEY_SPEC -#define CERT_NCRYPT_KEY_SPEC 0xFFFFFFFF -#endif /* CERT_NCRYPT_KEY_SPEC */ - -typedef ULONG_PTR HCRYPTPROV_OR_NCRYPT_KEY_HANDLE; - -#ifndef __BCRYPT_H__ -#define BCRYPT_PAD_PKCS1 0x00000002 -#define BCRYPT_SHA1_ALGORITHM L"SHA1" -typedef struct _BCRYPT_PKCS1_PADDING_INFO -{ - LPCWSTR pszAlgId; -} BCRYPT_PKCS1_PADDING_INFO; -#endif /* __BCRYPT_H__ */ - -#ifndef CRYPT_ACQUIRE_SILENT_FLAG -#define CRYPT_ACQUIRE_SILENT_FLAG 0x00000040 -#endif /* CRYPT_ACQUIRE_SILENT_FLAG */ - -typedef PCCERT_CONTEXT(WINAPI *DFNCryptUIDlgSelectCertificateFromStore)(HCERTSTORE, HWND, LPCWSTR, LPCWSTR, DWORD, DWORD, PVOID); - -/* - * Dynamically lookup NCryptSignHash to avoid link dependency to ncrypt.dll (not supported by Windows XP). - */ -typedef LONG(WINAPI *DFNNCryptSignHash)(ULONG_PTR, PVOID, PBYTE, DWORD, PBYTE, DWORD, PDWORD, DWORD); - -/* - * convert sha1 string to binary data - */ -void capi_sha1_to_binary(PSTR szHex, PBYTE pbBin) -{ - unsigned char i, h, l; - for (i = 0; i < 20; i++) { - h = szHex[i << 1]; - l = szHex[(i << 1) + 1]; - pbBin[i] = - (((h >= '0' && h <= '9') ? h - '0' : ((h >= 'a' && h <= 'f') ? h - 'a' + 10 : ((h >= 'A' && h <= 'F') ? h - 'A' + 10 : 0))) << 4) + - (((l >= '0' && l <= '9') ? l - '0' : ((l >= 'a' && l <= 'f') ? l - 'a' + 10 : ((l >= 'A' && l <= 'F') ? l - 'A' + 10 : 0)))); - } -} - -/* - * Windows XP do not support CryptBinaryToString with raw hex. - */ -PSTR capi_binary_to_hex(PBYTE pbBinary, DWORD cbBinary) -{ - PSTR szHex; - DWORD idx; - BYTE b; - szHex = snewn((cbBinary << 1) + 1, char); - szHex[cbBinary << 1] = 0; - for (idx = 0; idx < cbBinary; idx++) { - b = (pbBinary[idx] >> 4) & 0x0F; - szHex[idx << 1] = (b < 10) ? b + '0' : b - 10 + 'a'; - b = pbBinary[idx] & 0x0F; - szHex[(idx << 1) + 1] = (b < 10) ? b + '0' : b - 10 + 'a'; - } - return szHex; -} - -/* - * Reverse a byte array. - */ -void capi_reverse_array(PBYTE pb, DWORD cb) -{ - DWORD i; - BYTE t; - for (i = 0; i < cb >> 1; i++) { - t = pb[i]; - pb[i] = pb[cb - i - 1]; - pb[cb - i - 1] = t; - } -} - -/* - * Select a certificate given the criteria provided. - * If a criterion is absent it will be disregarded. - */ -void capi_select_cert_2(PBYTE pbSHA1, LPWSTR wszCN, PCCERT_CONTEXT *ppCertCtx, HCERTSTORE *phStore) -{ - HCERTSTORE hStoreMY = NULL, hStoreTMP = NULL; - PCCERT_CONTEXT pCertCtx = NULL; - HMODULE hCryptUIDLL = NULL; - DFNCryptUIDlgSelectCertificateFromStore dfnCryptUIDlgSelectCertificateFromStore; - CRYPT_HASH_BLOB cryptHashBlob; - DWORD dwCertCount = 0; - if (!(hStoreMY = CertOpenStore((LPCSTR)CERT_STORE_PROV_SYSTEM, 0, 0, CERT_SYSTEM_STORE_CURRENT_USER, L"MY"))) { - goto error; - } - if (pbSHA1) { - cryptHashBlob.cbData = 20; - cryptHashBlob.pbData = pbSHA1; - if ((*ppCertCtx = CertFindCertificateInStore(hStoreMY, X509_ASN_ENCODING, 0, CERT_FIND_SHA1_HASH, &cryptHashBlob, pCertCtx))) { - *phStore = hStoreMY; - return; - } else { - goto error; - } - } - if (!(hStoreTMP = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0, 0, NULL))) { - goto error; - } - while (TRUE) { - if (wszCN) { - pCertCtx = CertFindCertificateInStore(hStoreMY, X509_ASN_ENCODING, 0, CERT_FIND_SUBJECT_STR, wszCN, pCertCtx); - } else { - pCertCtx = CertEnumCertificatesInStore(hStoreMY, pCertCtx); - } - if (!pCertCtx) { - break; - } - /* - if (!CryptAcquireCertificatePrivateKey(pCertCtx, CRYPT_ACQUIRE_CACHE_FLAG | CRYPT_ACQUIRE_NO_HEALING | CRYPT_ACQUIRE_SILENT_FLAG | CRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAG, NULL, &hCryptProvOrNCryptKey, &dwKeySpec, &fCallerFreeProvAlwaysFalse)) { - continue; - } - */ - dwCertCount++; - CertAddCertificateContextToStore(hStoreTMP, pCertCtx, CERT_STORE_ADD_ALWAYS, NULL); - } - CertCloseStore(hStoreMY, CERT_CLOSE_STORE_FORCE_FLAG); - hStoreMY = NULL; - if (dwCertCount == 1) { - *ppCertCtx = CertEnumCertificatesInStore(hStoreTMP, NULL); - *phStore = hStoreTMP; - return; - } else if ((dwCertCount > 1) && - (hCryptUIDLL = LoadLibrary("cryptui.dll")) && - (dfnCryptUIDlgSelectCertificateFromStore = (DFNCryptUIDlgSelectCertificateFromStore)GetProcAddress(hCryptUIDLL, "CryptUIDlgSelectCertificateFromStore")) && - (*ppCertCtx = dfnCryptUIDlgSelectCertificateFromStore(hStoreTMP, NULL, NULL, NULL, 0, 0, NULL))) { - *phStore = hStoreTMP; - FreeLibrary(hCryptUIDLL); - return; - } -error: - if (hCryptUIDLL) { FreeLibrary(hCryptUIDLL); } - if (hStoreTMP) { CertCloseStore(hStoreTMP, CERT_CLOSE_STORE_FORCE_FLAG); } - if (hStoreMY) { CertCloseStore(hStoreMY, CERT_CLOSE_STORE_FORCE_FLAG); } - *ppCertCtx = NULL; - *phStore = NULL; -} - -/* - * Return a malloc'ed string containing the requested subitem. - */ -PSTR capi_select_cert_finditem(PSTR szCert, PCSTR szStart) -{ - PSTR ptrStart, ptrEnd, szResult; - ptrStart = strstr(szCert, szStart); - ptrEnd = strstr(szCert, ","); - if (!ptrEnd || ptrEnd < ptrStart) { - ptrEnd = szCert + strlen(szCert); - } - if (!ptrStart || ptrStart > ptrEnd) { - return NULL; - } - ptrStart += strlen(szStart); - szResult = (PSTR)calloc(ptrEnd - ptrStart + 1, sizeof(char)); - memcpy(szResult, ptrStart, ptrEnd - ptrStart); - return szResult; -} - -/* - * Select a certificate given the definition string. - */ -void capi_select_cert(PSTR szCert, PCCERT_CONTEXT *ppCertCtx, HCERTSTORE *phStore) -{ - PSTR szCN = NULL, szThumb, ptrStart, ptrStartAll; - LPWSTR wszCN = NULL; - DWORD i, len; - PBYTE pbThumb = snewn(20, BYTE); - ptrStart = strstr(szCert, "cert://"); - ptrStartAll = strstr(szCert, "cert://*"); - if (ptrStart != szCert) { - ptrStart = strstr(szCert, "x509://"); - ptrStartAll = strstr(szCert, "x509://*"); - if (ptrStart != szCert) { - *ppCertCtx = NULL; - *phStore = NULL; - return; - } - } - if (ptrStartAll) { - capi_select_cert_2(NULL, NULL, ppCertCtx, phStore); - return; - } - szThumb = capi_select_cert_finditem(szCert, "thumbprint="); - if (szThumb && 40 == strlen(szThumb)) { - capi_sha1_to_binary(szThumb, pbThumb); - capi_select_cert_2(pbThumb, NULL, ppCertCtx, phStore); - } else { - szCN = capi_select_cert_finditem(szCert, "cn="); - if (szCN) { - len = strlen(szCN); - wszCN = (LPWSTR)calloc(len + 1, sizeof(wchar_t)); - for (i = 0; i < len; i++) { - wszCN[i] = szCN[i]; - } - } - capi_select_cert_2(NULL, wszCN, ppCertCtx, phStore); - } - if (szCN) { free(szCN); } - if (wszCN) { free(wszCN); } - sfree(pbThumb); -} - -/* - * Get rsa key comment on the form "cert://cn=,thumbprint=". - */ -static PSTR capi_get_description(PSTR file, PCCERT_CONTEXT pCertContext) -{ - DWORD thumbPrintSize = 20; - BYTE thumbPrint[20]; - if (!CryptHashCertificate(0, CALG_SHA1, 0, pCertContext->pbCertEncoded, - pCertContext->cbCertEncoded, (PBYTE)&thumbPrint, &thumbPrintSize)) { - } - PSTR tp = capi_binary_to_hex((PBYTE)&thumbPrint, thumbPrintSize); - return dupcat(file, "thumbprint=", tp, NULL); -} - -char *wincrypto_invalid(ssh_key *key, unsigned flags) -{ - RSAKey *rsa = container_of(key, RSAKey, sshk); - HCERTSTORE hCertStore; - PCCERT_CONTEXT pCertCtx; - HCRYPTPROV_OR_NCRYPT_KEY_HANDLE hCryptProvOrNCryptKey = 0; - DWORD dwSpec, cbSig = 0; - BOOL fCallerFreeProvAlwaysFalse = TRUE; - - capi_select_cert(rsa->comment, &pCertCtx, &hCertStore); - if (pCertCtx) - { - if (CryptAcquireCertificatePrivateKey(pCertCtx, CRYPT_ACQUIRE_CACHE_FLAG | CRYPT_ACQUIRE_PREFER_NCRYPT_KEY_FLAG, 0, &hCryptProvOrNCryptKey, &dwSpec, &fCallerFreeProvAlwaysFalse)) { - return NULL; - } - CertFreeCertificateContext(pCertCtx); - CertCloseStore(hCertStore, CERT_CLOSE_STORE_FORCE_FLAG); - } - return dupstr("Could not acquire private key.."); -} - -static void wincrypto_public_blob(ssh_key *key, BinarySink *bs) -{ - DWORD cbPublicKeyBlob = 8192; - PBYTE pbPublicKeyBlob = NULL; - bool isX509 = false; - uintmax_t size = 0, i = 0; - - RSAKey *rsa = container_of(key, RSAKey, sshk); - - if (0 == strncmp("x509://", rsa->comment, 7)) { - isX509 = true; - } - - if (isX509) { - size = mp_get_integer(rsa->private_exponent); - for (i = 0; i < size; i++) { - BYTE f = mp_get_byte(rsa->iqmp, (size_t)(size - i - 1)); - put_byte(bs, f); - } - } else { - put_stringz(bs, "ssh-rsa"); - put_mp_ssh2(bs, rsa->exponent); - put_mp_ssh2(bs, rsa->modulus); - } - -} - -static ssh_key *wincrypto_new_priv(const ssh_keyalg *self, - ptrlen pub, ptrlen priv) -{ - RSAKey *rsa; - BOOL result; - PCCERT_CONTEXT pCertContext; - HCERTSTORE hCertStore; - DWORD cbPublicKeyBlob = 8192; - PBYTE pbPublicKeyBlob = NULL; - RSAPUBKEY *pRSAPubKey; - bool isX509 = false; - - int len = strlen(pub.ptr); - if ((len < 7) - || !(0 == strncmp("cert://", pub.ptr, 7) - || (isX509 = (0 == strncmp("x509://", pub.ptr, 7))))) { - return NULL; - } - - capi_select_cert((PSTR)pub.ptr, &pCertContext, &hCertStore); - if (!pCertContext) { - return NULL; - } - - rsa = snew(RSAKey); - rsa->p = mp_from_integer(0); - rsa->q = mp_from_integer(0); - if (isX509) - rsa->sshk.vt = &ssh_x509_wincrypt; - else - rsa->sshk.vt = &ssh_rsa_wincrypt; - rsa->comment = dupstr(pub.ptr); - - result = CryptDecodeObject( - X509_ASN_ENCODING, - RSA_CSP_PUBLICKEYBLOB, - pCertContext->pCertInfo->SubjectPublicKeyInfo.PublicKey.pbData, - pCertContext->pCertInfo->SubjectPublicKeyInfo.PublicKey.cbData, - 0, - (void*)(pbPublicKeyBlob = snewn(cbPublicKeyBlob, BYTE)), - &cbPublicKeyBlob); - if (!result) { - CertFreeCertificateContext(pCertContext); - sfree(pbPublicKeyBlob); - return NULL; - } - - pRSAPubKey = (RSAPUBKEY*)(pbPublicKeyBlob + sizeof(BLOBHEADER)); - capi_reverse_array(pbPublicKeyBlob + sizeof(BLOBHEADER) + sizeof(RSAPUBKEY), pRSAPubKey->bitlen / 8); - rsa->exponent = mp_from_integer(pRSAPubKey->pubexp); - rsa->modulus = mp_from_bytes_be(make_ptrlen(pbPublicKeyBlob + sizeof(BLOBHEADER) + sizeof(RSAPUBKEY), pRSAPubKey->bitlen / 8)); - rsa->iqmp = mp_from_bytes_be(make_ptrlen(pCertContext->pbCertEncoded, pCertContext->cbCertEncoded)); - rsa->private_exponent = mp_from_integer(pCertContext->cbCertEncoded); - - // cleanup - sfree(pbPublicKeyBlob); - CertFreeCertificateContext(pCertContext); - CertCloseStore(hCertStore, CERT_CLOSE_STORE_FORCE_FLAG); - - return &rsa->sshk; -} - -static void wincrypto_sign(ssh_key *key, ptrlen data, - unsigned flags, BinarySink *bs) -{ - HCERTSTORE hCertStore; - PCCERT_CONTEXT pCertCtx; - HCRYPTPROV_OR_NCRYPT_KEY_HANDLE hCryptProvOrNCryptKey = 0; - HCRYPTHASH hHash = 0; - PBYTE pbSig = NULL; - DWORD dwSpec, cbSig = 0; - BOOL fCallerFreeProvAlwaysFalse = TRUE; - bool isX509 = false; - BCRYPT_PKCS1_PADDING_INFO padInfo; - BCRYPT_ALG_HANDLE hHashAlg = NULL; - BCRYPT_HASH_HANDLE hHashBcrypt = NULL; - NTSTATUS status = 0; - DWORD cbData = 0, cbHash = 0, cbHashObject = 0; - PBYTE pbHashObject = NULL; - PBYTE pbHash = NULL; - - unsigned short* bcrypt_alg = NULL; - ALG_ID alg = CALG_SHA1; - const char *sign_alg_name; - RSAKey *rsa = container_of(key, RSAKey, sshk); - - int len = strlen(rsa->comment); - if ((len < 7) - || !(0 == strncmp("cert://", rsa->comment, 7) - || (isX509 = (0 == strncmp("x509://", rsa->comment, 7))))) { - return; - } - - if (isX509) { - alg = CALG_SHA1; - bcrypt_alg = BCRYPT_SHA1_ALGORITHM; - padInfo.pszAlgId = BCRYPT_SHA1_ALGORITHM; - sign_alg_name = "x509v3-sign-rsa"; - } else { - if (flags & SSH_AGENT_RSA_SHA2_256) { - alg = CALG_SHA_256; - bcrypt_alg = BCRYPT_SHA256_ALGORITHM; - padInfo.pszAlgId = BCRYPT_SHA256_ALGORITHM; - sign_alg_name = "rsa-sha2-256"; - } else if (flags & SSH_AGENT_RSA_SHA2_512) { - alg = CALG_SHA_512; - bcrypt_alg = BCRYPT_SHA512_ALGORITHM; - padInfo.pszAlgId = BCRYPT_SHA512_ALGORITHM; - sign_alg_name = "rsa-sha2-512"; - } else { - alg = CALG_SHA1; - bcrypt_alg = BCRYPT_SHA1_ALGORITHM; - padInfo.pszAlgId = BCRYPT_SHA1_ALGORITHM; - sign_alg_name = "ssh-rsa"; - } - } - - capi_select_cert(rsa->comment, &pCertCtx, &hCertStore); - if (pCertCtx) - { - if (CryptAcquireCertificatePrivateKey(pCertCtx, CRYPT_ACQUIRE_CACHE_FLAG | CRYPT_ACQUIRE_PREFER_NCRYPT_KEY_FLAG, 0, &hCryptProvOrNCryptKey, &dwSpec, &fCallerFreeProvAlwaysFalse)) { - if (dwSpec == AT_KEYEXCHANGE || dwSpec == AT_SIGNATURE) { - /* A lot faster for smartcards because CryptSignHash for asking buffersize is querying sc already */ - cbSig = 2048; - pbSig = snewn(cbSig, BYTE); - - /* CSP implementation */ - if (!CryptCreateHash((HCRYPTPROV)hCryptProvOrNCryptKey, alg, 0, 0, &hHash)) { - goto Cleanup; - } - - if (!CryptHashData(hHash, data.ptr, data.len, 0)) { - goto Cleanup; - } - - if (!CryptSignHash(hHash, dwSpec, NULL, 0, pbSig, &cbSig)) { - goto Cleanup; - } - - capi_reverse_array(pbSig, cbSig); - put_stringz(bs, sign_alg_name); - put_uint32(bs, cbSig); - put_data(bs, pbSig, cbSig); - } else if (dwSpec == CERT_NCRYPT_KEY_SPEC) { - /* KSP/CNG implementation */ - - if ((status = BCryptOpenAlgorithmProvider( - &hHashAlg, bcrypt_alg, NULL, 0)) != 0) { - goto Cleanup; - } - - if ((status = BCryptGetProperty( - hHashAlg, BCRYPT_OBJECT_LENGTH, (PBYTE)&cbHashObject, sizeof(DWORD), &cbData, 0)) != 0) { - goto Cleanup; - } - - pbHashObject = snewn(cbHashObject, BYTE); - if (NULL == pbHashObject) { - goto Cleanup; - } - - if ((status = BCryptGetProperty( - hHashAlg, BCRYPT_HASH_LENGTH, (PBYTE)&cbHash, sizeof(DWORD), &cbData, 0)) != 0) { - goto Cleanup; - } - - pbHash = snewn(cbHash, BYTE); - if (NULL == pbHash) { - goto Cleanup; - } - - if ((status = BCryptCreateHash( - hHashAlg, &hHashBcrypt, pbHashObject, cbHashObject, NULL, 0, 0)) != 0) { - goto Cleanup; - } - - if ((status = BCryptHashData( - hHashBcrypt, (PUCHAR)data.ptr, data.len, 0)) != 0) { - goto Cleanup; - } - - if ((status = BCryptFinishHash( - hHashBcrypt, pbHash, cbHash, 0)) != 0) { - goto Cleanup; - } - - if ((status = NCryptSignHash( - hCryptProvOrNCryptKey, &padInfo, pbHash, cbHash, NULL, 0, &cbSig, BCRYPT_PAD_PKCS1)) != 0) { - goto Cleanup; - } - - pbSig = snewn(cbSig, BYTE); - if (NULL == pbSig) { - goto Cleanup; - } - - if ((status = NCryptSignHash( - hCryptProvOrNCryptKey, &padInfo, pbHash, cbHash, pbSig, cbSig, &cbSig, BCRYPT_PAD_PKCS1)) != 0) { - goto Cleanup; - } - - put_stringz(bs, sign_alg_name); - put_uint32(bs, cbSig); - put_data(bs, pbSig, cbSig); - } - } - } -Cleanup: - if (hHashAlg) - BCryptCloseAlgorithmProvider(hHashAlg, 0); - if (hHash) - BCryptDestroyHash(hHashBcrypt); - if (pbHashObject) - sfree(pbHashObject); - if (pbHash) - sfree(pbHash); - if (pbSig) - sfree(pbSig); - if (hHash) - CryptDestroyHash(hHash); - if (pCertCtx) - CertFreeCertificateContext(pCertCtx); - if (hCertStore) - CertCloseStore(hCertStore, CERT_CLOSE_STORE_FORCE_FLAG); -} - -static void wincrypto_freekey(ssh_key *key) -{ - RSAKey *rsa = container_of(key, RSAKey, sshk); - freersakey(rsa); - sfree(rsa); -} - -/* - * Load a rsa key from a certificate in windows certificate personal store. - */ -BOOL capi_load_key(const Filename **filename, BinarySink *bs) -{ - BOOL result; - PCCERT_CONTEXT pCertContext; - HCERTSTORE hCertStore; - DWORD cbPublicKeyBlob = 8192; - PBYTE pbPublicKeyBlob = NULL; - RSAPUBKEY *pRSAPubKey; - bool isX509 = false; - - int len = strlen((*filename)->path); - if ((len < 7) - || !(0 == strncmp("cert://", (*filename)->path, 7) - || (isX509 = (0 == strncmp("x509://", (*filename)->path, 7))))) { - return false; - } - capi_select_cert((*filename)->path, &pCertContext, &hCertStore); - if (!pCertContext) { - return false; - } - - if ((*filename)->path[7] == '*') { - (*filename)->path[7] = '\0'; - (*filename) = filename_from_str(capi_get_description((*filename)->path, pCertContext)); - } - - result = CryptDecodeObject( - X509_ASN_ENCODING, - RSA_CSP_PUBLICKEYBLOB, - pCertContext->pCertInfo->SubjectPublicKeyInfo.PublicKey.pbData, - pCertContext->pCertInfo->SubjectPublicKeyInfo.PublicKey.cbData, - 0, - (void*)(pbPublicKeyBlob = snewn(cbPublicKeyBlob, BYTE)), - &cbPublicKeyBlob); - if (!result) { - CertFreeCertificateContext(pCertContext); - sfree(pbPublicKeyBlob); - return false; - } - - pRSAPubKey = (RSAPUBKEY*)(pbPublicKeyBlob + sizeof(BLOBHEADER)); - if (isX509) { - put_data(bs, pCertContext->pbCertEncoded, pCertContext->cbCertEncoded); - } else { - put_stringz(bs, "ssh-rsa"); - put_uint32(bs, 3); - capi_reverse_array((PBYTE)&(pRSAPubKey->pubexp), 4); - put_uint32(bs, pRSAPubKey->pubexp); - capi_reverse_array(pbPublicKeyBlob + sizeof(BLOBHEADER) + sizeof(RSAPUBKEY), pRSAPubKey->bitlen / 8); - put_uint16(bs, 1); - put_uint16(bs, pRSAPubKey->bitlen / 8); - put_data(bs, pbPublicKeyBlob + sizeof(BLOBHEADER) + sizeof(RSAPUBKEY), pRSAPubKey->bitlen / 8); - } - - /* cleanup */ - sfree(pbPublicKeyBlob); - CertFreeCertificateContext(pCertContext); - CertCloseStore(hCertStore, CERT_CLOSE_STORE_FORCE_FLAG); - return true; -} - -const ssh_keyalg ssh_rsa_wincrypt = { - NULL /*rsa2_new_pub*/, - wincrypto_new_priv /*rsa2_new_priv*/, - NULL /*rsa2_new_priv_openssh*/, - - wincrypto_freekey /*rsa2_freekey*/, - wincrypto_invalid, - wincrypto_sign /*rsa2_sign*/, - NULL /*rsa2_verify*/, - wincrypto_public_blob /*rsa2_public_blob*/, - NULL /*rsa2_private_blob*/, - NULL /*rsa2_openssh_blob*/, - NULL /*rsa2_cache_str*/, - - NULL /*rsa2_pubkey_bits*/, - - "ssh-rsa", - "rsa2", - NULL, - SSH_AGENT_RSA_SHA2_256 | SSH_AGENT_RSA_SHA2_512, -}; - - -const ssh_keyalg ssh_x509_wincrypt = { - NULL /*rsa2_new_pub*/, - wincrypto_new_priv /*rsa2_new_priv*/, - NULL /*rsa2_new_priv_openssh*/, - - wincrypto_freekey /*rsa2_freekey*/, - wincrypto_invalid, - wincrypto_sign /*rsa2_sign*/, - NULL /*rsa2_verify*/, - wincrypto_public_blob /*rsa2_public_blob*/, - NULL /*rsa2_private_blob*/, - NULL /*rsa2_openssh_blob*/, - NULL /*rsa2_cache_str*/, - - NULL /*rsa2_pubkey_bits*/, - - "x509v3-sign-rsa", - "rsa2", - NULL, - SSH_AGENT_RSA_SHA2_256 | SSH_AGENT_RSA_SHA2_512, -}; - -#endif diff --git a/wincrypt/wincrypto.h b/wincrypt/wincrypto.h deleted file mode 100644 index 0a4581f..0000000 --- a/wincrypt/wincrypto.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifdef MOD_WINCRYPT -#ifdef HAS_WINX509 -BOOL capi_load_key(const Filename **filename, BinarySink *bs) ; - - -#define ALG_SID_SHA_256 12 -#define ALG_SID_SHA_512 14 -#define CALG_SHA_256 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_256) -#define CALG_SHA_512 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_512) - -#define CRYPT_ACQUIRE_PREFER_NCRYPT_KEY_FLAG 0x20000 -#define HCRYPTPROV_LEGACY void* -typedef unsigned int ALG_ID; -BOOL CryptHashCertificate( - HCRYPTPROV_LEGACY hCryptProv, - ALG_ID Algid, - DWORD dwFlags, - const BYTE *pbEncoded, - DWORD cbEncoded, - BYTE *pbComputedHash, - DWORD *pcbComputedHash -); -#endif /* HAS_WINX509 */ -#endif