Exemple #1
0
/**
 * @brief	Initialize a TLS session for an unauthenticated POP3 session.
 * @note	RFC 2595 / section 4 dictates that the STLS/STARTTLS command should only be available in the authorization state.
 * @param	con		the connection of the POP3 client requesting the transport layer security upgrade.
 * @return	This function returns no value (all error messages are written directly to the requesting client).
 */
void pop_starttls(connection_t *con) {

	if (con->pop.session_state != 0) {
		pop_invalid(con);
		return;
	}
	else if (con_secure(con) == 1) {
		con_write_bl(con, "-ERR Session is already encrypted.\r\n", 36);
		return;
	}
	else if (con_secure(con) == -1) {
		con_write_bl(con, "-ERR This server has not been configured to support STLS.\r\n", 59);
		return;
	}

	// Tell the user that we are ready to start the negotiation.
	con_write_bl(con, "+OK Ready to start TLS negotiation.\r\n", 37);

	if (!(con->network.ssl = ssl_alloc(con->server, con->network.sockd, M_SSL_BIO_NOCLOSE))) {
		con_write_bl(con, "-ERR STARTTLS FAILED\r\n", 22);
		log_pedantic("The SSL connection attempt failed.");
		return;
	}

	stats_increment_by_name("pop.connections.secure");
	st_length_set(con->network.buffer, 0);
	con->network.line = pl_null();
	con->network.status = 1;
	pop_session_reset(con);

	return;
}
Exemple #2
0
/**
 * @brief	Get a message, in response to a POP3 DELE command.
 * @param	con		the POP3 client connection issuing the command.
 * @return	This function returns no value.
 */
void pop_dele(connection_t *con) {

	uint64_t number;
	meta_message_t *active = NULL;

	if (con->pop.session_state != 1) {
		pop_invalid(con);
		return;
	}

	// A message number is a required argument.
	if (!pop_num_parse(con, &number, true)) {
		con_write_bl(con, "-ERR The delete command requires a numeric argument.\r\n", 54);
		return;
	}

	meta_user_wlock(con->pop.user);

	if (!(active = pop_get_message(con->pop.user->messages, number))) {
		con_write_bl(con, "-ERR Message not found.\r\n", 25);
	}
	else if ((active->status & MAIL_STATUS_HIDDEN) == MAIL_STATUS_HIDDEN) {
		con_write_bl(con, "-ERR Message already deleted.\r\n", 31);
	}
	else {
		active->status += MAIL_STATUS_HIDDEN;
		con_write_bl(con, "+OK Message marked for deletion.\r\n", 34);
	}

	meta_user_unlock(con->pop.user);

	return;
}
Exemple #3
0
/**
 * @brief	Accept a username for POP3 authentication.
 * @note	This command is only allowed for sessions which have not yet been authenticated.
 * 			If the username has already been supplied pre-authentication, the old value will be overwritten with the new one.
 * @param	con		the POP3 client connection issuing the command.
 * @brief	This function returns no value.
 */
void pop_user(connection_t *con) {

	stringer_t *username, *clean;

	if (con->pop.session_state != 0) {
		pop_invalid(con);
		return;
	}

	// If they didn't pass in a valid username.
	if (!(username = pop_user_parse(con)) || !(clean = credential_address(username))) {
		con_write_bl(con, "-ERR Invalid USER command.\r\n", 28);
		st_cleanup(username);
		return;
	}

	// Check for a previously provided value and free it.
	st_cleanup(con->pop.username);
	st_free(username);

	// Store the value we were given. Until authentication, this will be the fully qualified username.
	con->pop.username = clean;

	// Tell the client everything worked.
	con_write_bl(con, "+OK Username accepted.\r\n", 24);

	return;
}
Exemple #4
0
/**
 * @brief	Get the UIDL for a message or collection of messages, in response to a POP3 UIDL command.
 * @param	con		the POP3 client connection issuing the command.
 * @return	This function returns no value.
 */
