/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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; }