/* * 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 extern int Globaltime; DB_record *DB_start = NULL; DB_record *DB_end = NULL; /* add a record to the linked list. * The record is copied. * DB_start and DB_end are updated. * the prev and next field in the new record are changed * the message structured are presumed empty */ int db_add(DB_record *rec) { DB_record *p; if(!(p = (DB_record *)malloc(sizeof(DB_record)))) { debug("db_add(): Could not malloc.\n"); return -1; } memcpy(p,rec,sizeof(DB_record)); if(DB_end) DB_end->next = p; p->prev = DB_end; p->next = NULL; if(!DB_end) DB_start = p; DB_end = p; return 0; } /* this function cleans up a record * it removes everything, except the parts necessary for keeping alive the connection * * what it doesn't remove : * username * password * server socket * status * next and prev pointers */ int db_cleanup(DB_record *rec) { /* keep : username/password * server socket * status * next and prev pointers */ /* close client socket */ close(rec->clientsocket); rec->clientsocket = -1; /* remove pid */ rec->pid = -1; /* authenticate tags */ if(rec->clientauthtag) { free(rec->clientauthtag); rec->clientauthtag = NULL; } if(rec->proxyauthtag) { free(rec->proxyauthtag); rec->proxyauthtag = NULL; } /* remove messages */ db_delete_client_message(rec); db_delete_server_message(rec); /* close pipe */ close(rec->ipcfd[0]); close(rec->ipcfd[1]); rec->ipcfd[0] = -1; rec->ipcfd[1] = -1; rec->serverlastnoop = Globaltime; rec->clientidlesince = Globaltime; rec->serveridlesince = Globaltime; rec->clientlocaladdress.s_addr = 0; rec->clientlocalport = 0; return 0; } /* remove a record from the linked list * the record is freed * all resources are freed * DB_start and DB_end are updated */ int db_del(DB_record *rec) { DB_record *p,*n; p = rec->prev; n = rec->next; if(p) p->next = n; else DB_start = n; if(n) n->prev = p; else DB_end = p; db_cleanup(rec); /* don't leave password lying around */ memset(rec->password,0,DB_STRING_LENGTH); close(rec->serversocket); free(rec); return 0; } /* initialize a record */ int db_init_record(DB_record *rec) { memset(rec,0,sizeof(DB_record)); db_set_server_connection_state(rec,DB_STATE_SERVER_NOT_CONNECTED); db_set_client_state(rec,DB_STATE_CLIENT_NOT_AUTHENTICATED); db_set_server_state(rec,DB_STATE_SERVER_NONE); db_set_proxy_state(rec,DB_STATE_PROXY_NONE); db_set_misc_state(rec,DB_STATE_MISC_NONE); db_set_record_state(rec,DB_STATE_RECORD_NONE); db_set_capability_state(rec,DB_STATE_CAPABILITY_NONE); rec->clientsocket = -1; rec->serversocket = -1; rec->ipcfd[0] = -1; rec->ipcfd[1] = -1; rec->pid = -1; /* set these to 1, so we know there is no command in progress */ rec->clientmessage.completed = 1; rec->servermessage.completed = 1; /* no messages yet, so they are unchecked. */ rec->clientmessage.status = DB_MESSAGE_UNCHECKED; rec->servermessage.status = DB_MESSAGE_UNCHECKED; rec->clientidlesince = Globaltime; rec->serverlastnoop = Globaltime; rec->serveridlesince = Globaltime; rec->clientlocaladdress.s_addr = 0; rec->clientlocalport = 0; rec->proxylocalport = 0; rec->reuse = 0; return 0; } #define INDEX_FOR_SERVER 0 #define INDEX_FOR_CLIENT 1 /* when in process mode or unchecked mode : * this function appends a string to the current line in the message (client or server) * when in passthrough mode : * this function sends the received data to the destination, without appending it * * the function checks if the command is LOGOUT or not * if it is LOGOUT, the message is switched to process mode, and the message is interpreted * otherwise, we just send away the message * * ALL OF THIS IS ONLY DONE IN THE CHILD !!! * when in the parent, the message is just processed. (since we only do authentication there, and don't expect big messages) * */ int db_append_to_message(DB_record *rec,char *buf,int what) { struct message_t *currmessage = NULL; int newsize = 0; char *currbuf = NULL; char *newbuf = NULL; int outfd = -1,i = 0; Split_string ss; /* decide which message we want to append to */ if(what == INDEX_FOR_SERVER) { currmessage = &(rec->servermessage); outfd = rec->clientsocket; } else { currmessage = &(rec->clientmessage); outfd = rec->serversocket; } /* message is not complete */ currmessage->completed = 0; /* only care about the state of the message if we are in a forked child */ if(!forkedchild) goto db_process; /* what mode are we in ? */ switch(currmessage->status) { case DB_MESSAGE_UNCHECKED: //debug("db_append_to_buffer(%d): We are in unchecked mode ! (%s)\n",what,buf); init_split_string(&ss); if(!currmessage->count) { make_split_string_strip_crlf(buf,&ss," "); } else { /* something is in the first slot of the message. * check if it contains our command * if it doesn't don't check the buffer ! * just let the buffer be appended to the message, and check the first line * on the next append */ make_split_string(currmessage->lines[0],&ss," "); } if(split_string_count_parts(&ss) > 1) { if(!strcasecmp(split_string_get(&ss,1),"LOGOUT" )) { debug("db_append_to_buffer(%d): LOGOUT received !!! going to process mode\n",what); currmessage->status = DB_MESSAGE_PROCESS; } else { currmessage->status = DB_MESSAGE_PASSTHROUGH; /* if any previous lines, send them */ /* there should only be 1 (one) line, otherwise there is a problem */ for(i = 0;i < currmessage->count;i++) { socket_printf(outfd,"%s",currmessage->lines[i]); } if(currmessage->count > 1) { debug("db_append_to_buffer(%d): There is more than one previous line ! this cannot happen ?\n"); } /* send current buffer */ socket_printf(outfd,"%s",buf); /* we're in passthrough mode, and everything sent so far, was passed on * we can go home now */ delete_split_string(&ss); return 0; } } else { /* message is not complete so we'll check again in the next append ! */ } delete_split_string(&ss); break; case DB_MESSAGE_PASSTHROUGH: //debug("db_append_to_buffer(%d): We are in passthrough mode !\n",what); /* send current buffer */ socket_printf(outfd,"%s",buf); return 0; break; case DB_MESSAGE_PROCESS: //debug("db_append_to_buffer(%d): We are in process mode !\n",what); break; } db_process: if(!currmessage->count) { if(db_create_new_line_in_message(rec,what) < 0) { debug("db_append_to_buffer(): db_create_new_line_in_message() returned error !\n"); return -1; } } currbuf = currmessage->lines[currmessage->count - 1]; /* calculate new length */ newsize = strlen(buf); if(currbuf) newsize += strlen(currbuf); newsize++; // \0 /* allocate space */ if(!(newbuf = (char *)calloc(1,newsize))) { debug("db_append_to_buffer(): Out of memory.\n"); return -1; } /* copy and append */ if(currbuf) strcpy(newbuf,currbuf); strcat(newbuf,buf); /* free */ if(currbuf) free(currbuf); /* assign */ currmessage->lines[currmessage->count - 1] = newbuf; return 0; } /* create a new line in the message = allocate an extra pointer for it */ int db_create_new_line_in_message(DB_record *rec,int what) { struct message_t *currmessage = NULL; if(what == INDEX_FOR_SERVER) currmessage = &(rec->servermessage); else currmessage = &(rec->clientmessage); currmessage->completed = 0; currmessage->count++; if(!(currmessage->lines = (char **)realloc(currmessage->lines, sizeof(char *) * currmessage->count))) { debug("db_create_new_line_in_message(): Out of memory.\n"); return -1; } currmessage->lines[currmessage->count - 1] = NULL; return 0; } /* delete a message and release the allocated space */ int db_delete_message(DB_record *rec, int what) { struct message_t *x; int i; if(what == INDEX_FOR_SERVER) x = &(rec->servermessage); else x = &(rec->clientmessage); /* clear the messages, if any */ if(x->count != 0) { for(i = 0;i < x->count; i++) free(x->lines[i]); free(x->lines); } x->count = 0; x->completed = 1; x->lines = NULL; x->status = DB_MESSAGE_UNCHECKED; return 0; } int db_append_to_client_message(DB_record *rec,char *buf) { return db_append_to_message(rec,buf,INDEX_FOR_CLIENT); } int db_append_to_server_message(DB_record *rec,char *buf) { return db_append_to_message(rec,buf,INDEX_FOR_SERVER); } int db_delete_client_message(DB_record *rec) { return db_delete_message(rec,INDEX_FOR_CLIENT); } int db_delete_server_message(DB_record *rec) { return db_delete_message(rec,INDEX_FOR_SERVER); } int db_create_new_line_in_client_message(DB_record *rec) { return db_create_new_line_in_message(rec,INDEX_FOR_CLIENT); } int db_create_new_line_in_server_message(DB_record *rec) { return db_create_new_line_in_message(rec,INDEX_FOR_SERVER); } /* return the i'th line of a message * or NULL if i is out of range */ char *db_get_message(DB_record *rec, int i,int what) { struct message_t *currmessage = NULL; if(what == INDEX_FOR_SERVER) currmessage = &(rec->servermessage); else currmessage = &(rec->clientmessage); if(i < 0 || i >= currmessage->count) return NULL; return currmessage->lines[i]; } char *db_get_server_message(DB_record *rec,int i) { return db_get_message(rec,i,INDEX_FOR_SERVER); } char *db_get_client_message(DB_record *rec,int i) { return db_get_message(rec,i,INDEX_FOR_CLIENT); } /* return a specific field from the status field * see the headers for more information */ int db_get_state(DB_record *p, int mask) { if(!(p->status & mask)) debug("db_get_state(): state vs. mask mismatch ! %0.8p %0.8p\n",p->status,mask); return p->status & mask; } /* set a specific field in the status field */ void db_set_state(DB_record *p,int s,int mask) { if(!(mask & s)) debug("db_set_state(): state vs. mask mismatch ! %0.8p %0.8p\n",s,mask); p->status = ((mask ^ 0xffffffff) & p->status) | s; } int db_get_server_connection_state(DB_record *p) { return db_get_state(p,DB_STATE_SERVER_CONNECTION_MASK); } void db_set_server_connection_state(DB_record *p,int s) { db_set_state(p,s,DB_STATE_SERVER_CONNECTION_MASK); } int db_get_client_state(DB_record *p) { return db_get_state(p,DB_STATE_CLIENT_MASK); } void db_set_client_state(DB_record *p,int s) { db_set_state(p,s,DB_STATE_CLIENT_MASK); } int db_get_server_state(DB_record *p) { return db_get_state(p,DB_STATE_SERVER_MASK); } void db_set_server_state(DB_record *p,int s) { db_set_state(p,s,DB_STATE_SERVER_MASK); } int db_get_proxy_state(DB_record *p) { return db_get_state(p,DB_STATE_PROXY_MASK); } void db_set_proxy_state(DB_record *p,int s) { db_set_state(p,s,DB_STATE_PROXY_MASK); } int db_get_misc_state(DB_record *p) { return db_get_state(p,DB_STATE_MISC_MASK); } void db_set_misc_state(DB_record *p,int s) { db_set_state(p,s,DB_STATE_MISC_MASK); } int db_get_record_state(DB_record *p) { return db_get_state(p,DB_STATE_RECORD_MASK); } void db_set_record_state(DB_record *p,int s) { db_set_state(p,s,DB_STATE_RECORD_MASK); } int db_get_capability_state(DB_record *p) { return db_get_state(p,DB_STATE_CAPABILITY_MASK); } void db_set_capability_state(DB_record *p,int s) { db_set_state(p,s,DB_STATE_CAPABILITY_MASK); } /* find the next free inactive record * this record will be assigned to an authenticated temporary record * * it should : * contain the same username * contain the same password * be INACTIVE * not be in the middle of processing a message * not be "me" (the temporary record) */ DB_record *db_find_record(DB_record *me,char *username,char *password) { DB_record *p = NULL; p = DB_start; while(p) { /* if username and password match and if the record is inactive, and not processing a server message, * and if has need been reused too much, return it !*/ if(p != me && db_get_record_state(p) == DB_STATE_RECORD_INACTIVE && p->servermessage.completed && (p->reuse < config_max_reuse || config_max_reuse == 0) && !strcmp(p->username,username) && !strcmp(p->password,password)) { return p; } p = p->next; } return NULL; } char *db_get_state_string(int state) { int i,max; static char ret[80]; int intvalues[] = { DB_STATE_SERVER_CONNECTION_MASK, DB_STATE_SERVER_NOT_CONNECTED, DB_STATE_SERVER_CONNECTION_STARTED, DB_STATE_SERVER_CONNECTED, DB_STATE_CLIENT_MASK, DB_STATE_CLIENT_NOT_AUTHENTICATED, DB_STATE_CLIENT_SENT_AUTHENTICATE_COMMAND, DB_STATE_CLIENT_SENT_LOGIN_COMMAND, DB_STATE_CLIENT_SENT_USERNAME, DB_STATE_CLIENT_SENT_PASSWORD, DB_STATE_CLIENT_AUTHENTICATED, DB_STATE_CLIENT_SENT_LOGOUT_COMMAND, DB_STATE_SERVER_MASK, DB_STATE_SERVER_NONE, DB_STATE_SERVER_SENT_GREETING, DB_STATE_SERVER_SENT_USERNAME_REPLY, DB_STATE_SERVER_SENT_PASSWORD_REPLY, DB_STATE_SERVER_SENT_LOGIN_OK, DB_STATE_SERVER_SENT_LOGIN_FAILED, DB_STATE_PROXY_MASK, DB_STATE_PROXY_NONE, DB_STATE_PROXY_SENT_GREETING_TO_CLIENT, DB_STATE_PROXY_SENT_USERNAME_REPLY_TO_CLIENT, DB_STATE_PROXY_SENT_PASSWORD_REPLY_TO_CLIENT, DB_STATE_PROXY_SENT_AUTHENTICATE_COMMAND_TO_SERVER, DB_STATE_PROXY_SENT_LOGIN_COMMAND_TO_SERVER, DB_STATE_PROXY_SENT_USERNAME_TO_SERVER, DB_STATE_PROXY_SENT_PASSWORD_TO_SERVER, DB_STATE_PROXY_SENT_LOGIN_OK_TO_CLIENT, DB_STATE_PROXY_SENT_LOGIN_FAILED_TO_CLIENT, DB_STATE_MISC_MASK, DB_STATE_MISC_NONE, DB_STATE_MISC_REMOVE_RECORD, DB_STATE_RECORD_MASK, DB_STATE_RECORD_NONE, DB_STATE_RECORD_FORKED_CHILD }; char *values[] = { "DB_STATE_SERVER_CONNECTION_MASK", "DB_STATE_SERVER_NOT_CONNECTED", "DB_STATE_SERVER_CONNECTION_STARTED", "DB_STATE_SERVER_CONNECTED", "DB_STATE_CLIENT_MASK", "DB_STATE_CLIENT_NOT_AUTHENTICATED", "DB_STATE_CLIENT_SENT_AUTHENTICATE_COMMAND", "DB_STATE_CLIENT_SENT_LOGIN_COMMAND", "DB_STATE_CLIENT_SENT_USERNAME", "DB_STATE_CLIENT_SENT_PASSWORD", "DB_STATE_CLIENT_AUTHENTICATED", "DB_STATE_CLIENT_SENT_LOGOUT_COMMAND", "DB_STATE_SERVER_MASK", "DB_STATE_SERVER_NONE", "DB_STATE_SERVER_SENT_GREETING", "DB_STATE_SERVER_SENT_USERNAME_REPLY", "DB_STATE_SERVER_SENT_PASSWORD_REPLY", "DB_STATE_SERVER_SENT_LOGIN_OK", "DB_STATE_SERVER_SENT_LOGIN_FAILED", "DB_STATE_PROXY_MASK", "DB_STATE_PROXY_NONE", "DB_STATE_PROXY_SENT_GREETING_TO_CLIENT", "DB_STATE_PROXY_SENT_USERNAME_REPLY_TO_CLIENT", "DB_STATE_PROXY_SENT_PASSWORD_REPLY_TO_CLIENT", "DB_STATE_PROXY_SENT_AUTHENTICATE_COMMAND_TO_SERVER", "DB_STATE_PROXY_SENT_LOGIN_COMMAND_TO_SERVER", "DB_STATE_PROXY_SENT_USERNAME_TO_SERVER", "DB_STATE_PROXY_SENT_PASSWORD_TO_SERVER", "DB_STATE_PROXY_SENT_LOGIN_OK_TO_CLIENT", "DB_STATE_PROXY_SENT_LOGIN_FAILED_TO_CLIENT", "DB_STATE_MISC_MASK", "DB_STATE_MISC_NONE", "DB_STATE_MISC_REMOVE_RECORD", "DB_STATE_RECORD_MASK", "DB_STATE_RECORD_NONE", "DB_STATE_MISC_FORKED_CHILD" }; max = sizeof(intvalues) / sizeof(int); for(i = 0;i < max; i++) if(state == intvalues[i]) return values[i]; snprintf(ret,sizeof(ret),"UNKNOWN_STATUS_%s%#0x",state?"":"0x",state); return ret; } void db_print_state(DB_record *p) { debug("Server connection state: %s\n",db_get_state_string(db_get_server_connection_state(p))); debug("Client state: %s\n",db_get_state_string(db_get_client_state(p))); debug("Server state: %s\n",db_get_state_string(db_get_server_state(p))); debug("Proxy state: %s\n",db_get_state_string(db_get_proxy_state(p))); debug("Misc state: %s\n",db_get_state_string(db_get_misc_state(p))); } void db_log_stats() { DB_record *p = NULL; int active = 0,inactive = 0, temporary = 0, other = 0,total = 0; p = DB_start; while(p) { switch(db_get_record_state(p)) { case DB_STATE_RECORD_INACTIVE: inactive++; break; case DB_STATE_RECORD_ACTIVE: active++; break; case DB_STATE_RECORD_TEMPORARY: temporary++; break; default: other++; break; } total++; p = p->next; } log(LOG_INFO,"Status: %d temporary, %d active, %d inactive, %d other, %d total.\n",temporary,active,inactive,other,total); } char *db_get_record_info(DB_record *p) { static char buffer[64]; snprintf(buffer,sizeof(buffer),"[%s:%d (%d)]\n",inet_ntoa(p->clientlocaladdress),p->clientlocalport,p->proxylocalport); return buffer; }