void pop_uidl(connection_t *con) {

	uint64_t number;
	inx_cursor_t *cursor;
	meta_message_t *active;
	bool_t result;

	if (con->pop.session_state != 1) {
		pop_invalid(con);
		return;
	}

	// Get the argument, if any.
	result = pop_num_parse(con, &number, false);

	meta_user_rlock(con->pop.user);

	// Output all of the messages that aren't deleted or appended.
	if (!result) {
		number = 1;
		con_print(con, "+OK %llu messages total.\r\n", pop_total_messages(con->pop.user->messages));

		if (con->pop.user->messages && (cursor = inx_cursor_alloc(con->pop.user->messages))) {

			while ((active = inx_cursor_value_next(cursor))) {

				if ((active->status & (MAIL_STATUS_APPENDED | MAIL_STATUS_HIDDEN)) == 0) {
					con_print(con, "%llu %llu\r\n", number++, active->messagenum);
				} else if ((active->status & MAIL_STATUS_APPENDED) == 0) {
					number++;
				}

			}

			inx_cursor_free(cursor);
		}

		con_write_bl(con, ".\r\n", 3);
	}
	// Output a specific message.
	else {

		if (!(active = pop_get_message(con->pop.user->messages, number))) {
			con_write_bl(con, "-ERR Message not found.\r\n", 25);
		}
		else if ((active->status & MAIL_STATUS_HIDDEN) == MAIL_STATUS_HIDDEN) {
			con_write_bl(con, "-ERR Message marked for deletion.\r\n", 35);
		}
		else {
			con_print(con, "+OK %llu %llu\r\n", number, active->messagenum);
		}

	}

	meta_user_unlock(con->pop.user);

	return;
}
Exemple #5
0
/**
 * @brief	Initialize a new POP3 connection.
 * @param	con		the newly connected POP3 client connection.
 * @return	This function returns no value.
 */
void pop_init(connection_t *con) {

	con_write_bl(con, "+OK magma\r\n", 11);
	pop_requeue(con);

	return;
}
Exemple #6
0
/**
 * @brief	Gracefully destroy a POP3 session, whether because of an error or in response to a user QUIT command.
 * @param	con		the POP3 client connection to be shut down.
 * @brief	This function returns no value.
 */
void pop_quit(connection_t *con) {

	if (con_status(con) == 2) {
		con_write_bl(con, "-ERR Unexpected connection shutdown detected. Goodbye.\r\n", 56);
	}
	else if (con_status(con) >= 0) {
		con_write_bl(con, "+OK Goodbye.\r\n", 14);
	}
	else {
		con_write_bl(con, "-ERR Network connection failure.\r\n", 34);
	}

	con_destroy(con);

	return;
}
Exemple #7
0
void molten_stats(connection_t *con) {

	size_t length;

	length = stats_get_count();

	for(size_t i = 0; i < length; i++) {

		if (con_print(con, "STAT %s %lu\r\n", stats_get_name(i), stats_get_value_by_num(i)) < 0) {
			enqueue(&molten_quit, con);
			return;
		}

	}

	length = stats_derived_count();

	for(size_t i = 0; i < length; i++) {

		if (con_print(con, "STAT %s %lu\r\n", stats_derived_name(i), stats_derived_value(i)) < 0) {
			enqueue(&molten_quit, con);
			return;
		}

	}

	con_write_bl(con, "END\r\n", 5) < 0 ? enqueue(&molten_quit, con) : enqueue(&molten_parse, con);

	return;
}
Exemple #8
0
/**
 * @brief	A function handler for invalid POP3 commands.
 * @param	con		the POP3 client connection issuing the command.
 * @return	This function returns no value.
 */
void pop_invalid(connection_t *con) {

	con->protocol.violations++;
	usleep(con->server->violations.delay);
	con_write_bl(con, "-ERR Unrecognized command.\r\n", 28);

	return;
}
Exemple #9
0
/**
 * @brief	Reset the user's mailbox, in response to a POP3 RSET command.
 * @see		pop_session_reset()
 * @param	con		the POP3 client connection issuing the command.
 * @return	This function returns no value.
 */
