/*
* 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 <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <arpa/inet.h>
#include <stdarg.h>
#include <ctype.h>
#include <signal.h>
#include <output.h>
#include <network.h>
#include <base64.h>
#include <database.h>
#include <splitstring.h>
#include <configfile.h>
#include <processing_child.h>
#include <defines.h>
#include <misc.h>
DB_record *forkedchild = NULL;
int idle = 0;
/* this function is used to send back an IPC message to the parent
* it will send a code, that can be found in the headers
*/
int child_send_ipc_returncode(DB_record *p,int code)
{
write(p->ipcfd[1],&code,sizeof(int));
idle = 1;
return 0;
}
/*
* This function handles a closed connection from the client side.
* Whatever the current command to the server is, don't send it
*/
int child_handle_client_connection_closed(DB_record *p)
{
debug("[CHILD CLIENT CLOSED] <%d>\n",p->clientsocket);
/* do nothing except report this to the parent ! */
child_send_ipc_returncode(p,DB_IPC_RETURNCODE_CLIENT_CLOSED);
return 0;
}
/* This function handles errors in the client connection.
*/
int child_handle_client_connection_error(DB_record *p)
{
debug("child_handle_client_connection_error(): read() error on socket %d (%m)\n",p->clientsocket);
/* same as child_handle_client_connection_closed */
child_send_ipc_returncode(p,DB_IPC_RETURNCODE_CLIENT_ERROR);
return 0;
}
#define INDEX_FOR_SERVER 0
#define INDEX_FOR_CLIENT 1
/* this function handles a complete message.
* in the child, we only need to process LOGOUT messages
*/
int child_process_client_message(DB_record *p)
{
struct message_t *currmessage = NULL;
int i = 0;
int argcount = 0;
char *tmp = NULL;
Split_string ss;
currmessage = &(p->clientmessage);
if(!currmessage->count || !db_get_client_message(p,0))
return 0;
for(i = 0;i < currmessage->count; i++)
{
if((tmp = db_get_client_message(p,i)))
{
if(!i)
debug("[CC:] %.80s%s",tmp,strlen(tmp)>80?"...\n":"");
else
debug("[CC:] ...\n");
}
}
init_split_string(&ss);
make_split_string_strip_crlf(db_get_client_message(p,0),&ss," ");
/* since we are already authenticated, there are no continuation requests, and every message should have at least 2 words */
if((argcount = split_string_count_parts(&ss)) < 2)
goto c_process_error;
tmp = split_string_get(&ss,1);
if(!strcasecmp(tmp,"LOGOUT"))
{
if(argcount != 2)
{
debug("child_process_client_message(): LOGOUT attempted with invalid amount of arguments (argcount = %d).\n",argcount);
goto c_process_error;
}
debug("[CHILD CLIENT PROCESS] LOGOUT received\n");
socket_printf(p->clientsocket,"* BYE " PROGNAME " " VERSION " closing connection.\r\n");
socket_printf(p->clientsocket,"%s OK LOGOUT completed\r\n",split_string_get(&ss,0));
/* instead of logging out, keep the connection open
* and open inbox readonly
* that way there is mess with mailbox locks
*/
/* FIXME: This is dirty. We don't catch the return of the server,
* so the next client could possibly receive the replies
*/
socket_printf(p->serversocket,"1234 EXAMINE INBOX\r\n");
/* this is a normal exit.
* the client will wait for the server to close the connection
* but the server doesn't know the client is logging out
* so, the task is up to the parent to close the client connection
*/
child_send_ipc_returncode(p,DB_IPC_RETURNCODE_NORMAL);
goto c_real_end_process;
}
//c_end_process:
for(i = 0;i < currmessage->count; i++)
if((tmp = db_get_client_message(p,i)))
write(p->serversocket, tmp, strlen(tmp));
c_real_end_process:
delete_split_string(&ss);
return 0;
c_process_error:
for(i = 0;i < currmessage->count; i++)
if((tmp = db_get_client_message(p,i)))
write(p->serversocket, tmp, strlen(tmp));
//c_real_process_error:
delete_split_string(&ss);
debug("child error occured\n");
return -1;
}
/* handle messages from the server
*/
int child_process_server_message(DB_record *p)
{
struct message_t *currmessage = NULL;
int i = 0;
int argcount = 0;
char *tmp = NULL;
Split_string ss;
currmessage = &(p->servermessage);
/* print to screen */
for(i = 0;i < currmessage->count; i++)
{
if((tmp = db_get_server_message(p,i)))
{
if(!i)
debug("[CS:] %.80s%s",tmp,strlen(tmp)>80?"...\n":"");
else
debug("[CS:] ...\n");
}
}
/* split the message */
init_split_string(&ss);
make_split_string_strip_crlf(db_get_server_message(p,0),&ss," ");
if((argcount = split_string_count_parts(&ss)) < 1)
goto s_process_error;
if(!strcmp(split_string_get(&ss,0),"*"))
{
if(!strcasecmp(split_string_get(&ss,1),"BYE"))
{
debug("child_process_server_message(): Server sent \"* BYE ...\"\n");
child_send_ipc_returncode(p,DB_IPC_RETURNCODE_SERVER_CLOSED);
}
goto s_end_process;
}
s_end_process:
for(i = 0;i < currmessage->count; i++)
if((tmp = db_get_server_message(p,i)))
write(p->clientsocket, tmp, strlen(tmp));
delete_split_string(&ss);
return 0;
s_process_error:
for(i = 0;i < currmessage->count; i++)
if((tmp = db_get_server_message(p,i)))
write(p->clientsocket, tmp, strlen(tmp));
delete_split_string(&ss);
debug("child error occured\n");
return -1;
}
/* this function handles new data from a connection
* the data is parsed into a message here
*/
int child_handle_connection_new_data(DB_record *p,char *buffer,int len,int what)
{
struct message_t *currmessage = NULL;
char *tmpbuf = NULL, *tmpp = NULL;
char *buf = NULL;
int tmpi = 0;
int n = 0;
/* new data, store it in buffer
* handle the data if it is complete (contains \r\n)
*/
if(db_get_server_connection_state(p) != DB_STATE_SERVER_CONNECTED)
{
debug("child_handle_connection_new_data(): Connection to server is not initialised yet (this should not happen) !!!\n");
child_handle_client_connection_error(p);
return -1;
}
if(what == INDEX_FOR_SERVER)
currmessage = &(p->servermessage);
else
currmessage = &(p->clientmessage);
buf = buffer;
while(len > 0 && !idle)
{
if(currmessage->literalcountdown > 0)
{
/* we are in literal mode
* meaning : append everything to the message, untill the literalcountdown is 0
*/
if(currmessage->literalcountdown < len)
{
n = currmessage->literalcountdown;
if(!(tmpbuf = (char *)calloc(n+1,sizeof(char))))
{
debug("child_handle_connection_new_data(): Out of memory !\n");
return -1;
}
strncpy(tmpbuf,buf,n);
if(what == INDEX_FOR_SERVER)
db_append_to_server_message(p,tmpbuf);
else
db_append_to_client_message(p,tmpbuf);
free(tmpbuf);
tmpbuf = NULL;
}
else
{
n = len;
if(what == INDEX_FOR_SERVER)
db_append_to_server_message(p,buf);
else
db_append_to_client_message(p,buf);
}
/* change literal countdown */
currmessage->literalcountdown -= n;
/* update pointer in current buffer */
buf += n;
len -= n;
}
else
{
/* we are in normal mode
* meaning: whatever lies ahead, process it normally
*/
/* find \r\n */
tmpp = strstr(buf,"\r\n");
if(tmpp)
{
/* a command ends in this line
*
* there are 2 options:
* * the last word is a {123} literal header -> a literal starts and the message doesn't end
* * no {123} -> the command is complete, and the message ends
*/
/* append to the message */
n = tmpp - buf + strlen("\r\n");
if(!(tmpbuf = (char *)calloc(n+1,sizeof(char))))
{
debug("child_handle_connection_new_data(): Out of memory !\n");
return -1;
}
strncpy(tmpbuf,buf,n);
if(what == INDEX_FOR_SERVER)
db_append_to_server_message(p,tmpbuf);
else
db_append_to_client_message(p,tmpbuf);
/* now that it is appended, check for {123} */
if((tmpi = check_for_literal_count(tmpbuf)) < 0)
{
/* no literal count -> process and delete message */
if(what == INDEX_FOR_SERVER)
{
if(p->servermessage.status != DB_MESSAGE_PASSTHROUGH)
child_process_server_message(p);
db_delete_server_message(p);
}
else
{
if(p->clientmessage.status != DB_MESSAGE_PASSTHROUGH)
child_process_client_message(p);
db_delete_client_message(p);
}
}
else
{
/* start of literal -> set all vars, and jump back in the loop */
currmessage->literalcountdown = tmpi;
if(what == INDEX_FOR_SERVER)
db_create_new_line_in_server_message(p);
else
db_create_new_line_in_client_message(p);
}
free(tmpbuf);
tmpbuf = NULL;
len -= n;
buf += n;
}
else
{
/* no \r\n
* just plain old regular data, append to the message
*/
if(what == INDEX_FOR_SERVER)
db_append_to_server_message(p,buf);
else
db_append_to_client_message(p,buf);
len -= strlen(buf);
buf += strlen(buf);
}
}
}
return 0;
}
int child_handle_client_connection_new_data(DB_record *p,char *buffer,int len)
{
return child_handle_connection_new_data(p,buffer,len,INDEX_FOR_CLIENT);
}
int child_handle_server_connection_closed(DB_record *p)
{
debug("[CHILD SERVER CLOSED] <%d>\n",p->serversocket);
child_send_ipc_returncode(p,DB_IPC_RETURNCODE_SERVER_CLOSED);
return 0;
}
int child_handle_server_connection_error(DB_record *p)
{
debug("child_handle_server_connection_error(): read() error on socket %d (%m)\n",p->serversocket);
child_send_ipc_returncode(p,DB_IPC_RETURNCODE_SERVER_ERROR);
return 0;
}
int child_handle_server_connection_new_data(DB_record *p,char *buffer,int len)
{
return child_handle_connection_new_data(p,buffer,len,INDEX_FOR_SERVER);
}
/* this function checks if the server or client sent anything, and processes it */
int child_conversate()
{
char buffer[1024];
fd_set readset;
struct timeval tv;
int r = 0, rr = 0, maxfd = 0;
bzero(buffer,sizeof(buffer));
FD_ZERO(&readset);
#define SELECT_TIMEOUT_VALUE 60
tv.tv_sec = SELECT_TIMEOUT_VALUE;
tv.tv_usec = 0;
/* check if we are a forked process ... */
if(forkedchild && !idle)
{
maxfd = forkedchild->serversocket;
if(maxfd < forkedchild->clientsocket)
maxfd = forkedchild->clientsocket;
maxfd++;
FD_SET(forkedchild->serversocket,&readset);
FD_SET(forkedchild->clientsocket,&readset);
if((r = select(maxfd,&readset,NULL,NULL,&tv)) > 0)
{
if((db_get_server_connection_state(forkedchild) == DB_STATE_SERVER_CONNECTED) && FD_ISSET(forkedchild->clientsocket,&readset))
{
rr = read(forkedchild->clientsocket,buffer,sizeof(buffer)-1);
if(rr < 0)
{
child_handle_client_connection_error(forkedchild);
}
else if(rr == 0)
{
child_handle_client_connection_closed(forkedchild);
}
else
{
buffer[rr] = '\0';
child_handle_client_connection_new_data(forkedchild,buffer,rr);
}
}
else if(FD_ISSET(forkedchild->serversocket,&readset))
{
rr = read(forkedchild->serversocket,buffer,sizeof(buffer)-1);
if(rr < 0)
{
child_handle_server_connection_error(forkedchild);
}
else if(rr == 0)
{
child_handle_server_connection_closed(forkedchild);
}
else
{
buffer[rr] = '\0';
child_handle_server_connection_new_data(forkedchild,buffer,rr);
}
}
}
else if(!r)
{
debug("child_conversate(): select() timed out. (%d seconds without activity)\n",SELECT_TIMEOUT_VALUE);
return 1;
}
else
{
if(errno != EINTR)
{
debug("child_conversate(): select() error (%m)\n");
return -1;
}
}
return 0;
}
else if(idle)
{
/* if we get here, it means the child has done processing data, and set forkedchild to NULL
* now, we have to wait untill the parent processes the ipc returncode, and kills this child.
*/
while(1)
{
debug("child %d in waitcycle\n",getpid());
sleep(10);
}
}
return -1;
}
syntax highlighted by Code2HTML, v. 0.9.1