/*
* 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 <sys/wait.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 <time.h>
#include <output.h>
#include <network.h>
#include <base64.h>
#include <database.h>
#include <splitstring.h>
#include <configfile.h>
#include <processing_parent.h>
#include <processing_child.h>
#include <misc.h>
#include <defines.h>
#include <parsed_message.h>
extern int Globaltime;
/* this function checks if the server allows authenticate command.
* it checks the given split string for AUTH=LOGIN
*/
int parent_can_use_authenticate_login(Parsed_message *pm)
{
int i,n;
return 0;
n = parsed_message_count_args(pm);
/* start at 2, because the string starts with * and CAPABILITY, so we can skip those
*/
for(i = 2;i < n; i++)
{
if(!strcasecmp(parsed_message_get_arg(pm,i),"AUTH=LOGIN"))
{
debug("parent_can_use_authenticate_login(): AUTHENTICATE LOGIN is allowed !! using it...\n");
return 1;
}
}
debug("parent_can_use_authenticate_login(): AUTHENTICATE LOGIN is not allowed !! Falling back on LOGIN instead.\n");
return 0;
}
/* this function sets up a connection to the server
* and sets appropriate flags
*/
int parent_init_server_connection(DB_record *p)
{
struct sockaddr_in remote,local;
int size = 0;
if((p->serversocket = nonblock_socket()) < 0)
{
debug("parent_init_server_connection(): Couldn't not open a non-blocking socket for serverconnection !\n");
return -1;
}
bzero(&remote,sizeof(remote));
remote.sin_family = PF_INET;
remote.sin_port = htons(config_remote_port);
remote.sin_addr.s_addr = config_remote_address;
if(connect(p->serversocket,(const struct sockaddr *)&remote,sizeof(remote)))
{
if(errno != EINPROGRESS)
{
debug("parent_init_server_connection(): Couldn't connect to remote (%m)\n");
return -1;
}
}
size = sizeof(local);
if ((getsockname(p->serversocket,(struct sockaddr *) &local, &size ) < 0))
{
debug("parent_init_server_connection(): getsockname error ! (%s)\n",strerror(errno));
return -1;
}
p->proxylocalport = ntohs(local.sin_port);
db_set_server_connection_state(p,DB_STATE_SERVER_CONNECTION_STARTED);
return 0;
}
/* fork record and set flags */
int parent_fork_record(DB_record *p,int mainfd)
{
DB_record *x = NULL,*y = NULL;
pid_t pid;
if(pipe(p->ipcfd))
{
debug("parent_conversate(): Couldn't create ipc pipe %m\n");
return -1;
}
pid = fork();
if(pid < 0)
{
debug("parent_conversate(): Couldn't fork ! (%m)\n");
return -1;
}
else if(pid == 0)
{
/* child process */
/* we don't need to listening port */
close(mainfd);
/* we don't need the other records either */
x = DB_start;
while(x)
{
y = x->next;
if(x != p)
{
/* remove all except ourselves */
db_del(x);
}
x = y;
}
db_set_record_state(p,DB_STATE_RECORD_FORKED_CHILD);
forkedchild = p;
/* reset the loop */
return 0;
}
else /* pid > 0 */
{
/* parent process */
db_set_record_state(p,DB_STATE_RECORD_ACTIVE);
p->pid = pid;
debug("Forked a child %d\n",pid);
/* set nonblocking */
if(fcntl(p->ipcfd[0],F_SETFL,O_NONBLOCK)<0)
{
debug("parent_conversate(): Couldn't set O_NONBLOCK (%m)\n");
return -1;
}
forkedchild = NULL;
/* we don't need the client anymore */
close(p->clientsocket);
p->clientsocket = -1;
return pid;
}
return -1;
}
/* return 0 on success
*/
int parent_locate_and_activate_record(DB_record *p)
{
DB_record *r = NULL;
if(!(r = db_find_record(p,p->username,p->password)))
{
debug("parent_locate_and_activate_record(): No free record found for \"%s\"\n",p->username);
return -1;
}
/* we found an inactive record for this user.
*
* instead of reactivating it, we will copy the necessary data to the temporary record
* and remove the inactive one.
*/
debug("parent_locate_and_activate_record(): Free record found for \"%s\". Reactivating ...\n",p->username);
p->serversocket = r->serversocket;
r->serversocket = -1;
/* remember the local port */
p->proxylocalport = r->proxylocalport;
p->reuse = r->reuse + 1;
/* we are ready to fork now */
db_set_record_state(p,DB_STATE_RECORD_READY_TO_FORK);
/* the server is connected */
db_set_server_connection_state(p,DB_STATE_SERVER_CONNECTED);
/* the server has authenticated us */
socket_printf(p->clientsocket,"%s OK Reactivating record\r\n",p->clientauthtag);
log(LOG_NOTICE,"%s Reactivating record for user \"%s\" (%d).\n",db_get_record_info(p),p->username,p->reuse);
db_set_server_state(p,DB_STATE_SERVER_SENT_LOGIN_OK);
/* the client is authenticated */
db_set_client_state(p,DB_STATE_CLIENT_AUTHENTICATED);
/* the proxy passed the LOGIN OK message to the client */
db_set_proxy_state(p,DB_STATE_PROXY_SENT_LOGIN_OK_TO_CLIENT);
db_set_misc_state(p,DB_STATE_MISC_NONE);
/* remove the old record though */
db_set_misc_state(r,DB_STATE_MISC_REMOVE_RECORD);
return 0;
}
/*
* This function handles a new incoming connection (connecting to the proxy)
* It should :
* check if the connection is allowed and close it if necessary
* create a new database record and initialise it.
*/
int parent_handle_new_connection(int fd)
{
int newfd;
DB_record rec;
struct sockaddr_in addr;
int addrlen = sizeof(struct sockaddr);
/* new connection arrived, add to database */
newfd = accept(fd,(struct sockaddr *)&addr,&addrlen);
if(newfd < 0)
{
debug("parent_handle_new_connection(): accept returned error: %m\n");
return -1;
}
log(LOG_NOTICE,"Connection received from %s:%d.\n",inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));
if(check_if_blocked(addr.sin_addr.s_addr))
{
/* ip is blocked ! */
debug("[PARENT CLIENT %s:%d REFUSED]\n",inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));
log(LOG_NOTICE,"Connection refused from %s:%d.\n",inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));
socket_printf(newfd,"* BYE Access to proxy denied.\r\n");
close(newfd);
return 0;
}
db_init_record(&rec);
rec.clientsocket = newfd;
rec.clientlocaladdress.s_addr = addr.sin_addr.s_addr;
rec.clientlocalport = ntohs(addr.sin_port);
/* record will be temporary untill enough data is gathered to authenticate (or reject) it
*/
db_set_record_state(&rec,DB_STATE_RECORD_TEMPORARY);
socket_printf(newfd,"* OK IMAP4rev1 "PROGNAME" "VERSION" Welcome to my dominion.\r\n");
db_set_proxy_state(&rec,DB_STATE_PROXY_SENT_GREETING_TO_CLIENT);
db_add(&rec);
debug("[PARENT CLIENT CONNECT] <%d>\n",rec.clientsocket);
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 parent_handle_client_connection_closed(DB_record *p)
{
debug("[PARENT CLIENT CLOSED] <%d>\n",p->clientsocket);
/* if a client is connected, we are not in DB_STATE_RECORD_INACTIVE mode
* DB_STATE_RECORD_ACTIVE is not possible (otherwise we would be forked)
* so, there is no server to send anything to.
*/
db_set_misc_state(p,DB_STATE_MISC_REMOVE_RECORD);
return 0;
}
/* This function handles errors in the client connection.
*/
int parent_handle_client_connection_error(DB_record *p)
{
debug("parent_handle_client_connection_error(): read() error on socket %d (%m)\n",p->clientsocket);
db_set_misc_state(p,DB_STATE_MISC_REMOVE_RECORD);
return 0;
}
#define INDEX_FOR_SERVER 0
#define INDEX_FOR_CLIENT 1
/* process message coming from the client */
int parent_process_client_message(DB_record *p)
{
struct message_t *currmessage = NULL;
int i = 0;
int argcount = 0;
char *tmp = NULL;
Parsed_message pm;
currmessage = &(p->clientmessage);
if(!currmessage->count || !db_get_client_message(p,0))
{
debug("empty message ?!?! how does this happen ??\n");
return 0;
}
if(db_get_client_state(p) == DB_STATE_CLIENT_AUTHENTICATED)
{
debug("parent_process_client_message(): We should not be in this state !!! Authenticated clients should fork.\n");
}
/* output some things for debug */
if(config_enable_debug)
{
for(i = 0;i < currmessage->count; i++)
{
if((tmp = db_get_client_message(p,i)))
{
if(!i)
debug("[PC:] %.80s%s",tmp,strlen(tmp)>80?"...\n":"");
else
debug("[PC:] ...\n");
}
}
}
/* parse the message */
init_parsed_message(&pm);
make_parsed_message(currmessage,&pm);
/* some debug */
print_parsed_message(&pm);
/* continuation of the authenicate command
* if user sends "*", then he/she wants to abort !
*/
if(db_get_client_state(p) == DB_STATE_CLIENT_SENT_AUTHENTICATE_COMMAND && db_get_proxy_state(p) == DB_STATE_PROXY_SENT_USERNAME_REPLY_TO_CLIENT)
{
char username[DB_STRING_LENGTH+1];
tmp = parsed_message_get_idtag(&pm);
if(tmp)
{
if(!strcmp(tmp,"*"))
{
log(LOG_NOTICE,"%s User aborted login.\n",db_get_record_info(p));
socket_printf(p->clientsocket,"%s BAD AUTHENTICATE LOGIN cancelled\r\n",p->clientauthtag);
db_set_proxy_state(p,DB_STATE_PROXY_SENT_GREETING_TO_CLIENT);
db_set_client_state(p,DB_STATE_CLIENT_NOT_AUTHENTICATED);
free(p->clientauthtag);
p->clientauthtag = NULL;
goto c_real_end_process;
}
else
{
base64decode(username,tmp,strlen(tmp));
strncpy(p->username,username,DB_STRING_LENGTH);
}
}
else
{
strcpy(p->username,"");
}
db_set_client_state(p, DB_STATE_CLIENT_SENT_USERNAME);
debug("[PARENT CLIENT PROCESS] Client sent username \"%s\"\n",tmp?username:"");
log(LOG_NOTICE,"%s Authentication attempt for user \"%s\".\n",db_get_record_info(p),tmp?username:"");
/* send "Password" in base64 encoding */
socket_printf(p->clientsocket,"+ UGFzc3dvcmQA\r\n");
db_set_proxy_state(p,DB_STATE_PROXY_SENT_PASSWORD_REPLY_TO_CLIENT);
goto c_real_end_process;
}
else if(db_get_client_state(p) == DB_STATE_CLIENT_SENT_USERNAME && db_get_proxy_state(p) == DB_STATE_PROXY_SENT_PASSWORD_REPLY_TO_CLIENT)
{
char password[DB_STRING_LENGTH+1];
tmp = parsed_message_get_idtag(&pm);
if(tmp)
{
if(!strcmp(tmp,"*"))
{
log(LOG_NOTICE,"%s User \"%s\" aborted login.\n",db_get_record_info(p),p->username);
socket_printf(p->clientsocket,"%s BAD AUTHENTICATE LOGIN cancelled\r\n",p->clientauthtag);
db_set_proxy_state(p,DB_STATE_PROXY_SENT_GREETING_TO_CLIENT);
db_set_client_state(p,DB_STATE_CLIENT_NOT_AUTHENTICATED);
free(p->clientauthtag);
p->clientauthtag = NULL;
strcpy(p->username,"");
goto c_real_end_process;
}
else
{
base64decode(password,tmp,strlen(tmp));
strncpy(p->password,password,DB_STRING_LENGTH);
}
}
else
{
strcpy(p->password,"");
}
db_set_client_state(p, DB_STATE_CLIENT_SENT_PASSWORD);
debug("[PARENT CLIENT PROCESS] Client sent password\n");
/* check if we have a free slot, otherwise connect to server */
if(parent_locate_and_activate_record(p) == 0)
goto c_real_end_process;
if(parent_init_server_connection(p))
{
log(LOG_CRIT,"%s Could not initiate connection to server ?\n",db_get_record_info(p));
goto c_send_bye;
}
else
goto c_real_end_process;
}
/* we need at least 1 string to work with (ID tag) */
if((argcount = parsed_message_count_args(&pm)) < 1)
{
socket_printf(p->clientsocket,"* BAD Null command\r\n");
goto c_real_end_process;
}
if(argcount < 2)
{
socket_printf(p->clientsocket,"%s BAD Missing command\r\n",parsed_message_get_idtag(&pm));
goto c_real_end_process;
}
tmp = parsed_message_get_command(&pm);
if(db_get_proxy_state(p) == DB_STATE_PROXY_SENT_GREETING_TO_CLIENT && db_get_client_state(p) == DB_STATE_CLIENT_NOT_AUTHENTICATED && !strcasecmp(tmp,"AUTHENTICATE"))
{
if(argcount != 3)
{
debug("parent_process_client_message(): AUTHENTICATE attempted with invalid amount of arguments (argcount = %d).\n",argcount);
socket_printf(p->clientsocket,"%s BAD Missing or invalid argument to AUTHENTICATE\r\n",parsed_message_get_idtag(&pm));
goto c_real_end_process;
}
tmp = parsed_message_get_arg(&pm,2);
if(!strcasecmp(tmp,"LOGIN"))
{
debug("[PARENT CLIENT PROCESS] Authenticated started (AUTHENTICATE LOGIN)\n");
db_set_client_state(p, DB_STATE_CLIENT_SENT_AUTHENTICATE_COMMAND);
if(p->clientauthtag)
{
debug("parent_process_client_message(): Previous tag message in place (%s)!\n",p->clientauthtag);
}
if(!(p->clientauthtag = strdup(parsed_message_get_idtag(&pm))))
{
debug("parent_process_client_message(): Out of memory\n");
log(LOG_CRIT,"%s Out of memory\n",db_get_record_info(p));
goto c_send_bye;
}
/* send User Name in base64 */
socket_printf(p->clientsocket,"+ VXNlciBOYW1lAA==\r\n");
db_set_proxy_state(p,DB_STATE_PROXY_SENT_USERNAME_REPLY_TO_CLIENT);
goto c_real_end_process;
}
else
{
debug("parent_process_client_message(): Client sent unsupported authentication method (AUTHENTICATE %s)\n",tmp);
socket_printf(p->clientsocket,"%s NO AUTHENTICATE %s failed\r\n",parsed_message_get_idtag(&pm),parsed_message_get_arg(&pm,2));
goto c_real_end_process;
}
}
else if(db_get_proxy_state(p) == DB_STATE_PROXY_SENT_GREETING_TO_CLIENT && db_get_client_state(p) == DB_STATE_CLIENT_NOT_AUTHENTICATED && !strcasecmp(tmp,"LOGIN"))
{
if(argcount < 4)
{
debug("parent_process_client_message(): LOGIN attempted with invalid amount of arguments (argcount = %d).\n",argcount);
socket_printf(p->clientsocket,"%s BAD Missing or invalid argument to LOGIN\r\n",parsed_message_get_idtag(&pm));
goto c_real_end_process;
}
strncpy(p->username,parsed_message_get_arg(&pm,2),DB_STRING_LENGTH);
strncpy(p->password,parsed_message_get_arg(&pm,3),DB_STRING_LENGTH);
debug("[PARENT CLIENT PROCESS] LOGIN [%s] [*****]\n",p->username);
log(LOG_NOTICE,"%s Authentication attempt for user \"%s\".\n",db_get_record_info(p),p->username);
if(p->clientauthtag)
{
debug("parent_process_client_message(): Previous tag message in place (%s)!\n",p->clientauthtag);
free(p->clientauthtag);
p->clientauthtag = NULL;
}
if(!(p->clientauthtag = strdup(parsed_message_get_idtag(&pm))))
{
debug("parent_process_client_message(): Out of memory\n");
goto c_real_end_process;
}
db_set_client_state(p, DB_STATE_CLIENT_SENT_PASSWORD);
/* if we find an inactive connection, activate it
* otherwise, open a new one.
*/
if(parent_locate_and_activate_record(p) == 0)
goto c_real_end_process;
if(parent_init_server_connection(p))
{
log(LOG_CRIT,"%s Could not initiate connection to server ?\n",db_get_record_info(p));
goto c_send_bye;
}
else
goto c_real_end_process;
}
else if(!strcasecmp(tmp,"LOGOUT"))
{
if(argcount != 2)
{
debug("parent_process_client_message(): LOGOUT attempted with invalid amount of arguments (argcount = %d).\n",argcount);
socket_printf(p->clientsocket,"%s BAD\r\n",parsed_message_get_idtag(&pm));
goto c_real_end_process;
}
debug("[PARENT CLIENT PROCESS] LOGOUT received\n");
/* print a nice message for the client to stare at, and close the connection */
socket_printf(p->clientsocket,"* BYE " PROGNAME " " VERSION " closing connection.\r\n");
socket_printf(p->clientsocket,"%s OK LOGOUT completed\r\n",parsed_message_get_idtag(&pm));
db_set_misc_state(p, DB_STATE_MISC_REMOVE_RECORD);
goto c_real_end_process;
}
else if(!strcasecmp(tmp,"NOOP"))
{
socket_printf(p->clientsocket,"%s OK NOOP completed\r\n",parsed_message_get_idtag(&pm));
goto c_real_end_process;
}
else if(!strcasecmp(tmp,"CAPABILITY"))
{
socket_printf(p->clientsocket,"* CAPABILITY IMAP4REV1 AUTH=LOGIN\r\n");
socket_printf(p->clientsocket,"%s OK CAPABILITY completed\r\n",parsed_message_get_idtag(&pm));
goto c_real_end_process;
}
else
{
socket_printf(p->clientsocket,"%s BAD Command unrecognized/login please: %s\r\n",parsed_message_get_idtag(&pm),parsed_message_get_command(&pm));
goto c_real_end_process;
}
c_real_end_process:
delete_parsed_message(&pm);
return 0;
c_send_bye:
/*
* something happened and we need to send a bye to the client.
* this means :
* send "BYE"
* close the socket
* set the correct states
* clean the messages
*
* since we can only be here if the client didn't authenticate yet,
* it means the state is DB_STATE_RECORD_TEMPORARY.
* this also means that there is no lasting connection to the server yet
* so, close the connection to the server as well, and remove the record all together.
*
*/
socket_printf(p->clientsocket,"* BYE " PROGNAME " " VERSION " closing connection.\r\n");
delete_parsed_message(&pm);
debug("parent error occured\n");
db_set_misc_state(p,DB_STATE_MISC_REMOVE_RECORD);
return -1;
}
/* process server message */
int parent_process_server_message(DB_record *p)
{
struct message_t *currmessage = NULL;
int i = 0;
int argcount = 0;
char *tmp = NULL;
//Split_string ss;
Parsed_message pm;
currmessage = &(p->servermessage);
if(config_enable_debug)
{
for(i = 0;i < currmessage->count; i++)
{
if((tmp = db_get_server_message(p,i)))
{
if(!i)
debug("[PS:] %.80s%s",tmp,strlen(tmp)>80?"...\n":"");
else
debug("[PS:] ...\n");
}
}
}
if(!(db_get_record_state(p) == DB_STATE_RECORD_ACTIVE || db_get_record_state(p) == DB_STATE_RECORD_INACTIVE || db_get_record_state(p) == DB_STATE_RECORD_TEMPORARY))
{
debug("parent_process_server_message(): We are in an impossible record state !!! %s\n",db_get_state_string(db_get_record_state(p)));
return -1;
}
if(db_get_record_state(p) == DB_STATE_RECORD_INACTIVE)
{
/* if server sends a BYE, it will close the connection, and we'll clean it up in the next select() */
return 0;
}
if(db_get_record_state(p) == DB_STATE_RECORD_ACTIVE)
{
debug("parent_process_server_message(): We should not be in DB_STATE_RECORD_ACTIVE state !! Did we forget to fork ?.\n");
return -1;
}
/* we're now in TEMPORARY state */
init_parsed_message(&pm);
make_parsed_message(currmessage,&pm);
print_parsed_message(&pm);
if((argcount = parsed_message_count_args(&pm)) < 1)
{
debug("parent_process_server_message(): Server sent empty message\n");
log(LOG_WARNING,"%s Server sent empty message\n",db_get_record_info(p));
goto s_send_bye;
}
if(db_get_server_state(p) != DB_STATE_SERVER_NONE && db_get_proxy_state(p) != DB_STATE_PROXY_SENT_CAPABILITY_COMMAND_TO_SERVER && !strcmp(parsed_message_get_idtag(&pm),"*"))
{
/* if we are not expecting a greeting or the reply to the capability command, just treat this as a general notice */
goto s_end_process;
}
if(db_get_server_state(p) == DB_STATE_SERVER_NONE)
{
char *respcode = NULL;
if(argcount < 2)
{
debug("parent_process_server_message(): Server greeting is not long enough.\n");
log(LOG_WARNING,"%s Server greeting is not long enough.\n",db_get_record_info(p));
goto s_send_bye;
}
/* condition is true if :
* the first argument is not "*"
* or
* the second argument is not one of "OK" "PREAUTH" "BYE"
*/
if(!(!strcmp(parsed_message_get_idtag(&pm),"*") && (!strcasecmp(parsed_message_get_command(&pm),"OK") ||
!strcasecmp(parsed_message_get_command(&pm),"PREAUTH") ||
!strcasecmp(parsed_message_get_command(&pm),"BYE"))))
{
debug("parent_process_server_message(): Couldn't understand server (%s %s ...)\n",parsed_message_get_idtag(&pm),parsed_message_get_command(&pm));
log(LOG_WARNING,"%s Server sent weird command in DB_STATE_SERVER_NONE\n",db_get_record_info(p));
goto s_send_bye;
}
/* process the response code, because we care (really) */
respcode = parsed_message_get_command(&pm);
if(!strcasecmp(respcode,"BYE"))
{
debug("parent_process_server_message(): Server sent a BYE ! should expect a panic close\n");
log(LOG_NOTICE,"%s Server sent BYE to proxy !\n",db_get_record_info(p));
goto s_send_bye;
}
/* otherwise, it's OK or PREAUTH */
db_set_server_state(p,DB_STATE_SERVER_SENT_GREETING);
debug("[PARENT SERVER PROCESS] Server sent Greeting\n");
socket_printf(p->serversocket,"0 CAPABILITY\r\n");
debug("parent_process_server_message(): We sent CAPABILITY command\n");
if(!(p->proxyauthtag = strdup("0")))
{
debug("parent_process_server_message(): Out of memory !\n");
log(LOG_CRIT,"%s Out of memory\n",db_get_record_info(p));
goto s_send_bye;
}
db_set_proxy_state(p,DB_STATE_PROXY_SENT_CAPABILITY_COMMAND_TO_SERVER);
goto s_end_process;
}
else if(db_get_server_state(p) == DB_STATE_SERVER_SENT_GREETING && db_get_proxy_state(p) == DB_STATE_PROXY_SENT_CAPABILITY_COMMAND_TO_SERVER)
{
/* first check if its an untagged response ! if it's not, well, go on */
if(!strcmp("*",parsed_message_get_idtag(&pm)))
{
/* check if there is more than 1 word, and if so, if the second is "CAPABILITY" */
if(!((argcount > 1) && !strcasecmp("CAPABILITY",parsed_message_get_command(&pm))))
{
/* no it isn't ...
* silently leave without changing any states */
goto s_end_process;
}
/* check if we can use authenticate login */
if(parent_can_use_authenticate_login(&pm))
{
db_set_capability_state(p,DB_STATE_CAPABILITY_AUTH_LOGIN);
}
else
{
db_set_capability_state(p,DB_STATE_CAPABILITY_LOGIN);
}
goto s_end_process;
}
if(!p->proxyauthtag)
{
debug("parent_process_server_message(): No recorded ID tag ?? This should not happen\n");
goto s_send_bye;
}
/* check if the given ID tag matches our request */
if(strcmp(p->proxyauthtag,parsed_message_get_idtag(&pm)))
{
debug("parent_process_server_message(): Server response does not match recorded client capability tag ! (\"%s\" <> \"%s\")\n",
parsed_message_get_idtag(&pm),p->clientauthtag);
log(LOG_WARNING,"%s Server returned an unknown ID tag\n",db_get_record_info(p));
goto s_send_bye;
}
/* free it */
free(p->proxyauthtag);
p->proxyauthtag = NULL;
/* check for weird conditions (maybe the server sent NO ?) */
if(strcasecmp(parsed_message_get_command(&pm),"OK"))
{
debug("parent_process_server_message(): Server did not like CAPABILITY command ?!\n");
log(LOG_WARNING,"%s Server did not like CAPABILITY command.\n",db_get_record_info(p));
goto s_send_bye;
}
/* check whether we can use authenticate login */
if(db_get_capability_state(p) == DB_STATE_CAPABILITY_AUTH_LOGIN)
{
socket_printf(p->serversocket,"1 AUTHENTICATE LOGIN\r\n");
}
else
{
/* we send the username and password as literals.
* this way, we avoid trouble with weird passwords that have \r\n in them and things
*
*/
socket_printf(p->serversocket,"1 LOGIN {%d}\r\n",strlen(p->username));
}
/* we'll be using the same states for both authenticate login and the normal login
* to remember which method we used, later on
* we just have to look at the capability state.
*/
db_set_proxy_state(p,DB_STATE_PROXY_SENT_AUTHENTICATE_COMMAND_TO_SERVER);
db_set_server_state(p,DB_STATE_SERVER_SENT_CAPABILITY_REPLY);
/* remember the ID tag we used */
if(!(p->proxyauthtag = strdup("1")))
{
debug("parent_process_server_message(): Out of memory !\n");
log(LOG_CRIT,"%s Out of memory\n",db_get_record_info(p));
goto s_send_bye;
}
goto s_end_process;
}
else if(db_get_server_state(p) == DB_STATE_SERVER_SENT_CAPABILITY_REPLY && db_get_proxy_state(p) == DB_STATE_PROXY_SENT_AUTHENTICATE_COMMAND_TO_SERVER)
{
char buffer[DB_STRING_LENGTH+1];
if(strcmp(parsed_message_get_idtag(&pm),"+"))
{
debug("parent_process_server_message(): Couldn't understand server (%s ...)\n",parsed_message_get_idtag(&pm));
log(LOG_WARNING,"%s Server didn't continue with continuation of AUTHENTICATE command\n",db_get_record_info(p));
goto s_send_bye;
}
#if 0
base64decode(buffer,parsed_message_get_command(&pm),sizeof(buffer));
/* Server should now send "User Name" or maybe "Username", as the rfc is not specific about this... */
if(strcasecmp(buffer,"user name") && strcasecmp(buffer,"username"))
{
debug("parent_process_server_message(): Couldn't understand server (+ %s ...) (Should send username)\n",buffer);
log(LOG_WARNING,"Server did not sent \"User Name\" or \"Username\" in base64 as a continuation command\n");
goto s_send_bye;
}
#endif
db_set_server_state(p,DB_STATE_SERVER_SENT_USERNAME_REPLY);
debug("[PARENT SERVER PROCESS] Server sent continuation for username\n");
if(db_get_capability_state(p) == DB_STATE_CAPABILITY_AUTH_LOGIN)
{
memset(buffer,0,sizeof(buffer));
base64encode(buffer,p->username,strlen(p->username)+1);
socket_printf(p->serversocket,"%s\r\n",buffer);
}
else
{
socket_printf(p->serversocket,"%s {%d}\r\n",p->username,strlen(p->password));
}
db_set_proxy_state(p,DB_STATE_PROXY_SENT_USERNAME_TO_SERVER);
goto s_end_process;
}
else if(db_get_server_state(p) == DB_STATE_SERVER_SENT_USERNAME_REPLY && db_get_proxy_state(p) == DB_STATE_PROXY_SENT_USERNAME_TO_SERVER)
{
char buffer[DB_STRING_LENGTH+1];
if(strcmp(parsed_message_get_idtag(&pm),"+"))
{
debug("parent_process_server_message(): Couldn't understand server (%s ...)\n",parsed_message_get_idtag(&pm));
log(LOG_WARNING,"%s Server didn't continue with continuation of AUTHENTICATE command\n",db_get_record_info(p));
goto s_send_bye;
}
#if 0
base64decode(buffer,parsed_message_get_command(&pm),sizeof(buffer));
if(strcasecmp(buffer,"password"))
{
debug("parent_process_server_message(): Couldn't understand server (+ %s ...) (Should send username)\n",buffer);
log(LOG_WARNING,"Server did not sent \"Password\" in base64 as a continuation command\n");
goto s_send_bye;
}
#endif
db_set_server_state(p,DB_STATE_SERVER_SENT_PASSWORD_REPLY);
debug("[PARENT SERVER PROCESS] Server sent continuation for password\n");
if(db_get_capability_state(p) == DB_STATE_CAPABILITY_AUTH_LOGIN)
{
memset(buffer,0,sizeof(buffer));
base64encode(buffer,p->password,strlen(p->password)+1);
socket_printf(p->serversocket,"%s\r\n",buffer);
}
else
{
socket_printf(p->serversocket,"%s\r\n",p->password);
}
db_set_proxy_state(p,DB_STATE_PROXY_SENT_PASSWORD_TO_SERVER);
goto s_end_process;
}
else if(db_get_server_state(p) == DB_STATE_SERVER_SENT_PASSWORD_REPLY && db_get_proxy_state(p) == DB_STATE_PROXY_SENT_PASSWORD_TO_SERVER)
{
char *tmp;
if(!p->proxyauthtag)
{
debug("parent_process_server_message(): No proxy auth tag recorded !\n");
log(LOG_WARNING,"%s Proxy didn't send ID tag ??\n",db_get_record_info(p));
goto s_send_bye;
}
if(strcmp(parsed_message_get_idtag(&pm),p->proxyauthtag))
{
debug("parent_process_server_message(): Server response does not match recorded client auth tag ! (\"%s\" <> \"%s\")\n",parsed_message_get_idtag(&pm),
p->clientauthtag);
/* untagged response from server ?? */
log(LOG_WARNING,"%s Server returned an unknown ID tag\n",db_get_record_info(p));
goto s_send_bye;
}
free(p->proxyauthtag);
p->proxyauthtag = NULL;
/* this is a message with the response code of the authentication login */
/* possible replies : OK, NO, BAD */
tmp = parsed_message_get_command(&pm);
if(!strcasecmp(tmp,"OK"))
{
debug("[PARENT SERVER PROCESS] Server says login ok\n");
socket_printf(p->clientsocket,"%s OK\r\n",p->clientauthtag);
db_set_client_state(p,DB_STATE_CLIENT_AUTHENTICATED);
db_set_proxy_state(p,DB_STATE_PROXY_SENT_LOGIN_OK_TO_CLIENT);
db_set_server_state(p,DB_STATE_SERVER_SENT_LOGIN_OK);
db_set_record_state(p,DB_STATE_RECORD_READY_TO_FORK);
log(LOG_INFO,"%s User \"%s\" logged in successfully.\n",db_get_record_info(p),p->username);
goto s_end_process;
}
else if(!strcasecmp(tmp,"NO"))
{
debug("[PARENT SERVER PROCESS] Server says login failed\n");
socket_printf(p->clientsocket,"%s NO\r\n",p->clientauthtag);
db_set_proxy_state(p,DB_STATE_PROXY_SENT_LOGIN_FAILED_TO_CLIENT);
log(LOG_INFO,"%s User \"%s\" failed login.\n",db_get_record_info(p),p->username);
/* remove this record since it was temporary ! */
goto s_send_bye;
}
else if(!strcasecmp(tmp,"BAD"))
{
debug("[PARENTSERVER PROCESS] Server claims bad command\n");
socket_printf(p->clientsocket,"%s BAD\r\n",p->clientauthtag);
db_set_proxy_state(p,DB_STATE_PROXY_SENT_LOGIN_FAILED_TO_CLIENT);
db_set_server_state(p,DB_STATE_SERVER_SENT_GREETING);
log(LOG_CRIT,"%s Server send BAD reply to login attempt.\n",db_get_record_info(p));
/* remove this record since it was temporary ! */
goto s_send_bye;
}
debug("parent_process_server_message(): Server sent some weird reply (%s %s ...)\n",parsed_message_get_idtag(&pm),tmp);
log(LOG_WARNING,"%s Server sent something weird\n",db_get_record_info(p));
goto s_send_bye;
}
s_end_process:
delete_parsed_message(&pm);
return 0;
s_send_bye:
/*
* if we get here, it means we can close the client and server socket, and free the record
* no matter what the record state is
*/
/* provide client and server with a clean exit */
socket_printf(p->clientsocket,"* BYE " PROGNAME " " VERSION " closing connection.\r\n");
socket_printf(p->serversocket,"0 LOGOUT\r\n");
delete_parsed_message(&pm);
debug("parent error occured\n");
db_set_misc_state(p,DB_STATE_MISC_REMOVE_RECORD);
return -1;
}
/* handle new data and parse it into a message */
int parent_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(what == INDEX_FOR_SERVER)
currmessage = &(p->servermessage);
else
currmessage = &(p->clientmessage);
buf = buffer;
while(len > 0)
{
if(currmessage->literalcountdown > 0)
{
/* we are in literal mode
* meaning : append everything to the message, untill we the literalcountdown is 0
*/
if(currmessage->literalcountdown < len)
{
n = currmessage->literalcountdown;
if(!(tmpbuf = (char *)calloc(n+1,sizeof(char))))
{
debug("parent_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("parent_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)
{
parent_process_server_message(p);
db_delete_server_message(p);
}
else
{
parent_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
{
/* server sends a continuation request for each literal */
db_create_new_line_in_client_message(p);
socket_printf(p->clientsocket,"+ Ready for argument\r\n");
}
}
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 parent_handle_client_connection_new_data(DB_record *p,char *buffer,int len)
{
return parent_handle_connection_new_data(p,buffer,len,INDEX_FOR_CLIENT);
}
int parent_handle_server_connection_closed(DB_record *p)
{
/* server connection closed, close client connection if any, and remove from database */
debug("[PARENT SERVER CLOSED] <%d>\n",p->serversocket);
/* inform only when a client is connected */
if(p->clientsocket >= 0)
{
socket_printf(p->clientsocket,"* BYE\r\n");
}
db_set_misc_state(p,DB_STATE_MISC_REMOVE_RECORD);
return 0;
}
int parent_handle_server_connection_error(DB_record *p)
{
debug("parent_handle_server_connection_error(): read() error on socket %d (%m)\n",p->serversocket);
/* inform only when a client is connected */
if(p->clientsocket >= 0)
{
socket_printf(p->clientsocket,"* BYE\r\n");
}
db_set_misc_state(p,DB_STATE_MISC_REMOVE_RECORD);
return 0;
}
int parent_handle_server_connection_new_data(DB_record *p,char *buffer,int len)
{
return parent_handle_connection_new_data(p,buffer,len,INDEX_FOR_SERVER);
}
int parent_conversate(int fd)
{
char buffer[1024];
fd_set readset, writeset;
struct timeval tv;
int r = 0, rr = 0, maxfd = 0;
DB_record *p,*q;
bzero(buffer,sizeof(buffer));
FD_ZERO(&readset);
FD_ZERO(&writeset);
FD_SET(fd,&readset);
#define SELECT_TIMEOUT_VALUE 60
tv.tv_sec = SELECT_TIMEOUT_VALUE;
tv.tv_usec = 0;
maxfd = fd;
/* check if we got a reload signal */
if(rehash_flag)
{
rehash_flag = 0;
debug("Reloading configfile...\n");
if(config_file_read(option_config_file))
{
debug("Error loading configfile !\n");
exit(0);
}
}
/* add each socket from the records, to the readfdset */
for(p = DB_start; p != NULL; p = p->next)
{
if(db_get_misc_state(p) == DB_STATE_MISC_REMOVE_RECORD)
{
/* do nothing ! */
}
else
if(db_get_record_state(p) == DB_STATE_RECORD_INACTIVE)
{
/* if the server sends anything besides a close, throw it away
*/
FD_SET(p->serversocket,&readset);
if(p->serversocket > maxfd)
maxfd = p->serversocket;
}
else
/* if the session is forked, only add the *pipe* to the readset
*/
if(db_get_record_state(p) == DB_STATE_RECORD_ACTIVE)
{
/* record forked, so keep an eye on the ipc pipe */
FD_SET(p->ipcfd[0],&readset);
if(p->ipcfd[0] > maxfd)
maxfd = p->ipcfd[0];
}
else
{
/* DB_STATE_RECORD_TEMPORARY */
FD_SET(p->clientsocket,&readset);
if(p->clientsocket > maxfd)
maxfd = p->clientsocket;
/* we need to know if the nonblocking server-socket has connected successfully
* only add to writeset if serversocket is in non-connected mode ! */
if(db_get_server_connection_state(p) == DB_STATE_SERVER_CONNECTION_STARTED)
FD_SET(p->serversocket,&writeset);
if(db_get_server_connection_state(p) != DB_STATE_SERVER_NOT_CONNECTED)
{
FD_SET(p->serversocket,&readset);
if(p->serversocket > maxfd)
maxfd = p->serversocket;
}
}
}
maxfd++;
if((r = select(maxfd,&readset,&writeset,NULL,&tv)) > 0)
{
/* reset Globaltime, because select() might have been blocking for a while */
Globaltime = time(NULL);
/* new connection ? */
if(FD_ISSET(fd,&readset))
{
parent_handle_new_connection(fd);
}
p = DB_start;
/* check the status of the socket for every record */
while(p)
{
q = p->next;
/* first of all
* if we marked another record than the current, for removal
* (as is the case with a reactivation of an inactive record)
* we need to check if the old record has the remove flag set.
* if it has, then skip ahead to the remove part, and don't check it's sockets.
*/
if(db_get_misc_state(p) == DB_STATE_MISC_REMOVE_RECORD)
{
goto all_your_record_are_belong_to_us;
}
/* IMPORTANT NOTE:
* while checking the sockets, it is possible that a DB_record was removed.
* so it is important not to do anything else with the DB_record, once its connections have been checked.
*/
/* if this is a forked record's parent, only check the ipcfd */
if((db_get_record_state(p) == DB_STATE_RECORD_ACTIVE))
{
if(FD_ISSET(p->ipcfd[0],&readset))
{
int retcode = 0;
int temppid = 0;
rr = read(p->ipcfd[0],&retcode,sizeof(int));
if(rr < 0)
{
debug("parent_conversate(): Error reading from ipc socket.\n");
return -1;
}
else if(rr == 0)
{
debug("parent_conversate(): Child closed ipc socket.\n");
/* kill child ! and remove structure. */
debug("killing process ID %d because of closed connection\n",p->pid);
kill(p->pid,9);
p->pid = -1;
db_set_misc_state(p,DB_STATE_MISC_REMOVE_RECORD);
}
else
{
/* if we get here, it means that the forked process has finished.
* this means the session is done.
* so we can clean up the mess, and make the record available again
* (if the server didn't close the connection)
*/
log(LOG_INFO,"%s Session for user \"%s\" terminated.\n",db_get_record_info(p),p->username);
temppid = p->pid;
debug("cleaning up record with pid %d\n",temppid);
db_cleanup(p);
debug("killing process ID %d\n",temppid);
kill(temppid,9);
db_set_record_state(p,DB_STATE_RECORD_INACTIVE);
switch(retcode)
{
case DB_IPC_RETURNCODE_NORMAL:
debug("parent_conversate(): Child reports normal exit.\n");
break;
case DB_IPC_RETURNCODE_SERVER_CLOSED:
debug("parent_conversate(): Child reports server closed connection.\n");
close(p->serversocket);
p->serversocket = -1;
db_del(p);
break;
case DB_IPC_RETURNCODE_SERVER_ERROR:
debug("parent_conversate(): Child reports error on server connection.\n");
close(p->serversocket);
p->serversocket = -1;
db_del(p);
break;
case DB_IPC_RETURNCODE_CLIENT_CLOSED:
debug("parent_conversate(): Child reports client closed connection.\n");
break;
case DB_IPC_RETURNCODE_CLIENT_ERROR:
debug("parent_conversate(): Child reports error on client connection.\n");
break;
}
/* if we have used this record too often, remove it. */
if(p->reuse >= config_max_reuse && p->serversocket != -1)
{
log(LOG_INFO,"%s Record has been reused %d times. Removing it...\n",db_get_record_info(p),p->reuse);
socket_printf(p->serversocket,"0 LOGOUT\r\n");
close(p->serversocket);
db_del(p);
}
}
}
}
else /* don't read from client socket unless we are a temporary record */
if((db_get_record_state(p) == DB_STATE_RECORD_TEMPORARY) && FD_ISSET(p->clientsocket,&readset))
{
rr = read(p->clientsocket,buffer,sizeof(buffer)-1);
p->clientidlesince = Globaltime;
if(rr < 0)
{
parent_handle_client_connection_error(p);
}
else if(rr == 0)
{
parent_handle_client_connection_closed(p);
}
else
{
buffer[rr] = '\0';
parent_handle_client_connection_new_data(p,buffer,rr);
}
}
else /* only check if server is connected, if we started a connection, and if we are in temporary state */
if((db_get_server_connection_state(p) == DB_STATE_SERVER_CONNECTION_STARTED) &&
(db_get_record_state(p) == DB_STATE_RECORD_TEMPORARY) && FD_ISSET(p->serversocket,&writeset))
{
int sockval = 0;
int ret;
int len = sizeof(int);
ret = getsockopt(p->serversocket, SOL_SOCKET, SO_ERROR, &sockval, &len);
if(ret < 0)
{
/* panic and jump out of the window */
debug("parent_conversate(): getsockopt error (%m) CLOSE\n");
/* tell client to leave */
parent_handle_server_connection_closed(p);
db_set_misc_state(p,DB_STATE_MISC_REMOVE_RECORD);
}
else switch(sockval)
{
case 0:
/* connected */
debug("parent_conversate(): Server connected ! (client %d - server %d)\n",p->clientsocket,p->serversocket);
db_set_server_connection_state(p, DB_STATE_SERVER_CONNECTED);
break;
default:
/* not connected */
debug("parent_conversate(): Connection to server failed. CLOSE\n");
parent_handle_server_connection_closed(p);
break;
}
}
else /* only read from the server socket if it is connected, and if the record is not active */
if((db_get_server_connection_state(p) != DB_STATE_SERVER_NOT_CONNECTED) &&
(db_get_record_state(p) != DB_STATE_RECORD_ACTIVE) && FD_ISSET(p->serversocket,&readset))
{
rr = read(p->serversocket,buffer,sizeof(buffer)-1);
if(rr < 0)
{
parent_handle_server_connection_error(p);
}
else if(rr == 0)
{
parent_handle_server_connection_closed(p);
}
else
{
buffer[rr] = '\0';
parent_handle_server_connection_new_data(p,buffer,rr);
}
}
/* if client is authenticated, fork here */
if( db_get_record_state(p) == DB_STATE_RECORD_READY_TO_FORK &&
db_get_misc_state(p) != DB_STATE_MISC_REMOVE_RECORD &&
db_get_client_state(p) == DB_STATE_CLIENT_AUTHENTICATED)
{
rr = parent_fork_record(p,fd);
if(rr <= 0)
return rr;
}
all_your_record_are_belong_to_us:
/* if record was flagged for removal, then remove it ! */
if(db_get_misc_state(p) == DB_STATE_MISC_REMOVE_RECORD)
{
if(p->pid > 0)
{
debug("parent_conversate(): while removing record, child still exists ? pid = %d\n",p->pid);
}
db_del(p);
}
p = q;
}
}
else if(!r)
{
debug("parent_conversate(): select() timed out. (%d seconds without activity)\n",SELECT_TIMEOUT_VALUE);
return 1;
}
else
{
if(errno != EINTR)
{
debug("parent_conversate(): select() error (%m)\n");
return -1;
}
}
return -1;
}
/* this function checks for idle connections
* and acts upon them
*
* we close idle client connections
*
* we send NOOP's over inactive serverconnections for some time
* we close idle server connections after some other time
*/
int parent_handle_idlers()
{
DB_record *p,*q;
p = DB_start;
while(p)
{
q = p->next;
if(db_get_record_state(p) == DB_STATE_RECORD_INACTIVE)
{
if(p->serveridlesince > 0 && (Globaltime - p->serveridlesince > config_server_timeout))
{
debug("parent_handle_idlers(): Server %d has been inactive for too long (%d s)... closing.\n",p->serversocket,Globaltime - p->serveridlesince);
/* connection was open too long, gracefully close it */
if(p->serversocket >= 0)
{
socket_printf(p->serversocket,"0 LOGOUT\r\n");
}
db_del(p);
}
else
if(p->serverlastnoop > 0 && (Globaltime - p->serverlastnoop > config_keepalive))
{
/* it's been too long since we last sent a NOOP
*/
debug("parent_handle_idlers(): Sending NOOP to server %d.\n",p->serversocket);
socket_printf(p->serversocket,"0 NOOP\r\n");
p->serverlastnoop = Globaltime;
}
}
else if(db_get_record_state(p) == DB_STATE_RECORD_TEMPORARY)
{
if(p->clientidlesince > 0 && (Globaltime - p->clientidlesince > config_client_timeout))
{
/* client is just sitting there, remove it */
debug("parent_handle_idlers(): Client %d idle for too long: closing.\n",p->clientsocket);
if(p->clientsocket >= 0)
{
socket_printf(p->clientsocket,"* BYE Autologout after %d seconds.\r\n",config_client_timeout);
}
db_del(p);
}
}
p = q;
}
return 0;
}
syntax highlighted by Code2HTML, v. 0.9.1