void pop_rset(connection_t *con) {

	int_t ret;

	if (con->pop.session_state != 1) {
		pop_invalid(con);
	}
	else if ((ret = pop_session_reset(con)) == 1) {
		con_write_bl(con, "+OK All messages were reset.\r\n", 30);
	}
	else if (ret == -1) {
		con_write_bl(con, "-ERR Message reset failed.\r\n", 28);
	}
	else {
		con_write_bl(con, "-ERR Session reset complete.\r\n", 30);
	}

	return;
}
Exemple #10
0
/**
 * @brief	Retrieve a user's message, in response to a POP3 RETR command.
 * @note	This function will fail if a deleted message was specified by the user.
 * @param	con		the POP3 client connection issuing the command.
 * @return	This function returns no value.
 */
void pop_retr(connection_t *con) {

	uint64_t number;
	meta_message_t *meta;
	mail_message_t *message;

	if (con->pop.session_state != 1) {
		pop_invalid(con);
		return;
	}

	// Which message are we getting.
	if (!pop_num_parse(con, &number, true)) {
		con_write_bl(con, "-ERR The retrieve command requires a numeric argument.\r\n", 56);
		return;
	}

	meta_user_rlock(con->pop.user);

	// Get the message.
	if (!(meta = pop_get_message(con->pop.user->messages, number))) {
		meta_user_unlock(con->pop.user);
		con_write_bl(con, "-ERR Message not found.\r\n", 25);
		return;
	}

	// Check for deletion.
	if ((meta->status & MAIL_STATUS_HIDDEN) == MAIL_STATUS_HIDDEN) {
		meta_user_unlock(con->pop.user);
		con_write_bl(con,  "-ERR This message has been marked for deletion.\r\n", 49);
		return;
	}

	// Load the message and spit back the right number of lines.
	if (!(message = mail_load_message(meta, con->pop.user, con->server, 1))) {
		meta_user_unlock(con->pop.user);
		con_write_bl(con, "-ERR The message you requested could not be loaded into memory. It has either been "
			"deleted by another connection or is corrupted.\r\n", 131);
		return;
	}

	meta_user_unlock(con->pop.user);

	// Dot stuff the message.
	st_replace(&(message->text), PLACER("\n.", 2), PLACER("\n..", 3));

	// Tell the client to prepare for a message. The size is strictly informational.
	con_print(con, "+OK %u characters follow.\r\n", st_length_get(message->text));

	// We use raw socket IO because it is much faster when writing large amounts of data.
	con_write_st(con, message->text);

	// If the message didn't end with a line break, spit two.
	if (*(st_char_get(message->text) + st_length_get(message->text) - 1) == '\n') {
		con_write_bl(con, ".\r\n", 3);
	}
	else {
		con_write_bl(con, "\r\n.\r\n", 5);
	}

	mail_destroy(message);

	return;
}
Exemple #11
0
/**
 * @brief	Execute a POP3 no-operation command.
 * @param	con		the POP3 client connection issuing the command.
 * @return	This function returns no value.
 */
void pop_noop(connection_t *con) {

	con_write_bl(con, "+OK\r\n", 5);
	return;
}
Exemple #12
0
/**
 * @brief	Accept and verify a password for POP3 authentication.
 * @note	This command is only allowed for sessions which have not yet been authenticated, but which have already supplied a username.
 *			If the username/password combo was validated, the account information is retrieved and checked to see if it is locked.
 *			After successful authentication, this function will prohibit insecure connections for any user configured to use SSL only,
 *			and enforce the existence of only one POP3 session at a time.
 *			Finally, the database Log table for this user's POP3 access is updated, and all the user's messages are retrieved.
 * @param	con		the POP3 client connection issuing the command.
 * @return	This function returns no value.
 */
