/* =========================================================================== = (C) Copyright 1997,1998 Michael Stenns = = = = Permission to use, copy, modify, and distribute this program for = = non-commercial use and without fee is hereby granted. = = = = This software 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. = = = =========================================================================== = = = (C) Copyright 1991-1994 The Trustees of Indiana University = = = = Permission to use, copy, modify, and distribute this program for = = non-commercial use and without fee is hereby granted, provided that = = this copyright and permission notice appear on all copies and = = supporting documentation, the name of Indiana University not be used = = in advertising or publicity pertaining to distribution of the program = = without specific prior permission, and notice be given in supporting = = documentation that copying and distribution is by permission of = = Indiana University. = = = = Indiana University makes no representations about the suitability of = = this software for any purpose. It is provided "as is" without express = = or implied warranty. = = = =========================================================================== = = = File: = = IUPOP3.C - Version 2.0 = = = = Synopsis: = = This file contains miscellaneous functions that support the = = IUPOP3 server for VMS. = = = = Author: = = Michael Stenns = = Institute for Technical Chemistry, University of Hanover, Germany = = = = Authors of Version 1.8: = = Jacob Levanon & Larry Hughes = = Indiana University = = University Computing Services, Network Applications = = = = Credits: = = This software is based on the Post Office Protocol version 3, = = as implemented by the University of California at Berkeley. = = = =========================================================================== */ /* ======================================================================== */ /* Includes */ /* ======================================================================== */ /* Notes: The MULTINET and WINS macros are currently unsupported and may be removed in future versions. The suggested method is to enable the UCX compability mode on these stacks. For NETLIB, at least version 2.0 is required. */ #if !defined(WINS) && !defined(MULTINET) && !defined(UCX)&& !defined(NETLIB) "Don't know how to make IUPOP3 for your TCP/IP implementation!" #endif /* ansi-c header files */ #include #include #include #include #include #include # include # include # include # include # include # include # include /* INET symbol definitions */ /* OpenVMS specific header files */ #include #include /* system services */ #include #include #include #include #include #include /* Own header files */ #include "iupop3_general.h" #define MAIN_MODULE #include "iupop3_global.h" #undef MAIN_MODULE #include "iupop3_vms.h" #include "version.h" /* ======================================================================== */ /* Defines */ /* ======================================================================== */ #ifndef TCP_NODELAY # define TCP_NODELAY 0x01 /* don't delay send to coalesce packets */ #endif #define LOGFILE_NEW 1 #define LOGFILE_FLUSH 2 # define ERRNO errno #ifdef ALPHA # define OS_TYPE "ALPHA" #else # define OS_TYPE "VAX" #endif /* ======================================================================== */ /* Global Variables only used within this module */ /* ======================================================================== */ static int initial_socket; static int max_netwrite_bytes = USHRT_MAX; /* used in client_unblock_ast and net_write_async */ static bintime_type client_timeout_interval; /* issue_client_timeout_timer */ static struct IOSB connect_iosb; static POP pop[MAX_THREADS]; /* this array contains all private thread data */ /* ======================================================================== */ /* Local Prototypes */ /* ======================================================================== */ void write_sync_ast (int efn); static int process_commandline_options (int argc, char *argv[]); static int init_global_timers (void); static int get_attn_state (POP **pop_ptr); static int compar_attn_states (const void *thread_1, const void *thread_2); /* ======================================================================== */ /* Main */ /* ======================================================================== */ int main (int argc, char *argv[]) { POP *p = NULL; int serving = TRUE; int pop_port; int status; int count; init_global_timers(); pop_port = process_commandline_options (argc, argv); logfile_flush_ast (); system_log(LOG_INFO, "starting IUPOP3 V%s (%s/%s) server on port %d", VERSION, COMPILER, OS_TYPE, pop_port); status = master_log_level; master_log_level = LOG_DEBUG; process_environment_options(); master_log_level = status; system_log(LOG_INFO, "timezone offset to UTC: %s", get_timezone()); for (count=0; count < MAX_THREADS; count++) pop[count].in_use = FALSE; if (!create_initial_socket(pop_port)) { system_log(LOG_ERROR, "could not create or bind initial socket"); exit (SS$_CONNECFAIL); } issue_new_connect_qio(); /* run loop */ while (serving && (!server_shutdown || current_threads)) { main_loop_count++; switch (get_attn_state (&p)) { case ATTN_SLEEP: hibernation_count++; status = sys$hiber(); break; case ATTN_CONNECT: total_threads++; if (process_new_connect(p)) { issue_client_read_qio (p); issue_client_timeout_timer (p); } else { pop_log(LOG_ERROR, p, "new connect failed"); p->attn_state = ATTN_DISCONNECT; } break; case ATTN_CLOSE: normal_disconnects++; pop_log(LOG_DEBUG, p, "normal disconnect"); BINTIME_TO_ZERO (p->timeout); close_pop_thread(p); break; case ATTN_DISCONNECT: abnormal_disconnects++; pop_log(LOG_ERROR, p, "abnormal disconnect"); BINTIME_TO_ZERO (p->timeout); close_pop_thread(p); break; case ATTN_DATA: BINTIME_TO_ZERO (p->timeout); process_thread(p); if ((p->CurrentState == halt) && (p->attn_state == ATTN_SLEEP)) { p->attn_state = ATTN_CLOSE; } else if (p->authentication_attempts >= MAX_AUTHENTICATION_ATTEMPTS) { pop_log(LOG_ERROR, p, "auth attempts exceeded"); p->attn_state = ATTN_DISCONNECT; } else if (p->retrieve.flags & RETR_IN_PROGRESS_FLAG) { issue_client_timeout_timer (p); } else { if (strstr (p->command_buffer, "\r\n")) p->attn_state = ATTN_DATA; else { issue_client_read_qio (p); issue_client_timeout_timer (p); } } break; case ATTN_UNBLOCK: BINTIME_TO_ZERO (p->timeout); if (p->retrieve.function) (*p->retrieve.function)(p); else p->retrieve.flags ^= RETR_IN_PROGRESS_FLAG; if (!(p->retrieve.flags & RETR_IN_PROGRESS_FLAG)) /* process next command */ { p->attn_state = ATTN_SLEEP; if (strstr (p->command_buffer, "\r\n")) p->attn_state = ATTN_DATA; else issue_client_read_qio (p); } issue_client_timeout_timer (p); break; case ATTN_TIMEOUT: timeouts++; pop_log(LOG_ERROR, p,"timed out (originally %s)", time_from_bintime(&p->timeout,1)); close_pop_thread(p); break; case ATTN_UPDATE: update_maildrop (p); issue_client_timeout_timer (p); issue_client_purge_timer (p); break; case ATTN_PURGE: BINTIME_TO_ZERO (p->timeout); mail_purge_waste (p); issue_client_timeout_timer (p); issue_client_purge_timer (p); if (p->attn_state == ATTN_SLEEP) { p->attn_state = ATTN_CLOSE; } break; default: system_log (LOG_ERROR, "invalid attn_state, program stopped"); server_shutdown = EXIT_FAILURE; serving = FALSE; break; } } system_log(LOG_INFO, "server is shutting down"); #ifdef UCX status = sys$cancel ((unsigned)vaxc$get_sdc(initial_socket)); #elif defined NETLIB status = SS$_NORMAL; #else status = sys$cancel (initial_socket); #endif netclose (initial_socket); fclose (log_file); status = server_shutdown; exit (status); } /* ======================================================================== */ /* check running threads for attn_state != sleep and for client timeouts */ /* ======================================================================== */ static int get_attn_state (POP **pop_ptr) { static int num_att=0; /* number of threads with attn_state != sleep */ static int threadnum[MAX_THREADS]; /* list of threads with attn_state's */ static bintime_type logfile_flush_timer; int attn_state = ATTN_SLEEP; POP *p = NULL; if (!num_att) { bintime_type current_time; int num_threads = current_threads; int i; sys$gettim (¤t_time); for (i = 0, p = &pop[0]; num_threads && (i < MAX_THREADS); i++,p++) { if (p->in_use) { num_threads--; /* check for client timeouts */ if (!BINTIME_IS_ZERO(p->timeout) && (BINTIME_COMPARE(current_time,p->timeout) > 0)) { p->attn_state = ATTN_TIMEOUT; # ifndef NETLIB sys$cancel (p->channel); # endif } if (p->attn_state == ATTN_PURGE) { /* p->retrieve.start_time is here used as purge timer */ if (BINTIME_COMPARE(current_time,p->retrieve.start_time) >= 0) threadnum[num_att++] = i; } else if (p->attn_state != ATTN_SLEEP) { threadnum[num_att++] = i; } } } /* flush the logfile in predefined intervals */ if (BINTIME_COMPARE(current_time,logfile_flush_timer) > 0) { bintime_type flush_interval; set_delta_time (5, &flush_interval); issue_logfile_flush_timer (); lib$add_times (&flush_interval, ¤t_time, &logfile_flush_timer); } if (num_att > 2) /* sort for evidence under heavy load */ { qsort (threadnum, num_att, sizeof threadnum[0], compar_attn_states); } } if (num_att) { p = &pop[threadnum[--num_att]]; attn_state = p->attn_state; p->attn_state = ATTN_SLEEP; } *pop_ptr = p; return attn_state; } /* ======================================================================== */ /* compar_attn_states compares the attn_state of two threads */ /* ======================================================================== */ static int compar_attn_states (const void *t1, const void *t2) { return pop[*((int *)t2)].attn_state - pop[*((int *)t1)].attn_state; } /* ======================================================================== */ /* get_environment_list returns a pointer to the list of environment options */ /* ======================================================================== */ const environ_var_type * get_environment_list (void) { static char *auth_command = "both"; static char *header_delim = ""; const static environ_var_type env[] = { {"IGNORE_MAIL11_HEADERS",ENV_BOOL,&ignore_mail11_headers,FALSE,TRUE}, {"DEFAULT_TO_SMTP",ENV_BOOL,&default_to_smtp,FALSE,TRUE}, {"USE_BOTTOM_HEADERS",ENV_BOOL,&use_bottom_headers,FALSE,TRUE}, {"PERSONAL_NAME",ENV_BOOL,&personal_name,FALSE,TRUE}, {"USE_MAIL_FOLDER",ENV_BOOL,&use_mail_folder,FALSE,TRUE}, {"FAST_SCAN",ENV_BOOL,&fast_scan,FALSE,TRUE}, {"SCAN_INTRUSION",ENV_BOOL,&scan_intrusion,FALSE,TRUE}, {"APOP_CHECK_DUPLICATE",ENV_BOOL,&apop_check_duplicate,FALSE,TRUE}, {"IGNORE_EXPIRED_PASSWORDS",ENV_BOOL,&ignore_expired_passwords,FALSE,TRUE}, {"ENABLE_LONG_LINES",ENV_BOOL,&enable_long_lines,FALSE,TRUE}, {"PURGE_MAILBOXES",ENV_BOOL,&purge_mailboxes,FALSE,TRUE}, {"PURGE_RECLAIM_THRESHOLD",ENV_NUMERICAL,&purge_reclaim_threshold,0,INT_MAX}, {"READ_DIRECT_THRESHOLD",ENV_NUMERICAL,&read_direct_threshold,0,INT_MAX}, {"DEFAULT_SENDBUFFER_SIZE",ENV_NUMERICAL,&default_sendbuffer_size, MIN_SENDBUFFER_SIZE,MAX_SENDBUFFER_SIZE}, {"MAX_MESSAGES",ENV_NUMERICAL,&max_messages,1,USHRT_MAX}, {"CLIENT_TIMEOUT",ENV_DELTATIME,&client_timeout_interval,1,60*24-1}, {"HEADER_DELIMINATOR",ENV_STRING,&header_delim,0,0}, {"AUTH_COMMANDS",ENV_STRING,&auth_command,0,0}, {NULL, 0, NULL} }; header_delim = getenv("IUPOP3_HEADER_DELIMINATOR"); if (!header_delim) { if (use_bottom_headers) header_delim = ""; else header_delim = ""; } return env; } /* ======================================================================== */ /* Process the environment options */ /* ======================================================================== */ void process_environment_options (void) { char *env_string; char *prefix[] = {"IUPOP3_","IUPOP3$",NULL}; char search_string[80]; int i, j; const environ_var_type *env = get_environment_list(); system_log (LOG_INFO, "scanning environment options"); for (i=0; env[i].name; i++) { int ioption = -1; j = 0; do { strcpy (search_string, prefix[j]); strcat (search_string, env[i].name); env_string = getenv (search_string); } while (!env_string && prefix[++j]); if (env_string) { switch (env[i].type) { case ENV_BOOL : lower (env_string); if ((*env_string == '0') || (*env_string == 'f') ) ioption = FALSE; else if ((*env_string == '1') || (*env_string == 't') ) ioption = TRUE; else if (!strcmp(env_string,"off")) ioption = FALSE; else if (!strcmp(env_string,"on")) ioption = TRUE; else system_log (LOG_ERROR, "value \"%s\" for %s not applicable", env_string,env[i].name); if (ioption != -1) { system_log (LOG_DEBUG, "%s set to %s", search_string, (ioption) ? "TRUE" : "FALSE"); *(int *)env[i].varptr = ioption; } break; case ENV_NUMERICAL : ioption = atoi(env_string); if (ioption > env[i].max) ioption = env[i].max; else if (ioption < env[i].min) ioption = env[i].min; if (ioption >= 0) { *(int *)env[i].varptr = ioption; system_log (LOG_DEBUG, "%s set to %d", search_string, ioption); } else system_log (LOG_ERROR, "negative values not supported for %s", search_string); break; case ENV_STRING : if (!strcmp(env[i].name,"HEADER_DELIMINATOR")) { system_log (LOG_DEBUG, "%s is %s",search_string,*(char **)env[i].varptr); } else if (!strcmp(env[i].name,"AUTH_COMMANDS")) { char *found_user = NULL, *found_apop = NULL; set_auth_commands (AUTH_NONE); if ((found_user = strstr(lower(env_string),"user"))) { set_auth_commands (AUTH_USER); } if ((found_apop = strstr(env_string,"apop"))) { set_auth_commands (AUTH_APOP); } if (!found_user && !found_apop) { env_string = "user,apop"; set_auth_commands (AUTH_ALL); } if (strcmp(*(char **)env[i].varptr,env_string)) ioption = LOG_INFO; else ioption = LOG_DEBUG; system_log (ioption, "auth. commands allowed: %s", env_string); *(char **)env[i].varptr = env_string; } else { system_log (LOG_DEBUG, "%s is %s",search_string,env_string); } break; case ENV_DELTATIME : /* currently only client timeout */ ioption = atoi(env_string); if (ioption > env[i].max) ioption = env[i].max; else if (ioption < env[i].min) ioption = env[i].min; if (set_delta_time (ioption,env[i].varptr)) { system_log (LOG_DEBUG, "%s set to %s", search_string, time_from_bintime(env[i].varptr,0)); } else system_log (LOG_ERROR, "value %d is out of range for %s", ioption, search_string); default: break; } } else system_log (LOG_DEBUG, "%s%s is not defined",prefix[0],env[i].name); } if (max_messages <= 0) max_messages = MAX_MESSAGES; } /* ======================================================================== */ /* Close POP Thread */ /* ======================================================================== */ int close_pop_thread (POP *p) { static int num_too_many_threads = 0; static int nullarg = 0; char * errptr; int status = SS$_NORMAL; if (p->in_use) { current_threads--; pop_log (LOG_THREAD, p, "closing thread"); p->connected = FALSE; if (p->channel) status = sys$cancel(p->channel); if (vms_error(status)) pop_log(LOG_ERROR, p,"sys$cancel: %s", vms_message(status)); # ifdef UCX /* * On some UCX/OS combinations (OpenVMS alpha 6.1, UCX 3.3) socket * descriptors becomes eaten after some error conditions. Clearing * the error condition with getsockopt() avoided this. */ if (p->sockfd) { p->retrieve.bufsize = 10; status = getsockopt (p->sockfd, SOL_SOCKET, SO_ERROR, p->retrieve.buffer,(void *) &p->retrieve.bufsize); if (status == -1) pop_log(LOG_ERROR,p,"getsockopt: %s", vms_strerror(ERRNO)); } # endif if (p->sockfd) status = netclose (p->sockfd); if (status == -1) pop_log (LOG_ERROR, p,"netclose: error closing socket %d (%s)", p->sockfd,vms_strerror(ERRNO)); if ((int) p->message_context != NO_CONTEXT) mail_close_message_context(p); if ((int) p->file_context != NO_CONTEXT) mail_close_file_context(p); if ((int) p->user_context != NO_CONTEXT) mail_close_user_context(p); close_external_file (p); if (p->retrieve.send_buffer) free (p->retrieve.send_buffer); if (p->mptr) free (p->mptr); memset(p,0,sizeof(pop[0])); p->in_use = FALSE; if (too_many_threads > num_too_many_threads) { num_too_many_threads = too_many_threads; system_log (LOG_THREAD, "reinitiate new_connect_ast"); new_connect_ast (&nullarg); } status = TRUE; } else { pop_log(LOG_ERROR, p, "thread not in use -- cannot close it!"); status = FALSE; } if (reset_runtime_options && !current_threads) { /* option reset requested */ reset_runtime_options = FALSE; process_environment_options(); } return status; } /* ======================================================================== */ /* Client Read AST */ /* ======================================================================== */ void client_read_ast (int threadnum) { POP * p = &pop[threadnum]; if (!p->connected || !p->in_use) return; /* just ignore */ switch (p->read_iosb.status) { case SS$_NORMAL: if (p->read_iosb.count <= 0) p->attn_state = ATTN_DISCONNECT; else p->attn_state = ATTN_DATA; break; case SS$_CANCEL: case SS$_ABORT: p->attn_state = ATTN_TIMEOUT; break; case SS$_CONNECFAIL: case SS$_LINKDISCON: pop_log (LOG_ERROR, p,"read iosb: connection dropped by client"); p->attn_state = ATTN_DISCONNECT; p->connected = FALSE; break; default: pop_log (LOG_ERROR, p,"read iosb: %s",vms_message(p->read_iosb.status)); /* Be paranoid and flag it as disconnected right now */ p->attn_state = ATTN_DISCONNECT; p->connected = FALSE; break; } sys$wake(0,0); } /* ======================================================================== */ /* Client Unblock AST */ /* ======================================================================== */ void client_unblock_ast (int threadnum) { POP *p = &pop[threadnum]; if (!p->connected || !p->in_use) return; if (vms_error(p->write_iosb.status)) { if ((p->write_iosb.status == SS$_CANCEL) || (p->write_iosb.status == SS$_ABORT) ) p->attn_state = ATTN_TIMEOUT; else { pop_log (LOG_ERROR, p, "write iosb: %s",vms_message(p->write_iosb.status)); p->connected = FALSE; p->attn_state = ATTN_DISCONNECT; } } else { if (!p->write_iosb.count) /* OpenCMU fails to set this value */ p->write_iosb.count = Min (p->retrieve.send_bufsize, max_netwrite_bytes); net_bytes_written += p->write_iosb.count; net_write_count++; blocked_count++; /* asyn. write count */ pop_log (LOG_DEBUG, p, "%d bytes send (%d bytes left)",p->write_iosb.count, p->retrieve.send_bufsize - p->write_iosb.count); if (p->write_iosb.count < p->retrieve.send_bufsize) /* not all bytes sent yet */ { p->retrieve.send_bufsize -= p->write_iosb.count; p->retrieve.send_count += p->write_iosb.count; netwrite_async (p, p->retrieve.send_buffer+p->retrieve.send_count, p->retrieve.send_bufsize); } else { p->attn_state = ATTN_UNBLOCK; p->retrieve.send_bufsize = 0; p->retrieve.send_count = 0; } } if (p->attn_state != ATTN_SLEEP) sys$wake(0,0); } /* ======================================================================== */ /* Issue Client Read QIO */ /* ======================================================================== */ int issue_client_read_qio (POP *p) { int status = SS$_NORMAL; #ifdef NETLIB if (p->sockfd) { status = netlib_read ((void *)&p->sockfd, des(p->read_buffer, sizeof p->read_buffer - 1), 0, 0, 0, 0, &p->read_iosb, client_read_ast, p->threadnum); } #else if (p->channel) { status = sys$qio(0, p->channel, IO$_READVBLK, &p->read_iosb, client_read_ast, p->threadnum, p->read_buffer, sizeof p->read_buffer - 1, 0, 0, 0, 0); } #endif if (vms_error(status)) { pop_log(LOG_ERROR, p, "client read sys$qio: %s", vms_message(status)); p->connected = FALSE; p->attn_state = ATTN_DISCONNECT; } return status; } /* ======================================================================== */ /* Write data async */ /* ======================================================================== */ int netwrite_async (POP *p, char * message, int buflen) { int status = SS$_NORMAL; if (p->connected) { int length = Min (buflen, max_netwrite_bytes); #ifdef NETLIB status = netlib_write ((void *)&p->sockfd, des(message,length), 0, 0, &p->write_iosb, client_unblock_ast, p->threadnum); #else status = sys$qio (0, p->channel, IO$_WRITEVBLK, &p->write_iosb, client_unblock_ast, p->threadnum, message, length, 0, 0, 0, 0); #endif if (vms_error(status)) { switch (status) { /* OpenCMU fails writing buffers > (SYSGEN MAXBUF) size */ case SS$_EXQUOTA: case SS$_INSFMEM: if (max_netwrite_bytes > 512) /* do not reduce beyond this */ { max_netwrite_bytes = (max_netwrite_bytes > default_sendbuffer_size) ? default_sendbuffer_size : max_netwrite_bytes * 0.9; pop_log (LOG_INFO, p, "max. async. write size now %d bytes",max_netwrite_bytes); buflen = netwrite_async (p, message, buflen); break; } default: pop_log(LOG_ERROR, p, "client write async: %s", vms_message(status)); buflen = 0; p->connected = FALSE; p->attn_state = ATTN_DISCONNECT; break; } } else p->retrieve.flags |= RETR_IN_PROGRESS_FLAG; } return buflen; } /* ======================================================================== */ /* Write data sync */ /* ======================================================================== */ int netwrite_sync (POP *p, char * message, int buflen) { static int first = TRUE, timer_efn, interval[2]; unsigned long reqidt = (unsigned long) &p->write_iosb; unsigned int unused = 0; int status = SS$_NORMAL; if (first) { first = FALSE; status = sys$bintim(desz ("0 00:00:10.00"), interval); if (vms_error(status)) system_log (LOG_ERROR, "sys$bintim: %s", vms_message(status)); status = lib$get_ef (&timer_efn); if (vms_error(status)) system_log (LOG_ERROR, "lib$get_ef: %s", vms_message(status)); sys$setef (timer_efn); } if (p->connected) { p->write_iosb.status = 0; sys$setimr (timer_efn, interval, 0, reqidt ,0); #ifdef NETLIB status = netlib_write ((void *)&p->sockfd, des(message,buflen), 0, 0, &p->write_iosb, write_sync_ast, timer_efn); #else status = sys$qio (timer_efn, p->channel, IO$_WRITEVBLK, &p->write_iosb, 0, 0, message, buflen, 0, 0, 0, 0); #endif if (vms_error(status)) { pop_log(LOG_ERROR, p, "client write sync: %s", vms_message(status)); buflen = 0; } else { status = sys$waitfr (timer_efn); if (!p->write_iosb.status) /* $qio has not completed */ { pop_log(LOG_ERROR, p, "synchron. write is blocked by client"); buflen = 0; p->attn_state = ATTN_DISCONNECT; p->connected = FALSE; } else { status = sys$cantim (reqidt, 0); if (vms_error(status)) pop_log(LOG_ERROR, p, "status von sys$cantim: %d",status); net_bytes_written += buflen; net_write_count++; } } } return buflen; } /* ======================================================================== */ /* Write data sync ast */ /* ======================================================================== */ #ifdef NETLIB void write_sync_ast (int efn) { sys$setef (efn); return; } #endif /* ======================================================================== */ /* Issue Client Timeout Timer */ /* ======================================================================== */ int issue_client_timeout_timer (POP *p) { int status; bintime_type current_time; sys$gettim (¤t_time); status = lib$add_times (&client_timeout_interval, ¤t_time, &p->timeout); return status; } /* ======================================================================== */ /* Issue Client Purge Timer */ /* Defer the next purge cycle to minimize slowdown of other threads */ /* ======================================================================== */ int issue_client_purge_timer (POP *p) { bintime_type purge_interval = BINTIME_INIT_ZERO; int num_interval = current_threads / 2; int status; status = sys$bintim(desz("0 00:00:00.50"),&purge_interval); if (vms_error(status)) { pop_log (LOG_ERROR,p,"sys$bintim failed: %s",vms_message(status)); return status; } sys$gettim (&p->retrieve.start_time); while (num_interval--) lib$add_times (&purge_interval, &p->retrieve.start_time, &p->retrieve.start_time); pop_log (LOG_DEBUG,p,"wastebasket purge deferred to %s", time_from_bintime (&p->retrieve.start_time, 1)); return status; } /* ======================================================================== */ /* Issue New Connect QIO */ /* ======================================================================== */ int issue_new_connect_qio (void) { int status; #ifdef UCX status = sys$qio(0, (unsigned)vaxc$get_sdc(initial_socket), IO$_SETMODE | IO$M_READATTN, &connect_iosb, 0, 0, &new_connect_ast, 0, 0, 0, 0, 0); #elif defined NETLIB static int acc_socket = 0; status = netlib_accept ((void *)&initial_socket, (void *)&acc_socket, 0, 0, 0, &connect_iosb, new_connect_ast, &acc_socket); #else status = sys$qio(0, (u_long)initial_socket, IO$_ACCEPT_WAIT, &connect_iosb, &new_connect_ast, 0, 0, 0, 0, 0, 0, 0); #endif if (!vms_error(status)) status = TRUE; else { system_log(LOG_ERROR, "new connect sys$qio: %s", vms_message(status)); status = FALSE; } return status; } /* ======================================================================== */ /* New Connect AST */ /* accept_socket_ptr parameter only used by netlib */ /* ======================================================================== */ void new_connect_ast (int * accept_socket_ptr) { static int max_threads = MAX_THREADS; static int connect_socket = 0; /* accepted socket */ int threadnum; POP *p = &pop[0]; switch (connect_iosb.status) { case SS$_NORMAL: break; case SS$_CANCEL: /* shutdown in progress */ case SS$_ABORT: return; break; case SS$_TIMEOUT : /* happens sometimes on OpenCMU / VMS 5.3 */ case SS$_UNREACHABLE : system_log(LOG_ERROR,"connect iosb: %s",vms_message(connect_iosb.status)); break; default : /* unexpected error, restart server */ system_log(LOG_ERROR,"unexpected connect error: %s", vms_message(connect_iosb.status)); server_shutdown = connect_iosb.status; return; break; } #ifdef NETLIB if (!connect_socket) { if (*accept_socket_ptr) { connect_socket = *accept_socket_ptr; *accept_socket_ptr = 0; } else if (server_shutdown && !current_threads) { return; } else { issue_new_connect_qio(); return; } } #else /* not NETLIB */ if (!connect_socket) { struct sockaddr addr; int length = sizeof(addr); connect_socket = accept (initial_socket,&addr,(void *)&length); if (connect_socket < 0) { if (ERRNO == EVMSERR) system_log (LOG_ERROR,"accept error: vms error code %d",vaxc$errno); else system_log (LOG_ERROR,"accept error: %s",vms_strerror(ERRNO)); server_shutdown = SS$_SHUT; return; } } #ifdef UCX if (connect_socket > 200) { /* OpenVMS alpha 6.1 eats sometimes file descriptors */ system_log (LOG_ERROR,"accept: run out of file descriptors "); server_shutdown = SS$_FILNOTACC; } #endif /* UCX */ #endif /* not NETLIB */ /* * New client successful accepted, assign thread number now. */ for (threadnum=0; (threadnum < max_threads) && p->in_use; threadnum++,p++); if (threadnum < MAX_THREADS) { current_threads++; p->in_use = TRUE; p->threadnum = threadnum; p->attn_state = ATTN_CONNECT; p->sockfd = connect_socket; connect_socket = 0; sys$gettim (&p->start_time); issue_new_connect_qio(); pop_log (LOG_THREAD,p,"new connection accepted on socket %d",p->sockfd); } else { /* * Too many threads! * Issue no new connect qio. * This function will be reentered from close_pop_thread(). */ too_many_threads++; system_log (LOG_THREAD, "new_connect_ast warning: too many threads!"); } sys$wake(0,0); } /* ======================================================================== */ /* Open or Flush Log File */ /* ======================================================================== */ int open_log_file (int modus) { int retval; int status; if (log_filename == NULL) { log_file = stdout; retval = FALSE; } else { retval = TRUE; if (modus == LOGFILE_NEW) { if (log_file) fclose (log_file); log_file = fopen (log_filename, "w", "shr=upd", "dna=.log"); } else if (modus == LOGFILE_FLUSH) { fflush (log_file); status = fsync ( fileno(log_file)); if (status == -1) system_log(LOG_ERROR, "fsync: errno %d", ERRNO); } else { fprintf (stderr, "open_log_file: unknown mode %d",modus); if (log_file) fclose (log_file); log_file = stdout; retval = FALSE; } if (!log_file) { log_file = stdout; system_log(LOG_ERROR, "log file could not be opened: error %d", ERRNO); retval = FALSE; } } return retval; } /* ======================================================================== */ /* Issue Logfile Flush Timer */ /* ======================================================================== */ int issue_logfile_flush_timer (void) { static int first = TRUE, timer_efn = 0, interval[2]; int status = SS$_NORMAL; int value = FALSE; unsigned int efncluster; if (first) { first = FALSE; status = sys$bintim(desz ("0 00:00:30.00"), interval); if (vms_error(status)) system_log (LOG_ERROR, "sys$bintim: %s", vms_message(status)); status = lib$get_ef (&timer_efn); if (vms_error(status)) system_log (LOG_ERROR, "lib$get_ef: %s", vms_message(status)); sys$setef (timer_efn); } if (sys$readef(timer_efn, &efncluster) == SS$_WASSET) { status = sys$setimr(timer_efn, interval, logfile_flush_ast, 0, 0); if (vms_error(status)) { sys$setef (timer_efn); system_log (LOG_ERROR, "sys$setimr (logfile): %s", vms_message(status)); } else value = TRUE; system_log (LOG_DEBUG, "issue logfile flush timer"); } return value; } /* ======================================================================== */ /* Logfile Flush AST */ /* flushes current logfile in regular intervals */ /* opens a new logfile shortly after midnight */ /* checks for sufficient process quotas */ /* ======================================================================== */ void logfile_flush_ast (void) { static int day = 0; bintime_type current_time; short int numtime[7]; proc_info_type *info = get_process_information (); char *message = NULL; /* initiates a process restart if running out of quota */ if (info->cur[JPI_PAGFILCNT] < MIN_PAGFILCNT) { server_shutdown = SS$_INSFMEM; message = "low paging file quota"; } else if (info->cur[JPI_ASTCNT] < MIN_ASTCNT) { server_shutdown = SS$_EXQUOTA; message = "low AST quota"; } else if (info->cur[JPI_ENQCNT] < MIN_ENQCNT) { server_shutdown = SS$_EXQUOTA; message = "low lock request quota"; } else if (info->cur[JPI_FILCNT] < MIN_FILCNT) { server_shutdown = SS$_EXQUOTA; message = "low open file quota"; } if (message) { system_log (LOG_ERROR, "server restart: %s", message); } sys$gettim (¤t_time); sys$numtim (numtime, ¤t_time); if (day != numtime[2]) { open_log_file (LOGFILE_NEW); if (day) system_log (LOG_INFO, "new log file, up since %20.20s", time_from_bintime (¤t_time,0)); } else { open_log_file (LOGFILE_FLUSH); } day = numtime[2]; } /* ======================================================================== */ /* Process New Connect */ /* ======================================================================== */ int process_new_connect (POP *p) { int retval = FALSE; int ok = FALSE; int status; size_t length; if (p->threadnum >= 0) { #ifndef NETLIB /* Keepalive is default with NETLIB */ static int one = 1; static int have_tcp_nodelay = TRUE; if (setsockopt(p->sockfd, SOL_SOCKET, SO_KEEPALIVE, (char *)&one, sizeof(one)) == -1) system_log(LOG_ERROR, "error setting SO_KEEPALIVE"); if (have_tcp_nodelay) { if (setsockopt(p->sockfd, IPPROTO_TCP, TCP_NODELAY, (char *)&one, sizeof(one)) == -1) { system_log(LOG_ERROR, "error setting SO_KEEPALIVE"); have_tcp_nodelay = FALSE; /* try never again */ } } #endif if (current_threads > maximum_threads) maximum_threads = current_threads; if ((retval = init_pop_thread (p))) { #ifndef KERBEROS char timestamp[100]; sprintf (timestamp, TIMESTAMP_FORMAT, VERSION, COMPILER, p->threadnum, myhostname, stamptime (&p->start_time)); pop_msg (p, POP_SUCCESS, "IUPOP3 server %s",timestamp); #endif /* !KERBEROS */ } else { pop_log(LOG_ERROR, p, "thread could not be initialized"); } } #ifdef KERBEROS if (retval && !client_authenticated(p)) { authentication_failures++; retval = FALSE; } #endif /* KERBEROS */ return retval; } /* ====================================================================== */ /* Init POP Thread */ /* ====================================================================== */ int init_pop_thread (POP *p) { int status; size_t len, retlen = 0; char *pointer; struct hostent *ch; /* Initialize this POP structure -- all fields below are vital! */ /* p->threadnum and p->sockfd already set in connect ast */ #ifdef UCX p->channel = vaxc$get_sdc(p->sockfd); #elif defined NETLIB p->channel = 0; #else p->channel = p->sockfd; #endif p->bytes_deleted = 0; p->msg_count = 0; p->newmail_size = 0; p->msgs_deleted = 0; p->newmail_selected = FALSE; p->user_context = NO_CONTEXT; p->file_context = NO_CONTEXT; p->message_context = NO_CONTEXT; p->CurrentState = auth1; p->connected = TRUE; p->command_pointer = p->command_buffer; /* ** Get the address and socket of the client to whom we're speaking */ len = sizeof p->sin; #ifdef NETLIB status = netlib_getpeername ((void *)&p->sockfd, &p->sin, &len, &retlen, 0, 0, 0); if (vms_error(status)) { pop_log(LOG_ERROR, p, "getpeername failed: %s", vms_message(status)); return FALSE; } retlen = 0; p->ipport = netlib_ntoh_word (&p->sin.sin_w_port); strcpy (p->ipaddr,"000.000.000.000"); netlib_addrtostr (&p->sin.sin_x_addr, desz(p->ipaddr), &retlen); p->ipaddr[retlen] = '\0'; #else if ((status = getpeername (p->sockfd,(struct sockaddr *)&p->sin,(void *) &len)) < 0) { pop_log(LOG_ERROR, p, "getpeername failed: %s", vms_strerror(ERRNO)); return FALSE; } pointer = inet_ntoa(p->sin.sin_addr); strcpy(p->ipaddr, pointer); p->ipport = ntohs(p->sin.sin_port); #endif p->client = p->ipaddr; /* ** Everything succeeded, so mark this thread as "in use" */ pop_log(LOG_THREAD, p,"client address is %s,%d",p->client, p->ipport); p->in_use = TRUE; return TRUE; } /* ======================================================================== */ /* Process Thread */ /* ======================================================================== */ void process_thread (POP *p) { int status, size, maxsize; state_table *s; char *pointer, *tmp, *command_pointer; size = p->read_iosb.count; if (size) { net_bytes_read += size; net_read_count++; /* Check to see if single character == backspace or delete */ if ((size == 1) && ((*p->read_buffer == 8) || (*p->read_buffer == 127))) { if (p->command_pointer > p->command_buffer) *(--p->command_pointer) = '\0'; return; } /* Move data from read_buffer to latest position in command_buffer */ maxsize = Min (size, sizeof p->command_buffer - (p->command_pointer - p->command_buffer) - 1); memcpy(p->command_pointer, p->read_buffer, maxsize); p->command_pointer += maxsize; p->read_iosb.count = 0; if (maxsize < size) /* overflow! */ { p->command_pointer = p->command_buffer; pop_log (LOG_ERROR, p, "input overflow detected, some data may be lost"); } *p->command_pointer = '\0'; } /* Check for the CR/LF terminator; if it exists, continue. */ if ((pointer = strstr (p->command_buffer, "\r\n"))) { /* Remove the terminator, and advance the pointer. */ *pointer++ = '\0'; pointer++; /* scroll to first usable character */ command_pointer = p->command_buffer; while (*command_pointer && (isspace(*command_pointer) || iscntrl(*command_pointer))) command_pointer++; if (strlen(command_pointer) > 0) { commands_processed++; pop_log(LOG_THREAD, p, "rx: \"%.36s\"", (my_strncasecmp(command_pointer, "pass ", 5) ? command_pointer : "pass")); /* * If the previous command was "retr", it is finally safe to * mark the message as successfully retrieved. */ if ((p->retrieve.flags & (RETR_IS_RETRIEVE_FLAG + RETR_COMPLETED_FLAG)) == (RETR_IS_RETRIEVE_FLAG + RETR_COMPLETED_FLAG)) { Message *mp; p->last_msg = p->retrieve.mp->number; mp = &(p->mptr[p->last_msg-1]); pop_log(LOG_DEBUG, p, "marking message #%d as retrieved", mp->number); mp->msg_flags |= MSG_RETR_FLAG; } p->retrieve.flags = 0; /* clear all flags */ /* Process the command. */ if ((s = pop_get_command(p, command_pointer)) != NULL) { if (s && s->function) { p->CurrentState = s->PostState[(*s->function)(p)]; } else { p->CurrentState = s->PostState[0]; pop_msg(p, POP_SUCCESS, NULL); } } } /* Slide the buffer back to the beginning. */ for (tmp = p->command_buffer;pointer < p->command_pointer; pointer++) *tmp++ = *pointer; p->command_pointer = tmp; *p->command_pointer = '\0'; } } /*************************************************************************** ** pop_xtnd_kill kill the specified thread ****************************************************************************/ int pop_xtnd_kill (POP *p) { int retval = TRUE; int threadnum; if (isdigit(*p->pop_args[2]) || (*p->pop_args[2] == '+')) threadnum = atoi(p->pop_args[2]); else threadnum = -1; if ((threadnum >= 0) && (threadnum <= MAX_THREADS) && (pop[threadnum].in_use)) { pop[threadnum].attn_state = ATTN_DISCONNECT; pop_log (LOG_THREAD,p, "thread %d will be disconnected", threadnum); pop_msg (p,POP_SUCCESS,"thread %d will be disconnected", threadnum); } else { pop_log (LOG_INFO,p, "thread %d is out of range or not in use", threadnum); pop_msg (p,POP_FAILURE,"thread %d is out of range or not in use", threadnum); retval = FALSE; } return retval; } /*************************************************************************** ** get_pop_array returns the address of the thread's private data array ****************************************************************************/ POP * get_pop_array (void) { return &pop[0]; } #ifdef KERBEROS /* ======================================================================== */ /* Client Authenticated */ /* ======================================================================== */ int client_authenticated(POP *p) { int retval = FALSE; int status; /* Receive the client's Kerberos ticket */ strcpy(p->instance, "*"); status = krb_recvauth(0L, p->sockfd, &p->ticket, "pop", p->instance, &p->sin, (struct sockaddr_in *) NULL, &p->kdata, "", p->schedule, p->version); if (status != KSUCCESS) { pop_log(LOG_ERROR, p, "ticket receive error: %s", krb_err_txt[status]); pop_msg(p, POP_FAILURE, "ticket receive error: %s", krb_err_txt[status]); } else { /* Partial success -- ticket at least is OK */ strcpy(p->user, p->kdata.pname); pop_log(LOG_THREAD, p, "ticket: %s.%s@%s (%s)", p->kdata.pname, p->kdata.pinst, p->kdata.prealm, inet_ntoa(p->sin.sin_addr)); /* Is the user in OUR Kerberos realm? */ if (strcmp(p->kdata.prealm, local_realm) != 0) { pop_log(LOG_ERROR, p, "(%s.%s@%s) realm not accepted", p->kdata.pname, p->kdata.pinst, p->kdata.prealm); pop_msg(p, POP_FAILURE, "(%s.%s@%s) realm not accepted", p->kdata.pname, p->kdata.pinst, p->kdata.prealm); } else { /* Usernames should have a null instance! */ if (strcmp(p->kdata.pinst, "")) { pop_log(LOG_ERROR, p, "(%s.%s@%s) instance not accepted", p->kdata.pname, p->kdata.pinst, p->kdata.prealm); pop_msg(p, POP_FAILURE, "(%s.%s@%s) instance not accepted", p->kdata.pname, p->kdata.pinst, p->kdata.prealm); } else { pop_log(LOG_DEBUG, p, "Kerberos authenticated - client is '%s'", p->user); if (valid_vms_user(p)) { pop_log(LOG_DEBUG, p, "vms authenticated"); pop_msg(p, POP_SUCCESS, "IUPOP3 server (Kerberos) V%s at %s, up since %s", VERSION, myhostname, logtime(&server_start_time)); retval = TRUE; } else pop_msg(p, POP_FAILURE, "user account \"%s\" not available", p->user); } } } return(retval); } #endif /* KERBEROS */ /* ======================================================================== */ /* Sets the client timeout value as a VMS delta time */ /* ======================================================================== */ int set_delta_time (int minutes, bintime_type *deltatime) { int retval = FALSE; if ((minutes >= 1) && (minutes < (24*60-1))) { int status; char time_str[sizeof "0 00:00:00.00"]; sprintf (time_str, "0 %02.2d:%02.2d:00.00", minutes / 60, minutes % 60); status = sys$bintim(desz(time_str),deltatime); if (vms_error(status)) /* probably called before logfile is open */ fprintf (stderr,"can't change client timer %s",vms_message(status)); else retval = TRUE; } return retval; } /* ======================================================================== */ /* Process the commandline parameters */ /* ======================================================================== */ static int process_commandline_options (int argc, char *argv[]) { int pop_port = RFC_PORT; /* return value */ int bad_option = FALSE; int status; while ((argc > 1) && !bad_option) { argc--; argv++; if (strcmp(argv[0], "-port") == 0) { argc--; argv++; if (!argc) bad_option = TRUE; else { pop_port = atoi(argv[0]); if (pop_port <= 0) bad_option = TRUE; } } else if (strcmp(argv[0], "-loglevel") == 0) { argc--; argv++; if (!argc) bad_option = TRUE; else { if (strcmp(argv[0], "debug") == 0) master_log_level = LOG_DEBUG; else if (strcmp(argv[0], "thread") == 0) master_log_level = LOG_THREAD; else if (strcmp(argv[0], "info") == 0) master_log_level = LOG_INFO; else if (strcmp(argv[0], "error") == 0) master_log_level = LOG_ERROR; else bad_option = TRUE; } } else if (strcmp(argv[0], "-logfile") == 0) { argc--; argv++; if (!argc) bad_option = TRUE; else log_filename = argv[0]; } else if (strcmp(argv[0], "-maxmsg") == 0) { argc--; argv++; if (!argc) bad_option = TRUE; else { max_messages = atoi(argv[0]); if (max_messages <= 0) max_messages = MAX_MESSAGES; } } else if (strcmp(argv[0], "-purge_reclaim") == 0) { purge_reclaim_threshold = 32 * 1024; purge_reclaim = TRUE; } else if (strcmp(argv[0], "-xtnd") == 0) { argc--; argv++; if (!argc) bad_option = TRUE; else xtnd_filename = argv[0]; } else if (strcmp(argv[0], "-timeout") == 0) { argc--; argv++; if (!argc) bad_option = TRUE; else { bad_option = !set_delta_time (atoi(argv[0]),&client_timeout_interval); if (bad_option) printf ("timeout value out of range, valid are 1 to %d minutes\n",(24*60-1)); } } else if (strcmp(argv[0], "-default_type") == 0) { argc--; argv++; if (!argc) bad_option = TRUE; else { lower(argv[0]); if (strstr (argv[0], "smtp")) default_to_smtp = TRUE; else if (strstr (argv[0], "internet")) default_to_smtp = TRUE; else if (strstr (argv[0], "decnet")) default_to_smtp = FALSE; } } else bad_option = TRUE; } if (bad_option) { printf("Usage: iupop3 [-port n] [xtnd file] [-loglevel level]\n"); printf(" [-logfile file] [-maxmsg N] [-purge_reclaim]\n"); printf(" [-default_type type] [-timeout N] \n"); exit (CLI$_INVQUAL); } return pop_port; } /* ======================================================================== */ /* init global timer values used in issue_client_timeout_timer */ /* and some additional initializations */ /* ======================================================================== */ static int init_global_timers (void) { int i, status; int interval[2]; proc_info_type *info; /* * get the start time */ sys$gettim (&server_start_time); /* * do some additional initializations */ get_cpu(); /* sets startup value */ info = get_process_information (); for (i=0; i < JPI_MAX; i++) info->min[i] = info->max[i] = info->cur[i]; /* init. the timer values */ status = sys$bintim (desz("0 00:00:30.00"),interval); status = sys$schdwk (0,0,interval,interval); if (vms_error(status)) fprintf (stderr, "sys$schdwk: %s", vms_message(status)); status = sys$bintim(desz("0 00:02:00.00"),&client_timeout_interval); if (vms_error(status)) fprintf (stderr, "sys$bintim: %s", vms_message(status)); return status; } /* ======================================================================== */ /* Create Initial Socket */ /* ======================================================================== */ int create_initial_socket(int pop_port) { int return_value = FALSE; int status; #ifdef NETLIB unsigned int socket_type = NETLIB_K_TYPE_STREAM; unsigned int socket_fam = NETLIB_K_AF_INET; unsigned int retlen = 0; status = netlib_get_hostname (des(myhostname,MAXHOSTNAMELEN-1), &retlen); if (vms_error(status) || !retlen) strcpy (myhostname, "localhost"); else myhostname[retlen] = '\0'; system_log(LOG_INFO, "local hostname is %s", lower(myhostname)); status = netlib_socket ((void *)&initial_socket, &socket_type, &socket_fam); if (vms_error(status)) initial_socket = -1; #else struct sockaddr_in server; static int one = 1; gethostname(myhostname, MAXHOSTNAMELEN); system_log(LOG_INFO, "local hostname is %s", lower(myhostname)); initial_socket = socket (AF_INET,SOCK_STREAM,0); #endif if (initial_socket < 0) system_log(LOG_ERROR, "error creating initial socket"); else { #ifdef NETLIB unsigned int level = NETLIB_K_LEVEL_SOCKET; unsigned int option = NETLIB_K_OPTION_REUSEADDR; unsigned int value = 1; unsigned int vallen = sizeof value; unsigned short port = pop_port; struct SINDEF server; status = netlib_setsockopt ((void *)&initial_socket, &level, &option, &value, &vallen, 0, 0, 0); if (vms_error(status)) system_log(LOG_ERROR, "error setting SO_REUSEADDR (%s)",vms_message(status)); memset (&server, 0, sizeof server); server.sin_w_family = NETLIB_K_AF_INET; server.sin_w_port = netlib_hton_word (&port); vallen = sizeof server; status = netlib_bind ((void *)&initial_socket, &server, &vallen, 0, 0, 0); if (vms_error(status)) system_log(LOG_ERROR, "error binding initial socket"); else { value = 5; status = netlib_listen ((void *)&initial_socket, &value, 0, 0, 0); if (vms_error(status)) system_log(LOG_ERROR, "netlib_listen: %s",vms_message(status)); return_value = TRUE; } #else if (setsockopt(initial_socket, SOL_SOCKET, SO_KEEPALIVE, (char *)&one, sizeof(one)) == -1) system_log(LOG_ERROR, "error setting SO_KEEPALIVE"); if (setsockopt(initial_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one)) == -1) system_log(LOG_ERROR, "error setting SO_REUSEADDR"); server.sin_family = AF_INET; server.sin_addr.s_addr = INADDR_ANY; server.sin_port = htons (pop_port); /* * The 2nd argument of bind is a structure from type "struct sockaddr", * the generic type of "struct sockaddr_in". */ status = bind (initial_socket, (struct sockaddr *)&server, sizeof(server)); if (status != 0) system_log(LOG_ERROR, "error binding initial socket"); else { listen(initial_socket, SOMAXCONN); return_value = TRUE; } #endif } return return_value; }