Пример #1
0
void socket_write_cb(int fd UNUSED, short what UNUSED, void *arg)
{
	ClientSession_t *session = (ClientSession_t *)arg;
	TRACE(TRACE_DEBUG,"[%p] state: [%d]", session, session->state);

	if (session->ci->cb_write)
		session->ci->cb_write(session);

	switch(session->state) {
		case CLIENTSTATE_INITIAL_CONNECT:
		case CLIENTSTATE_NON_AUTHENTICATED:
			TRACE(TRACE_DEBUG,"reset timeout [%d]", server_conf->login_timeout);
			client_session_set_timeout(session, server_conf->login_timeout);
			break;

		case CLIENTSTATE_AUTHENTICATED:
		case CLIENTSTATE_SELECTED:
			TRACE(TRACE_DEBUG,"reset timeout [%d]", server_conf->timeout);
			client_session_set_timeout(session, server_conf->timeout);
			break;

		default:
		case CLIENTSTATE_ANY:
			break;

		case CLIENTSTATE_LOGOUT:
		case CLIENTSTATE_QUIT:
		case CLIENTSTATE_ERROR:
			client_session_bailout(&session);
			break;


	}
}
Пример #2
0
static int _pop3_session_authenticated(ClientSession_T *session, int user_idnr)
{
    ClientBase_T *ci = session->ci;
    int result = 0;

    session->state = CLIENTSTATE_AUTHENTICATED;

    ci_authlog_init(ci, THIS_MODULE, (const char *)session->username, AUTHLOG_ACT);
    client_session_set_timeout(session, server_conf->timeout);

    /* user seems to be valid, let's build a session */
    TRACE(TRACE_DEBUG, "validation OK, building a session for user [%s]",
          session->username);

    /* if pop_before_smtp is active, log this ip */
    if (pop_before_smtp)
        db_log_ip(ci->src_ip);

    result = db_createsession(user_idnr, session);
    if (result == 1) {
        ci_write(ci, "+OK %s has %" PRIu64 " messages (%" PRIu64 " octets)\r\n",
                 session->username,
                 session->virtual_totalmessages,
                 session->virtual_totalsize);
        TRACE(TRACE_NOTICE, "user %s logged in [messages=%" PRIu64 ", octets=%" PRIu64 "]",
              session->username,
              session->virtual_totalmessages,
              session->virtual_totalsize);
    } else
        session->SessionResult = 4;	/* Database error. */

    return result;
}
Пример #3
0
int lmtp_handle_connection(client_sock *c)
{
	ClientSession_T *session = client_session_new(c);
	client_session_set_timeout(session, server_conf->login_timeout);
	reset_callbacks(session);
        send_greeting(session);
	return 0;
}
Пример #4
0
int tims_handle_connection(client_sock *c)
{
	ClientSession_T *session = client_session_new(c);
	session->state = CLIENTSTATE_NON_AUTHENTICATED;
	client_session_set_timeout(session, server_conf->login_timeout);
	send_greeting(session);
	reset_callbacks(session);
	return 0;
}
Пример #5
0
int pop3_handle_connection(client_sock *c)
{
	ClientSession_t *session = client_session_new(c);
	session->state = CLIENTSTATE_INITIAL_CONNECT;
	client_session_set_timeout(session, server_conf->login_timeout);
        reset_callbacks(session);
        send_greeting(session);
	return 0;
}
Пример #6
0
int tims(ClientSession_t *session)
{
	/* returns values:
	 *  0 to quit
	 * -1 on failure
	 *  1 on success */
	
	char *arg;
	size_t scriptlen = 0;
	int ret;
	char *script = NULL, *scriptname = NULL;
	sort_result_t *sort_result = NULL;
	clientbase_t *ci = session->ci;

	TRACE(TRACE_DEBUG,"[%p] [%d][%s]", session, session->command_type, commands[session->command_type]);
	switch (session->command_type) {
	case TIMS_LOUT:
		ci_write(ci, "OK \"Bye.\"\r\n");
		session->state = QUIT;
		return 1;
		
	case TIMS_STLS:
		ci_write(ci, "NO\r\n");
		return 1;
		
	case TIMS_CAPA:
		send_greeting(session);
		return 1;
		
	case TIMS_AUTH:
		/* We currently only support plain authentication,
		 * which means that the command we accept will look
		 * like this: Authenticate "PLAIN" "base64-password"
		 * */
		session->args = g_list_first(session->args);
		if (! (session->args && session->args->data))
			return 1;

		arg = (char *)session->args->data;
		if (strcasecmp(arg, "PLAIN") == 0) {
			int i = 0;
			u64_t useridnr;
			if (! g_list_next(session->args))
				return tims_error(session, "NO \"Missing argument.\"\r\n");	
			session->args = g_list_next(session->args);
			arg = (char *)session->args->data;

			char **tmp64 = NULL;

			tmp64 = base64_decodev(arg);
			if (tmp64 == NULL)
				return tims_error(session, "NO \"SASL decode error.\"\r\n");
			for (i = 0; tmp64[i] != NULL; i++)
				;
			if (i < 3)
				tims_error(session, "NO \"Too few encoded SASL arguments.\"\r\n");

			/* The protocol specifies that the base64 encoding
			 * be made up of three parts: proxy, username, password
			 * Between them are NULLs, which are conveniently encoded
			 * by the base64 process... */
			if (auth_validate(ci, tmp64[1], tmp64[2], &useridnr) == 1) {
				ci_authlog_init(ci, THIS_MODULE, tmp64[1], AUTHLOG_ACT);

				ci_write(ci, "OK\r\n");
				session->state = AUTH;
				session->useridnr = useridnr;
				session->username = g_strdup(tmp64[1]);
				session->password = g_strdup(tmp64[2]);

				client_session_set_timeout(session, server_conf->timeout);

			} else {
				ci_authlog_init(ci, THIS_MODULE, tmp64[1], AUTHLOG_ERR);
				g_strfreev(tmp64);
				return tims_error(session, "NO \"Username or password incorrect.\"\r\n");
			}
			g_strfreev(tmp64);
		} else
			return tims_error(session, "NO \"Authentication scheme not supported.\"\r\n");

		return 1;

	case TIMS_PUTS:
		if (session->state != AUTH) {
			ci_write(ci, "NO \"Please authenticate first.\"\r\n");
			break;
		}
		
		session->args = g_list_first(session->args);
		if (! (session->args && session->args->data))
			return 1;

		scriptname = (char *)session->args->data;
		session->args = g_list_next(session->args);
		assert(session->args);

		script = (char *)session->args->data;

		scriptlen = strlen(script);
		TRACE(TRACE_INFO, "Client sending script of length [%ld]", scriptlen);
		if (scriptlen >= UINT_MAX)
			return tims_error(session, "NO \"Invalid script length.\"\r\n");

		if (dm_sievescript_quota_check(session->useridnr, scriptlen))
			return tims_error(session, "NO \"Script exceeds available space.\"\r\n");

		/* Store the script temporarily,
		 * validate it, then rename it. */
		if (dm_sievescript_add(session->useridnr, "@!temp-script!@", script)) {
			dm_sievescript_delete(session->useridnr, "@!temp-script!@");
			return tims_error(session, "NO \"Error inserting script.\"\r\n");
		}
		
		sort_result = sort_validate(session->useridnr, "@!temp-script!@");
		if (sort_result == NULL) {
			dm_sievescript_delete(session->useridnr, "@!temp-script!@");
			return tims_error(session, "NO \"Error inserting script.\"\r\n");
		} else if (sort_get_error(sort_result) > 0) {
			dm_sievescript_delete(session->useridnr, "@!temp-script!@");
			return tims_error(session, "NO \"Script error: %s.\"\r\n", sort_get_errormsg(sort_result));
		} 
		/* According to the draft RFC, a script with the same
		 * name as an existing script should [atomically] replace it. */
		if (dm_sievescript_rename(session->useridnr, "@!temp-script!@", scriptname))
			return tims_error(session, "NO \"Error inserting script.\"\r\n");

		ci_write(ci, "OK \"Script successfully received.\"\r\n");

		break;
		
	case TIMS_SETS:
		if (session->state != AUTH) {
			ci_write(ci, "NO \"Please authenticate first.\"\r\n");
			break;
		}
		

		scriptname = NULL;

		session->args = g_list_first(session->args);
		if ((session->args && session->args->data))
			scriptname = (char *)session->args->data;

		if (strlen(scriptname)) {
			if (! dm_sievescript_activate(session->useridnr, scriptname))
				ci_write(ci, "NO \"Error activating script.\"\r\n");
			else
				ci_write(ci, "OK \"Script activated.\"\r\n");
		} else {
			ret = dm_sievescript_get(session->useridnr, &scriptname);
			if (scriptname == NULL) {
				ci_write(ci, "OK \"No scripts are active at this time.\"\r\n");
			} else {
				if (! dm_sievescript_deactivate(session->useridnr, scriptname))
					ci_write(ci, "NO \"Error deactivating script.\"\r\n");
				else
					ci_write(ci, "OK \"All scripts deactivated.\"\r\n");
				g_free(scriptname);
			}
		}
		return 1;
		
	case TIMS_GETS:
		if (session->state != AUTH) {
			ci_write(ci, "NO \"Please authenticate first.\"\r\n");
			break;
		}
		
		session->args = g_list_first(session->args);
		if (! (session->args && session->args->data))
			return 1;

		scriptname = (char *)session->args->data;

		if (! strlen(scriptname))
			return tims_error(session, "NO \"Script name required.\"\r\n");

		ret = dm_sievescript_getbyname(session->useridnr, scriptname, &script);
		if (script == NULL) {
			return tims_error(session, "NO \"Script not found.\"\r\n");
		} else if (ret < 0) {
			g_free(script);
			return tims_error(session, "NO \"Internal error.\"\r\n");
		} else {
			ci_write(ci, "{%u+}\r\n", (unsigned int)strlen(script));
			ci_write(ci, "%s\r\n", script);
			ci_write(ci, "OK\r\n");
			g_free(script);
		}
		return 1;
		
	case TIMS_DELS:
		if (session->state != AUTH) {
			ci_write(ci, "NO \"Please authenticate first.\"\r\n");
			break;
		}
		
		session->args = g_list_first(session->args);
		if (! (session->args && session->args->data))
			return 1;

		scriptname = (char *)session->args->data;

		if (! strlen(scriptname))
			return tims_error(session, "NO \"Script name required.\"\r\n");

		if (! dm_sievescript_delete(session->useridnr, scriptname))
			return tims_error(session, "NO \"Error deleting script.\"\r\n");
		else
			ci_write(ci, "OK \"Script deleted.\"\r\n", scriptname);

		return 1;

	case TIMS_SPAC:
		if (session->state != AUTH) {
			ci_write(ci, "NO \"Please authenticate first.\"\r\n");
			break;
		}

		// Command is in format: HAVESPACE "scriptname" 12345
		// TODO: this is a fake
		if (dm_sievescript_quota_check(session->useridnr, 12345) == DM_SUCCESS)
			ci_write(ci, "OK (QUOTA)\r\n");
		else
			ci_write(ci, "NO (QUOTA) \"Quota exceeded\"\r\n");
		return 1;
		
	case TIMS_LIST:
		if (session->state != AUTH) {
			ci_write(ci, "NO \"Please authenticate first.\"\r\n");
			break;
		}
		
		GList *scriptlist = NULL;

		if (dm_sievescript_list (session->useridnr, &scriptlist) < 0) {
			ci_write(ci, "NO \"Internal error.\"\r\n");
		} else {
			if (g_list_length(scriptlist) == 0) {
				/* The command hasn't failed, but there aren't any scripts */
				ci_write(ci, "OK \"No scripts found.\"\r\n");
			} else {
				scriptlist = g_list_first(scriptlist);
				while (scriptlist) {
					sievescript_info_t *info = (sievescript_info_t *) scriptlist->data;
					ci_write(ci, "\"%s\"%s\r\n", info->name, (info-> active == 1 ?  " ACTIVE" : ""));
					if (! g_list_next(scriptlist)) break;
					scriptlist = g_list_next(scriptlist);
				}
				ci_write(ci, "OK\r\n");
			}
			g_list_destroy(scriptlist);
		}
		return 1;
		
	default:
		return tims_error(session, "NO \"What are you trying to say here?\"\r\n");
	}

	if (sort_result)
		sort_free_result(sort_result);
	
	return 1;
}
Пример #7
0
int lmtp(ClientSession_T * session)
{
	DbmailMessage *msg;
	ClientBase_T *ci = session->ci;
	int helpcmd;
	const char *class, *subject, *detail;
	size_t tmplen = 0, tmppos = 0;
	char *tmpaddr = NULL, *tmpbody = NULL, *arg;

	switch (session->command_type) {

	case LMTP_QUIT:
		ci_write(ci, "221 %s BYE\r\n", session->hostname);
		session->state = QUIT;
		return 1;

	case LMTP_NOOP:
		ci_write(ci, "250 OK\r\n");
		return 1;

	case LMTP_RSET:
		ci_write(ci, "250 OK\r\n");
		lmtp_rset(session,TRUE);
		return 1;

	case LMTP_LHLO:
		/* Reply wth our hostname and a list of features.
		 * The RFC requires a couple of SMTP extensions
		 * with a MUST statement, so just hardcode them.
		 * */
		ci_write(ci, "250-%s\r\n250-PIPELINING\r\n"
			"250-ENHANCEDSTATUSCODES\r\n250 SIZE\r\n", 
			session->hostname);
				/* This is a SHOULD implement:
				 * "250-8BITMIME\r\n"
				 * Might as well do these, too:
				 * "250-CHUNKING\r\n"
				 * "250-BINARYMIME\r\n"
				 * */
		client_session_reset(session);
		session->state = AUTH;
		client_session_set_timeout(session, server_conf->timeout);

		return 1;

	case LMTP_HELP:
	
		session->args = g_list_first(session->args);
		if (session->args && session->args->data)
			arg = (char *)session->args->data;
		else
			arg = NULL;

		if (arg == NULL)
			helpcmd = LMTP_END;
		else
			for (helpcmd = LMTP_LHLO; helpcmd < LMTP_END; helpcmd++)
				if (strcasecmp (arg, commands[helpcmd]) == 0)
					break;

		TRACE(TRACE_DEBUG, "LMTP_HELP requested for commandtype %d", helpcmd);

		if ((helpcmd == LMTP_LHLO) || (helpcmd == LMTP_DATA) || 
			(helpcmd == LMTP_RSET) || (helpcmd == LMTP_QUIT) || 
			(helpcmd == LMTP_NOOP) || (helpcmd == LMTP_HELP)) {
			ci_write(ci, "%s", LMTP_HELP_TEXT[helpcmd]);
		} else
			ci_write(ci, "%s", LMTP_HELP_TEXT[LMTP_END]);
		return 1;

	case LMTP_VRFY:
		/* RFC 2821 says this SHOULD be implemented...
		 * and the goal is to say if the given address
		 * is a valid delivery address at this server. */
		ci_write(ci, "502 Command not implemented\r\n");
		return 1;

	case LMTP_EXPN:
		/* RFC 2821 says this SHOULD be implemented...
		 * and the goal is to return the membership
		 * of the specified mailing list. */
		ci_write(ci, "502 Command not implemented\r\n");
		return 1;

	case LMTP_MAIL:
		/* We need to LHLO first because the client
		 * needs to know what extensions we support.
		 * */
		if (session->state != AUTH) {
			ci_write(ci, "550 Command out of sequence.\r\n");
			return 1;
		} 
		if (g_list_length(session->from) > 0) {
			ci_write(ci, "500 Sender already received. Use RSET to clear.\r\n");
			return 1;
		}
		/* First look for an email address.
		 * Don't bother verifying or whatever,
		 * just find something between angle brackets!
		 * */

		session->args = g_list_first(session->args);
		if (! (session->args && session->args->data))
			return 1;
		arg = (char *)session->args->data;

		if (find_bounded(arg, '<', '>', &tmpaddr, &tmplen, &tmppos) < 0) {
			ci_write(ci, "500 No address found. Missing <> boundries.\r\n");
			return 1;
		}

		/* Second look for a BODY keyword.
		 * See if it has an argument, and if we
		 * support that feature. Don't give an OK
		 * if we can't handle it yet, like 8BIT!
		 * */

		/* Find the '=' following the address
		 * then advance one character past it
		 * (but only if there's more string!)
		 * */
		if ((tmpbody = strstr(arg + tmppos, "=")) != NULL)
			if (strlen(tmpbody))
				tmpbody++;

		/* This is all a bit nested now... */
		if (tmpbody) {
			if (MATCH(tmpbody, "8BITMIME")) {   // RFC1652
				ci_write(ci, "500 Please use 7BIT MIME only.\r\n");
				return 1;
			}
			if (MATCH(tmpbody, "BINARYMIME")) { // RFC3030
				ci_write(ci, "500 Please use 7BIT MIME only.\r\n");
				return 1;
			}
		}

		session->from = g_list_prepend(session->from, g_strdup(tmpaddr));
		ci_write(ci, "250 Sender <%s> OK\r\n", (char *)(session->from->data));

		g_free(tmpaddr);

		return 1;

	case LMTP_RCPT:
		if (session->state != AUTH) {
			ci_write(ci, "550 Command out of sequence.\r\n");
			return 1;
		} 

		session->args = g_list_first(session->args);
		if (! (session->args && session->args->data))
			return 1;
		arg = (char *)session->args->data;

		if (find_bounded(arg, '<', '>', &tmpaddr, &tmplen, &tmppos) < 0 || tmplen < 1) {
			ci_write(ci, "500 No address found. Missing <> boundries or address is null.\r\n");
			return 1;
		}

		Delivery_T *dsnuser = g_new0(Delivery_T,1);

		dsnuser_init(dsnuser);

		/* find_bounded() allocated tmpaddr for us, and that's ok
		 * since dsnuser_free() will free it for us later on. */
		dsnuser->address = tmpaddr;

		if (dsnuser_resolve(dsnuser) != 0) {
			TRACE(TRACE_ERR, "dsnuser_resolve_list failed");
			ci_write(ci, "430 Temporary failure in recipient lookup\r\n");
			dsnuser_free(dsnuser);
			g_free(dsnuser);
			return 1;
		}

		/* Class 2 means the address was deliverable in some way. */
		switch (dsnuser->dsn.class) {
			case DSN_CLASS_OK:
				ci_write(ci, "250 Recipient <%s> OK\r\n", dsnuser->address);
				session->rcpt = g_list_prepend(session->rcpt, dsnuser);
				break;
			default:
				ci_write(ci, "550 Recipient <%s> FAIL\r\n", dsnuser->address);
				dsnuser_free(dsnuser);
				g_free(dsnuser);
				break;
		}
		return 1;

	/* Here's where it gets really exciting! */
	case LMTP_DATA:
		msg = dbmail_message_new();
		dbmail_message_init_with_string(msg, session->rbuff);
		dbmail_message_set_header(msg, "Return-Path", (char *)session->from->data);
		g_string_truncate(session->rbuff,0);
		g_string_maybe_shrink(session->rbuff);

		if (insert_messages(msg, session->rcpt) == -1) {
			ci_write(ci, "430 Message not received\r\n");
			dbmail_message_free(msg);
			return 1;
		}
		/* The DATA command itself it not given a reply except
		 * that of the status of each of the remaining recipients. */

		/* The replies MUST be in the order received */
		session->rcpt = g_list_reverse(session->rcpt);
		while (session->rcpt) {
			Delivery_T * dsnuser = (Delivery_T *)session->rcpt->data;
			dsn_tostring(dsnuser->dsn, &class, &subject, &detail);

			/* Give a simple OK, otherwise a detailed message. */
			switch (dsnuser->dsn.class) {
				case DSN_CLASS_OK:
					ci_write(ci, "%d%d%d Recipient <%s> OK\r\n",
							dsnuser->dsn.class, dsnuser->dsn.subject, dsnuser->dsn.detail,
							dsnuser->address);
					break;
				default:
					ci_write(ci, "%d%d%d Recipient <%s> %s %s %s\r\n",
							dsnuser->dsn.class, dsnuser->dsn.subject, dsnuser->dsn.detail,
							dsnuser->address, class, subject, detail);
			}

			if (! g_list_next(session->rcpt)) break;
			session->rcpt = g_list_next(session->rcpt);
		}
		dbmail_message_free(msg);
		/* Reset the session after a successful delivery;
		 * MTA's like Exim prefer to immediately begin the
		 * next delivery without an RSET or a reconnect. */
		lmtp_rset(session,TRUE);
		return 1;

	default:
		return lmtp_error(session, "500 What are you trying to say here?\r\n");

	}
	return 1;
}