void pop_pass(connection_t *con) {

	int_t state;
	credential_t *cred;
	stringer_t *password, *username;

	if (con->pop.session_state != 0) {
		pop_invalid(con);
		return;
	}

	// The user must come before the PASS command.
	if (st_empty(con->pop.username)) {
		con_write_bl(con, "-ERR You must supply a username first.\r\n", 40);
		return;
	}

	// If they didn't pass in a valid password.
	if (!(password = pop_pass_parse(con))) {
		con_write_bl(con, "-ERR Invalid PASS command.\r\n", 28);
		return;
	}

	// Hash the password.
	// First we need to get the regular username from the fully qualified one.
	if (!(username = credential_username(con->pop.username))) {
		con_write_bl(con, "-ERR Internal server error. Please try again later.\r\n", 53);
		st_wipe(password);
		st_free(password);
	}

	st_free(con->pop.username);
	con->pop.username = username;

	if (!(cred = credential_alloc_auth(con->pop.username, password))) {
		con_write_bl(con, "-ERR Internal server error. Please try again later.\r\n", 53);
		st_wipe(password);
		st_free(password);
		return;
	}

	st_wipe(password);
	st_free(password);

	// Pull the user info out.
	state = meta_get(con->pop.username, cred->auth.domain, cred->auth.password, cred->auth.key, META_PROT_POP, META_GET_MESSAGES, &(con->pop.user));

	// Securely delete this information, as these are the keys to the castle.
	credential_free(cred);

	// Not found, or invalid password.
	if (state == 0) {
		con_write_bl(con,  "-ERR The username and password combination is invalid.\r\n", 56);
		return;
	}
	// Internal error.
	else if (state < 0 || !con->pop.user) {
		con_write_bl(con, "-ERR [SYS/TEMP] Internal server error. Please try again later.\r\n", 64);
		return;
	}

	// Locks
	else if (con->pop.user->lock_status != 0) {
		// What type of lock is it.
		if (con->pop.user->lock_status == 1) {
			con_write_bl(con, "-ERR [SYS/PERM] This account has been administratively locked.\r\n", 64);
		}
		else if (con->pop.user->lock_status == 2) {
			con_write_bl(con, "-ERR [SYS/PERM] This account has been locked for inactivity.\r\n", 62);
		}
		else if (con->pop.user->lock_status == 3) {
			con_write_bl(con, "-ERR [SYS/PERM] This account has been locked on suspicion of abuse.\r\n", 69);
		}
		else if (con->pop.user->lock_status == 4) {
			con_write_bl(con, "-ERR [SYS/PERM] This account has been locked at the request of the user.\r\n", 74);
		}
		else {
			con_write_bl(con, "-ERR [SYS/PERM] This account has been locked.\r\n", 47);
		}

		con->pop.user = NULL;
		meta_remove(con->pop.username, META_PROT_POP);
		return;
	}

	// SSL check.
	else if ((con->pop.user->flags & META_USER_SSL) == META_USER_SSL && con_secure(con) != 1) {
		con->pop.user = NULL;
		meta_remove(con->pop.username, META_PROT_POP);
		con_write_bl(con, "-ERR [SYS/PERM] This user account is configured to require that all POP sessions be connected over SSL.\r\n", 105);
		return;
	}

	// Single session check.
	else if (con->pop.user->refs.pop != 1) {
		con->pop.user = NULL;
		meta_remove(con->pop.username, META_PROT_POP);
		con_write_bl(con, "-ERR [IN-USE] This account is being used by another session. Please try again in a few minutes.\r\n", 97);
		return;
	}

	// Debug logging.
	log_pedantic("User %.*s logged in from %s via POP. {poprefs = %lu, imaprefs = %lu, messages = %lu}",
		st_length_int(con->pop.username), st_char_get(con->pop.username), st_char_get(con_addr_presentation(con, MANAGEDBUF(256))),
		con->pop.user->refs.pop, con->pop.user->refs.imap, con->pop.user->messages ? inx_count(con->pop.user->messages) : 0);

	// Update the log and unlock the session.
	meta_data_update_log(con->pop.user, META_PROT_POP);

	meta_user_wlock(con->pop.user);
	meta_messages_login_update(con->pop.user, META_LOCKED);
	meta_user_unlock(con->pop.user);

	// Update session state.
	con->pop.session_state = 1;

	// Tell the client everything worked.
	con_write_bl(con, "+OK Password accepted.\r\n", 24);

	return;
}
/**
 * @brief	Perform client command processing on an established imap session.
 * @note	This function will read the next line of user input, parse the command, and then attempt to execute it with the appropriate handler.
 * @param	con		a pointer to the connection object underlying the imap session.
 * @return	This function returns no value.
 */
