/* $CoreSDI: packet.c,v 1.24 2001/10/05 22:04:06 claudio Exp $ */ /* * Copyright (c) 2000, 2001, Core SDI S.A., Argentina * All rights reserved * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither name of the Core SDI S.A. nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef WIN32 /* Unix */ #include #include #include #include #include #include #include #include #ifndef USE_SELECT #include #endif #include #include #include #include #include #include #else /* Win32 */ #include #include #include #include #include #include #include #include #include "exits.h" #include "winsyslog.h" #endif /* WIN32 */ #include #include "sysdep.h" #include "log.h" #include "packet.h" #define PACKET_PLAIN_BUFSIZE 8192 #define PACKET_COMPR_BUFSIZE 8221 /* see buf_compress() */ #define PACKET_COMPR_LEVEL 6 /* see zlib manual */ #define PACKET_ENCRYPT BUFFER_ENCRYPT #define PACKET_DECRYPT BUFFER_DECRYPT #define PACKET_ENCRYPTION_ENABLED(p) (p->pkt_cipher != NULL) #define PACKET_LFLAGS(p) (p->pkt_lflags) typedef enum { PACKET_READ, PACKET_WRITE } PACKET_MODE; static void _packet_poll (PACKET *, PACKET_MODE); static void _packet_read (PACKET *); static void _packet_write (PACKET *); static void _packet_compress (PACKET *); static void _packet_uncompress (PACKET *); static void _packet_cipher (PACKET *, int); /* * _packet_poll(): * Do a read or write poll. */ static void _packet_poll(PACKET *p, PACKET_MODE mode) { /* Poll is better than select */ #if defined(USE_SELECT) || defined(WIN32) fd_set set; #ifdef WIN32 set.fd_count = 0; /* Windows VC cries if not initialized */ #endif FD_SET(p->pkt_infd, &set); if (select(p->pkt_infd + 1, mode ? NULL : &set, mode ? &set : NULL, NULL, NULL) <= 0) #else struct pollfd pfd; pfd.fd = p->pkt_infd; if (mode) pfd.events = POLLOUT; else pfd.events = POLLIN; if (poll(&pfd, 1, -1) != 1) #endif /* USE_SELECT || WIN32 */ #ifndef WIN32 fatal(EX_OSERR, "_packet_poll(): %s.", strerror(errno)); #else fatal(EX_OSERR, "_packet_poll(): %d.", WSAGetLastError()); #endif /* WIN32 */ } /* * _packet_read(): * Read data from the peer. * If encryption is not enabled: * 1. Read data and put it into the reading buffer. * If encryption is enabled: * 1. Read data and put it into the encryption buffer; * 2. Decrypt encryption buffer and put data into the * compression buffer; * 3. Uncompress compression buffer and put data it into * the reading buffer. * NOTE 1: Since the audit connection is half-duplex, we do not read * anything until all outgoing data was sent to the peer * (we use the same encryption and compression buffers for * reading and writing). * NOTE 2: Each transfer between both sides has the following stream * format: * ... */ static void _packet_read(PACKET *p) { #ifndef WIN32 /* Unix */ BUFFER *buffer; int32_t to_read, readed; /* Do not read until all util data in the buffer was readed */ if (buf_util_space(p->pkt_rbuf) == 0) { /* Flush any writing data before reading */ _packet_write(p); /* Select appropiate buffer */ buffer = (PACKET_ENCRYPTION_ENABLED(p)) ? p->pkt_cbuf : p->pkt_rbuf; /* Read no. of incoming data bytes */ _packet_poll(p, PACKET_READ); readed = read(p->pkt_infd, &to_read, sizeof(int32_t)); if (readed != sizeof(int32_t)) { if (readed < 0) fatal(EX_OSERR, "Read failed: %s.", strerror(errno)); else if (readed != 0) fatal(EX_OSERR, "Read failed."); fatal(EX_IOERR, "Connection closed by peer."); } to_read = ntohl(to_read); if (to_read > buf_size(buffer)) fatal(EX_PROTOCOL, "Protocol violation."); /* Read data */ buf_reset(buffer); readed = buf_read(buffer, p->pkt_infd, to_read); if (readed < 0) fatal(EX_OSERR, "Read failed: %s.", strerror(errno)); if (readed == 0) fatal(EX_IOERR, "Connection closed by peer."); if (readed != to_read) fatal(EX_PROTOCOL, "Protocol violation."); if (PACKET_ENCRYPTION_ENABLED(p)) { _packet_cipher(p, PACKET_DECRYPT); _packet_uncompress(p); } } #else /* Win32 */ BUFFER *buffer; int32_t to_read, readed; /* Do not read until all util data in the buffer was readed */ if (buf_util_space(p->pkt_rbuf) == 0) { /* Flush any writing data before reading */ _packet_write(p); /* Select appropiate buffer */ buffer = (PACKET_ENCRYPTION_ENABLED(p)) ? p->pkt_cbuf : p->pkt_rbuf; /* Read no. of incoming data bytes */ _packet_poll(p, PACKET_READ); readed = recv(p->pkt_infd, (char *) &to_read, sizeof(int32_t), 0); if (readed != sizeof(int32_t)) { if (readed < SOCKET_ERROR) fatal(EX_OSERR, "Read failed: %d.", WSAGetLastError()); else if (readed != 0) fatal(EX_OSERR, "Read failed."); fatal(EX_IOERR, "Connection closed by peer."); } to_read = ntohl(to_read); if (to_read > buf_size(buffer)) fatal(EX_PROTOCOL, "Protocol violation."); /* Read data */ buf_reset(buffer); readed = buf_read(buffer, p->pkt_infd, to_read); if (readed == SOCKET_ERROR ) fatal(EX_OSERR, "Read failed: %d.", WSAGetLastError()); if (readed == 0) fatal(EX_IOERR, "Connection closed by peer."); if (readed != to_read) fatal(EX_PROTOCOL, "Protocol violation."); if (PACKET_ENCRYPTION_ENABLED(p)) { _packet_cipher(p, PACKET_DECRYPT); _packet_uncompress(p); } } #endif /* WIN32 */ } /* * _packet_write(): * Flush writing buffer (send it to the peer). * If encryption is not enabled: * 1. Send writing buffer directly to the peer. * If encryption is enabled: * 1. Compress writing buffer; * 2. Encrypt compression buffer; * 3. Send encryption buffer to the peer. * NOTE 1: This function acts like a flush. * NOTE 2: Written stream: * ... */ static void _packet_write(PACKET *p) { #ifndef WIN32 /* Unix */ BUFFER *buffer; int32_t to_write, to_write_net, written; /* Do nothing if there are not data to write */ if (buf_util_space(p->pkt_wbuf) > 0) { if (PACKET_ENCRYPTION_ENABLED(p)) { _packet_compress(p); _packet_cipher(p, PACKET_ENCRYPT); buffer = p->pkt_cbuf; } else buffer = p->pkt_wbuf; /* Write no. of sending data bytes (in network order) */ to_write = buf_util_space(buffer); to_write_net = htonl(to_write); _packet_poll(p, PACKET_WRITE); written = write(p->pkt_outfd, &to_write_net, sizeof(int32_t)); if (written != sizeof(int32_t)) fatal(EX_OSERR, "Write failed: %s.", strerror(errno)); /* Write data */ while (to_write) { _packet_poll(p, PACKET_WRITE); written = buf_write(buffer, p->pkt_outfd); if (written <= 0) fatal(EX_OSERR, "Write failed: %s.", strerror(errno)); to_write -= written; } /* All data was sent, reset writing buffer */ buf_reset(p->pkt_wbuf); } #else /* Win32 */ BUFFER *buffer; int32_t to_write, to_write_net, written; /* Do nothing if there are not data to write */ if (buf_util_space(p->pkt_wbuf) > 0) { if (PACKET_ENCRYPTION_ENABLED(p)) { _packet_compress(p); _packet_cipher(p, PACKET_ENCRYPT); buffer = p->pkt_cbuf; } else buffer = p->pkt_wbuf; /* Write no. of sending data bytes (in network order) */ to_write = buf_util_space(buffer); to_write_net = htonl(to_write); _packet_poll(p, PACKET_WRITE); written = send(p->pkt_outfd, (char *) &to_write_net, sizeof(int32_t), 0); if (written != sizeof(int32_t)) fatal(EX_OSERR, "Write failed: %d.", WSAGetLastError()); /* Write data */ while (to_write) { _packet_poll(p, PACKET_WRITE); written = buf_write(buffer, p->pkt_outfd); if (written == SOCKET_ERROR || written == 0) fatal(EX_OSERR, "Write failed: %d.", WSAGetLastError()); to_write -= written; } /* All data was sent, reset writing buffer */ buf_reset(p->pkt_wbuf); } #endif /* WIN32 */ } /* * _packet_compress(): * Compress writing buffer and put data into the compression buffer. */ static void _packet_compress(PACKET *p) { int status; if (buf_compress(p->pkt_zbuf, p->pkt_wbuf, PACKET_COMPR_LEVEL, &status) < 0) fatal(EX_OSERR, "Compression error: [ %i ]: %s.", status, strerror(errno)); } /* * _packet_uncompress(): * Uncompress compression buffer and put data into the reading buffer. */ static void _packet_uncompress(PACKET *p) { int status; if (buf_uncompress(p->pkt_rbuf, p->pkt_zbuf, &status) < 0) fatal(EX_OSERR, "Uncompression error: [ %i ]: %s.", status, strerror(errno)); } /* * _packet_cipher(): * Encrypt/decrypt compression/encryption buffer and put data into * the encryption/compression buffer. */ static void _packet_cipher(PACKET *p, int enc) { BUFFER *dst, *src; switch(enc) { case PACKET_ENCRYPT: dst = p->pkt_cbuf; src = p->pkt_zbuf; break; case PACKET_DECRYPT: dst = p->pkt_zbuf; src = p->pkt_cbuf; break; default: return; } buf_cipher(dst, src, p->pkt_cipher, p->pkt_key, enc); } /* * init_cryptostrings(): * Load libcrypto lookup table and error strings. */ void init_cryptostrings() { OpenSSL_add_all_algorithms(); ERR_load_crypto_strings(); } /* * release_cryptostrings() * Release libcrypto lookup table and error strings. */ void release_cryptostrings() { ERR_free_strings(); EVP_cleanup(); } /* * packet_init(): * Create PACKET structure and return a pointer to it, * on error NULL pointer is returned. * Encryption is not enabled by default, to use it you should * call packet_init_encrypt() after this function. */ PACKET * packet_init(int infd, int outfd) { PACKET *p; if (infd < 0 || outfd < 0) { errno = EINVAL; return (NULL); } /* Create PACKET structure */ p = (PACKET *) calloc(1, sizeof(PACKET)); if (p == NULL) return (NULL); /* Create reading and writing buffers */ p->pkt_rbuf = buf_create(PACKET_PLAIN_BUFSIZE); p->pkt_wbuf = buf_create(PACKET_PLAIN_BUFSIZE); if (p->pkt_rbuf == NULL || p->pkt_wbuf == NULL) { packet_release(p); return (NULL); } p->pkt_outfd = outfd; p->pkt_infd = infd; return (p); } /* * packet_init_encrypt(): * Enable encryption/decryption; return 0 on success and -1 * on error. * NOTE 1: Any contents in the reading and writing buffers are * destroyed; sync with other side before calling this * funcion (see packet_flush(3)). * NOTE 2: Since 'key' data is copied into the PACKET struct * you should destroy it after calling this function. */ int packet_init_encrypt(PACKET *p, const EVP_CIPHER *type, unsigned char *key, ssize_t keylen) { int32_t buflen; if (p == NULL || type == NULL || keylen <= 0) { errno = EINVAL; return (-1); } /* Setup compression and encryption buffers */ buflen = PACKET_COMPR_BUFSIZE / EVP_CIPHER_block_size(type); if (buflen * EVP_CIPHER_block_size(type) < PACKET_COMPR_BUFSIZE) buflen++; buflen *= EVP_CIPHER_block_size(type); p->pkt_cbuf = buf_create(buflen); p->pkt_zbuf = buf_create(PACKET_COMPR_BUFSIZE); if (p->pkt_cbuf == NULL || p->pkt_zbuf == NULL) return (-1); /* Setup session key and cipher type */ memcpy(p->pkt_key, key, (keylen > sizeof(p->pkt_key)) ? sizeof(p->pkt_key) : keylen); p->pkt_cipher = type; buf_reset(p->pkt_rbuf); buf_reset(p->pkt_wbuf); return (0); } /* * packet_release(): * Release packet structure, flush any pending data. * NOTE: before release, it cleans reading, writing, and * compression buffers, and session key. */ void packet_release(PACKET *p) { if (p != NULL) { _packet_write(p); buf_release(p->pkt_rbuf, BUFFER_CLEAN); buf_release(p->pkt_wbuf, BUFFER_CLEAN); buf_release(p->pkt_zbuf, BUFFER_CLEAN); buf_release(p->pkt_cbuf, BUFFER_NOCLEAN); bzero(p->pkt_key, sizeof(p->pkt_key)); free(p); } } /* * packet_flush(): * Flush any pending data on the writing buffer. */ void packet_flush(PACKET *p) { if (p != NULL) _packet_write(p); } /* * packet_put_raw(): * Put raw data into the writing buffer; if the buffer gets * full before all data was stored, its data is sent to the peer. * NOTE: Data on the buffer is flushed only when it is full, * and this don't means that _all_ data passed to this * function was sent (notice that _packet_read() flushes * all outgoing data before read). */ void packet_put_raw(PACKET *p, const void *_data, ssize_t size) { ssize_t written; char *data; if (p == NULL || _data == NULL || size <= 0) return; data = (char *) _data; while (size) { written = buf_put_raw(p->pkt_wbuf, data, size); if (written < 0) fatal(EX_SOFTWARE, "Invalid internal packet structure. " "Connection aborted."); size -= written; data += written; if (size) _packet_write(p); } } /* * packet_put_int32(): * Put a 32 bit integer value into the writing buffer (network order). */ void packet_put_int32(PACKET *p, int32_t value) { value = htonl(value); packet_put_raw(p, &value, sizeof(int32_t)); } /* * packet_put_string(): * Put a string into the writing buffer. * NOTE: A string is coded as follows: * ... * The final \0 is not included. */ void packet_put_string(PACKET *p, const char *string) { int32_t len; len = strlen(string); packet_put_int32(p, len); if (len) packet_put_raw(p, string, len); } /* * packet_get_raw(): * Retrieve data from the reading buffer (if this buffer is * empty read from the peer), blocks until all wanted data * was readed. */ void packet_get_raw(PACKET *p, void *data, ssize_t size) { ssize_t readed; char *cdata; if (p == NULL || data == NULL || size <= 0) return; cdata = (char *)data; while (size) { readed = buf_get_raw(p->pkt_rbuf, cdata, size); size -= readed; cdata += readed; if (size) _packet_read(p); } } /* * packet_get_int32(): * Retrieve a 32 bit integer value from the reading buffer. * NOTE: See packet_get_raw(). */ void packet_get_int32(PACKET *p, int32_t *value) { packet_get_raw(p, value, sizeof(int32_t)); *value = ntohl(*value); } /* * packet_get_string(): * Retrieve a string from the reading buffer. * NOTE: See packet_get_raw(). */ void packet_get_string(PACKET *p, char *string, ssize_t size) { int32_t string_len; char lost; packet_get_int32(p, &string_len); if (string_len) { if (string_len < size) size = string_len; packet_get_raw(p, string, size); string_len -= size; string += size; /* Retrieve remaining lost string data */ while(string_len--) packet_get_raw(p, &lost, 1); } *string = '\0'; } /* * packet_write() * Read data from a file descriptor at specified offset and * send it to a packet connection; return last offset written. * If specified offset is less than last file offset the operation * is not done and -1 is returned. * NOTE: The receiving PACKET should use packet_read(). */ off_t packet_write(PACKET *p, int fd, off_t offset) { char buf[PACKET_PLAIN_BUFSIZE]; int32_t readed, total, to_read; off_t last_off; last_off = lseek(fd, 0, SEEK_END); if (last_off < 0 || lseek(fd, offset, SEEK_SET) < 0) fatal(EX_OSERR, "packet_write(): %s.", strerror(errno)); total = (int32_t) (last_off - offset); if (total < 0) return (-1); packet_put_int32(p, total); while (total > 0) { to_read = (total > sizeof(buf)) ? sizeof(buf) : total; readed = read(fd, buf, to_read); if (readed < 0) fatal(EX_OSERR, "packet_write(): %s.", strerror(errno)); packet_put_raw(p, buf, readed); total -= readed; } return (last_off); } /* * packet_read() * Read data from packet and write it into a file descriptor. * NOTE: On windows, the file descriptor should point to a file. */ void packet_read(PACKET *p, int fd) { char buf[PACKET_PLAIN_BUFSIZE]; int32_t total, to_read, written; packet_get_int32(p, &total); while (total) { to_read = (total > sizeof(buf)) ? sizeof(buf) : total; packet_get_raw(p, buf, to_read); written = write(fd, buf, to_read); if (written < 0) fatal(EX_OSERR, "packet_read(): %s.", strerror(errno)); total -= to_read; } } /* * resolve(): * XXX: must change: use getaddrinfo(3). */ in_addr_t resolve(char *host) { struct hostent *he; in_addr_t ip; if ((ip = inet_addr(host)) == INADDR_NONE) { if ((he = gethostbyname(host)) == NULL) return (0); memcpy(&ip, he->h_addr, sizeof(in_addr_t)); } return (ip); }