| /* |
| * Dropbear - a SSH2 server |
| * |
| * Copyright (c) 2005 Matt Johnston |
| * All rights reserved. |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a copy |
| * of this software and associated documentation files (the "Software"), to deal |
| * in the Software without restriction, including without limitation the rights |
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| * copies of the Software, and to permit persons to whom the Software is |
| * furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in |
| * all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| * SOFTWARE. */ |
| |
| #include "includes.h" |
| |
| #ifdef ENABLE_CLI_AGENTFWD |
| |
| #include "agentfwd.h" |
| #include "session.h" |
| #include "ssh.h" |
| #include "dbutil.h" |
| #include "chansession.h" |
| #include "channel.h" |
| #include "packet.h" |
| #include "buffer.h" |
| #include "dbrandom.h" |
| #include "listener.h" |
| #include "runopts.h" |
| #include "atomicio.h" |
| #include "signkey.h" |
| #include "auth.h" |
| |
| /* The protocol implemented to talk to OpenSSH's SSH2 agent is documented in |
| PROTOCOL.agent in recent OpenSSH source distributions (5.1p1 has it). */ |
| |
| static int new_agent_chan(struct Channel * channel); |
| |
| const struct ChanType cli_chan_agent = { |
| 0, /* sepfds */ |
| "auth-agent@openssh.com", |
| new_agent_chan, |
| NULL, |
| NULL, |
| NULL |
| }; |
| |
| static int connect_agent() { |
| |
| int fd = -1; |
| char* agent_sock = NULL; |
| |
| agent_sock = getenv("SSH_AUTH_SOCK"); |
| if (agent_sock == NULL) |
| return -1; |
| |
| fd = connect_unix(agent_sock); |
| |
| if (fd < 0) { |
| dropbear_log(LOG_INFO, "Failed to connect to agent"); |
| } |
| |
| return fd; |
| } |
| |
| /* handle a request for a connection to the locally running ssh-agent |
| or forward. */ |
| static int new_agent_chan(struct Channel * channel) { |
| |
| int fd = -1; |
| |
| if (!cli_opts.agent_fwd) |
| return SSH_OPEN_ADMINISTRATIVELY_PROHIBITED; |
| |
| fd = connect_agent(); |
| if (fd < 0) { |
| return SSH_OPEN_CONNECT_FAILED; |
| } |
| |
| setnonblocking(fd); |
| |
| ses.maxfd = MAX(ses.maxfd, fd); |
| |
| channel->readfd = fd; |
| channel->writefd = fd; |
| |
| return 0; |
| } |
| |
| /* Sends a request to the agent, returning a newly allocated buffer |
| * with the response */ |
| /* This function will block waiting for a response - it will |
| * only be used by client authentication (not for forwarded requests) |
| * won't cause problems for interactivity. */ |
| /* Packet format (from draft-ylonen) |
| 4 bytes Length, msb first. Does not include length itself. |
| 1 byte Packet type. The value 255 is reserved for future extensions. |
| data Any data, depending on packet type. Encoding as in the ssh packet |
| protocol. |
| */ |
| static buffer * agent_request(unsigned char type, buffer *data) { |
| |
| buffer * payload = NULL; |
| buffer * inbuf = NULL; |
| size_t readlen = 0; |
| ssize_t ret; |
| const int fd = cli_opts.agent_fd; |
| unsigned int data_len = 0; |
| if (data) |
| { |
| data_len = data->len; |
| } |
| |
| payload = buf_new(4 + 1 + data_len); |
| |
| buf_putint(payload, 1 + data_len); |
| buf_putbyte(payload, type); |
| if (data) { |
| buf_putbytes(payload, data->data, data->len); |
| } |
| buf_setpos(payload, 0); |
| |
| ret = atomicio(write, fd, buf_getptr(payload, payload->len), payload->len); |
| if ((size_t)ret != payload->len) { |
| TRACE(("write failed fd %d for agent_request, %s", fd, strerror(errno))) |
| goto out; |
| } |
| |
| buf_free(payload); |
| payload = NULL; |
| TRACE(("Wrote out bytes for agent_request")) |
| /* Now we read the response */ |
| inbuf = buf_new(4); |
| ret = atomicio(read, fd, buf_getwriteptr(inbuf, 4), 4); |
| if (ret != 4) { |
| TRACE(("read of length failed for agent_request")) |
| goto out; |
| } |
| buf_setpos(inbuf, 0); |
| buf_setlen(inbuf, ret); |
| |
| readlen = buf_getint(inbuf); |
| if (readlen > MAX_AGENT_REPLY) { |
| TRACE(("agent reply is too big")); |
| goto out; |
| } |
| |
| inbuf = buf_resize(inbuf, readlen); |
| buf_setpos(inbuf, 0); |
| ret = atomicio(read, fd, buf_getwriteptr(inbuf, readlen), readlen); |
| if ((size_t)ret != readlen) { |
| TRACE(("read of data failed for agent_request")) |
| goto out; |
| } |
| buf_incrwritepos(inbuf, readlen); |
| buf_setpos(inbuf, 0); |
| |
| out: |
| if (payload) |
| buf_free(payload); |
| |
| return inbuf; |
| } |
| |
| static void agent_get_key_list(m_list * ret_list) |
| { |
| buffer * inbuf = NULL; |
| unsigned int num = 0; |
| unsigned char packet_type; |
| unsigned int i; |
| int ret; |
| |
| inbuf = agent_request(SSH2_AGENTC_REQUEST_IDENTITIES, NULL); |
| if (!inbuf) { |
| TRACE(("agent_request failed returning identities")) |
| goto out; |
| } |
| |
| /* The reply has a format of: |
| byte SSH2_AGENT_IDENTITIES_ANSWER |
| uint32 num_keys |
| Followed by zero or more consecutive keys, encoded as: |
| string key_blob |
| string key_comment |
| */ |
| packet_type = buf_getbyte(inbuf); |
| if (packet_type != SSH2_AGENT_IDENTITIES_ANSWER) { |
| goto out; |
| } |
| |
| num = buf_getint(inbuf); |
| for (i = 0; i < num; i++) { |
| sign_key * pubkey = NULL; |
| enum signkey_type key_type = DROPBEAR_SIGNKEY_ANY; |
| buffer * key_buf; |
| |
| /* each public key is encoded as a string */ |
| key_buf = buf_getstringbuf(inbuf); |
| pubkey = new_sign_key(); |
| ret = buf_get_pub_key(key_buf, pubkey, &key_type); |
| buf_free(key_buf); |
| if (ret != DROPBEAR_SUCCESS) { |
| TRACE(("Skipping bad/unknown type pubkey from agent")); |
| sign_key_free(pubkey); |
| } else { |
| pubkey->type = key_type; |
| pubkey->source = SIGNKEY_SOURCE_AGENT; |
| |
| list_append(ret_list, pubkey); |
| } |
| |
| /* We'll ignore the comment for now. might want it later.*/ |
| buf_eatstring(inbuf); |
| } |
| |
| out: |
| if (inbuf) { |
| buf_free(inbuf); |
| inbuf = NULL; |
| } |
| } |
| |
| void cli_setup_agent(struct Channel *channel) { |
| if (!getenv("SSH_AUTH_SOCK")) { |
| return; |
| } |
| |
| start_send_channel_request(channel, "auth-agent-req@openssh.com"); |
| /* Don't want replies */ |
| buf_putbyte(ses.writepayload, 0); |
| encrypt_packet(); |
| } |
| |
| /* Returned keys are prepended to ret_list, which will |
| be updated. */ |
| void cli_load_agent_keys(m_list *ret_list) { |
| /* agent_fd will be closed after successful auth */ |
| cli_opts.agent_fd = connect_agent(); |
| if (cli_opts.agent_fd < 0) { |
| return; |
| } |
| |
| agent_get_key_list(ret_list); |
| } |
| |
| void agent_buf_sign(buffer *sigblob, sign_key *key, |
| buffer *data_buf) { |
| buffer *request_data = NULL; |
| buffer *response = NULL; |
| unsigned int siglen; |
| int packet_type; |
| |
| /* Request format |
| byte SSH2_AGENTC_SIGN_REQUEST |
| string key_blob |
| string data |
| uint32 flags |
| */ |
| request_data = buf_new(MAX_PUBKEY_SIZE + data_buf->len + 12); |
| buf_put_pub_key(request_data, key, key->type); |
| |
| buf_putbufstring(request_data, data_buf); |
| buf_putint(request_data, 0); |
| |
| response = agent_request(SSH2_AGENTC_SIGN_REQUEST, request_data); |
| |
| if (!response) { |
| goto fail; |
| } |
| |
| packet_type = buf_getbyte(response); |
| if (packet_type != SSH2_AGENT_SIGN_RESPONSE) { |
| goto fail; |
| } |
| |
| /* Response format |
| byte SSH2_AGENT_SIGN_RESPONSE |
| string signature_blob |
| */ |
| siglen = buf_getint(response); |
| buf_putbytes(sigblob, buf_getptr(response, siglen), siglen); |
| goto cleanup; |
| |
| fail: |
| /* XXX don't fail badly here. instead propagate a failure code back up to |
| the cli auth pubkey code, and just remove this key from the list of |
| ones to try. */ |
| dropbear_exit("Agent failed signing key"); |
| |
| cleanup: |
| if (request_data) { |
| buf_free(request_data); |
| } |
| if (response) { |
| buf_free(response); |
| } |
| } |
| |
| #endif |