void imap_process(connection_t *con) {

	int_t state;
	command_t *command, client = { .function = NULL };

	// If the connection indicates an error occurred, or the socket was closed by the client we send the connection to the logout function.
	if (((state = con_read_line(con, false)) < 0) || (state == -2)) {
		con->command = NULL;
		enqueue(&imap_logout, con);
		return;
	}
	else if (pl_empty(con->network.line) && ((con->protocol.spins++) + con->protocol.violations) > con->server->violations.cutoff) {
		con->command = NULL;
		enqueue(&imap_logout, con);
		return;
	}
	else if (pl_empty(con->network.line)) {
		con->command = NULL;
		enqueue(&imap_process, con);
		return;
	}

	// Parse the line into its tag and command elements.
	if ((state = imap_command_parser(con)) < 0) {

		// Try to be helpful about the parsing error.
		if (state == -1) {
			con_write_bl(con, "* BAD Unable to parse the command tag.\r\n", 40);
		}
		else if (state == -2) {
			con_print(con, "%.*s BAD Unable to parse the command.\r\n", st_length_int(con->imap.tag), st_char_get(con->imap.tag));
		}
		else {
			con_print(con, "%.*s BAD The command arguments were submitted using an invalid syntax.\r\n", st_length_int(con->imap.tag), st_char_get(con->imap.tag));
		}

		// If the client keeps breaking rules drop them.
		if (((con->protocol.spins++) + con->protocol.violations) > con->server->violations.cutoff) {
			con->command = NULL;
			enqueue(&imap_logout, con);
			return;
		}

		// Requeue and hope the next line of data is useful.
		con->command = NULL;
		enqueue(&imap_process, con);
		return;

	}

	client.string = st_char_get(con->imap.command);
	client.length = st_length_get(con->imap.command);

	if ((command = bsearch(&client, imap_commands, sizeof(imap_commands) / sizeof(imap_commands[0]), sizeof(command_t), imap_compare))) {

		con->command = command;
		con->protocol.spins = 0;

		if (command->function == &imap_logout) {
			enqueue(command->function, con);
		}
		else {
			requeue(command->function, &imap_requeue, con);
		}
	}
	else {
		con->command = NULL;
		requeue(&imap_invalid, &imap_requeue, con);
	}
	return;
}
Exemple #14
0
/**
 * @brief	Extract the contents of a literal string and advance the position of the parser stream.
 * @note	This function expects as input a string beginning with '{' and followed by a numerical string, an optional '+', and a closing '}'.
 	 	 	After reading in the numerical size parameter, it then attempts to read in that many bytes of input from the network stream.
 * @param	con		the client IMAP connection passing the literal string as input to the server.
 * @param	output	the address of a managed string that will receive a copy of the literal string's contents on success, or NULL on failure or if it is zero length.
 * @param	start	the address of a pointer to the start of the buffer to be parsed (beginning with '{'), that will also be updated to
 * 					point to the next argument in the sequence on success.
 * @param	length	a pointer to a size_t variable that contains the length of the string to be parsed, and that will be updated to reflect
 * 					the length of the remainder of the input string that follows the parsed literal string.
 * @return	-1 on general or parse error or if an enclosing pair of double quotes was not found, or 1 if the supplied quoted string was valid.
 */
