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; } }
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; }
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; }
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; }
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; }
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; }
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; }