/* Copyright (C) 2007-2008 The Android Open Source Project
**
** This software is licensed under the terms of the GNU General Public
** License version 2, as published by the Free Software Foundation, and
** may be copied, distributed, and modified under those terms.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
*/
#include "proxy_int.h"
#include "sockets.h"
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "android/utils/misc.h"
#include "android/utils/system.h"
#include <stdlib.h>
int proxy_log = 0;
void
proxy_LOG(const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
fprintf(stderr, "\n");
}
void
proxy_set_verbose(int mode)
{
proxy_log = mode;
}
/** Global connection list
**/
static ProxyConnection s_connections[1];
#define MAX_HEX_DUMP 512
static void
hex_dump( void* base, int size, const char* prefix )
{
STRALLOC_DEFINE(s);
if (size > MAX_HEX_DUMP)
size = MAX_HEX_DUMP;
stralloc_add_hexdump(s, base, size, prefix);
proxy_LOG( "%s", stralloc_cstr(s) );
stralloc_reset(s);
}
void
proxy_connection_init( ProxyConnection* conn,
int socket,
SockAddress* address,
ProxyService* service,
ProxyConnectionFreeFunc conn_free,
ProxyConnectionSelectFunc conn_select,
ProxyConnectionPollFunc conn_poll )
{
conn->socket = socket;
conn->address = address[0];
conn->service = service;
conn->next = NULL;
conn->conn_free = conn_free;
conn->conn_select = conn_select;
conn->conn_poll = conn_poll;
socket_set_nonblock(socket);
{
SocketType type = socket_get_type(socket);
snprintf( conn->name, sizeof(conn->name),
"%s:%s(%d)",
(type == SOCKET_STREAM) ? "tcp" : "udp",
sock_address_to_string(address), socket );
/* just in case */
conn->name[sizeof(conn->name)-1] = 0;
}
stralloc_reset(conn->str);
conn->str_pos = 0;
}
void
proxy_connection_done( ProxyConnection* conn )
{
stralloc_reset( conn->str );
if (conn->socket >= 0) {
socket_close(conn->socket);
conn->socket = -1;
}
}
void
proxy_connection_rewind( ProxyConnection* conn )
{
stralloc_t* str = conn->str;
/* only keep a small buffer in the heap */
conn->str_pos = 0;
str->n = 0;
if (str->a > 1024)
stralloc_reset(str);
}
DataStatus
proxy_connection_send( ProxyConnection* conn, int fd )
{
stralloc_t* str = conn->str;
int avail = str->n - conn->str_pos;
conn->str_sent = 0;
if (avail <= 0)
return 1;
if (proxy_log) {
PROXY_LOG("%s: sending %d bytes:", conn->name, avail );
hex_dump( str->s + conn->str_pos, avail, ">> " );
}
while (avail > 0) {
int n = socket_send(fd, str->s + conn->str_pos, avail);
if (n == 0) {
PROXY_LOG("%s: connection reset by peer (send)",
conn->name);
return DATA_ERROR;
}
if (n < 0) {
if (errno == EWOULDBLOCK || errno == EAGAIN)
return DATA_NEED_MORE;
PROXY_LOG("%s: error: %s", conn->name, errno_str);
return DATA_ERROR;
}
conn->str_pos += n;
conn->str_sent += n;
avail -= n;
}
proxy_connection_rewind(conn);
return DATA_COMPLETED;
}
DataStatus
proxy_connection_receive( ProxyConnection* conn, int fd, int wanted )
{
stralloc_t* str = conn->str;
conn->str_recv = 0;
while (wanted > 0) {
int n;
stralloc_readyplus( str, wanted );
n = socket_recv(fd, str->s + str->n, wanted);
if (n == 0) {
PROXY_LOG("%s: connection reset by peer (receive)",
conn->name);
return DATA_ERROR;
}
if (n < 0) {
if (errno == EWOULDBLOCK || errno == EAGAIN)
return DATA_NEED_MORE;
PROXY_LOG("%s: error: %s", conn->name, errno_str);
return DATA_ERROR;
}
if (proxy_log) {
PROXY_LOG("%s: received %d bytes:", conn->name, n );
hex_dump( str->s + str->n, n, "<< " );
}
str->n += n;
wanted -= n;
conn->str_recv += n;
}
return DATA_COMPLETED;
}
DataStatus
proxy_connection_receive_line( ProxyConnection* conn, int fd )
{
stralloc_t* str = conn->str;
for (;;) {
char c;
int n = socket_recv(fd, &c, 1);
if (n == 0) {
PROXY_LOG("%s: disconnected from server", conn->name );
return DATA_ERROR;
}
if (n < 0) {
if (errno == EWOULDBLOCK || errno == EAGAIN) {
PROXY_LOG("%s: blocked", conn->name);
return DATA_NEED_MORE;
}
PROXY_LOG("%s: error: %s", conn->name, errno_str);
return DATA_ERROR;
}
stralloc_add_c(str, c);
if (c == '\n') {
str->s[--str->n] = 0;
if (str->n > 0 && str->s[str->n-1] == '\r')
str->s[--str->n] = 0;
PROXY_LOG("%s: received '%s'", conn->name,
quote_bytes(str->s, str->n));
return DATA_COMPLETED;
}
}
}
static void
proxy_connection_insert( ProxyConnection* conn, ProxyConnection* after )
{
conn->next = after->next;
after->next->prev = conn;
after->next = conn;
conn->prev = after;
}
static void
proxy_connection_remove( ProxyConnection* conn )
{
conn->prev->next = conn->next;
conn->next->prev = conn->prev;
conn->next = conn->prev = conn;
}
/** Global service list
**/
#define MAX_SERVICES 4
static ProxyService* s_services[ MAX_SERVICES ];
static int s_num_services;
static int s_init;
static void proxy_manager_atexit( void );
static void
proxy_manager_init(void)
{
s_init = 1;
s_connections->next = s_connections;
s_connections->prev = s_connections;
atexit( proxy_manager_atexit );
}
extern int
proxy_manager_add_service( ProxyService* service )
{
if (!service || s_num_services >= MAX_SERVICES)
return -1;
if (!s_init)
proxy_manager_init();
s_services[s_num_services++] = service;
return 0;
}
extern void
proxy_manager_atexit( void )
{
ProxyConnection* conn = s_connections->next;
int n;
/* free all proxy connections */
while (conn != s_connections) {
ProxyConnection* next = conn->next;
conn->conn_free( conn );
conn = next;
}
conn->next = conn;
conn->prev = conn;
/* free all proxy services */
for (n = s_num_services; n-- > 0;) {
ProxyService* service = s_services[n];
service->serv_free( service->opaque );
}
s_num_services = 0;
}
void
proxy_connection_free( ProxyConnection* conn,
int keep_alive,
ProxyEvent event )
{
if (conn) {
int fd = conn->socket;
proxy_connection_remove(conn);
if (event != PROXY_EVENT_NONE)
conn->ev_func( conn->ev_opaque, fd, event );
if (keep_alive)
conn->socket = -1;
conn->conn_free(conn);
}
}
int
proxy_manager_add( SockAddress* address,
SocketType sock_type,
ProxyEventFunc ev_func,
void* ev_opaque )
{
int n;
if (!s_init) {
proxy_manager_init();
}
for (n = 0; n < s_num_services; n++) {
ProxyService* service = s_services[n];
ProxyConnection* conn = service->serv_connect( service->opaque,
sock_type,
address );
if (conn != NULL) {
conn->ev_func = ev_func;
conn->ev_opaque = ev_opaque;
proxy_connection_insert(conn, s_connections->prev);
return 0;
}
}
return -1;
}
/* remove an on-going proxified socket connection from the manager's list.
* this is only necessary when the socket connection must be canceled before
* the connection accept/refusal occured
*/
void
proxy_manager_del( void* ev_opaque )
{
ProxyConnection* conn = s_connections->next;
for ( ; conn != s_connections; conn = conn->next ) {
if (conn->ev_opaque == ev_opaque) {
proxy_connection_remove(conn);
conn->conn_free(conn);
return;
}
}
}
void
proxy_select_set( ProxySelect* sel,
int fd,
unsigned flags )
{
if (fd < 0 || !flags)
return;
if (*sel->pcount < fd+1)
*sel->pcount = fd+1;
if (flags & PROXY_SELECT_READ) {
FD_SET( fd, sel->reads );
} else {
FD_CLR( fd, sel->reads );
}
if (flags & PROXY_SELECT_WRITE) {
FD_SET( fd, sel->writes );
} else {
FD_CLR( fd, sel->writes );
}
if (flags & PROXY_SELECT_ERROR) {
FD_SET( fd, sel->errors );
} else {
FD_CLR( fd, sel->errors );
}
}
unsigned
proxy_select_poll( ProxySelect* sel, int fd )
{
unsigned flags = 0;
if (fd >= 0) {
if ( FD_ISSET(fd, sel->reads) )
flags |= PROXY_SELECT_READ;
if ( FD_ISSET(fd, sel->writes) )
flags |= PROXY_SELECT_WRITE;
if ( FD_ISSET(fd, sel->errors) )
flags |= PROXY_SELECT_ERROR;
}
return flags;
}
/* this function is called to update the select file descriptor sets
* with those of the proxified connection sockets that are currently managed */
void
proxy_manager_select_fill( int *pcount, fd_set* read_fds, fd_set* write_fds, fd_set* err_fds)
{
ProxyConnection* conn;
ProxySelect sel[1];
if (!s_init)
proxy_manager_init();
sel->pcount = pcount;
sel->reads = read_fds;
sel->writes = write_fds;
sel->errors = err_fds;
conn = s_connections->next;
while (conn != s_connections) {
ProxyConnection* next = conn->next;
conn->conn_select(conn, sel);
conn = next;
}
}
/* this function is called to act on proxified connection sockets when network events arrive */
void
proxy_manager_poll( fd_set* read_fds, fd_set* write_fds, fd_set* err_fds )
{
ProxyConnection* conn = s_connections->next;
ProxySelect sel[1];
sel->pcount = NULL;
sel->reads = read_fds;
sel->writes = write_fds;
sel->errors = err_fds;
while (conn != s_connections) {
ProxyConnection* next = conn->next;
conn->conn_poll( conn, sel );
conn = next;
}
}
int
proxy_base64_encode( const char* src, int srclen,
char* dst, int dstlen )
{
static const char cb64[64]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const char* srcend = src + srclen;
int result = 0;
while (src+3 <= srcend && result+4 <= dstlen)
{
dst[result+0] = cb64[ src[0] >> 2 ];
dst[result+1] = cb64[ ((src[0] & 3) << 4) | ((src[1] & 0xf0) >> 4) ];
dst[result+2] = cb64[ ((src[1] & 0xf) << 2) | ((src[2] & 0xc0) >> 6) ];
dst[result+3] = cb64[ src[2] & 0x3f ];
src += 3;
result += 4;
}
if (src < srcend) {
unsigned char in[4];
if (result+4 > dstlen)
return -1;
in[0] = src[0];
in[1] = src+1 < srcend ? src[1] : 0;
in[2] = src+2 < srcend ? src[2] : 0;
dst[result+0] = cb64[ in[0] >> 2 ];
dst[result+1] = cb64[ ((in[0] & 3) << 4) | ((in[1] & 0xf0) >> 4) ];
dst[result+2] = (unsigned char) (src+1 < srcend ? cb64[ ((in[1] & 0xf) << 2) | ((in[2] & 0xc0) >> 6) ] : '=');
dst[result+3] = (unsigned char) (src+2 < srcend ? cb64[ in[2] & 0x3f ] : '=');
result += 4;
}
return result;
}
int
proxy_resolve_server( SockAddress* addr,
const char* servername,
int servernamelen,
int serverport )
{
char name0[64], *name = name0;
int result = -1;
if (servernamelen < 0)
servernamelen = strlen(servername);
if (servernamelen >= sizeof(name0)) {
AARRAY_NEW(name, servernamelen+1);
}
memcpy(name, servername, servernamelen);
name[servernamelen] = 0;
if (sock_address_init_resolve( addr, name, serverport, 0 ) < 0) {
PROXY_LOG("%s: can't resolve proxy server name '%s'",
__FUNCTION__, name);
goto Exit;
}
PROXY_LOG("server name '%s' resolved to %s", name, sock_address_to_string(addr));
result = 0;
Exit:
if (name != name0)
AFREE(name);
return result;
}