jcs
/subtext
/amendments
/357
mail: Import FidoNet mail to local users, store fidopkt data
jcs made amendment 357 about 1 year ago
--- db.c Thu Mar 2 08:06:08 2023
+++ db.c Thu Mar 2 17:38:42 2023
@@ -23,6 +23,7 @@
#include "bile.h"
#include "db.h"
#include "folder.h"
+#include "mail.h"
#include "user.h"
#include "util.h"
@@ -659,6 +660,49 @@ db_migrate(struct db *tdb, short is_new, Str255 fullpa
db_cache_boards(tdb);
+ break;
+ }
+ case 15: {
+ /* 15->16, mail gets fido fields */
+ Str255 newfullpath;
+ struct bile *mail_bile;
+ struct bile_object *o;
+ struct mail_message mail;
+ size_t nids, n, size;
+ unsigned long *ids;
+ char *data;
+
+ memcpy(newfullpath, fullpath, sizeof(newfullpath));
+ PtoCstr(newfullpath);
+ strlcat((char *)&newfullpath, "-mail", sizeof(newfullpath));
+ CtoPstr(newfullpath);
+
+ mail_bile = bile_open(newfullpath, tdb->bile->vrefnum);
+ if (mail_bile == NULL)
+ /* no mail here, no problem */
+ break;
+
+ nids = bile_ids_by_type(mail_bile, MAIL_SPOOL_MESSAGE_RTYPE,
+ &ids);
+ if (ids == NULL) {
+ bile_close(mail_bile);
+ break;
+ }
+ for (n = 0; n < nids; n++) {
+ o = bile_find(mail_bile, MAIL_SPOOL_MESSAGE_RTYPE, ids[n]);
+ if (!o)
+ panic("can't find message %ld but it's in ids",
+ ids[n]);
+ progress("Growing mail message %ld/%ld...", n + 1, nids);
+ size = o->size +
+ (sizeof(struct mail_message) -
+ offsetof(struct mail_message, fidonet_msgid));
+ if (bile_resize(mail_bile, o->type, o->id, size) != size)
+ panic("failed resizing message %ld", o->id);
+ xfree(&o);
+ }
+ xfree(&ids);
+ bile_close(mail_bile);
break;
}
}
--- db.h Tue Feb 28 19:27:37 2023
+++ db.h Thu Mar 2 15:37:19 2023
@@ -19,7 +19,7 @@
#include <time.h>
-#define DB_CUR_VERS 15
+#define DB_CUR_VERS 16
#define SUBTEXT_CREATOR 'SUBT'
#define DB_TYPE 'STDB'
--- mail.c Thu Mar 2 09:28:03 2023
+++ mail.c Sat Mar 4 21:30:22 2023
@@ -20,6 +20,7 @@
#include <string.h>
#include "ansi.h"
+#include "logger.h"
#include "mail.h"
#include "subtext.h"
#include "session.h"
@@ -54,11 +55,27 @@ static const struct bile_object_field mail_object_fiel
-1, offsetof(struct mail_message, body_size) },
{ offsetof(struct mail_message, parent_message_id),
member_size(struct mail_message, parent_message_id), -1 },
+
+ { offsetof(struct mail_message, fidonet_msgid),
+ member_size(struct mail_message, fidonet_msgid), -1 },
+ { offsetof(struct mail_message, fidonet_source),
+ member_size(struct mail_message, fidonet_source), -1 },
+ { offsetof(struct mail_message, fidonet_dest),
+ member_size(struct mail_message, fidonet_dest), -1 },
+ { offsetof(struct mail_message, fidonet_from),
+ member_size(struct mail_message, fidonet_from), -1 },
+ { offsetof(struct mail_message, fidonet_to),
+ member_size(struct mail_message, fidonet_to), -1 },
+ { offsetof(struct mail_message, fidonet_msgid_orig),
+ member_size(struct mail_message, fidonet_msgid_orig), -1 },
+ { offsetof(struct mail_message, fidonet_origin),
+ member_size(struct mail_message, fidonet_origin), -1 },
+ { offsetof(struct mail_message, fidonet_reply),
+ member_size(struct mail_message, fidonet_reply), -1 },
};
static const size_t nmail_object_fields = nitems(mail_object_fields);
void mail_free_message_strings(struct mail_message *msg);
-short mail_save(struct session *s, struct mail_message *msg);
short mail_read(struct session *s, unsigned long id, short idx);
void mail_list(struct session *s, size_t nmsgs, unsigned long *mail_ids,
size_t page, size_t pages);
@@ -385,7 +402,11 @@ mail_compose_start:
msg.time = Time;
msg.sender_user_id = s->user->id;
- mail_save(s, &msg);
+ if (mail_save(&msg) != 0) {
+ session_printf(s, "failed!\r\n");
+ session_flush(s);
+ break;
+ }
session_printf(s, " sent!\r\n");
session_flush(s);
@@ -412,7 +433,7 @@ void
mail_list(struct session *s, size_t nmail_ids, unsigned long *mail_ids,
size_t page, size_t pages)
{
- char time[10];
+ char time[10], from[40];
size_t n, size;
struct mail_message msg;
struct username_cache *user;
@@ -420,7 +441,7 @@ mail_list(struct session *s, size_t nmail_ids, unsigne
session_printf(s, "{{B}}Private Mail (Page %ld of %ld){{/B}}\r\n",
page, pages);
- session_printf(s, "%s# Flg Date From Subject%s\r\n",
+ session_printf(s, "%s# Flg Date From Subject%s\r\n",
ansi(s, ANSI_BOLD, ANSI_END), ansi(s, ANSI_RESET, ANSI_END));
session_flush(s);
@@ -433,15 +454,25 @@ mail_list(struct session *s, size_t nmail_ids, unsigne
nitems(mail_object_fields), data, size, &msg, sizeof(msg), true);
xfree(&data);
- user = user_username(msg.sender_user_id);
+ if (msg.fidonet_from[0] && msg.recipient_user_id) {
+ snprintf(from, sizeof(from), "%s@%d:%d/%d.%d",
+ msg.fidonet_from, msg.fidonet_source.zone,
+ msg.fidonet_source.net, msg.fidonet_source.node,
+ msg.fidonet_source.point);
+ } else {
+ user = user_username(msg.sender_user_id);
+ strlcpy(from, user ? user->username : "(unknown)",
+ sizeof(from));
+ }
+
strftime(time, sizeof(time), "%b %d", localtime(&msg.time));
- session_printf(s, "%s%ld %c %s %- 10s {{#}}%- 40s%s\r\n",
+ session_printf(s, "%s%ld %c %s %-15.15s {{#}}%.40s%s\r\n",
msg.read ? "" : ansi(s, ANSI_BOLD, ANSI_END),
n + 1,
msg.read ? ' ' : 'N',
time,
- user ? user->username : "(unknown)",
+ from,
msg.subject,
msg.read ? "" : ansi(s, ANSI_RESET, ANSI_END));
@@ -491,10 +522,24 @@ mail_read(struct session *s, unsigned long id, short i
strftime(time, sizeof(time), "%Y-%m-%d %H:%M:%S",
localtime(&msg.time));
- session_printf(s, "{{B}}From:{{/B}} %s\r\n",
- sender ? sender->username : "(unknown)");
- session_printf(s, "{{B}}To:{{/B}} %s\r\n",
- recipient ? recipient->username : "(unknown)");
+ if (msg.fidonet_from[0] && msg.recipient_user_id) {
+ session_printf(s, "{{B}}From:{{/B}} %s@%d:%d/%d.%d",
+ msg.fidonet_from, msg.fidonet_source.zone,
+ msg.fidonet_source.net, msg.fidonet_source.node,
+ msg.fidonet_source.point);
+ } else {
+ session_printf(s, "{{B}}From:{{/B}} %s\r\n",
+ sender ? sender->username : "(unknown)");
+ }
+ if (msg.fidonet_to[0] && msg.sender_user_id) {
+ session_printf(s, "{{B}}To:{{/B}} %s@%d:%d/%d.%d",
+ msg.fidonet_to, msg.fidonet_dest.zone,
+ msg.fidonet_dest.net, msg.fidonet_dest.node,
+ msg.fidonet_dest.point);
+ } else {
+ session_printf(s, "{{B}}To:{{/B}} %s\r\n",
+ recipient ? recipient->username : "(unknown)");
+ }
session_printf(s, "{{B}}Date:{{/B}} %s %s\r\n", time,
db->config.timezone);
session_printf(s, "{{B}}Subject:{{/B}}{{#}} %s\r\n", msg.subject);
@@ -506,7 +551,7 @@ mail_read(struct session *s, unsigned long id, short i
if (!msg.read) {
msg.read = Time;
- if (mail_save(s, &msg) != 0)
+ if (mail_save(&msg) != 0)
session_printf(s, "Failed marking message read!\r\n");
}
@@ -549,7 +594,7 @@ mail_read(struct session *s, unsigned long id, short i
break;
case 'u':
msg.read = 0;
- if (mail_save(s, &msg) == 0)
+ if (mail_save(&msg) == 0)
session_printf(s, "Marked message {{B}}%d{{/B}} unread\r\n",
idx);
else
@@ -579,7 +624,7 @@ mail_read(struct session *s, unsigned long id, short i
}
short
-mail_save(struct session *s, struct mail_message *msg)
+mail_save(struct mail_message *msg)
{
size_t size;
char *data;
@@ -587,6 +632,8 @@ mail_save(struct session *s, struct mail_message *msg)
if (!msg->id)
msg->id = bile_next_id(db->mail_bile, MAIL_SPOOL_MESSAGE_RTYPE);
+ if (!msg->id)
+ return -1;
ret = bile_marshall_object(db->mail_bile, mail_object_fields,
nitems(mail_object_fields), msg, &data, &size);
@@ -604,93 +651,214 @@ mail_save(struct session *s, struct mail_message *msg)
return 0;
}
+/*
+ * return count of all ids for the user (for pagination), set nmail_ids to
+ * the count of the ids set in mail_ids based on offset/limit/only_unread
+ *
+ * nret_mail_ids or ret_mail_ids may be NULL to avoid building an array
+ */
size_t
-mail_find_ids_for_user(struct user *user, size_t *nmail_ids,
- unsigned long **mail_ids, size_t offset, size_t limit, bool only_unread)
+mail_find_ids_for_user(struct user *user, size_t *nret_mail_ids,
+ unsigned long **ret_mail_ids, size_t offset, size_t limit,
+ bool only_unread)
{
- unsigned long msg_user_id;
struct bile_object *o;
struct mail_message msg;
- size_t n, nmsgs_for_user, mail_ids_size, id, size;
+ size_t n, nall_mail_ids, nmail_ids, mail_ids_size, nlimit_mail_ids,
+ id, size;
+ bool failed = false;
+ unsigned long *mail_ids = NULL, *all_mail_ids;
short i, j;
- char *data;
- bool read;
- mail_ids_size = sizeof(long) * 16;
- if (mail_ids != NULL)
- *mail_ids = xmalloc(mail_ids_size);
- if (nmail_ids != NULL)
- *nmail_ids = 0;
+ if (ret_mail_ids != NULL)
+ *ret_mail_ids = NULL;
+ if (nret_mail_ids != NULL)
+ *nret_mail_ids = 0;
- nmsgs_for_user = 0;
- for (n = 0; (o = bile_get_nth_of_type(db->mail_bile, n,
- MAIL_SPOOL_MESSAGE_RTYPE)); n++) {
- id = o->id;
- bile_read(db->mail_bile, MAIL_SPOOL_MESSAGE_RTYPE, id,
- (char *)&msg_user_id, sizeof(msg_user_id));
- xfree(&o);
+ nall_mail_ids = bile_ids_by_type(db->mail_bile,
+ MAIL_SPOOL_MESSAGE_RTYPE, &all_mail_ids);
+
+ /* narrow all_mail_ids down to mail_ids for this user */
+ mail_ids_size = 0;
+ nmail_ids = 0;
+ for (n = 0; n < nall_mail_ids; n++) {
+ /* read only as far as we need to */
+ size = offsetof(struct mail_message, read) +
+ member_size(struct mail_message, read);
+ if (bile_read(db->mail_bile, MAIL_SPOOL_MESSAGE_RTYPE,
+ all_mail_ids[n], (char *)&msg, size) != size) {
+ failed = true;
+ goto done;
+ }
- if (msg_user_id != user->id)
+ if (msg.recipient_user_id != user->id)
continue;
-
- if (only_unread) {
- size = bile_read_alloc(db->mail_bile, MAIL_SPOOL_MESSAGE_RTYPE,
- id, &data);
- if (size == 0)
- break;
- bile_unmarshall_object(db->mail_bile, mail_object_fields,
- nmail_object_fields, data, size, &msg, sizeof(msg), false);
- xfree(&data);
- read = msg.read;
- if (read)
- continue;
- }
+ if (only_unread && msg.read)
+ continue;
- if (mail_ids != NULL) {
- if (!grow_to_fit(mail_ids, &mail_ids_size,
- (nmsgs_for_user + 1) * sizeof(long), sizeof(long),
- sizeof(long) * 16))
- break;
- (*mail_ids)[nmsgs_for_user] = id;
+ if (ret_mail_ids != NULL) {
+ if (!grow_to_fit(&mail_ids, &mail_ids_size,
+ (nmail_ids + 1) * sizeof(long), sizeof(long),
+ sizeof(long) * 16)) {
+ failed = true;
+ goto done;
+ }
+ mail_ids[nmail_ids] = all_mail_ids[n];
}
- nmsgs_for_user++;
+ nmail_ids++;
}
- if (mail_ids != NULL) {
+ if (all_mail_ids != NULL)
+ xfree(&all_mail_ids);
+
+ if (ret_mail_ids != NULL) {
/* sort by message id for consistent ordering */
- for (i = 0; i < nmsgs_for_user; i++) {
- for (j = 0; j < nmsgs_for_user - i - 1; j++) {
- if ((*mail_ids)[j] > (*mail_ids)[j + 1]) {
- id = (*mail_ids)[j];
- (*mail_ids)[j] = (*mail_ids)[j + 1];
- (*mail_ids)[j + 1] = id;
+ for (i = 0; i < nmail_ids; i++) {
+ for (j = 0; j < nmail_ids - i - 1; j++) {
+ if (mail_ids[j] > mail_ids[j + 1]) {
+ id = mail_ids[j];
+ mail_ids[j] = mail_ids[j + 1];
+ mail_ids[j + 1] = id;
}
}
}
}
- if (nmail_ids != NULL)
- *nmail_ids = nmsgs_for_user;
+ nlimit_mail_ids = nmail_ids;
if (offset) {
- if (offset >= nmsgs_for_user) {
- if (nmail_ids != NULL)
- *nmail_ids = 0;
+ if (offset >= nlimit_mail_ids) {
if (mail_ids != NULL)
- xfree(&(*mail_ids));
+ xfree(&mail_ids);
+ nlimit_mail_ids = 0;
} else {
if (mail_ids != NULL) {
- for (j = offset, i = 0; j < nmsgs_for_user; j++, i++)
- (*mail_ids)[i] = (*mail_ids)[j];
+ for (j = offset, i = 0; j < nmail_ids; j++, i++)
+ mail_ids[i] = mail_ids[j];
}
- if (nmail_ids != NULL)
- *nmail_ids -= offset;
+ nlimit_mail_ids -= offset;
}
}
- if (nmail_ids != NULL && *nmail_ids > limit)
- *nmail_ids = limit;
+ if (limit && nlimit_mail_ids > limit)
+ nlimit_mail_ids = limit;
+
+ if (ret_mail_ids != NULL)
+ *ret_mail_ids = mail_ids;
+ if (nret_mail_ids != NULL)
+ *nret_mail_ids = nlimit_mail_ids;
+
+done:
+ if (failed)
+ return 0;
+ return nmail_ids;
+}
+
+short
+mail_ingest_fidopkt(struct fidopkt *fidopkt)
+{
+ struct user *to = NULL;
+ struct fidopkt_address *our_address = NULL;
+ size_t n, nmail_ids, size;
+ unsigned long *mail_ids = NULL;
+ struct mail_message msg;
+ short ret = 1;
+ char *data;
+
+ if (db->config.binkp_node_addr[0] == 0) {
+ logger_printf("[mail] local FidoNet node address must be "
+ "configured in settings");
+ return -1;
+ }
- return nmsgs_for_user;
+ if (!fidopkt_parse_address(db->config.binkp_node_addr, &our_address)) {
+ logger_printf("[mail] failed parsing local FidoNet node address "
+ "\"%s\", fix in settings", db->config.binkp_node_addr);
+ return -1;
+ }
+
+ if (memcmp(&fidopkt->dest, our_address,
+ sizeof(struct fidopkt_address)) != 0) {
+ logger_printf("[mail] message is destined for %d:%d/%d.%d, not "
+ "us (%d:%d/%d.%d)",
+ fidopkt->dest.zone, fidopkt->dest.net,
+ fidopkt->dest.node, fidopkt->dest.point,
+ our_address->zone, our_address->net,
+ our_address->node, our_address->point);
+ ret = 0;
+ goto done;
+ }
+
+ to = user_find_by_username(fidopkt->to);
+ if (to == NULL) {
+ logger_printf("[mail] no local user \"%s\", discarding message",
+ fidopkt->to);
+ ret = 0;
+ goto done;
+ }
+
+ mail_find_ids_for_user(to, &nmail_ids, &mail_ids, 0, 0, false);
+ for (n = 0; n < nmail_ids; n++) {
+ size = bile_read_alloc(db->mail_bile, MAIL_SPOOL_MESSAGE_RTYPE,
+ mail_ids[n], &data);
+ if (size == 0)
+ break;
+ bile_unmarshall_object(db->mail_bile, mail_object_fields,
+ nitems(mail_object_fields), data, size, &msg, sizeof(msg), false);
+ xfree(&data);
+
+ if (memcmp(&msg.fidonet_msgid, &fidopkt->msgid,
+ sizeof(struct fidopkt_msgid)) == 0) {
+ logger_printf("[mail] already have fidonet msg %s (%ld), "
+ "skipping", fidopkt->msgid_orig, msg.id);
+ ret = 0;
+ break;
+ }
+ }
+ if (mail_ids != NULL)
+ xfree(&mail_ids);
+
+ if (ret < 1) {
+ xfree(&to);
+ return ret;
+ }
+
+ memset(&msg, 0, sizeof(msg));
+ msg.recipient_user_id = to->id;
+ msg.time = fidopkt->time;
+ msg.subject = xstrdup(fidopkt->subject);
+ msg.subject_size = strlen(msg.subject) + 1;
+ msg.body = fidopkt->message;
+ msg.body_size = fidopkt->message_len + 1;
+
+ msg.fidonet_msgid = fidopkt->msgid;
+ msg.fidonet_source = fidopkt->orig;
+ msg.fidonet_dest = fidopkt->dest;
+
+ strlcpy(msg.fidonet_from, fidopkt->from, sizeof(msg.fidonet_from));
+ strlcpy(msg.fidonet_msgid_orig, fidopkt->msgid_orig,
+ sizeof(msg.fidonet_msgid_orig));
+ strlcpy(msg.fidonet_origin, fidopkt->origin,
+ sizeof(msg.fidonet_origin));
+ strlcpy(msg.fidonet_reply, fidopkt->reply, sizeof(msg.fidonet_reply));
+
+ if (mail_save(&msg) != 0) {
+ logger_printf("[binkp] failed saving mail from %d:%d/%d.%d to "
+ "local user %s",
+ fidopkt->orig.zone, fidopkt->orig.net, fidopkt->orig.node,
+ fidopkt->orig.point, to->username);
+ ret = -1;
+ goto done;
+ }
+
+ logger_printf("[binkp] imported mail from %d:%d/%d.%d to local user %s",
+ fidopkt->orig.zone, fidopkt->orig.net, fidopkt->orig.node,
+ fidopkt->orig.point, to->username);
+
+done:
+ if (to != NULL)
+ xfree(&to);
+ xfree(&our_address);
+ return ret;
}
--- mail.h Wed May 25 15:04:00 2022
+++ mail.h Sat Mar 4 21:24:09 2023
@@ -18,6 +18,7 @@
#define __MAIL_H__
#include "bile.h"
+#include "fidopkt.h"
#include "session.h"
struct mail_message {
@@ -27,17 +28,30 @@ struct mail_message {
unsigned long id;
time_t time;
time_t read;
+ /* mail_find_ids_for_user has to be able to read this far without alloc */
+
unsigned long sender_user_id;
size_t subject_size;
char *subject;
size_t body_size;
char *body;
unsigned long parent_message_id;
+
+ struct fidopkt_msgid fidonet_msgid;
+ struct fidopkt_address fidonet_source;
+ struct fidopkt_address fidonet_dest;
+ char fidonet_from[32];
+ char fidonet_to[32];
+ char fidonet_msgid_orig[64];
+ char fidonet_origin[64];
+ char fidonet_reply[32];
};
void mail_menu(struct session *s);
void mail_compose(struct session *s, char *to, char *subject, char *body);
+short mail_save(struct mail_message *msg);
size_t mail_find_ids_for_user(struct user *user, size_t *nmail_ids,
unsigned long **mail_ids, size_t offset, size_t limit, bool only_unread);
+short mail_ingest_fidopkt(struct fidopkt *fidopkt);
#endif