/*
* ImapProxy - a caching IMAP proxy daemon
* Copyright (C) 2002 Steven Van Acker
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <database.h>
#include <output.h>
#include <splitstring.h>
#include <processing_child.h>
#include <network.h>
#include <configfile.h>
extern int Globaltime;
DB_record *DB_start = NULL;
DB_record *DB_end = NULL;
/* add a record to the linked list.
* The record is copied.
* DB_start and DB_end are updated.
* the prev and next field in the new record are changed
* the message structured are presumed empty
*/
int db_add(DB_record *rec)
{
DB_record *p;
if(!(p = (DB_record *)malloc(sizeof(DB_record))))
{
debug("db_add(): Could not malloc.\n");
return -1;
}
memcpy(p,rec,sizeof(DB_record));
if(DB_end) DB_end->next = p;
p->prev = DB_end;
p->next = NULL;
if(!DB_end) DB_start = p;
DB_end = p;
return 0;
}
/* this function cleans up a record
* it removes everything, except the parts necessary for keeping alive the connection
*
* what it doesn't remove :
* username
* password
* server socket
* status
* next and prev pointers
*/
int db_cleanup(DB_record *rec)
{
/* keep : username/password
* server socket
* status
* next and prev pointers
*/
/* close client socket */
close(rec->clientsocket);
rec->clientsocket = -1;
/* remove pid */
rec->pid = -1;
/* authenticate tags */
if(rec->clientauthtag)
{
free(rec->clientauthtag);
rec->clientauthtag = NULL;
}
if(rec->proxyauthtag)
{
free(rec->proxyauthtag);
rec->proxyauthtag = NULL;
}
/* remove messages */
db_delete_client_message(rec);
db_delete_server_message(rec);
/* close pipe */
close(rec->ipcfd[0]);
close(rec->ipcfd[1]);
rec->ipcfd[0] = -1;
rec->ipcfd[1] = -1;
rec->serverlastnoop = Globaltime;
rec->clientidlesince = Globaltime;
rec->serveridlesince = Globaltime;
rec->clientlocaladdress.s_addr = 0;
rec->clientlocalport = 0;
return 0;
}
/* remove a record from the linked list
* the record is freed
* all resources are freed
* DB_start and DB_end are updated
*/
int db_del(DB_record *rec)
{
DB_record *p,*n;
p = rec->prev;
n = rec->next;
if(p) p->next = n;
else DB_start = n;
if(n) n->prev = p;
else DB_end = p;
db_cleanup(rec);
/* don't leave password lying around */
memset(rec->password,0,DB_STRING_LENGTH);
close(rec->serversocket);
free(rec);
return 0;
}
/* initialize a record
*/
int db_init_record(DB_record *rec)
{
memset(rec,0,sizeof(DB_record));
db_set_server_connection_state(rec,DB_STATE_SERVER_NOT_CONNECTED);
db_set_client_state(rec,DB_STATE_CLIENT_NOT_AUTHENTICATED);
db_set_server_state(rec,DB_STATE_SERVER_NONE);
db_set_proxy_state(rec,DB_STATE_PROXY_NONE);
db_set_misc_state(rec,DB_STATE_MISC_NONE);
db_set_record_state(rec,DB_STATE_RECORD_NONE);
db_set_capability_state(rec,DB_STATE_CAPABILITY_NONE);
rec->clientsocket = -1;
rec->serversocket = -1;
rec->ipcfd[0] = -1;
rec->ipcfd[1] = -1;
rec->pid = -1;
/* set these to 1, so we know there is no command in progress */
rec->clientmessage.completed = 1;
rec->servermessage.completed = 1;
/* no messages yet, so they are unchecked. */
rec->clientmessage.status = DB_MESSAGE_UNCHECKED;
rec->servermessage.status = DB_MESSAGE_UNCHECKED;
rec->clientidlesince = Globaltime;
rec->serverlastnoop = Globaltime;
rec->serveridlesince = Globaltime;
rec->clientlocaladdress.s_addr = 0;
rec->clientlocalport = 0;
rec->proxylocalport = 0;
rec->reuse = 0;
return 0;
}
#define INDEX_FOR_SERVER 0
#define INDEX_FOR_CLIENT 1
/* when in process mode or unchecked mode :
* this function appends a string to the current line in the message (client or server)
* when in passthrough mode :
* this function sends the received data to the destination, without appending it
*
* the function checks if the command is LOGOUT or not
* if it is LOGOUT, the message is switched to process mode, and the message is interpreted
* otherwise, we just send away the message
*
* ALL OF THIS IS ONLY DONE IN THE CHILD !!!
* when in the parent, the message is just processed. (since we only do authentication there, and don't expect big messages)
*
*/
int db_append_to_message(DB_record *rec,char *buf,int what)
{
struct message_t *currmessage = NULL;
int newsize = 0;
char *currbuf = NULL;
char *newbuf = NULL;
int outfd = -1,i = 0;
Split_string ss;
/* decide which message we want to append to */
if(what == INDEX_FOR_SERVER)
{
currmessage = &(rec->servermessage);
outfd = rec->clientsocket;
}
else
{
currmessage = &(rec->clientmessage);
outfd = rec->serversocket;
}
/* message is not complete */
currmessage->completed = 0;
/* only care about the state of the message if we are in a forked child */
if(!forkedchild)
goto db_process;
/* what mode are we in ? */
switch(currmessage->status)
{
case DB_MESSAGE_UNCHECKED:
//debug("db_append_to_buffer(%d): We are in unchecked mode ! (%s)\n",what,buf);
init_split_string(&ss);
if(!currmessage->count)
{
make_split_string_strip_crlf(buf,&ss," ");
}
else
{
/* something is in the first slot of the message.
* check if it contains our command
* if it doesn't don't check the buffer !
* just let the buffer be appended to the message, and check the first line
* on the next append
*/
make_split_string(currmessage->lines[0],&ss," ");
}
if(split_string_count_parts(&ss) > 1)
{
if(!strcasecmp(split_string_get(&ss,1),"LOGOUT" ))
{
debug("db_append_to_buffer(%d): LOGOUT received !!! going to process mode\n",what);
currmessage->status = DB_MESSAGE_PROCESS;
}
else
{
currmessage->status = DB_MESSAGE_PASSTHROUGH;
/* if any previous lines, send them */
/* there should only be 1 (one) line, otherwise there is a problem */
for(i = 0;i < currmessage->count;i++)
{
socket_printf(outfd,"%s",currmessage->lines[i]);
}
if(currmessage->count > 1)
{
debug("db_append_to_buffer(%d): There is more than one previous line ! this cannot happen ?\n");
}
/* send current buffer */
socket_printf(outfd,"%s",buf);
/* we're in passthrough mode, and everything sent so far, was passed on
* we can go home now
*/
delete_split_string(&ss);
return 0;
}
}
else
{
/* message is not complete so we'll check again in the next append ! */
}
delete_split_string(&ss);
break;
case DB_MESSAGE_PASSTHROUGH:
//debug("db_append_to_buffer(%d): We are in passthrough mode !\n",what);
/* send current buffer */
socket_printf(outfd,"%s",buf);
return 0;
break;
case DB_MESSAGE_PROCESS:
//debug("db_append_to_buffer(%d): We are in process mode !\n",what);
break;
}
db_process:
if(!currmessage->count)
{
if(db_create_new_line_in_message(rec,what) < 0)
{
debug("db_append_to_buffer(): db_create_new_line_in_message() returned error !\n");
return -1;
}
}
currbuf = currmessage->lines[currmessage->count - 1];
/* calculate new length */
newsize = strlen(buf);
if(currbuf) newsize += strlen(currbuf);
newsize++; // \0
/* allocate space */
if(!(newbuf = (char *)calloc(1,newsize)))
{
debug("db_append_to_buffer(): Out of memory.\n");
return -1;
}
/* copy and append */
if(currbuf) strcpy(newbuf,currbuf);
strcat(newbuf,buf);
/* free */
if(currbuf) free(currbuf);
/* assign */
currmessage->lines[currmessage->count - 1] = newbuf;
return 0;
}
/* create a new line in the message = allocate an extra pointer for it */
int db_create_new_line_in_message(DB_record *rec,int what)
{
struct message_t *currmessage = NULL;
if(what == INDEX_FOR_SERVER)
currmessage = &(rec->servermessage);
else
currmessage = &(rec->clientmessage);
currmessage->completed = 0;
currmessage->count++;
if(!(currmessage->lines = (char **)realloc(currmessage->lines, sizeof(char *) * currmessage->count)))
{
debug("db_create_new_line_in_message(): Out of memory.\n");
return -1;
}
currmessage->lines[currmessage->count - 1] = NULL;
return 0;
}
/* delete a message and release the allocated space */
int db_delete_message(DB_record *rec, int what)
{
struct message_t *x;
int i;
if(what == INDEX_FOR_SERVER)
x = &(rec->servermessage);
else
x = &(rec->clientmessage);
/* clear the messages, if any */
if(x->count != 0)
{
for(i = 0;i < x->count; i++)
free(x->lines[i]);
free(x->lines);
}
x->count = 0;
x->completed = 1;
x->lines = NULL;
x->status = DB_MESSAGE_UNCHECKED;
return 0;
}
int db_append_to_client_message(DB_record *rec,char *buf)
{
return db_append_to_message(rec,buf,INDEX_FOR_CLIENT);
}
int db_append_to_server_message(DB_record *rec,char *buf)
{
return db_append_to_message(rec,buf,INDEX_FOR_SERVER);
}
int db_delete_client_message(DB_record *rec)
{
return db_delete_message(rec,INDEX_FOR_CLIENT);
}
int db_delete_server_message(DB_record *rec)
{
return db_delete_message(rec,INDEX_FOR_SERVER);
}
int db_create_new_line_in_client_message(DB_record *rec)
{
return db_create_new_line_in_message(rec,INDEX_FOR_CLIENT);
}
int db_create_new_line_in_server_message(DB_record *rec)
{
return db_create_new_line_in_message(rec,INDEX_FOR_SERVER);
}
/* return the i'th line of a message
* or NULL if i is out of range
*/
char *db_get_message(DB_record *rec, int i,int what)
{
struct message_t *currmessage = NULL;
if(what == INDEX_FOR_SERVER)
currmessage = &(rec->servermessage);
else
currmessage = &(rec->clientmessage);
if(i < 0 || i >= currmessage->count)
return NULL;
return currmessage->lines[i];
}
char *db_get_server_message(DB_record *rec,int i)
{
return db_get_message(rec,i,INDEX_FOR_SERVER);
}
char *db_get_client_message(DB_record *rec,int i)
{
return db_get_message(rec,i,INDEX_FOR_CLIENT);
}
/* return a specific field from the status field
* see the headers for more information
*/
int db_get_state(DB_record *p, int mask)
{
if(!(p->status & mask))
debug("db_get_state(): state vs. mask mismatch ! %0.8p %0.8p\n",p->status,mask);
return p->status & mask;
}
/* set a specific field in the status field
*/
void db_set_state(DB_record *p,int s,int mask)
{
if(!(mask & s))
debug("db_set_state(): state vs. mask mismatch ! %0.8p %0.8p\n",s,mask);
p->status = ((mask ^ 0xffffffff) & p->status) | s;
}
int db_get_server_connection_state(DB_record *p)
{
return db_get_state(p,DB_STATE_SERVER_CONNECTION_MASK);
}
void db_set_server_connection_state(DB_record *p,int s)
{
db_set_state(p,s,DB_STATE_SERVER_CONNECTION_MASK);
}
int db_get_client_state(DB_record *p)
{
return db_get_state(p,DB_STATE_CLIENT_MASK);
}
void db_set_client_state(DB_record *p,int s)
{
db_set_state(p,s,DB_STATE_CLIENT_MASK);
}
int db_get_server_state(DB_record *p)
{
return db_get_state(p,DB_STATE_SERVER_MASK);
}
void db_set_server_state(DB_record *p,int s)
{
db_set_state(p,s,DB_STATE_SERVER_MASK);
}
int db_get_proxy_state(DB_record *p)
{
return db_get_state(p,DB_STATE_PROXY_MASK);
}
void db_set_proxy_state(DB_record *p,int s)
{
db_set_state(p,s,DB_STATE_PROXY_MASK);
}
int db_get_misc_state(DB_record *p)
{
return db_get_state(p,DB_STATE_MISC_MASK);
}
void db_set_misc_state(DB_record *p,int s)
{
db_set_state(p,s,DB_STATE_MISC_MASK);
}
int db_get_record_state(DB_record *p)
{
return db_get_state(p,DB_STATE_RECORD_MASK);
}
void db_set_record_state(DB_record *p,int s)
{
db_set_state(p,s,DB_STATE_RECORD_MASK);
}
int db_get_capability_state(DB_record *p)
{
return db_get_state(p,DB_STATE_CAPABILITY_MASK);
}
void db_set_capability_state(DB_record *p,int s)
{
db_set_state(p,s,DB_STATE_CAPABILITY_MASK);
}
/* find the next free inactive record
* this record will be assigned to an authenticated temporary record
*
* it should :
* contain the same username
* contain the same password
* be INACTIVE
* not be in the middle of processing a message
* not be "me" (the temporary record)
*/
DB_record *db_find_record(DB_record *me,char *username,char *password)
{
DB_record *p = NULL;
p = DB_start;
while(p)
{
/* if username and password match and if the record is inactive, and not processing a server message,
* and if has need been reused too much, return it !*/
if(p != me && db_get_record_state(p) == DB_STATE_RECORD_INACTIVE && p->servermessage.completed &&
(p->reuse < config_max_reuse || config_max_reuse == 0) &&
!strcmp(p->username,username) && !strcmp(p->password,password))
{
return p;
}
p = p->next;
}
return NULL;
}
char *db_get_state_string(int state)
{
int i,max;
static char ret[80];
int intvalues[] = {
DB_STATE_SERVER_CONNECTION_MASK,
DB_STATE_SERVER_NOT_CONNECTED,
DB_STATE_SERVER_CONNECTION_STARTED,
DB_STATE_SERVER_CONNECTED,
DB_STATE_CLIENT_MASK,
DB_STATE_CLIENT_NOT_AUTHENTICATED,
DB_STATE_CLIENT_SENT_AUTHENTICATE_COMMAND,
DB_STATE_CLIENT_SENT_LOGIN_COMMAND,
DB_STATE_CLIENT_SENT_USERNAME,
DB_STATE_CLIENT_SENT_PASSWORD,
DB_STATE_CLIENT_AUTHENTICATED,
DB_STATE_CLIENT_SENT_LOGOUT_COMMAND,
DB_STATE_SERVER_MASK,
DB_STATE_SERVER_NONE,
DB_STATE_SERVER_SENT_GREETING,
DB_STATE_SERVER_SENT_USERNAME_REPLY,
DB_STATE_SERVER_SENT_PASSWORD_REPLY,
DB_STATE_SERVER_SENT_LOGIN_OK,
DB_STATE_SERVER_SENT_LOGIN_FAILED,
DB_STATE_PROXY_MASK,
DB_STATE_PROXY_NONE,
DB_STATE_PROXY_SENT_GREETING_TO_CLIENT,
DB_STATE_PROXY_SENT_USERNAME_REPLY_TO_CLIENT,
DB_STATE_PROXY_SENT_PASSWORD_REPLY_TO_CLIENT,
DB_STATE_PROXY_SENT_AUTHENTICATE_COMMAND_TO_SERVER,
DB_STATE_PROXY_SENT_LOGIN_COMMAND_TO_SERVER,
DB_STATE_PROXY_SENT_USERNAME_TO_SERVER,
DB_STATE_PROXY_SENT_PASSWORD_TO_SERVER,
DB_STATE_PROXY_SENT_LOGIN_OK_TO_CLIENT,
DB_STATE_PROXY_SENT_LOGIN_FAILED_TO_CLIENT,
DB_STATE_MISC_MASK,
DB_STATE_MISC_NONE,
DB_STATE_MISC_REMOVE_RECORD,
DB_STATE_RECORD_MASK,
DB_STATE_RECORD_NONE,
DB_STATE_RECORD_FORKED_CHILD
};
char *values[] = {
"DB_STATE_SERVER_CONNECTION_MASK",
"DB_STATE_SERVER_NOT_CONNECTED",
"DB_STATE_SERVER_CONNECTION_STARTED",
"DB_STATE_SERVER_CONNECTED",
"DB_STATE_CLIENT_MASK",
"DB_STATE_CLIENT_NOT_AUTHENTICATED",
"DB_STATE_CLIENT_SENT_AUTHENTICATE_COMMAND",
"DB_STATE_CLIENT_SENT_LOGIN_COMMAND",
"DB_STATE_CLIENT_SENT_USERNAME",
"DB_STATE_CLIENT_SENT_PASSWORD",
"DB_STATE_CLIENT_AUTHENTICATED",
"DB_STATE_CLIENT_SENT_LOGOUT_COMMAND",
"DB_STATE_SERVER_MASK",
"DB_STATE_SERVER_NONE",
"DB_STATE_SERVER_SENT_GREETING",
"DB_STATE_SERVER_SENT_USERNAME_REPLY",
"DB_STATE_SERVER_SENT_PASSWORD_REPLY",
"DB_STATE_SERVER_SENT_LOGIN_OK",
"DB_STATE_SERVER_SENT_LOGIN_FAILED",
"DB_STATE_PROXY_MASK",
"DB_STATE_PROXY_NONE",
"DB_STATE_PROXY_SENT_GREETING_TO_CLIENT",
"DB_STATE_PROXY_SENT_USERNAME_REPLY_TO_CLIENT",
"DB_STATE_PROXY_SENT_PASSWORD_REPLY_TO_CLIENT",
"DB_STATE_PROXY_SENT_AUTHENTICATE_COMMAND_TO_SERVER",
"DB_STATE_PROXY_SENT_LOGIN_COMMAND_TO_SERVER",
"DB_STATE_PROXY_SENT_USERNAME_TO_SERVER",
"DB_STATE_PROXY_SENT_PASSWORD_TO_SERVER",
"DB_STATE_PROXY_SENT_LOGIN_OK_TO_CLIENT",
"DB_STATE_PROXY_SENT_LOGIN_FAILED_TO_CLIENT",
"DB_STATE_MISC_MASK",
"DB_STATE_MISC_NONE",
"DB_STATE_MISC_REMOVE_RECORD",
"DB_STATE_RECORD_MASK",
"DB_STATE_RECORD_NONE",
"DB_STATE_MISC_FORKED_CHILD"
};
max = sizeof(intvalues) / sizeof(int);
for(i = 0;i < max; i++)
if(state == intvalues[i])
return values[i];
snprintf(ret,sizeof(ret),"UNKNOWN_STATUS_%s%#0x",state?"":"0x",state);
return ret;
}
void db_print_state(DB_record *p)
{
debug("Server connection state: %s\n",db_get_state_string(db_get_server_connection_state(p)));
debug("Client state: %s\n",db_get_state_string(db_get_client_state(p)));
debug("Server state: %s\n",db_get_state_string(db_get_server_state(p)));
debug("Proxy state: %s\n",db_get_state_string(db_get_proxy_state(p)));
debug("Misc state: %s\n",db_get_state_string(db_get_misc_state(p)));
}
void db_log_stats()
{
DB_record *p = NULL;
int active = 0,inactive = 0, temporary = 0, other = 0,total = 0;
p = DB_start;
while(p)
{
switch(db_get_record_state(p))
{
case DB_STATE_RECORD_INACTIVE:
inactive++;
break;
case DB_STATE_RECORD_ACTIVE:
active++;
break;
case DB_STATE_RECORD_TEMPORARY:
temporary++;
break;
default:
other++;
break;
}
total++;
p = p->next;
}
log(LOG_INFO,"Status: %d temporary, %d active, %d inactive, %d other, %d total.\n",temporary,active,inactive,other,total);
}
char *db_get_record_info(DB_record *p)
{
static char buffer[64];
snprintf(buffer,sizeof(buffer),"[%s:%d (%d)]\n",inet_ntoa(p->clientlocaladdress),p->clientlocalport,p->proxylocalport);
return buffer;
}
syntax highlighted by Code2HTML, v. 0.9.1