static int asn1_check_sequence(ssh_buffer buffer) { unsigned char *j = NULL; unsigned char tmp; int i; uint32_t size; uint32_t padding; if (ssh_buffer_get_data(buffer, &tmp, 1) == 0 || tmp != ASN1_SEQUENCE) { return 0; } size = asn1_get_len(buffer); if ((padding = ssh_buffer_get_len(buffer) - size) > 0) { for (i = ssh_buffer_get_len(buffer) - size, j = (unsigned char*)ssh_buffer_get(buffer) + size; i; i--, j++) { if (*j != padding) { /* padding is allowed */ return 0; /* but nothing else */ } } } return 1; }
/* this is a public key in openssh's format */ static ssh_string make_rsa1_string(ssh_string e, ssh_string n){ ssh_buffer buffer = NULL; ssh_string rsa = NULL; ssh_string ret = NULL; buffer = ssh_buffer_new(); rsa = ssh_string_from_char("ssh-rsa1"); if (rsa == NULL) { goto error; } if (ssh_buffer_add_ssh_string(buffer, rsa) < 0) { goto error; } if (ssh_buffer_add_ssh_string(buffer, e) < 0) { goto error; } if (ssh_buffer_add_ssh_string(buffer, n) < 0) { goto error; } ret = ssh_string_new(ssh_buffer_get_len(buffer)); if (ret == NULL) { goto error; } ssh_string_fill(ret, ssh_buffer_get(buffer), ssh_buffer_get_len(buffer)); error: ssh_buffer_free(buffer); ssh_string_free(rsa); return ret; }
int sftp_reply_names(sftp_client_message msg) { ssh_buffer out; out = ssh_buffer_new(); if (out == NULL) { ssh_buffer_free(msg->attrbuf); return -1; } if (ssh_buffer_add_u32(out, msg->id) < 0 || ssh_buffer_add_u32(out, htonl(msg->attr_num)) < 0 || ssh_buffer_add_data(out, ssh_buffer_get(msg->attrbuf), ssh_buffer_get_len(msg->attrbuf)) < 0 || sftp_packet_write(msg->sftp, SSH_FXP_NAME, out) < 0) { ssh_buffer_free(out); ssh_buffer_free(msg->attrbuf); return -1; } ssh_buffer_free(out); ssh_buffer_free(msg->attrbuf); msg->attr_num = 0; msg->attrbuf = NULL; return 0; }
static int privatekey_decrypt(int algo, int mode, unsigned int key_len, unsigned char *iv, unsigned int iv_len, ssh_buffer data, ssh_auth_callback cb, void *userdata, const char *desc) { char passphrase[MAX_PASSPHRASE_SIZE] = {0}; unsigned char key[MAX_KEY_SIZE] = {0}; unsigned char *tmp = NULL; gcry_cipher_hd_t cipher; int rc = -1; if (!algo) { return -1; } if (cb) { rc = (*cb)(desc, passphrase, MAX_PASSPHRASE_SIZE, 0, 0, userdata); if (rc < 0) { return -1; } } else if (cb == NULL && userdata != NULL) { snprintf(passphrase, MAX_PASSPHRASE_SIZE, "%s", (char *) userdata); } if (passphrase_to_key(passphrase, strlen(passphrase), iv, key, key_len) < 0) { return -1; } if (gcry_cipher_open(&cipher, algo, mode, 0) || gcry_cipher_setkey(cipher, key, key_len) || gcry_cipher_setiv(cipher, iv, iv_len) || (tmp = malloc(ssh_buffer_get_len(data) * sizeof (char))) == NULL || gcry_cipher_decrypt(cipher, tmp, ssh_buffer_get_len(data), ssh_buffer_get(data), ssh_buffer_get_len(data))) { gcry_cipher_close(cipher); return -1; } memcpy(ssh_buffer_get(data), tmp, ssh_buffer_get_len(data)); SAFE_FREE(tmp); gcry_cipher_close(cipher); return 0; }
/* * This function places the outgoing packet buffer into an outgoing * socket buffer */ static int ssh_packet_write(ssh_session session) { int rc = SSH_ERROR; rc=ssh_socket_write(session->socket, ssh_buffer_get(session->out_buffer), ssh_buffer_get_len(session->out_buffer)); return rc; }
int ssh_pki_export_signature_blob(const ssh_signature sig, ssh_string *sig_blob) { ssh_buffer buf = NULL; ssh_string str; int rc; if (sig == NULL || sig_blob == NULL) { return SSH_ERROR; } buf = ssh_buffer_new(); if (buf == NULL) { return SSH_ERROR; } str = ssh_string_from_char(sig->type_c); if (str == NULL) { ssh_buffer_free(buf); return SSH_ERROR; } rc = ssh_buffer_add_ssh_string(buf, str); ssh_string_free(str); if (rc < 0) { ssh_buffer_free(buf); return SSH_ERROR; } str = pki_signature_to_blob(sig); if (str == NULL) { ssh_buffer_free(buf); return SSH_ERROR; } rc = ssh_buffer_add_ssh_string(buf, str); ssh_string_free(str); if (rc < 0) { ssh_buffer_free(buf); return SSH_ERROR; } str = ssh_string_new(ssh_buffer_get_len(buf)); if (str == NULL) { ssh_buffer_free(buf); return SSH_ERROR; } ssh_string_fill(str, ssh_buffer_get(buf), ssh_buffer_get_len(buf)); ssh_buffer_free(buf); *sig_blob = str; return SSH_OK; }
static void *thread_ssh_buffer_add_format(void *threadid) { ssh_buffer buffer = NULL; uint8_t b; uint16_t w; uint32_t d; uint64_t q; ssh_string s = NULL; int rc; size_t len; uint8_t verif[] = "\x42\x13\x37\x0b\xad\xc0\xde\x13\x24\x35\x46" "\xac\xbd\xce\xdf" "\x00\x00\x00\x06" "libssh" "\x00\x00\x00\x05" "rocks" "So much" "Fun!"; /* Unused */ (void) threadid; /* Setup */ buffer = ssh_buffer_new(); if (buffer == NULL) { pthread_exit((void *)-1); } ssh_buffer_set_secure(buffer); b = 0x42; w = 0x1337; d = 0xbadc0de; q = 0x13243546acbdcedf; s = ssh_string_from_char("libssh"); rc = ssh_buffer_pack(buffer, "bwdqSsPt", b, w, d, q, s, "rocks", 7, "So much", "Fun!"); assert_int_equal(rc, SSH_OK); len = ssh_buffer_get_len(buffer); assert_int_equal(len, sizeof(verif) - 1); assert_memory_equal(ssh_buffer_get(buffer), verif, sizeof(verif) -1); SSH_STRING_FREE(s); /* Teardown */ SSH_BUFFER_FREE(buffer); pthread_exit(NULL); }
/* * Test the behavior of ssh_buffer_prepend_data */ static void *thread_buffer_prepend(void *threadid) { ssh_buffer buffer = NULL; uint32_t v; /* Unused */ (void) threadid; /* Setup */ buffer = ssh_buffer_new(); if (buffer == NULL) { pthread_exit((void *)-1); } ssh_buffer_set_secure(buffer); ssh_buffer_add_data(buffer, "abcdef", 6); ssh_buffer_prepend_data(buffer, "xyz", 3); assert_int_equal(ssh_buffer_get_len(buffer), 9); assert_memory_equal(ssh_buffer_get(buffer), "xyzabcdef", 9); /* Now remove 4 bytes and see if we can replace them */ ssh_buffer_get_u32(buffer, &v); assert_int_equal(ssh_buffer_get_len(buffer), 5); assert_memory_equal(ssh_buffer_get(buffer), "bcdef", 5); ssh_buffer_prepend_data(buffer, "aris", 4); assert_int_equal(ssh_buffer_get_len(buffer), 9); assert_memory_equal(ssh_buffer_get(buffer), "arisbcdef", 9); /* same thing but we add 5 bytes now */ ssh_buffer_get_u32(buffer, &v); assert_int_equal(ssh_buffer_get_len(buffer), 5); assert_memory_equal(ssh_buffer_get(buffer), "bcdef", 5); ssh_buffer_prepend_data(buffer, "12345", 5); assert_int_equal(ssh_buffer_get_len(buffer), 10); assert_memory_equal(ssh_buffer_get(buffer), "12345bcdef", 10); /* Teardown */ SSH_BUFFER_FREE(buffer); pthread_exit(NULL); }
/** * @internal * * @brief Verify the hmac of a packet * * @param session The session to use. * @param buffer The buffer to verify the hmac from. * @param mac The mac to compare with the hmac. * * @return 0 if hmac and mac are equal, < 0 if not or an error * occurred. */ int ssh_packet_hmac_verify(ssh_session session, ssh_buffer buffer, uint8_t *mac, enum ssh_hmac_e type) { unsigned char hmacbuf[DIGEST_MAX_LEN] = {0}; HMACCTX ctx; unsigned int len; uint32_t seq; /* AEAD type have no mac checking */ if (type == SSH_HMAC_AEAD_POLY1305) { return SSH_OK; } ctx = hmac_init(session->current_crypto->decryptMAC, hmac_digest_len(type), type); if (ctx == NULL) { return -1; } seq = htonl(session->recv_seq); hmac_update(ctx, (unsigned char *) &seq, sizeof(uint32_t)); hmac_update(ctx, ssh_buffer_get(buffer), ssh_buffer_get_len(buffer)); hmac_final(ctx, hmacbuf, &len); #ifdef DEBUG_CRYPTO ssh_print_hexa("received mac",mac,len); ssh_print_hexa("Computed mac",hmacbuf,len); ssh_print_hexa("seq",(unsigned char *)&seq,sizeof(uint32_t)); #endif if (secure_memcmp(mac, hmacbuf, len) == 0) { return 0; } return -1; }
/* * This function signs the session id as a string then * the content of sigbuf */ ssh_string ssh_pki_do_sign(ssh_session session, ssh_buffer sigbuf, const ssh_key privkey) { struct ssh_crypto_struct *crypto = session->current_crypto ? session->current_crypto : session->next_crypto; ssh_signature sig = NULL; ssh_string sig_blob; ssh_string session_id; int rc; if (privkey == NULL || !ssh_key_is_private(privkey)) { return NULL; } session_id = ssh_string_new(crypto->digest_len); if (session_id == NULL) { return NULL; } ssh_string_fill(session_id, crypto->session_id, crypto->digest_len); if (privkey->type == SSH_KEYTYPE_ECDSA) { #ifdef HAVE_ECC unsigned char ehash[EVP_DIGEST_LEN] = {0}; uint32_t elen; EVPCTX ctx; ctx = evp_init(privkey->ecdsa_nid); if (ctx == NULL) { ssh_string_free(session_id); return NULL; } evp_update(ctx, session_id, ssh_string_len(session_id) + 4); evp_update(ctx, ssh_buffer_get(sigbuf), ssh_buffer_get_len(sigbuf)); evp_final(ctx, ehash, &elen); #ifdef DEBUG_CRYPTO ssh_print_hexa("Hash being signed", ehash, elen); #endif sig = pki_do_sign(privkey, ehash, elen); #endif } else if (privkey->type == SSH_KEYTYPE_ED25519){ ssh_buffer buf; buf = ssh_buffer_new(); if (buf == NULL) { ssh_string_free(session_id); return NULL; } ssh_buffer_set_secure(buf); rc = ssh_buffer_pack(buf, "SP", session_id, ssh_buffer_get_len(sigbuf), ssh_buffer_get(sigbuf)); if (rc != SSH_OK) { ssh_string_free(session_id); ssh_buffer_free(buf); return NULL; } sig = pki_do_sign(privkey, ssh_buffer_get(buf), ssh_buffer_get_len(buf)); ssh_buffer_free(buf); } else { unsigned char hash[SHA_DIGEST_LEN] = {0}; SHACTX ctx; ctx = sha1_init(); if (ctx == NULL) { ssh_string_free(session_id); return NULL; } sha1_update(ctx, session_id, ssh_string_len(session_id) + 4); sha1_update(ctx, ssh_buffer_get(sigbuf), ssh_buffer_get_len(sigbuf)); sha1_final(hash, ctx); #ifdef DEBUG_CRYPTO ssh_print_hexa("Hash being signed", hash, SHA_DIGEST_LEN); #endif sig = pki_do_sign(privkey, hash, SHA_DIGEST_LEN); } ssh_string_free(session_id); if (sig == NULL) { return NULL; } rc = ssh_pki_export_signature_blob(sig, &sig_blob); ssh_signature_free(sig); if (rc < 0) { return NULL; } return sig_blob; }
int ssh_make_sessionid(ssh_session session) { ssh_string num = NULL; ssh_buffer server_hash = NULL; ssh_buffer client_hash = NULL; ssh_buffer buf = NULL; ssh_string server_pubkey_blob = NULL; const_bignum client_pubkey, server_pubkey; #ifdef WITH_GEX const_bignum modulus, generator; #endif int rc = SSH_ERROR; buf = ssh_buffer_new(); if (buf == NULL) { return rc; } rc = ssh_buffer_pack(buf, "ss", session->clientbanner, session->serverbanner); if (rc == SSH_ERROR) { goto error; } if (session->client) { server_hash = session->in_hashbuf; client_hash = session->out_hashbuf; } else { server_hash = session->out_hashbuf; client_hash = session->in_hashbuf; } /* * Handle the two final fields for the KEXINIT message (RFC 4253 7.1): * * boolean first_kex_packet_follows * uint32 0 (reserved for future extension) */ rc = ssh_buffer_add_u8(server_hash, 0); if (rc < 0) { goto error; } rc = ssh_buffer_add_u32(server_hash, 0); if (rc < 0) { goto error; } /* These fields are handled for the server case in ssh_packet_kexinit. */ if (session->client) { rc = ssh_buffer_add_u8(client_hash, 0); if (rc < 0) { goto error; } rc = ssh_buffer_add_u32(client_hash, 0); if (rc < 0) { goto error; } } rc = ssh_dh_get_next_server_publickey_blob(session, &server_pubkey_blob); if (rc != SSH_OK) { goto error; } rc = ssh_buffer_pack(buf, "dPdPS", ssh_buffer_get_len(client_hash), ssh_buffer_get_len(client_hash), ssh_buffer_get(client_hash), ssh_buffer_get_len(server_hash), ssh_buffer_get_len(server_hash), ssh_buffer_get(server_hash), server_pubkey_blob); ssh_string_free(server_pubkey_blob); if(rc != SSH_OK){ goto error; } switch(session->next_crypto->kex_type) { case SSH_KEX_DH_GROUP1_SHA1: case SSH_KEX_DH_GROUP14_SHA1: case SSH_KEX_DH_GROUP16_SHA512: case SSH_KEX_DH_GROUP18_SHA512: rc = ssh_dh_keypair_get_keys(session->next_crypto->dh_ctx, DH_CLIENT_KEYPAIR, NULL, &client_pubkey); if (rc != SSH_OK) { goto error; } rc = ssh_dh_keypair_get_keys(session->next_crypto->dh_ctx, DH_SERVER_KEYPAIR, NULL, &server_pubkey); if (rc != SSH_OK) { goto error; } rc = ssh_buffer_pack(buf, "BB", client_pubkey, server_pubkey); if (rc != SSH_OK) { goto error; } break; #ifdef WITH_GEX case SSH_KEX_DH_GEX_SHA1: case SSH_KEX_DH_GEX_SHA256: rc = ssh_dh_keypair_get_keys(session->next_crypto->dh_ctx, DH_CLIENT_KEYPAIR, NULL, &client_pubkey); if (rc != SSH_OK) { goto error; } rc = ssh_dh_keypair_get_keys(session->next_crypto->dh_ctx, DH_SERVER_KEYPAIR, NULL, &server_pubkey); if (rc != SSH_OK) { goto error; } rc = ssh_dh_get_parameters(session->next_crypto->dh_ctx, &modulus, &generator); if (rc != SSH_OK) { goto error; } rc = ssh_buffer_pack(buf, "dddBBBB", session->next_crypto->dh_pmin, session->next_crypto->dh_pn, session->next_crypto->dh_pmax, modulus, generator, client_pubkey, server_pubkey); if (rc != SSH_OK) { goto error; } break; #endif /* WITH_GEX */ #ifdef HAVE_ECDH case SSH_KEX_ECDH_SHA2_NISTP256: case SSH_KEX_ECDH_SHA2_NISTP384: case SSH_KEX_ECDH_SHA2_NISTP521: if (session->next_crypto->ecdh_client_pubkey == NULL || session->next_crypto->ecdh_server_pubkey == NULL) { SSH_LOG(SSH_LOG_WARNING, "ECDH parameted missing"); goto error; } rc = ssh_buffer_pack(buf, "SS", session->next_crypto->ecdh_client_pubkey, session->next_crypto->ecdh_server_pubkey); if (rc != SSH_OK) { goto error; } break; #endif #ifdef HAVE_CURVE25519 case SSH_KEX_CURVE25519_SHA256: case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: rc = ssh_buffer_pack(buf, "dPdP", CURVE25519_PUBKEY_SIZE, (size_t)CURVE25519_PUBKEY_SIZE, session->next_crypto->curve25519_client_pubkey, CURVE25519_PUBKEY_SIZE, (size_t)CURVE25519_PUBKEY_SIZE, session->next_crypto->curve25519_server_pubkey); if (rc != SSH_OK) { goto error; } break; #endif } rc = ssh_buffer_pack(buf, "B", session->next_crypto->shared_secret); if (rc != SSH_OK) { goto error; } #ifdef DEBUG_CRYPTO ssh_print_hexa("hash buffer", ssh_buffer_get(buf), ssh_buffer_get_len(buf)); #endif switch (session->next_crypto->kex_type) { case SSH_KEX_DH_GROUP1_SHA1: case SSH_KEX_DH_GROUP14_SHA1: #ifdef WITH_GEX case SSH_KEX_DH_GEX_SHA1: #endif /* WITH_GEX */ session->next_crypto->digest_len = SHA_DIGEST_LENGTH; session->next_crypto->digest_type = SSH_KDF_SHA1; session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len); if (session->next_crypto->secret_hash == NULL) { ssh_set_error_oom(session); goto error; } sha1(ssh_buffer_get(buf), ssh_buffer_get_len(buf), session->next_crypto->secret_hash); break; case SSH_KEX_ECDH_SHA2_NISTP256: case SSH_KEX_CURVE25519_SHA256: case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: #ifdef WITH_GEX case SSH_KEX_DH_GEX_SHA256: #endif /* WITH_GEX */ session->next_crypto->digest_len = SHA256_DIGEST_LENGTH; session->next_crypto->digest_type = SSH_KDF_SHA256; session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len); if (session->next_crypto->secret_hash == NULL) { ssh_set_error_oom(session); goto error; } sha256(ssh_buffer_get(buf), ssh_buffer_get_len(buf), session->next_crypto->secret_hash); break; case SSH_KEX_ECDH_SHA2_NISTP384: session->next_crypto->digest_len = SHA384_DIGEST_LENGTH; session->next_crypto->digest_type = SSH_KDF_SHA384; session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len); if (session->next_crypto->secret_hash == NULL) { ssh_set_error_oom(session); goto error; } sha384(ssh_buffer_get(buf), ssh_buffer_get_len(buf), session->next_crypto->secret_hash); break; case SSH_KEX_DH_GROUP16_SHA512: case SSH_KEX_DH_GROUP18_SHA512: case SSH_KEX_ECDH_SHA2_NISTP521: session->next_crypto->digest_len = SHA512_DIGEST_LENGTH; session->next_crypto->digest_type = SSH_KDF_SHA512; session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len); if (session->next_crypto->secret_hash == NULL) { ssh_set_error_oom(session); goto error; } sha512(ssh_buffer_get(buf), ssh_buffer_get_len(buf), session->next_crypto->secret_hash); break; } /* During the first kex, secret hash and session ID are equal. However, after * a key re-exchange, a new secret hash is calculated. This hash will not replace * but complement existing session id. */ if (!session->next_crypto->session_id) { session->next_crypto->session_id = malloc(session->next_crypto->digest_len); if (session->next_crypto->session_id == NULL) { ssh_set_error_oom(session); goto error; } memcpy(session->next_crypto->session_id, session->next_crypto->secret_hash, session->next_crypto->digest_len); } #ifdef DEBUG_CRYPTO printf("Session hash: \n"); ssh_print_hexa("secret hash", session->next_crypto->secret_hash, session->next_crypto->digest_len); ssh_print_hexa("session id", session->next_crypto->session_id, session->next_crypto->digest_len); #endif rc = SSH_OK; error: ssh_buffer_free(buf); ssh_buffer_free(client_hash); ssh_buffer_free(server_hash); session->in_hashbuf = NULL; session->out_hashbuf = NULL; ssh_string_free(num); return rc; }
static ssh_buffer privatekey_string_to_buffer(const char *pkey, int type, ssh_auth_callback cb, void *userdata, const char *desc) { ssh_buffer buffer = NULL; ssh_buffer out = NULL; const char *p; unsigned char *iv = NULL; const char *header_begin; const char *header_end; unsigned int header_begin_size; unsigned int header_end_size; unsigned int key_len = 0; unsigned int iv_len = 0; int algo = 0; int mode = 0; int len; buffer = ssh_buffer_new(); if (buffer == NULL) { return NULL; } switch(type) { case SSH_KEYTYPE_DSS: header_begin = DSA_HEADER_BEGIN; header_end = DSA_HEADER_END; break; case SSH_KEYTYPE_RSA: header_begin = RSA_HEADER_BEGIN; header_end = RSA_HEADER_END; break; default: ssh_buffer_free(buffer); return NULL; } header_begin_size = strlen(header_begin); header_end_size = strlen(header_end); p = pkey; len = 0; get_next_line(p, len); while(len > 0 && strncmp(p, header_begin, header_begin_size)) { /* skip line */ get_next_line(p, len); } if(len < 0) { /* no header found */ return NULL; } /* skip header line */ get_next_line(p, len); if (len > 11 && strncmp("Proc-Type: 4,ENCRYPTED", p, 11) == 0) { /* skip line */ get_next_line(p, len); if (len > 10 && strncmp("DEK-Info: ", p, 10) == 0) { p += 10; len = 0; get_next_line(p, len); if (privatekey_dek_header(p, len, &algo, &mode, &key_len, &iv, &iv_len) < 0) { ssh_buffer_free(buffer); SAFE_FREE(iv); return NULL; } } else { ssh_buffer_free(buffer); SAFE_FREE(iv); return NULL; } } else { if(len > 0) { if (ssh_buffer_add_data(buffer, p, len) < 0) { ssh_buffer_free(buffer); SAFE_FREE(iv); return NULL; } } } get_next_line(p, len); while(len > 0 && strncmp(p, header_end, header_end_size) != 0) { if (ssh_buffer_add_data(buffer, p, len) < 0) { ssh_buffer_free(buffer); SAFE_FREE(iv); return NULL; } get_next_line(p, len); } if (len == -1 || strncmp(p, header_end, header_end_size) != 0) { ssh_buffer_free(buffer); SAFE_FREE(iv); return NULL; } if (ssh_buffer_add_data(buffer, "\0", 1) < 0) { ssh_buffer_free(buffer); SAFE_FREE(iv); return NULL; } out = base64_to_bin(ssh_buffer_get(buffer)); ssh_buffer_free(buffer); if (out == NULL) { SAFE_FREE(iv); return NULL; } if (algo) { if (privatekey_decrypt(algo, mode, key_len, iv, iv_len, out, cb, userdata, desc) < 0) { ssh_buffer_free(out); SAFE_FREE(iv); return NULL; } } SAFE_FREE(iv); return out; }
/** @internal * @handles a data received event. It then calls the handlers for the different packet types * or and exception handler callback. * @param user pointer to current ssh_session * @param data pointer to the data received * @len length of data received. It might not be enough for a complete packet * @returns number of bytes read and processed. */ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) { ssh_session session= (ssh_session) user; unsigned int blocksize = (session->current_crypto ? session->current_crypto->in_cipher->blocksize : 8); unsigned char mac[DIGEST_MAX_LEN] = {0}; char buffer[16] = {0}; size_t current_macsize = 0; const uint8_t *packet; int to_be_read; int rc; uint32_t len, compsize, payloadsize; uint8_t padding; size_t processed = 0; /* number of byte processed from the callback */ if(session->current_crypto != NULL) { current_macsize = hmac_digest_len(session->current_crypto->in_hmac); } if (data == NULL) { goto error; } if (session->session_state == SSH_SESSION_STATE_ERROR) { goto error; } switch(session->packet_state) { case PACKET_STATE_INIT: if (receivedlen < blocksize) { /* * We didn't receive enough data to read at least one * block size, give up */ return 0; } memset(&session->in_packet, 0, sizeof(PACKET)); if (session->in_buffer) { rc = ssh_buffer_reinit(session->in_buffer); if (rc < 0) { goto error; } } else { session->in_buffer = ssh_buffer_new(); if (session->in_buffer == NULL) { goto error; } } memcpy(buffer, data, blocksize); processed += blocksize; len = ssh_packet_decrypt_len(session, buffer); rc = ssh_buffer_add_data(session->in_buffer, buffer, blocksize); if (rc < 0) { goto error; } if (len > MAX_PACKET_LEN) { ssh_set_error(session, SSH_FATAL, "read_packet(): Packet len too high(%u %.4x)", len, len); goto error; } to_be_read = len - blocksize + sizeof(uint32_t); if (to_be_read < 0) { /* remote sshd sends invalid sizes? */ ssh_set_error(session, SSH_FATAL, "Given numbers of bytes left to be read < 0 (%d)!", to_be_read); goto error; } /* Saves the status of the current operations */ session->in_packet.len = len; session->packet_state = PACKET_STATE_SIZEREAD; /* FALL TROUGH */ case PACKET_STATE_SIZEREAD: len = session->in_packet.len; to_be_read = len - blocksize + sizeof(uint32_t) + current_macsize; /* if to_be_read is zero, the whole packet was blocksize bytes. */ if (to_be_read != 0) { if (receivedlen - processed < (unsigned int)to_be_read) { /* give up, not enough data in buffer */ SSH_LOG(SSH_LOG_PACKET,"packet: partial packet (read len) [len=%d]",len); return processed; } packet = ((uint8_t*)data) + processed; #if 0 ssh_socket_read(session->socket, packet, to_be_read - current_macsize); #endif rc = ssh_buffer_add_data(session->in_buffer, packet, to_be_read - current_macsize); if (rc < 0) { goto error; } processed += to_be_read - current_macsize; } if (session->current_crypto) { /* * Decrypt the rest of the packet (blocksize bytes already * have been decrypted) */ uint32_t buffer_len = ssh_buffer_get_len(session->in_buffer); /* The following check avoids decrypting zero bytes */ if (buffer_len > blocksize) { uint8_t *payload = ((uint8_t*)ssh_buffer_get(session->in_buffer) + blocksize); uint32_t plen = buffer_len - blocksize; rc = ssh_packet_decrypt(session, payload, plen); if (rc < 0) { ssh_set_error(session, SSH_FATAL, "Decrypt error"); goto error; } } /* copy the last part from the incoming buffer */ packet = ((uint8_t *)data) + processed; memcpy(mac, packet, current_macsize); rc = ssh_packet_hmac_verify(session, session->in_buffer, mac, session->current_crypto->in_hmac); if (rc < 0) { ssh_set_error(session, SSH_FATAL, "HMAC error"); goto error; } processed += current_macsize; } /* skip the size field which has been processed before */ ssh_buffer_pass_bytes(session->in_buffer, sizeof(uint32_t)); rc = ssh_buffer_get_u8(session->in_buffer, &padding); if (rc == 0) { ssh_set_error(session, SSH_FATAL, "Packet too short to read padding"); goto error; } if (padding > ssh_buffer_get_len(session->in_buffer)) { ssh_set_error(session, SSH_FATAL, "Invalid padding: %d (%d left)", padding, ssh_buffer_get_len(session->in_buffer)); goto error; } ssh_buffer_pass_bytes_end(session->in_buffer, padding); compsize = ssh_buffer_get_len(session->in_buffer); #ifdef WITH_ZLIB if (session->current_crypto && session->current_crypto->do_compress_in && ssh_buffer_get_len(session->in_buffer) > 0) { rc = decompress_buffer(session, session->in_buffer,MAX_PACKET_LEN); if (rc < 0) { goto error; } } #endif /* WITH_ZLIB */ payloadsize = ssh_buffer_get_len(session->in_buffer); session->recv_seq++; if (session->raw_counter != NULL) { session->raw_counter->in_bytes += payloadsize; session->raw_counter->in_packets++; } /* * We don't want to rewrite a new packet while still executing the * packet callbacks */ session->packet_state = PACKET_STATE_PROCESSING; ssh_packet_parse_type(session); SSH_LOG(SSH_LOG_PACKET, "packet: read type %hhd [len=%d,padding=%hhd,comp=%d,payload=%d]", session->in_packet.type, len, padding, compsize, payloadsize); /* Execute callbacks */ ssh_packet_process(session, session->in_packet.type); session->packet_state = PACKET_STATE_INIT; if (processed < receivedlen) { /* Handle a potential packet left in socket buffer */ SSH_LOG(SSH_LOG_PACKET, "Processing %" PRIdS " bytes left in socket buffer", receivedlen-processed); packet = ((uint8_t*)data) + processed; rc = ssh_packet_socket_callback(packet, receivedlen - processed,user); processed += rc; } return processed; case PACKET_STATE_PROCESSING: SSH_LOG(SSH_LOG_PACKET, "Nested packet processing. Delaying."); return 0; } ssh_set_error(session, SSH_FATAL, "Invalid state into packet_read2(): %d", session->packet_state); error: session->session_state= SSH_SESSION_STATE_ERROR; return processed; }
static int packet_send2(ssh_session session) { unsigned int blocksize = (session->current_crypto ? session->current_crypto->out_cipher->blocksize : 8); enum ssh_hmac_e hmac_type = (session->current_crypto ? session->current_crypto->out_hmac : session->next_crypto->out_hmac); uint32_t currentlen = ssh_buffer_get_len(session->out_buffer); unsigned char *hmac = NULL; char padstring[32] = { 0 }; int rc = SSH_ERROR; uint32_t finallen,payloadsize,compsize; uint8_t padding; uint8_t header[sizeof(padding) + sizeof(finallen)] = { 0 }; payloadsize = currentlen; #ifdef WITH_ZLIB if (session->current_crypto && session->current_crypto->do_compress_out && ssh_buffer_get_len(session->out_buffer)) { if (compress_buffer(session,session->out_buffer) < 0) { goto error; } currentlen = ssh_buffer_get_len(session->out_buffer); } #endif /* WITH_ZLIB */ compsize = currentlen; padding = (blocksize - ((currentlen +5) % blocksize)); if(padding < 4) { padding += blocksize; } if (session->current_crypto) { ssh_get_random(padstring, padding, 0); } finallen = htonl(currentlen + padding + 1); memcpy(&header[0], &finallen, sizeof(finallen)); header[sizeof(finallen)] = padding; rc = ssh_buffer_prepend_data(session->out_buffer, &header, sizeof(header)); if (rc < 0) { goto error; } rc = ssh_buffer_add_data(session->out_buffer, padstring, padding); if (rc < 0) { goto error; } #ifdef WITH_PCAP if(session->pcap_ctx){ ssh_pcap_context_write(session->pcap_ctx,SSH_PCAP_DIR_OUT, ssh_buffer_get(session->out_buffer),ssh_buffer_get_len(session->out_buffer) ,ssh_buffer_get_len(session->out_buffer)); } #endif hmac = ssh_packet_encrypt(session, ssh_buffer_get(session->out_buffer), ssh_buffer_get_len(session->out_buffer)); if (hmac) { rc = ssh_buffer_add_data(session->out_buffer, hmac, hmac_digest_len(hmac_type)); if (rc < 0) { goto error; } } rc = ssh_packet_write(session); session->send_seq++; if (session->raw_counter != NULL) { session->raw_counter->out_bytes += payloadsize; session->raw_counter->out_packets++; } SSH_LOG(SSH_LOG_PACKET, "packet: wrote [len=%d,padding=%hhd,comp=%d,payload=%d]", ntohl(finallen), padding, compsize, payloadsize); if (ssh_buffer_reinit(session->out_buffer) < 0) { rc = SSH_ERROR; } error: return rc; /* SSH_OK, AGAIN or ERROR */ }
int ssh_packet_send1(ssh_session session) { unsigned int blocksize = (session->current_crypto ? session->current_crypto->out_cipher->blocksize : 8); uint32_t currentlen = ssh_buffer_get_len(session->out_buffer) + sizeof(uint32_t); char padstring[32] = {0}; int rc = SSH_ERROR; uint32_t finallen; uint32_t crc; uint8_t padding; SSH_LOG(SSH_LOG_PACKET,"Sending a %d bytes long packet",currentlen); /* TODO FIXME #ifdef WITH_ZLIB if (session->current_crypto && session->current_crypto->do_compress_out) { if (compress_buffer(session, session->out_buffer) < 0) { goto error; } currentlen = ssh_buffer_get_len(session->out_buffer); } #endif */ padding = blocksize - (currentlen % blocksize); if (session->current_crypto) { ssh_get_random(padstring, padding, 0); } else { memset(padstring, 0, padding); } finallen = htonl(currentlen); SSH_LOG(SSH_LOG_PACKET, "%d bytes after comp + %d padding bytes = %d bytes packet", currentlen, padding, ntohl(finallen)); if (ssh_buffer_prepend_data(session->out_buffer, &padstring, padding) < 0) { goto error; } if (ssh_buffer_prepend_data(session->out_buffer, &finallen, sizeof(uint32_t)) < 0) { goto error; } crc = ssh_crc32((char *)ssh_buffer_get(session->out_buffer) + sizeof(uint32_t), ssh_buffer_get_len(session->out_buffer) - sizeof(uint32_t)); if (ssh_buffer_add_u32(session->out_buffer, ntohl(crc)) < 0) { goto error; } #ifdef DEBUG_CRYPTO ssh_print_hexa("Clear packet", ssh_buffer_get(session->out_buffer), ssh_buffer_get_len(session->out_buffer)); #endif /* session->out_buffer should have more than sizeof(uint32_t) bytes in it as required for ssh_packet_encrypt */ ssh_packet_encrypt(session, (unsigned char *)ssh_buffer_get(session->out_buffer) + sizeof(uint32_t), ssh_buffer_get_len(session->out_buffer) - sizeof(uint32_t)); #ifdef DEBUG_CRYPTO ssh_print_hexa("encrypted packet",ssh_buffer_get(session->out_buffer), ssh_buffer_get_len(session->out_buffer)); #endif rc=ssh_socket_write(session->socket, ssh_buffer_get(session->out_buffer), ssh_buffer_get_len(session->out_buffer)); if(rc== SSH_ERROR) { goto error; } session->send_seq++; if (ssh_buffer_reinit(session->out_buffer) < 0) { rc = SSH_ERROR; } error: return rc; /* SSH_OK, AGAIN or ERROR */ }
int ssh_packet_socket_callback1(const void *data, size_t receivedlen, void *user) { void *packet = NULL; int to_be_read; size_t processed=0; uint32_t padding; uint32_t crc; uint32_t len, buffer_len; ssh_session session=(ssh_session)user; switch (session->packet_state){ case PACKET_STATE_INIT: memset(&session->in_packet, 0, sizeof(PACKET)); if (session->in_buffer) { if (ssh_buffer_reinit(session->in_buffer) < 0) { goto error; } } else { session->in_buffer = ssh_buffer_new(); if (session->in_buffer == NULL) { goto error; } } /* must have at least enough bytes for size */ if(receivedlen < sizeof(uint32_t)){ return 0; } memcpy(&len,data,sizeof(uint32_t)); processed += sizeof(uint32_t); /* len is not encrypted */ len = ntohl(len); if (len > MAX_PACKET_LEN) { ssh_set_error(session, SSH_FATAL, "read_packet(): Packet len too high (%u %.8x)", len, len); goto error; } SSH_LOG(SSH_LOG_PACKET, "Reading a %d bytes packet", len); session->in_packet.len = len; session->packet_state = PACKET_STATE_SIZEREAD; /* FALL THROUGH */ case PACKET_STATE_SIZEREAD: len = session->in_packet.len; /* SSH-1 has a fixed padding lenght */ padding = 8 - (len % 8); to_be_read = len + padding; if(to_be_read + processed > receivedlen){ /* wait for rest of packet */ return processed; } /* it is _not_ possible that to_be_read be < 8. */ packet = (char *)data + processed; if (ssh_buffer_add_data(session->in_buffer,packet,to_be_read) < 0) { goto error; } processed += to_be_read; #ifdef DEBUG_CRYPTO ssh_print_hexa("read packet:", ssh_buffer_get(session->in_buffer), ssh_buffer_get_len(session->in_buffer)); #endif if (session->current_crypto) { /* * We decrypt everything, missing the lenght part (which was * previously read, unencrypted, and is not part of the buffer */ buffer_len = ssh_buffer_get_len(session->in_buffer); if (buffer_len > 0) { int rc; rc = ssh_packet_decrypt(session, ssh_buffer_get(session->in_buffer), buffer_len); if (rc < 0) { ssh_set_error(session, SSH_FATAL, "Packet decrypt error"); goto error; } } } #ifdef DEBUG_CRYPTO ssh_print_hexa("read packet decrypted:", ssh_buffer_get(session->in_buffer), ssh_buffer_get_len(session->in_buffer)); #endif SSH_LOG(SSH_LOG_PACKET, "%d bytes padding", padding); if(((len + padding) != ssh_buffer_get_len(session->in_buffer)) || ((len + padding) < sizeof(uint32_t))) { SSH_LOG(SSH_LOG_RARE, "no crc32 in packet"); ssh_set_error(session, SSH_FATAL, "no crc32 in packet"); goto error; } memcpy(&crc, (unsigned char *)ssh_buffer_get(session->in_buffer) + (len+padding) - sizeof(uint32_t), sizeof(uint32_t)); ssh_buffer_pass_bytes_end(session->in_buffer, sizeof(uint32_t)); crc = ntohl(crc); if (ssh_crc32(ssh_buffer_get(session->in_buffer), (len + padding) - sizeof(uint32_t)) != crc) { #ifdef DEBUG_CRYPTO ssh_print_hexa("crc32 on",ssh_buffer_get(session->in_buffer), len + padding - sizeof(uint32_t)); #endif SSH_LOG(SSH_LOG_RARE, "Invalid crc32"); ssh_set_error(session, SSH_FATAL, "Invalid crc32: expected %.8x, got %.8x", crc, ssh_crc32(ssh_buffer_get(session->in_buffer), len + padding - sizeof(uint32_t))); goto error; } /* pass the padding */ ssh_buffer_pass_bytes(session->in_buffer, padding); SSH_LOG(SSH_LOG_PACKET, "The packet is valid"); /* TODO FIXME #ifdef WITH_ZLIB if(session->current_crypto && session->current_crypto->do_compress_in){ decompress_buffer(session,session->in_buffer); } #endif */ session->recv_seq++; /* We don't want to rewrite a new packet while still executing the packet callbacks */ session->packet_state = PACKET_STATE_PROCESSING; ssh_packet_parse_type(session); /* execute callbacks */ ssh_packet_process(session, session->in_packet.type); session->packet_state = PACKET_STATE_INIT; if(processed < receivedlen){ int rc; /* Handle a potential packet left in socket buffer */ SSH_LOG(SSH_LOG_PACKET,"Processing %" PRIdS " bytes left in socket buffer", receivedlen-processed); rc = ssh_packet_socket_callback1((char *)data + processed, receivedlen - processed,user); processed += rc; } return processed; case PACKET_STATE_PROCESSING: SSH_LOG(SSH_LOG_PACKET, "Nested packet processing. Delaying."); return 0; } error: session->session_state=SSH_SESSION_STATE_ERROR; return processed; }
static int packet_send2(ssh_session session) { unsigned int blocksize = (session->current_crypto ? session->current_crypto->out_cipher->blocksize : 8); unsigned int lenfield_blocksize = (session->current_crypto ? session->current_crypto->out_cipher->lenfield_blocksize : 0); enum ssh_hmac_e hmac_type = (session->current_crypto ? session->current_crypto->out_hmac : session->next_crypto->out_hmac); uint32_t currentlen = ssh_buffer_get_len(session->out_buffer); unsigned char *hmac = NULL; char padstring[32] = { 0 }; int rc = SSH_ERROR; uint32_t finallen,payloadsize,compsize; uint8_t padding; ssh_buffer header_buffer = ssh_buffer_new(); payloadsize = currentlen; #ifdef WITH_ZLIB if (session->current_crypto && session->current_crypto->do_compress_out && ssh_buffer_get_len(session->out_buffer)) { if (compress_buffer(session,session->out_buffer) < 0) { goto error; } currentlen = ssh_buffer_get_len(session->out_buffer); } #endif /* WITH_ZLIB */ compsize = currentlen; /* compressed payload + packet len (4) + padding len (1) */ /* totallen - lenfield_blocksize must be equal to 0 (mod blocksize) */ padding = (blocksize - ((blocksize - lenfield_blocksize + currentlen + 5) % blocksize)); if(padding < 4) { padding += blocksize; } if (session->current_crypto != NULL) { int ok; ok = ssh_get_random(padstring, padding, 0); if (!ok) { ssh_set_error(session, SSH_FATAL, "PRNG error"); goto error; } } if (header_buffer == NULL){ ssh_set_error_oom(session); goto error; } finallen = currentlen + padding + 1; rc = ssh_buffer_pack(header_buffer, "db", finallen, padding); if (rc == SSH_ERROR){ goto error; } rc = ssh_buffer_prepend_data(session->out_buffer, ssh_buffer_get(header_buffer), ssh_buffer_get_len(header_buffer)); if (rc < 0) { goto error; } rc = ssh_buffer_add_data(session->out_buffer, padstring, padding); if (rc < 0) { goto error; } #ifdef WITH_PCAP if (session->pcap_ctx) { ssh_pcap_context_write(session->pcap_ctx, SSH_PCAP_DIR_OUT, ssh_buffer_get(session->out_buffer), ssh_buffer_get_len(session->out_buffer), ssh_buffer_get_len(session->out_buffer)); } #endif hmac = ssh_packet_encrypt(session, ssh_buffer_get(session->out_buffer), ssh_buffer_get_len(session->out_buffer)); if (hmac) { rc = ssh_buffer_add_data(session->out_buffer, hmac, hmac_digest_len(hmac_type)); if (rc < 0) { goto error; } } rc = ssh_packet_write(session); session->send_seq++; if (session->raw_counter != NULL) { session->raw_counter->out_bytes += payloadsize; session->raw_counter->out_packets++; } SSH_LOG(SSH_LOG_PACKET, "packet: wrote [len=%d,padding=%hhd,comp=%d,payload=%d]", finallen, padding, compsize, payloadsize); if (ssh_buffer_reinit(session->out_buffer) < 0) { rc = SSH_ERROR; } error: if (header_buffer != NULL) { ssh_buffer_free(header_buffer); } return rc; /* SSH_OK, AGAIN or ERROR */ }
sftp_client_message sftp_get_client_message(sftp_session sftp) { ssh_session session = sftp->session; sftp_packet packet; sftp_client_message msg; ssh_buffer payload; int rc; msg = malloc(sizeof (struct sftp_client_message_struct)); if (msg == NULL) { ssh_set_error_oom(session); return NULL; } ZERO_STRUCTP(msg); packet = sftp_packet_read(sftp); if (packet == NULL) { ssh_set_error_oom(session); sftp_client_message_free(msg); return NULL; } payload = packet->payload; msg->type = packet->type; msg->sftp = sftp; /* take a copy of the whole packet */ msg->complete_message = ssh_buffer_new(); ssh_buffer_add_data(msg->complete_message, ssh_buffer_get(payload), ssh_buffer_get_len(payload)); ssh_buffer_get_u32(payload, &msg->id); switch(msg->type) { case SSH_FXP_CLOSE: case SSH_FXP_READDIR: msg->handle = ssh_buffer_get_ssh_string(payload); if (msg->handle == NULL) { ssh_set_error_oom(session); sftp_client_message_free(msg); return NULL; } break; case SSH_FXP_READ: rc = ssh_buffer_unpack(payload, "Sqd", &msg->handle, &msg->offset, &msg->len); if (rc != SSH_OK) { ssh_set_error_oom(session); sftp_client_message_free(msg); return NULL; } break; case SSH_FXP_WRITE: rc = ssh_buffer_unpack(payload, "SqS", &msg->handle, &msg->offset, &msg->data); if (rc != SSH_OK) { ssh_set_error_oom(session); sftp_client_message_free(msg); return NULL; } break; case SSH_FXP_REMOVE: case SSH_FXP_RMDIR: case SSH_FXP_OPENDIR: case SSH_FXP_READLINK: case SSH_FXP_REALPATH: rc = ssh_buffer_unpack(payload, "s", &msg->filename); if (rc != SSH_OK) { ssh_set_error_oom(session); sftp_client_message_free(msg); return NULL; } break; case SSH_FXP_RENAME: case SSH_FXP_SYMLINK: rc = ssh_buffer_unpack(payload, "sS", &msg->filename, &msg->data); if (rc != SSH_OK) { ssh_set_error_oom(session); sftp_client_message_free(msg); return NULL; } break; case SSH_FXP_MKDIR: case SSH_FXP_SETSTAT: rc = ssh_buffer_unpack(payload, "s", &msg->filename); if (rc != SSH_OK) { ssh_set_error_oom(session); sftp_client_message_free(msg); return NULL; } msg->attr = sftp_parse_attr(sftp, payload, 0); if (msg->attr == NULL) { ssh_set_error_oom(session); sftp_client_message_free(msg); return NULL; } break; case SSH_FXP_FSETSTAT: msg->handle = ssh_buffer_get_ssh_string(payload); if (msg->handle == NULL) { ssh_set_error_oom(session); sftp_client_message_free(msg); return NULL; } msg->attr = sftp_parse_attr(sftp, payload, 0); if (msg->attr == NULL) { ssh_set_error_oom(session); sftp_client_message_free(msg); return NULL; } break; case SSH_FXP_LSTAT: case SSH_FXP_STAT: rc = ssh_buffer_unpack(payload, "s", &msg->filename); if (rc != SSH_OK) { ssh_set_error_oom(session); sftp_client_message_free(msg); return NULL; } if(sftp->version > 3) { ssh_buffer_unpack(payload, "d", &msg->flags); } break; case SSH_FXP_OPEN: rc = ssh_buffer_unpack(payload, "sd", &msg->filename, &msg->flags); if (rc != SSH_OK) { ssh_set_error_oom(session); sftp_client_message_free(msg); return NULL; } msg->attr = sftp_parse_attr(sftp, payload, 0); if (msg->attr == NULL) { ssh_set_error_oom(session); sftp_client_message_free(msg); return NULL; } break; case SSH_FXP_FSTAT: rc = ssh_buffer_unpack(payload, "S", &msg->handle); if (rc != SSH_OK) { ssh_set_error_oom(session); sftp_client_message_free(msg); return NULL; } break; default: ssh_set_error(sftp->session, SSH_FATAL, "Received unhandled sftp message %d", msg->type); sftp_client_message_free(msg); return NULL; } sftp_packet_free(packet); return msg; }
/** \internal * \brief starts a nonblocking flush of the output buffer * */ int ssh_socket_nonblocking_flush(ssh_socket s) { ssh_session session = s->session; uint32_t len; int w; if (!ssh_socket_is_open(s)) { session->alive = 0; if(s->callbacks && s->callbacks->exception){ s->callbacks->exception( SSH_SOCKET_EXCEPTION_ERROR, s->last_errno,s->callbacks->userdata); }else{ ssh_set_error(session, SSH_FATAL, "Writing packet: error on socket (or connection closed): %s", strerror(s->last_errno)); } return SSH_ERROR; } len = ssh_buffer_get_len(s->out_buffer); if (!s->write_wontblock && s->poll_out && len > 0) { /* force the poll system to catch pollout events */ ssh_poll_add_events(s->poll_out, POLLOUT); return SSH_AGAIN; } if (s->write_wontblock && len > 0) { w = ssh_socket_unbuffered_write(s, ssh_buffer_get(s->out_buffer), len); if (w < 0) { session->alive = 0; ssh_socket_close(s); if(s->callbacks && s->callbacks->exception){ s->callbacks->exception( SSH_SOCKET_EXCEPTION_ERROR, s->last_errno,s->callbacks->userdata); }else{ ssh_set_error(session, SSH_FATAL, "Writing packet: error on socket (or connection closed): %s", strerror(s->last_errno)); } return SSH_ERROR; } ssh_buffer_pass_bytes(s->out_buffer, w); if (s->session->socket_counter != NULL) { s->session->socket_counter->out_bytes += w; } } /* Is there some data pending? */ len = ssh_buffer_get_len(s->out_buffer); if (s->poll_out && len > 0) { /* force the poll system to catch pollout events */ ssh_poll_add_events(s->poll_out, POLLOUT); return SSH_AGAIN; } /* all data written */ return SSH_OK; }
/** * @brief SSH poll callback. This callback will be used when an event * caught on the socket. * * @param p Poll object this callback belongs to. * @param fd The raw socket. * @param revents The current poll events on the socket. * @param userdata Userdata to be passed to the callback function, * in this case the socket object. * * @return 0 on success, < 0 when the poll object has been removed * from its poll context. */ int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p, socket_t fd, int revents, void *v_s) { ssh_socket s = (ssh_socket)v_s; char buffer[MAX_BUF_SIZE]; int r; int err = 0; socklen_t errlen = sizeof(err); /* Do not do anything if this socket was already closed */ if (!ssh_socket_is_open(s)) { return -1; } SSH_LOG(SSH_LOG_TRACE, "Poll callback on socket %d (%s%s%s), out buffer %d",fd, (revents & POLLIN) ? "POLLIN ":"", (revents & POLLOUT) ? "POLLOUT ":"", (revents & POLLERR) ? "POLLERR":"", ssh_buffer_get_len(s->out_buffer)); if (revents & POLLERR || revents & POLLHUP) { /* Check if we are in a connecting state */ if (s->state == SSH_SOCKET_CONNECTING) { s->state = SSH_SOCKET_ERROR; r = getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *)&err, &errlen); if (r < 0) { err = errno; } s->last_errno = err; ssh_socket_close(s); if (s->callbacks && s->callbacks->connected) { s->callbacks->connected(SSH_SOCKET_CONNECTED_ERROR, err, s->callbacks->userdata); } return -1; } /* Then we are in a more standard kind of error */ /* force a read to get an explanation */ revents |= POLLIN; } if ((revents & POLLIN) && s->state == SSH_SOCKET_CONNECTED) { s->read_wontblock = 1; r = ssh_socket_unbuffered_read(s, buffer, sizeof(buffer)); if (r < 0) { if (p != NULL) { ssh_poll_remove_events(p, POLLIN); } if (s->callbacks && s->callbacks->exception) { s->callbacks->exception(SSH_SOCKET_EXCEPTION_ERROR, s->last_errno, s->callbacks->userdata); /* p may have been freed, so don't use it * anymore in this function */ p = NULL; return -2; } } if (r == 0) { if (p != NULL) { ssh_poll_remove_events(p, POLLIN); } if (p != NULL) { ssh_poll_remove_events(p, POLLIN); } if (s->callbacks && s->callbacks->exception) { s->callbacks->exception(SSH_SOCKET_EXCEPTION_EOF, 0, s->callbacks->userdata); /* p may have been freed, so don't use it * anymore in this function */ p = NULL; return -2; } } if (r > 0) { if (s->session->socket_counter != NULL) { s->session->socket_counter->in_bytes += r; } /* Bufferize the data and then call the callback */ r = ssh_buffer_add_data(s->in_buffer, buffer, r); if (r < 0) { return -1; } if (s->callbacks && s->callbacks->data) { do { r = s->callbacks->data(ssh_buffer_get(s->in_buffer), ssh_buffer_get_len(s->in_buffer), s->callbacks->userdata); ssh_buffer_pass_bytes(s->in_buffer, r); } while ((r > 0) && (s->state == SSH_SOCKET_CONNECTED)); /* p may have been freed, so don't use it * anymore in this function */ p = NULL; } } } #ifdef _WIN32 if (revents & POLLOUT || revents & POLLWRNORM) { #else if (revents & POLLOUT) { #endif /* First, POLLOUT is a sign we may be connected */ if (s->state == SSH_SOCKET_CONNECTING) { SSH_LOG(SSH_LOG_PACKET, "Received POLLOUT in connecting state"); s->state = SSH_SOCKET_CONNECTED; if (p != NULL) { ssh_poll_set_events(p, POLLOUT | POLLIN); } r = ssh_socket_set_blocking(ssh_socket_get_fd_in(s)); if (r < 0) { return -1; } if (s->callbacks && s->callbacks->connected) { s->callbacks->connected(SSH_SOCKET_CONNECTED_OK, 0, s->callbacks->userdata); } return 0; } /* So, we can write data */ s->write_wontblock=1; if (p != NULL) { ssh_poll_remove_events(p, POLLOUT); } /* If buffered data is pending, write it */ if (ssh_buffer_get_len(s->out_buffer) > 0) { ssh_socket_nonblocking_flush(s); } else if (s->callbacks && s->callbacks->controlflow) { /* Otherwise advertise the upper level that write can be done */ SSH_LOG(SSH_LOG_TRACE,"sending control flow event"); s->callbacks->controlflow(SSH_SOCKET_FLOW_WRITEWONTBLOCK, s->callbacks->userdata); } /* TODO: Find a way to put back POLLOUT when buffering occurs */ } /* Return -1 if one of the poll handlers disappeared */ return (s->poll_in == NULL || s->poll_out == NULL) ? -1 : 0; } /** @internal * @brief returns the input poll handle corresponding to the socket, * creates it if it does not exist. * @returns allocated and initialized ssh_poll_handle object */ ssh_poll_handle ssh_socket_get_poll_handle_in(ssh_socket s){ if(s->poll_in) return s->poll_in; s->poll_in=ssh_poll_new(s->fd_in,0,ssh_socket_pollcallback,s); if(s->fd_in == s->fd_out && s->poll_out == NULL) s->poll_out=s->poll_in; return s->poll_in; } /** @internal * @brief returns the output poll handle corresponding to the socket, * creates it if it does not exist. * @returns allocated and initialized ssh_poll_handle object */ ssh_poll_handle ssh_socket_get_poll_handle_out(ssh_socket s){ if(s->poll_out) return s->poll_out; s->poll_out=ssh_poll_new(s->fd_out,0,ssh_socket_pollcallback,s); if(s->fd_in == s->fd_out && s->poll_in == NULL) s->poll_in=s->poll_out; return s->poll_out; } /** \internal * \brief Deletes a socket object */ void ssh_socket_free(ssh_socket s){ if (s == NULL) { return; } ssh_socket_close(s); ssh_buffer_free(s->in_buffer); ssh_buffer_free(s->out_buffer); SAFE_FREE(s); } #ifndef _WIN32 int ssh_socket_unix(ssh_socket s, const char *path) { struct sockaddr_un sunaddr; socket_t fd; sunaddr.sun_family = AF_UNIX; snprintf(sunaddr.sun_path, sizeof(sunaddr.sun_path), "%s", path); fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd == SSH_INVALID_SOCKET) { ssh_set_error(s->session, SSH_FATAL, "Error from socket(AF_UNIX, SOCK_STREAM, 0): %s", strerror(errno)); return -1; } if (fcntl(fd, F_SETFD, 1) == -1) { ssh_set_error(s->session, SSH_FATAL, "Error from fcntl(fd, F_SETFD, 1): %s", strerror(errno)); close(fd); return -1; } if (connect(fd, (struct sockaddr *) &sunaddr, sizeof(sunaddr)) < 0) { ssh_set_error(s->session, SSH_FATAL, "Error from connect(): %s", strerror(errno)); close(fd); return -1; } ssh_socket_set_fd(s,fd); return 0; } #endif /** \internal * \brief closes a socket */ void ssh_socket_close(ssh_socket s){ if (ssh_socket_is_open(s)) { #ifdef _WIN32 CLOSE_SOCKET(s->fd_in); /* fd_in = fd_out under win32 */ s->last_errno = WSAGetLastError(); #else if (s->fd_out != s->fd_in && s->fd_out != -1) { CLOSE_SOCKET(s->fd_out); } CLOSE_SOCKET(s->fd_in); s->last_errno = errno; #endif } if(s->poll_in != NULL){ if(s->poll_out == s->poll_in) s->poll_out = NULL; ssh_poll_free(s->poll_in); s->poll_in=NULL; } if(s->poll_out != NULL){ ssh_poll_free(s->poll_out); s->poll_out=NULL; } s->state = SSH_SOCKET_CLOSED; } /** * @internal * @brief sets the file descriptor of the socket. * @param[out] s ssh_socket to update * @param[in] fd file descriptor to set * @warning this function updates boths the input and output * file descriptors */ void ssh_socket_set_fd(ssh_socket s, socket_t fd) { s->fd_in = s->fd_out = fd; if (s->poll_in) { ssh_poll_set_fd(s->poll_in,fd); } else { s->state = SSH_SOCKET_CONNECTING; /* POLLOUT is the event to wait for in a nonblocking connect */ ssh_poll_set_events(ssh_socket_get_poll_handle_in(s), POLLOUT); #ifdef _WIN32 ssh_poll_add_events(ssh_socket_get_poll_handle_in(s), POLLWRNORM); #endif } }