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