/* =========================================================================== = (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_COMMANDS.C - Version 2.0 = = = = Synopsis: = = This file contains functions that implement the pop3 commands. = = = = 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. = = = =========================================================================== */ /************************************************************************* ** Include files **************************************************************************/ #include #include #include #include #include #include #include #ifdef KERBEROS # include # include #endif /* KERBEROS */ #include "iupop3_general.h" #include "iupop3_vms.h" #include "iupop3_global.h" #include "version.h" /* ======================================================================== */ /* Prototypes look into iupop3_general.h */ /* ======================================================================== */ /**************************************************************************** ** External Declarations ****************************************************************************/ static state_table states[] = { {auth1, "user", 1, 1, pop_user, {auth1, auth2}}, {auth2, "pass", 1, 1, pop_pass, {auth1, trans}}, {auth1, "apop", 2, 2, pop_apop, {auth1, trans}}, {auth1, "auth", 1, 1, pop_auth, {auth1, trans}}, {trans, "stat", 0, 0, pop_stat, {trans, trans}}, {trans, "list", 0, 1, pop_list, {trans, trans}}, {trans, "uidl", 0, 1, pop_uidl, {trans, trans}}, {trans, "retr", 1, 1, pop_send, {trans, trans}}, {trans, "dele", 1, 1, pop_dele, {trans, trans}}, {trans, "noop", 0, 0, pop_noop, {trans, trans}}, {trans, "top", 2, 2, pop_send, {trans, trans}}, {trans, "rset", 0, 0, pop_rset, {trans, trans}}, {trans, "last", 0, 0, pop_last, {trans, trans}}, {trans, "quit", 0, 0, pop_updt, {halt, halt} }, {auth1, "quit", 0, 0, pop_quit, {halt, halt} }, {auth2, "quit", 0, 0, pop_quit, {halt, halt} }, {error, "quit", 0, 0, pop_quit, {halt, halt} }, {(state)0, NULL, 0, 0, NULL, {halt, halt} }, }; /***************************************************************************** ** Pop_get_command: Extract the command from an input line form a POP client *****************************************************************************/ state_table *pop_get_command (POP *p, char *mp) { state_table *s; char buf[MAXMSGLINELEN]; p->arg_count = pop_parse(p,mp); /* ** Search for the POP command in the command/state table */ for (s = states; s->command; s++) { /* ** current operating state? */ if ((strcmp(s->command, p->pop_command) == 0) && (s->ValidCurrentState == p->CurrentState)) { /* ** Make sure command line is syntacticly correct */ if (p->arg_count < s->min_args) return((state_table *)pop_msg(p, POP_FAILURE, "Too few arguments for the %s command.",p->pop_command)); if (p->arg_count > s->max_args) return((state_table *)pop_msg(p, POP_FAILURE, "Too many arguments for the %s command.",p->pop_command)); /* ** Return a pointer to command in the command/state table */ return (s); } } pop_log (LOG_ERROR, p, "unknown command: \"%.20s\"", p->pop_command); pop_msg (p, POP_FAILURE, "Unknown command: \"%.20s\".", p->pop_command); return NULL; } /************************************************************************** ** Pop_dele: Delete a message from the VMS mail file ***************************************************************************/ int pop_dele (POP *p) { Message *mp; int msg_num; msg_num = atoi(p->pop_args[1]); /* ** Is message requested for deletetion out of range? */ if ( (msg_num < 1) || (msg_num > p->msg_count) ) { pop_log(LOG_ERROR, p, "Message %d does not exist.", msg_num); return(pop_msg(p,POP_FAILURE,"Message %d does not exist.",msg_num)); } /* ** Get a pointer to the message in the message list */ mp = &(p->mptr[msg_num-1]); /* ** If the message already flagged for deletion, signal error */ if ( mp->msg_flags & MSG_DEL_FLAG ) { pop_log(LOG_ERROR, p, "Message %d has already been deleted.", msg_num); return(pop_msg(p,POP_FAILURE,"Message %d has already been deleted.",msg_num)); } mp->msg_flags |= MSG_DEL_FLAG; p->msgs_deleted++; p->bytes_deleted += mp->length; if (p->last_msg < msg_num) p->last_msg = msg_num; return(pop_msg(p,POP_SUCCESS,"Message %d has been deleted.",msg_num)); } /************************************************************************ ** Pop_last: Return the last message accessed *************************************************************************/ int pop_last(POP *p) { return(pop_msg(p, POP_SUCCESS, " %d was last message accessed", p->last_msg)); } /************************************************************************ ** Pop_list: List the contents of the VMS mail folder *************************************************************************/ int pop_list (POP *p) { Message *mp; int i; int msg_num; if (!(p->retrieve.flags & RETR_IN_PROGRESS_FLAG) && (p->arg_count > 0)) { msg_num = atoi(p->pop_args[1]); /* ** Is the requested message out of range? */ if ((msg_num < 1) || (msg_num > p->msg_count)) return(pop_msg (p,POP_FAILURE, "Message %d does not exist.",msg_num)); /* ** Get a pointer to the message in the message list */ mp = &p->mptr[msg_num-1]; /* ** Is the message already flagged for deletion? */ if (mp->msg_flags & (MSG_DEL_FLAG + MSG_UNAVAILABLE_FLAG)) { if (mp->msg_flags & MSG_DEL_FLAG) return pop_msg (p,POP_FAILURE, "Message %d has been deleted.",msg_num); else return pop_msg (p,POP_FAILURE, "Message %d is unavailable.",msg_num); } /* ** Display message information */ return(pop_msg(p,POP_SUCCESS,"%u %u", msg_num, mp->length)); } /* ** Message number was not supplied - display the entire list of messages */ /* ** Walk through the message information list. ** Don't show messages marked as deleted. */ if (!allocate_send_buffer (p)) return POP_FAILURE; if (!(p->retrieve.flags & RETR_IN_PROGRESS_FLAG)) { pop_msg(p,POP_SUCCESS, "%u messages (%u octets)", p->msg_count - p->msgs_deleted, p->newmail_size - p->bytes_deleted); p->retrieve.lines_sent = 0; p->retrieve.bufsize = 0; p->retrieve.flags |= RETR_IN_PROGRESS_FLAG; } p->retrieve.send_bufsize = 0; for (mp = &p->mptr[p->retrieve.lines_sent]; p->retrieve.lines_sent < p->msg_count; p->retrieve.lines_sent++, mp++) { if (!(mp->msg_flags & (MSG_DEL_FLAG + MSG_UNAVAILABLE_FLAG))) { if (!netbprintf(p,"%u %u\r\n",mp->number,mp->length)) break; } } if (p->retrieve.lines_sent == p->msg_count) { netb_end (p); p->retrieve.function = NULL; } else { p->retrieve.function = pop_list; } netwrite_async (p, p->retrieve.send_buffer, p->retrieve.send_bufsize); return POP_SUCCESS; } /************************************************************************ ** Pop_uidl: give unique-id to messages ** M. Stenns 2-jun-1996 *************************************************************************/ int pop_uidl (POP *p) { Message *mp; int msg_num; unsigned int *bindate; if ( !(p->retrieve.flags & RETR_IN_PROGRESS_FLAG) && (p->arg_count > 0) ) { msg_num = atoi(p->pop_args[1]); /* ** Is the requested message out of range? */ if ( (msg_num < 1) || (msg_num > p->msg_count) ) return(pop_msg (p,POP_FAILURE,"Message %d out of range (1 - %u).",msg_num,p->msg_count)); /* ** Get a pointer to the message in the message list */ mp = &p->mptr[msg_num-1]; /* ** Is the message already flagged for deletion? */ if ( mp->msg_flags & (MSG_DEL_FLAG + MSG_UNAVAILABLE_FLAG) ) { if ( mp->msg_flags & MSG_DEL_FLAG ) return pop_msg (p,POP_FAILURE,"Message %d has been deleted.",msg_num); else return pop_msg (p,POP_FAILURE,"Message %d is unavailable.",msg_num); } /* ** Display message information */ bindate = (unsigned int *) &mp->bindate; return(pop_msg(p,POP_SUCCESS,"%u %s_%u%u",mp->number, p->user, bindate[1], bindate[0])); } /* ** Message number was not supplied - display the entire list of messages */ /* ** Walk through the message information list. ** Don't show messages marked as deleted. */ if ( !allocate_send_buffer (p) ) return POP_FAILURE; if ( !(p->retrieve.flags & RETR_IN_PROGRESS_FLAG) ) { pop_msg(p,POP_SUCCESS," "); p->retrieve.lines_sent = 0; p->retrieve.bufsize = 0; p->retrieve.flags |= RETR_IN_PROGRESS_FLAG; } p->retrieve.send_bufsize = 0; for (mp = &p->mptr[p->retrieve.lines_sent]; p->retrieve.lines_sent < p->msg_count; p->retrieve.lines_sent++, mp++) { if ( !(mp->msg_flags & (MSG_DEL_FLAG + MSG_UNAVAILABLE_FLAG)) ) { bindate = (unsigned int *) &mp->bindate; if ( !netbprintf (p,"%u %s_%u%u\r\n",mp->number, p->user , bindate[1], bindate[0])) break; } } if ( p->retrieve.lines_sent == p->msg_count ) { netb_end (p); p->retrieve.function = NULL; } else p->retrieve.function = pop_uidl; netwrite_async (p, p->retrieve.send_buffer, p->retrieve.send_bufsize); return POP_SUCCESS; } /***************************************************************************** ** Pop_parse: Parse an input line from a POP client ** into null-delimited tokens ** Return: the number of tokens extracted minus the command itself *****************************************************************************/ int pop_parse (POP *p, char *buf) { char *tok; int numtok; /* ** Loop through the POP command array */ for (numtok = 0; numtok < MAXARGCOUNT; numtok++) p->pop_args[numtok] = ""; for (tok = strtok(buf, " \r\n"), numtok = 0; tok && (numtok < MAXARGCOUNT); tok=strtok(NULL, " \r\n"), numtok++) { p->pop_args[numtok] = tok; } lower(p->pop_command); return numtok - 1; } /*************************************************************************** ** Pop_pass - Verify user's password in the VMS authorization file ** Return - POP_SUCCESS or POP_FAILURE ***************************************************************************/ int pop_pass (POP *p) { int status; char password[33],username[13]; if ( !valid_vms_user(p) ) { status = 0; /* If username is invalid, pretend it's a bad password */ check_scan_intrusion (p, "", SCAN_INVALID_USER); } else { strncpy(password, p->pop_args[1], sizeof password - 1); strncpy(username, p->user, sizeof username - 1); status = PWDcheck (upper(username),upper(password)); if (status == 1) status = check_scan_intrusion (p, "", SCAN_NORMAL); else if (status > 1) { pop_log (LOG_ERROR, p, "PWDcheck: %s",vms_message(status)); status = 0; } else check_scan_intrusion (p, password, SCAN_INVALID_PASS); } return pop_authcheck (p, status); } /*************************************************************************** ** Pop_authcheck - make final checks ** Return - POP_SUCCESS or POP_FAILURE ***************************************************************************/ int pop_authcheck (POP *p, int status) { switch (status) { case 0: authentication_failures++; p->authentication_attempts++; pop_log(LOG_ERROR, p, "user account \"%s\" not accessible.", p->user); return(pop_msg(p,POP_FAILURE, "user account \"%s\" not accessible.", p->user)); break; case 1: status = mail_open_user_context(p); if ( vms_error(status) ) { server_shutdown = status; return(pop_msg(p,POP_FAILURE,"Opening mail user context: %s", vms_message(status))); } else { status = mail_user_info(p); if (vms_error(status)) return(pop_msg(p,POP_FAILURE,"Getting mail user info: %s", vms_message(status))); else { status = mail_open_file_context(p); if (p->has_no_mail_file) { pop_msg(p,POP_SUCCESS,"Username/password combination ok"); p->folder_name = "empty"; return(POP_SUCCESS); } else if (vms_error(status)) return(pop_msg(p,POP_FAILURE,"Opening mail file context: %s", vms_message(status))); else { status = mail_open_message_context(p); if (vms_error(status)) return(pop_msg(p,POP_FAILURE, "Opening mail message context: %s", vms_message(status))); else { int messages = 0; if (!p->newmail_selected) { p->folder_name = NEWMAIL_FOLDER; status = mail_folder_select(p,NEWMAIL_FOLDER,&messages); if (use_mail_folder) { if (vms_error(status)) return(pop_msg(p,POP_FAILURE, "Selecting mail folder: %s",vms_message(status))); /* * if compiled with the MAIL_FOLDER option, IUPOP3 moves * all new messages into the mail folder and presents the * mail folder to the client instead of the newmail folder. */ if (messages) pop_log (LOG_DEBUG, p, "moving %d messages to the mail folder", messages); status = mail_message_new_count (p, messages); while ((status == SS$_NORMAL) && messages) { status = mail_message_move (p, MAIL_FOLDER, messages); messages--; } status = mail_folder_select (p, MAIL_FOLDER, &messages); p->folder_name = MAIL_FOLDER; } /* limit the number of newmail messages per connection */ p->msg_count = Min(messages, max_messages); p->msgs_deleted = 0; } if (vms_error(status)) return(pop_msg(p,POP_FAILURE, "Selecting mail folder: %s", vms_message(status))); else { status = pop_build_info(p); if (status&1) return pop_msg(p,POP_SUCCESS,"Username/password combination ok"); else return pop_msg(p,POP_FAILURE,"%s",vms_message(status)); } } } } } break; default: pop_log(LOG_ERROR, p, "error determining password validity"); return(pop_msg(p,POP_FAILURE, "Error determining password validity.")); break; } } /*************************************************************************** ** Pop_quit: Disconnect from server ** Return: Currently assumes POP_SUCCESS always... ****************************************************************************/ int pop_quit(POP *p) { int status = POP_SUCCESS; if ( p->connected ) { status = pop_msg(p, POP_SUCCESS,"IUPOP3 server at %s signing off.", myhostname); p->connected = FALSE; } return status; } /*************************************************************************** ** Pop_rset: Undelete all messages flagged for deletion ** Return: Message count and Total nuber of bytes ****************************************************************************/ int pop_rset (POP *p) { Message *mp; int i; for (i = p->msg_count, mp = p->mptr; i > 0; i--, mp++) { if (mp->msg_flags & MSG_DEL_FLAG) mp->msg_flags ^= MSG_DEL_FLAG; if (mp->msg_flags & MSG_RETR_FLAG) mp->msg_flags ^= MSG_RETR_FLAG; } p->msgs_deleted = 0; p->bytes_deleted = 0; p->last_msg = 0; /* Reset the last-message-access flag */ return (pop_msg(p,POP_SUCCESS,"%s Folder has %u messages (%u octets)", p->folder_name,p->msg_count, p->newmail_size)); } /**************************************************************************** ** Pop_send: Send the header and a specified number of lines ** from a mail message to a POP client. ** Return: POP_SUCCESS or POP_FAILURE ****************************************************************************/ int pop_send (POP *p) { Message *mp; int msg_num=0; int msg_lines=0; int status; p->retrieve.flags = 0; msg_num = atoi(p->pop_args[1]); /* ** Is requested message out of range? */ if ((msg_num < 1) || (msg_num > p->msg_count)) { pop_log(LOG_ERROR, p, "Message %d does not exist.",msg_num); return(pop_msg(p,POP_FAILURE,"Message %d does not exist.",msg_num)); } /* ** Get a pointer to the message in the message list */ mp = &p->mptr[msg_num-1]; if (mp->msg_flags & (MSG_DEL_FLAG + MSG_UNAVAILABLE_FLAG)) { /* The message is flagged for deletion */ if (mp->msg_flags & MSG_DEL_FLAG) { pop_log(LOG_ERROR, p, "Message %d has been deleted.", msg_num); status = pop_msg(p,POP_FAILURE,"Message %d has been deleted.",msg_num); } else { pop_log(LOG_ERROR, p, "Message %d is out of synch.", msg_num); status = pop_msg(p,POP_FAILURE,"Message %d temporarily unavailable.",msg_num); } return status; } /* ** If this is a TOP command, get the number of lines to send */ if (strcmp(p->pop_command,"top") == 0) { msg_lines = abs(atoi(p->pop_args[2])); if (msg_lines >= mp->lines) /* Sanity check */ msg_lines = mp->lines - 1; } else /* Assume RETR (retrieve) was issued */ { p->retrieve.flags |= RETR_IS_RETRIEVE_FLAG; msg_lines = mp->lines - 1; } /* ** Retrieve all the lines for this message. ** VMS MAIL failure logged in the log file */ p->retrieve.send_bufsize = 0; p->retrieve.lines_sent = 0; p->retrieve.mp = mp; p->retrieve.message_lines = msg_lines; status = mail_retrieve_message(p); return POP_SUCCESS; } /*************************************************************************** ** Pop_stat: Summary of NEW (unread) mail in the NEWMAIL folder ** Return: Message count and Total number of bytes for NEWMAIL folder. ****************************************************************************/ int pop_stat (POP *p) { int messages, bytes; messages = p->msg_count - p->msgs_deleted; bytes = p->newmail_size - p->bytes_deleted; pop_log(LOG_THREAD, p, "%u messages, %u bytes", messages, bytes); return pop_msg(p,POP_SUCCESS, "%u %u", messages, bytes); } /*************************************************************************** ** Pop_noop: no action ** Return: Success Message. ****************************************************************************/ int pop_noop (POP *p) { return pop_msg(p,POP_SUCCESS,"no action taken"); } /************************************************************************** ** Pop_updt: Update user's mail file. ** ** Return: POP_SUCCESS ***************************************************************************/ int pop_updt (POP *p) { pop_log(LOG_DEBUG, p, "updating %s\'s mail file", p->user); pop_quit(p); p->retrieve.send_count = 0; /* used in update_maildrop() */ p->retrieve.message_lines = p->msg_count; /* used in update_maildrop() */ p->attn_state = ATTN_UPDATE; return POP_SUCCESS; } /************************************************************************* ** Pop_user: verify existance of user in the authorization file ** currently only a dummy routine, the real checks are done ** in pop_pass() ** Return: POP_SUCCESS or POP_FAILURE *************************************************************************/ int pop_user(POP *p) { strncpy (p->user, p->pop_args[1],sizeof(p->user)); return(pop_msg(p,POP_SUCCESS,"Password required for '%s'",p->user)); } /************************************************************************* ** Pop_auth: POP3 AUTHentication command ** Currently none of the authentifications mechanism of ** specified in RFC1734 are supported. ** If anybody wishes to implement KERBEROS_V4, this would ** be the right place. ** Return: POP_FAILURE *************************************************************************/ int pop_auth (POP *p) { pop_log (LOG_THREAD,p,"Auth method '%s' not supported",p->pop_args[1]); return pop_msg(p, POP_FAILURE, "Unrecognized authentication type"); } /************************************************************************* ** Pop_apop: verify existance of user in the authorization file ** and check the password against the md5 digest sent by client ** Return: POP_SUCCESS or POP_FAILURE *************************************************************************/ int pop_apop (POP *p) { int status = 0; /* If something is invalid, pretend it's a bad password */ char shared_secret[512]; *p->user = '\0'; strncat (p->user, p->pop_args[1], sizeof p->user); lower (p->user); if (!valid_vms_user(p)) { check_scan_intrusion (p, "", SCAN_INVALID_USER); } else if (get_shared_secret (p, shared_secret, sizeof shared_secret)) { status = check_digest(&p->start_time,p->threadnum,shared_secret,p->pop_args[2]); if (status) { /* if scan_intrusion is disabled or not available, check_scan_intrusion() returns everytime TRUE */ status = check_scan_intrusion (p, "", SCAN_NORMAL); if (status && apop_check_duplicate) { status = !(PWDcheck(upper(p->user),upper(shared_secret))==1); lower (p->user); if (!status) pop_log (LOG_ERROR, p,"duplicate password detected for user %s", p->user); } } else check_scan_intrusion (p, shared_secret, SCAN_INVALID_PASS); memset (shared_secret, 0, sizeof shared_secret); } return pop_authcheck (p, status); } /***************************************************************************** ** set_auth_commands enables or disables the commands ** for user/pass and apop authorization. *****************************************************************************/ void set_auth_commands (int option) { state_table *s; for (s = states; s->command; s++) { if (!strcmp(s->command,"user")) { if ((option == AUTH_ALL) || (option == AUTH_USER)) s->ValidCurrentState = auth1; else if (option == AUTH_NONE) s->ValidCurrentState = disabled; } else if (!strcmp(s->command,"pass")) { if ((option == AUTH_ALL) || (option == AUTH_USER)) s->ValidCurrentState = auth2; else if (option == AUTH_NONE) s->ValidCurrentState = disabled; } else if (!strcmp(s->command,"apop")) { if ((option == AUTH_ALL) || (option == AUTH_APOP)) s->ValidCurrentState = auth1; else if (option == AUTH_NONE) s->ValidCurrentState = disabled; } } }