/******************************************************************************
* $Id: popserver.c,v 1.15 2006/01/15 19:43:13 gareuselesinge Exp $
* This file is part of liberopops (http://liberopops.sf.net) *
* This file is distributed under the terms of GNU GPL license. *
******************************************************************************/
/******************************************************************************
* File description:
* Implements the pop3 protocol interaction with the client,
* delegates actions to engine.c
* Notes:
*
* Authors:
* Enrico Tassi <gareuselesinge@users.sourceforge.net>
******************************************************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/types.h>
#include <unistd.h>
#if defined(BEOS)
# include <bsd_mem.h>
#endif
#ifdef __sun
# include <strings.h> /* for char* index() */
#endif
#include "socketcommon.h"
#include "popserver.h"
#include "popstate.h"
#include "threads.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "log.h"
#define LOG_ZONE "POPSERVER"
#define HIDDEN static
#define MIN(a,b) (((a)<(b))?(a):(b))
/***********************************************
* rfc 1939 strings and limits
*
*/
#define GUESSED_MAX_LINE_LEN 1024
#define RFC_1939_MAXLINELEN GUESSED_MAX_LINE_LEN
#define RFC_1939_OK "+OK"
#define RFC_1939_ERR "-ERR"
#define RFC_1939_QUIT "QUIT"
#define RFC_1939_STAT "STAT"
#define RFC_1939_LIST "LIST"
#define RFC_1939_RETR "RETR"
#define RFC_1939_DELE "DELE"
#define RFC_1939_NOOP "NOOP"
#define RFC_1939_RSET "RSET"
#define RFC_1939_TOP "TOP"
#define RFC_1939_UIDL "UIDL"
#define RFC_1939_USER "USER"
#define RFC_1939_PASS "PASS"
#define RFC_1734_AUTH "AUTH"
#define RFC_2449_CAPA "CAPA"
#define RFC_NETSCAPE_XSENDER "XSENDER"
#define RFC_DEPRECATED_LAST "LAST"
/*** local helper functions ***************************************************/
#define MAX_ERRORS_ACCEPTED 10
/************************************************
* Usefull macros and data structures
*
*/
#define P(a...) snprintf(ans,RFC_1939_MAXLINELEN,a)
struct triplet_t {void*s;void*f;void*p;};
/***********************************************
* Case insensitive strcmp
*
*/
HIDDEN int matches_bad_client(char* rfc_command,char* received_string)
{
int rc;
char lowercase_command[strlen(rfc_command)+1];
char lowercase_string[strlen(received_string)+1];
//try case
for(rc=0;rfc_command[rc] != '\0';rc++)
lowercase_command[rc] = (char)tolower((int)rfc_command[rc]);
for(rc=0;received_string[rc] != '\0';rc++)
lowercase_string[rc] = (char)tolower((int)received_string[rc]);
rc = strncmp(lowercase_command,lowercase_string,strlen(rfc_command));
if(rc == 0)
return 1;
return 0;
}
/***********************************************
* strcmp with case insensitive fallback
*
*/
HIDDEN int matches(char* rfc_command,char* received_string)
{
int rc;
//DBG("comparing '%s' '%s'\n",rfc_command,received_string);
//try strcmp
rc = strncmp(rfc_command,received_string,strlen(rfc_command));
if(rc == 0)
return 1;
return matches_bad_client(rfc_command,received_string);
}
/***********************************************
* extracts the n^th parameter from src and puts
* it in dest trucating to maxlen chars
*
*/
HIDDEN int extract_param(int n,char* src,char* dest,int maxlen)
{
char* start;
for(;n>0;n--)
{
start=index(src,' ');
if (start == NULL)
return 1;
src=start+1;
}
//now start is the end
start=index(src,' ');
if (start==NULL) start=&src[strlen(src)];
snprintf(dest,maxlen,"%s",src);
dest[MIN(start-src,maxlen-1)]='\0';
return 0;
}
/************************************************
* The logging function of all traffic on sockets
*
*/
HIDDEN void debug(char* c)
{
unsigned int pid;
if(c == NULL)
return;
#if !(defined(WIN32) && !defined(CYGWIN))
pid=getpid();
#else
pid=GetCurrentThreadId();
#endif
if(matches(SOCK_RECEIVED RFC_1939_PASS,c))
{
DBG("[%d] %s%s%s\n",pid,SOCK_RECEIVED,RFC_1939_PASS," *********");
}
else {
char * fmt = "[%d] %s\n";
if (matches(SOCK_INFO "A previous error occurred",c))
SAY(fmt,pid,c);
if (matches(SOCK_ERROR "Error calling \"recvstring_with_timeout",c))
{
ERROR_PRINT(fmt,pid,"Probably the client disconnected.");
ERROR_PRINT(fmt,pid,"Try increasing the client timeout.\n");
}
DBG("[%d] %s",pid,c);
}
}
/*** Pop3 implementation ******************************************************/
/***********************************************
* Pop3 states
*
*/
enum states_e {
POPSTATE_AUTH ,
POPSTATE_TRANS,
POPSTATE_ERR,
POPSTATE_END ,
POPSTATE_LAST
};
/***********************************************
* Pop3 errors
*
*/
char * POPSERVER_ERR_MSG[]={
"SYNTAX ERROR",
"NETWORK ERROR",
"AUTH FAILED",
"INTERNAL ERROR",
"NO SUCH MESSAGE",
"MAILBOX LOCKED",
"INTERNAL: END OF STREAM",
"DELAY TIME NOT EXPIRED, RETRY LATER",
"UNKNOWN ERROR, PLEASE FIX"};
enum states_e POPSERVER_ERR_STA[]={
POPSTATE_LAST,
POPSTATE_ERR,
POPSTATE_ERR,
POPSTATE_ERR,
POPSTATE_LAST,
POPSTATE_ERR,
POPSTATE_ERR,
POPSTATE_ERR,
POPSTATE_ERR};
int POPSERVER_ERR_NUM=POPSERVER_ERR_UNKNOWN;
/***********************************************
* sends a simple 1-line answer
*
*/
HIDDEN enum states_e send_result_simple(struct sock_state_t *s,int rc,
char* err_comment[],enum states_e err_next[],int numerr,
char* suc_comment,enum states_e suc_next)
{
char ans[RFC_1939_MAXLINELEN];
if(rc != 0)
{
P("%s %s",RFC_1939_ERR,err_comment[MIN(rc-1,numerr-1)]);
sock_send(s,ans);
SAY("%s\n",err_comment[MIN(rc-1,numerr-1)]);
return err_next[rc-1];
}
else
{
P("%s %s",RFC_1939_OK,suc_comment);
sock_send(s,ans);
return suc_next;
}
}
/***********************************************
* sends a multi-line answer
*
*/
HIDDEN enum states_e send_result_multiline(struct sock_state_t *s,int rc,
char* err_comment[],enum states_e err_next[],int numerr,
char* buff,enum states_e suc_next)
{
char ans[RFC_1939_MAXLINELEN];
if(rc != 0)
{
P("%s %s",RFC_1939_ERR,err_comment[MIN(rc-1,numerr-1)]);
sock_send(s,ans);
SAY("%s",err_comment[MIN(rc-1,numerr-1)]);
return err_next[rc-1];
}
else
{
P("%s ANSWER FOLLOW",RFC_1939_OK);
sock_send(s,ans);
if(*buff != '\0')
sock_send(s,buff);
P(".");
sock_send(s,ans);
return suc_next;
}
}
/*********************************************************
* marshaller for retr
*/
int marshaller_retr(struct popstate_t*p,struct popserver_functions_t *f,
struct sock_state_t *s, int msg,int lines)
{
return f->retr(p,msg,s);
}
/*********************************************************
* marshaller for top
*/
int marshaller_top(struct popstate_t*p,struct popserver_functions_t *f,
struct sock_state_t *s, int msg,int lines)
{
return f->top(p,msg,lines,s);
}
/*********************************************************
* sends an answer in a small pieces, all getted trough f
* that is called untinl returns != 0
*
*/
HIDDEN enum states_e send_result_callback(
struct sock_state_t *s,
struct popstate_t*p,
struct popserver_functions_t *f,
int msg,
int lines,
int (*marshaller)
(struct popstate_t*p,struct popserver_functions_t *f,
struct sock_state_t *s, int msg,int lines),
char* err_comment[],
enum states_e err_next[],
int numerr,
enum states_e suc_next)
{
char ans[RFC_1939_MAXLINELEN];
int rc = 0;
P("%s ANSWER FOLLOW",RFC_1939_OK);
sock_send(s,ans);
if(sock_error_occurred(s))
{
rc = POPSERVER_ERR_NETWORK;
return err_next[MIN(rc-1,numerr-1)];
}
rc = marshaller(p,f,s,msg,lines);
if(rc != POPSERVER_ERR_OK && rc != POPSERVER_ERR_EOF)
{
P(".");
sock_send(s,ans);
return err_next[MIN(rc-1,numerr-1)];
}
P(".");
sock_send(s,ans);
if(sock_error_occurred(s))
{
rc = POPSERVER_ERR_NETWORK;
return err_next[MIN(rc-1,numerr-1)];
}
return suc_next;
}
int popserver_callback(const char* buffer, void* popserver_data)
{
struct sock_state_t *s = (struct sock_state_t *) popserver_data;
if(buffer != NULL)
{
sock_sendraw(s,buffer);
}
if(sock_error_occurred(s))
{
return POPSERVER_ERR_NETWORK;
}
return POPSERVER_ERR_OK;
}
/***********************************************
* sends a simple 1-line answer
* use this for unsupported commands
*
*/
HIDDEN enum states_e send_unsupported(struct sock_state_t *s,
char* err_comment,enum states_e err_next)
{
char * TMP_ERR_MSG[]={err_comment};
enum states_e TMP_ERR_STA[]={err_next};
int TMP_ERR_NUM=1;
return send_result_simple(s,1,TMP_ERR_MSG,TMP_ERR_STA,TMP_ERR_NUM,"",POPSTATE_ERR);
}
/***********************************************
* sends a simple 1-line answer
* use this for wrong syntax
*
*/
HIDDEN enum states_e send_wrong_syntax(struct sock_state_t *s,char* ask)
{
char ans[RFC_1939_MAXLINELEN];
P("%s WRONG SYNTAX '%s'",RFC_1939_ERR,ask);
sock_send(s,ans);
SAY("WRONG SYNTAX '%s'",ask);
return POPSTATE_ERR;
}
/***********************************************
* Pop3 POPSTATE_AUTH state implementation
*
* Accepts:
* QUIT, USER, PASS, CAPA, AUTH
*
* Notes:
* The server accepts a PASS command without a USER before,
* this is not specifyed in the rfc, but is boaring to fix this
*/
HIDDEN enum states_e pop3_POPSTATE_AUTH(struct sock_state_t *s,
struct popserver_functions_t* f,
struct popstate_t* p,
int* error_counter) // this is to kik bad clients
{
char ask[RFC_1939_MAXLINELEN];
enum states_e next;
int rc=0;
sock_receive_with_timeout(s,ask,RFC_1939_MAXLINELEN,POPSERVER_NOOP_TIMEOUT);
if(sock_error_occurred(s))
return POPSTATE_AUTH;
if(matches(RFC_1939_QUIT,ask)) /*** QUIT *********************/
{
rc=f->quit(p);
next=send_result_simple(s,rc,
POPSERVER_ERR_MSG,POPSERVER_ERR_STA,POPSERVER_ERR_NUM,
"BYE BYE",POPSTATE_END);
}
else if (matches(RFC_1939_PASS,ask)) /*** PASS *********************/
{
char param[RFC_1939_MAXLINELEN];
rc=extract_param(1,ask,param,RFC_1939_MAXLINELEN);
if(rc != 0)
{
next=send_wrong_syntax(s,ask);
}
else
{
rc=f->pass(p,param);
next=send_result_simple(s,rc,
POPSERVER_ERR_MSG,POPSERVER_ERR_STA,POPSERVER_ERR_NUM,
"ACCESS ALLOWED",POPSTATE_TRANS);
}
}
else if (matches(RFC_1939_USER,ask)) /*** USER *********************/
{
char param[RFC_1939_MAXLINELEN];
rc=extract_param(1,ask,param,RFC_1939_MAXLINELEN);
if(rc != 0)
{
next=send_wrong_syntax(s,ask);
}
else
{
rc=f->user(p,param);
next=send_result_simple(s,rc,
POPSERVER_ERR_MSG,POPSERVER_ERR_STA,POPSERVER_ERR_NUM,
"PLEASE ENTER PASSWORD",POPSTATE_AUTH);
}
}
else if(matches(RFC_1734_AUTH,ask)) /*** AUTH *********************/
{
next=send_unsupported(s,"ONLY PASS IS SUPPORTED",POPSTATE_AUTH);
}
else if(matches(RFC_2449_CAPA,ask)) /*** CAPA *********************/
{
next=send_result_multiline(s,0,
POPSERVER_ERR_MSG,POPSERVER_ERR_STA,POPSERVER_ERR_NUM,
"TOP\r\nUSER\r\nUIDL",POPSTATE_AUTH);
}
else
{
if (*error_counter < MAX_ERRORS_ACCEPTED)
{
next=send_unsupported(s,
"WRONG/UNKNOWN COMMAND IN AUTHORIZATION STATE",
POPSTATE_LAST);
(*error_counter)++;
}
else
{
next=send_unsupported(s,
"WRONG/UNKNOWN COMMAND IN AUTHORIZATION STATE",
POPSTATE_ERR);
}
}
return next;
}
/***********************************************
* Pop3 POPSTATE_TRANS state implementation
*
* Accepts:
* STAT,LIST,UIDL,RETR,TOP,QUIT,NOOP,RSET,DELE,XSPOPSTATE_ENDER,LAST,CAPA
*
* Notes:
*/
HIDDEN enum states_e pop3_POPSTATE_TRANS(struct sock_state_t *s,
struct popserver_functions_t* f,
struct popstate_t* p,
int* error_counter) // this is to kik bad clients
{
char ask[RFC_1939_MAXLINELEN];
//char ans[RFC_1939_MAXLINELEN];
enum states_e next=POPSTATE_ERR;
int rc=0;
sock_receive_with_timeout(s,ask,RFC_1939_MAXLINELEN,POPSERVER_NOOP_TIMEOUT);
if(sock_error_occurred(s))
return POPSTATE_TRANS;
if(matches(RFC_1939_STAT,ask)) /*** STAT *********************/
{
int num=0,size=0;
char ans[RFC_1939_MAXLINELEN];
rc=f->stat(p,&num,&size);
P("%d %d",num,size);
next=send_result_simple(s,rc,
POPSERVER_ERR_MSG,POPSERVER_ERR_STA,POPSERVER_ERR_NUM,
ans,POPSTATE_TRANS);
}
else if(matches(RFC_1939_LIST,ask)) /*** LIST *********************/
{
char param[RFC_1939_MAXLINELEN];
char * buffer=NULL;
rc=extract_param(1,ask,param,RFC_1939_MAXLINELEN);
if(rc == 0)
{
//one size
int num=strtol(param,NULL,10);
rc=f->list(p,num,&buffer);
next=send_result_simple(s,rc,
POPSERVER_ERR_MSG,POPSERVER_ERR_STA,POPSERVER_ERR_NUM,
buffer,POPSTATE_TRANS);
free(buffer);
}
else
{
//all sizes
rc=f->list_all(p,&buffer);
next=send_result_multiline(s,rc,
POPSERVER_ERR_MSG,POPSERVER_ERR_STA,POPSERVER_ERR_NUM,
buffer,POPSTATE_TRANS);
free(buffer);
}
}
else if(matches(RFC_1939_UIDL,ask)) /*** UIDL *********************/
{
char param[RFC_1939_MAXLINELEN];
char * buffer=NULL;
rc=extract_param(1,ask,param,RFC_1939_MAXLINELEN);
if(rc == 0)
{
//one uidl
int num=strtol(param,NULL,10);
rc=f->uidl(p,num,&buffer);
next=send_result_simple(s,rc,
POPSERVER_ERR_MSG,POPSERVER_ERR_STA,POPSERVER_ERR_NUM,
buffer,POPSTATE_TRANS);
free(buffer);
}
else
{
//all uidl
rc=f->uidl_all(p,&buffer);
next=send_result_multiline(s,rc,
POPSERVER_ERR_MSG,POPSERVER_ERR_STA,POPSERVER_ERR_NUM, buffer,POPSTATE_TRANS);
free(buffer);
}
}
else if(matches(RFC_1939_RETR,ask)) /*** RETR *********************/
{
char param[RFC_1939_MAXLINELEN];
rc=extract_param(1,ask,param,RFC_1939_MAXLINELEN);
if(rc != 0)
{
next=send_wrong_syntax(s,ask);
}
else
{
int num;
num=strtol(param,NULL,10);
next=send_result_callback(s,p,f,num,0,marshaller_retr,
POPSERVER_ERR_MSG,POPSERVER_ERR_STA,POPSERVER_ERR_NUM,
POPSTATE_TRANS);
}
}
else if(matches(RFC_1939_TOP,ask)) /*** TOP *********************/
{
char param[RFC_1939_MAXLINELEN];
char param1[RFC_1939_MAXLINELEN];
int rc1;
rc=extract_param(1,ask,param,RFC_1939_MAXLINELEN);
if(rc != 0)
{
next=send_wrong_syntax(s,ask);
}
rc1=extract_param(2,ask,param1,RFC_1939_MAXLINELEN);
if(rc1 != 0)
{
next=send_wrong_syntax(s,ask);
}
if(rc1 == 0 && rc == 0)
{
long int num;
long int lines;
num=strtol(param,NULL,10);
lines=strtol(param1,NULL,10);
next=send_result_callback(s,p,f,num,lines,marshaller_top,
POPSERVER_ERR_MSG,POPSERVER_ERR_STA,POPSERVER_ERR_NUM,
POPSTATE_TRANS);
}
}
else if(matches(RFC_1939_QUIT,ask)) /*** QUIT *********************/
{
rc=f->quit_update(p);
next=send_result_simple(s,rc,
POPSERVER_ERR_MSG,POPSERVER_ERR_STA,POPSERVER_ERR_NUM,
"BYE BYE, UPDATING",POPSTATE_END);
}
else if(matches(RFC_1939_NOOP,ask)) /*** NOOP *********************/
{
rc = f->noop(p);
next=send_result_simple(s,rc,
POPSERVER_ERR_MSG,POPSERVER_ERR_STA,POPSERVER_ERR_NUM,
"I'LL WAIT",POPSTATE_TRANS);
}
else if(matches(RFC_1939_RSET,ask)) /*** RSET *********************/
{
rc = f->rset(p);
next=send_result_simple(s,rc,
POPSERVER_ERR_MSG,POPSERVER_ERR_STA,POPSERVER_ERR_NUM,
"MAILBOX RESTORED",POPSTATE_TRANS);
}
else if(matches(RFC_NETSCAPE_XSENDER,ask)) /*** XSENDER *********************/
{
next=send_unsupported(s,
"XSENDER NOT SUPPORTED",POPSTATE_TRANS);
}
else if(matches(RFC_DEPRECATED_LAST,ask)) /*** LAST **************************/
{
next=send_unsupported(s,
"LAST NOT SUPPORTED",POPSTATE_TRANS);
}
else if(matches(RFC_2449_CAPA,ask)) /*** CAPA *********************/
{
next=send_result_multiline(s,0,
POPSERVER_ERR_MSG,POPSERVER_ERR_STA,POPSERVER_ERR_NUM,
"TOP\r\nUSER\r\nUIDL",POPSTATE_TRANS);
}
else if(matches(RFC_1939_DELE,ask)) /*** DELE *********************/
{
char param[RFC_1939_MAXLINELEN];
rc=extract_param(1,ask,param,RFC_1939_MAXLINELEN);
if(rc != 0)
{
next=send_wrong_syntax(s,ask);
}
else
{
int num;
num=strtol(param,NULL,10);
rc=f->dele(p,num);
next=send_result_simple(s,rc,
POPSERVER_ERR_MSG,POPSERVER_ERR_STA,POPSERVER_ERR_NUM,
"MESSAGE MARKED FOR DELETION",POPSTATE_TRANS);
}
}
else
{
if (*error_counter < MAX_ERRORS_ACCEPTED)
{
next=send_unsupported(s,
"WRONG/UNKNOWN COMMAND IN TRANSACTION STATE",
POPSTATE_LAST);
(*error_counter)++;
}
else
{
next=send_unsupported(s,
"WRONG/UNKNOWN COMMAND IN TRANSACTION STATE",
POPSTATE_ERR);
}
}
return next;
}
/***********************************************
* The pop3 server is here
*
*/
HIDDEN void pop3_thread(void *data)
{
char ans[RFC_1939_MAXLINELEN];
enum states_e state=POPSTATE_AUTH,last;
struct sock_state_t* s = (struct sock_state_t*)((struct triplet_t*)data)->s;
struct popserver_functions_t* f = (struct popserver_functions_t*)
((struct triplet_t*)data)->f;
struct popstate_t* p = (struct popstate_t* )((struct triplet_t*)data)->p;
int stop=0;
int error_counter=0;
free(data);//malloc called by socketcommon
P("%s %s/%s pop3 server ready",RFC_1939_OK,PROGRAMNAME,VERSION);
sock_send(s,ans);
while(!stop)
{
if (sock_error_occurred(s))
{
if(state != POPSTATE_END)
f->quit(p);
sock_disconnect(s);
thread_die(pthread_self());
break;
}
last = state;
switch(state)
{
case POPSTATE_AUTH:
state = pop3_POPSTATE_AUTH(s,f,p,&error_counter);
break;
case POPSTATE_TRANS:
state = pop3_POPSTATE_TRANS(s,f,p,&error_counter);
break;
case POPSTATE_ERR:
f->quit(p); // to inform
sock_disconnect(s);
thread_die(pthread_self());
stop=1;
break;
case POPSTATE_END:
DBG("NORMAL EXIT\n");
sock_disconnect(s);
thread_die(pthread_self());
stop=1;
break;
case POPSTATE_LAST:
ERROR_ABORT("internal");
break;
}
if (state == POPSTATE_LAST)
state = last;
}
delete_popstate_t(p,f->delete_other);
if(!stop)
{
DBG("a network error occurred, this thread will die\n");
}
}
/*** external function implementation *****************************************/
void popserver_start(struct popserver_functions_t* f,
struct in_addr address,unsigned short port, int maxthreads,
int (*set_rights)(uid_t,gid_t),uid_t uid,gid_t gid)
{
struct sock_state_t* s,*new;
struct popstate_t* p;
struct triplet_t *data;
pthread_t *pth;
pthread_attr_t *att;
int rc;
thread_init(maxthreads);
s = sock_bind(address,port,RFC_1939_MAXLINELEN,debug);
if(s == NULL)
{
ERROR_ABORT("Unable to bind\n");
}
if(set_rights != NULL)
{
if( set_rights(uid,gid) != 0)
ERROR_ABORT("Unable to set_rights\n");;
}
while(1)
{
new=sock_listen(s);
thread_clean(); //clean dead threads
thread_get_free(&pth,&att); //get a free thread
if(pth == NULL)
{
sock_disconnect(new);
thread_notborn(pth);
SAY("unable to handle connection, no more threads",s);
continue;
}
p = new_popstate_t();
//create the data to pass to pop3 thread
data = malloc(sizeof(struct triplet_t)); //free called by pop3_thread
MALLOC_CHECK(data);
data->f=f;
data->p=p;
data->s=new;
rc = pthread_create(pth,att,
(void *(*)(void *))pop3_thread,data);
if(rc != 0)
{
free(data);
ERROR_ABORT("pthread_create failed\n");
}
#ifdef WIN32
//FIXME
// richiama il log_rotate a run time
if((char*)log_get_logfile()!=NULL)
log_rotate((char*)log_get_logfile());
#endif
}
}
syntax highlighted by Code2HTML, v. 0.9.1