int_t imap_parse_literal(connection_t *con, stringer_t **output, chr_t **start, size_t *length) {

	chr_t *holder;
	int_t plus = 0;
	stringer_t *result;
	size_t characters, left;
	ssize_t nread;
	uint64_t literal, number;

	// Get setup.
	holder = *start;
	left = *length;
	*output = NULL;

	// Skip the opening bracket.
	if (*holder != '{' || !left) {
		return -1;
	}
	else {
		holder++;
		left--;
	}

	// Advance until we have a break character.
	while (left && *holder >= '0' && *holder <= '9') {
		holder++;
		left--;
	}

	// Store the length.
	characters = holder - *start - 1;

	if (left && *holder == '+') {
		plus = 1;
		holder++;
		left--;
	}

	if (*holder != '}' || !characters) {
		return -1;
	}

	// Convert to a number. Make sure the number is positive.
	if (!uint64_conv_bl(*start + 1, characters, &number)) {
		return -1;
	}

	literal = (size_t)number;

	// If the number is larger than 128 megabytes, then reject it.
	if (!plus && number > 134217728) {
		return -1;
	}
	// They client is already transmitting, so read the entire file, then reject it.
	else if (number > 134217728) {

		while (number > 0) {

			// Read the data.
			if ((nread = con_read(con)) <= 0) {
				log_pedantic("The connection was dropped while reading the literal.");
				return -1;
			}

			// Deal with signedness problem.
			characters = nread;

			if (number > (uint64_t)characters) {
				number -= characters;
			}
			else {

				// If we have any extra characters in the buffer, move them to the beginning.
				if ((uint64_t)characters > number) {
					mm_move(st_char_get(con->network.buffer), st_char_get(con->network.buffer) + number, characters - number);
					st_length_set(con->network.buffer, characters - number);
					con->network.line = line_pl_st(con->network.buffer, 0);
				}
				else {
					st_length_set(con->network.buffer, 0);
					con->network.line = pl_null();
				}

				// Make sure we have a full line.
				if (pl_empty(con->network.line) && con_read_line(con, true) <= 0) {
					log_pedantic("The connection was dropped while reading the literal.");
					return -1;
				}

				number = 0;
			}

		}

		return -1;
	}

	// If this is not a plus literal, output the proceed statement.
	if (!plus) {
		con_write_bl(con, "+ GO\r\n", 6);
	}

	// Handle the special case of a zero length literal.
	if (literal == 0) {

		// Read the next line.
		if (con_read_line(con, true) <= 0) {
			log_pedantic("The connection was dropped while reading the literal.");
			return -1;
		}

		*start = st_char_get(con->network.buffer);
		*length = pl_length_get(con->network.line);

		// There should be a space before the next argument.
		if (*length && **start == ' ') {
			(*start)++;
			(*length)--;
		}

		return 1;
	}

	// Allocate a stringer for the buffer.
	if (!(result = st_alloc(literal))) {
		log_pedantic("Unable to allocate a buffer of %lu bytes for the literal argument.", literal);
		return -1;
	}

	// So we know how many more characters to read.
	left = literal;

	// Where we put the data.
	holder = st_char_get(result);

	// Keep looping until we run out of data.
	while (left) {

		// Read the data.
		if ((nread = con_read(con)) <= 0) {
			log_pedantic("The connection was dropped while reading the literal.");
			st_free(result);
			return -1;
		}

		characters = nread;

		// If we have a buffer, copy the data into the buffer.
		mm_copy(holder, st_char_get(con->network.buffer), (left > characters) ? characters : left);

		if (left > characters) {
			holder += characters;
			left -= characters;
		}
		else {
		 	st_length_set(result, literal);

			// If we have any extra characters in the buffer, move them to the beginning.
			if (characters > left) {
				mm_move(st_char_get(con->network.buffer), st_char_get(con->network.buffer) + left, characters - left);
				st_length_set(con->network.buffer, characters - left);
				con->network.line = line_pl_st(con->network.buffer, 0);
			}
			else {
					st_length_set(con->network.buffer, 0);
					con->network.line = pl_null();
			}

			// Make sure we have a full line.
			if (pl_empty(con->network.line) && con_read_line(con, true) <= 0) {
				log_pedantic("The connection was dropped while reading the literal.");
				st_free(result);
				return -1;
			}
			left = 0;
		}
	}

	*start = st_char_get(con->network.buffer);
	*length = pl_length_get(con->network.line);

	// There should be a space before the next argument.
	if (*length && **start == ' ') {
		(*start)++;
		(*length)--;
	}

	if (result != NULL) {
		*output = result;
	}
	else {
		return -1;
	}

	return 1;
}
Exemple #15
0
void molten_invalid(connection_t *con) {

	con_write_bl(con, "ERROR\r\n", 7) < 0 ? enqueue(&molten_quit, con) : enqueue(&molten_parse, con);
	return;
}