/****************************************************************************** * $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 ******************************************************************************/ #include #include #include #include #include #include #if defined(BEOS) # include #endif #ifdef __sun # include /* 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 } }