implement basic POP interface

itoomail now works as P2P with ~~no problems~~ a million problems

Signed-off-by: AGentooCat <agentoocat@mail.i2p>
This commit is contained in:
2025-04-18 11:02:11 +00:00
parent 4a2d53a3ab
commit 373d91b406

173
src/pop.c
View File

@ -142,6 +142,10 @@ int parse_popline(struct POPCommand *com, char *line) {
struct pop_conn_state {
char *addr;
char *authas;
char *_authas;
struct SizedMsgid *msgids;
long msgidl;
};
#define SENDFAIL(x) "[fd%d] Couldn't send " #x " line to client"
int pop_connect(struct lineconn_t *hand, void *me) {
@ -165,21 +169,184 @@ int pop_connect(struct lineconn_t *hand, void *me) {
int pop_disconnect(struct lineconn_t *hand) {
struct pop_conn_state *state = (struct pop_conn_state*)hand->data;
log(DEBUG, "Closing POP connection at FD %d", hand->fd);
if (state->authas)
logout_user(state->authas);
xfree(state->_authas);
free(state);
return 0;
}
int commit_pop_session(struct pop_conn_state *state) {
if (!state->authas)
return 1;
int all_good = 1;
for (long i = 0; i < state->msgidl; i++)
if (state->msgids[i].deleted && wipemail(state->msgids[i].msgid) < 0)
all_good = -1;
return all_good;
}
int pop_line(struct lineconn_t *hand) {
struct pop_conn_state *state = (struct pop_conn_state*)hand->data;
struct POPCommand com;
if (parse_popline(&com, hand->buf) < 0)
return -1;
return 0;
char *msg = NULL;
#define CHKLOG \
if (!state->authas) { msg = "-ERR Specify user to login as."; break; }
#define CHKMID \
if (com.arg1 < 1 || com.arg1 > state->msgidl) { \
msg = "-ERR Invalid message ID."; break; \
} else if (state->msgids[com.arg1 - 1].deleted) { \
msg = "-ERR Message deleted."; break; \
}
#define SENDOK if (sendline(hand->fd, "+OK", 1) < 1) return -1;
switch (com.type) {
case USER:
if (state->authas) {
msg = "-ERR Already logged in.";
break;
}
int ret = login_user(com.strarg);
if (ret == -1) return -1;
else if (!ret) {
msg = "-ERR User already logged in.";
break;
}
if ((state->msgidl = getpendingids(&state->msgids, com.strarg)) < 0)
return -1;
state->_authas = com._backline;
com._backline = NULL;
state->authas = com.strarg;
msg = "+OK Logged in; waiting for unneeded PASS.";
break;
case PASS:
CHKLOG;
msg = "+OK Password ignored.";
break;
case STAT:
CHKLOG;
long len = 0, tlen = 0;
for (long i = 0; i < state->msgidl; i++) {
if (state->msgids[i].deleted) continue;
len++;
tlen += state->msgids[i].headlen + 2 + state->msgids[i].maillen;
}
if (sendfmtline(hand->fd, "+OK %ld %ld", len, tlen) < 1)
return -1;
break;
case LIST:
CHKLOG;
if (com.arg1 > -1) {
CHKMID;
long len = state->msgids[com.arg1 - 1].headlen + 2 + state->msgids[com.arg1 - 1].maillen;
if (sendfmtline(hand->fd, "+OK %d %d", com.arg1, len) < 1)
return -1;
break;
}
SENDOK;
for (long i = 0; i < state->msgidl; i++) {
if (state->msgids[i].deleted) continue;
long len = state->msgids[i].headlen + 2 + state->msgids[i].maillen;
if (sendfmtline(hand->fd, "%d %d", i + 1, len) < 1)
return -1;
}
msg = ".";
break;
case TOP:
case RETR:
CHKLOG;
CHKMID;
if (com.type == TOP && com.arg2 < 1) {
msg = "-ERR Invalid line count.";
break;
}
char *perr;
struct Mail mail;
if (getmaillines(&mail, state->msgids[com.arg1 - 1].msgid, com.type == TOP ? com.arg2 : 0, &perr) < 1) {
log(ERROR, "couldn't pull mail %s of user %s: %s", state->msgids[com.arg1 - 1].msgid, state->authas, perr);
msg = "-ERR Internal error.";
break;
}
SENDOK;
for (long i = 0; i < mail.hdrl; i++)
if (sendfmtline(hand->fd, "%s: %s", mail.hdrs[i].key, mail.hdrs[i].val) < 1) {
freemail(&mail);
return -1;
}
if (write(hand->fd, mail.lines, mail.size) < mail.size)
return -1;
// the ending dot-crlf comes from database
freemail(&mail);
break;
case P_NOOP:
msg = "+OK";
break;
case RSET:
CHKLOG;
for (long i = 0; i < state->msgidl; i++)
state->msgids[i].deleted = 0;
msg = "+OK";
break;
case DELE:
CHKLOG;
CHKMID;
state->msgids[com.arg1 - 1].deleted = 1;
msg = "+OK";
break;
case UIDL:
CHKLOG;
// breaks compat with pop3 spec: uidl ids must be 1-70 bytes long.
// we send the message-id which can be longer.
if (com.arg1 > -1) {
CHKMID;
char *mid = state->msgids[com.arg1 - 1].msgid;
if (sendfmtline(hand->fd, "+OK %ld %s", com.arg1, mid) < 1)
return -1;
break;
}
SENDOK;
for (long i = 0; i < state->msgidl; i++) {
if (state->msgids[i].deleted) continue;
char *mid = state->msgids[i].msgid;
if (sendfmtline(hand->fd, "%d %s", i + 1, mid) < 1)
return -1;
}
msg = ".";
break;
case P_QUIT:
if (commit_pop_session(state) < 0)
msg = "-ERR Couldn't commit POP session.";
else
msg = "+OK Done! See you later.";
break;
default:
msg = "-ERR Unrecognized command";
break;
}
if (msg && sendline(hand->fd, msg, 1) < 1)
return -1;
if (com.type == P_QUIT)
return -1;
return com._backline ? 0 : 1;
}
void *popserv(void *me) {
pthread_mutex_init(&login_lock, NULL);
struct linepoll_t hand = {
.sockfd = pop_serv,
.init_ptr = pop_connect,