/* * IMC2 - an inter-mud communications protocol * * imc-mail.c: IMC mailer functions * * Copyright (C) 1996,1997 Oliver Jowett <oliver@jowett.manawatu.planet.co.nz> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program (see the file COPYING); if not, write to the * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ #include <stdlib.h> #include <stdio.h> #include <ctype.h> #include <string.h> #include "imc.h" /* * general stuff */ /* escape a string for writing to a file */ static const char *escape(const char *data) { char *buf=imc_getsbuf(IMC_DATA_LENGTH); char *p; for (p=buf; *data && (p-buf < IMC_DATA_LENGTH-1); data++, p++) { if (*data == '\n') { *p++='\\'; *p='n'; } else if (*data == '\r') { *p++='\\'; *p='r'; } else if (*data == '\\') { *p++='\\'; *p='\\'; } else if (*data == '"') { *p++='\\'; *p='"'; } else *p=*data; } *p=0; imc_shrinksbuf(buf); return buf; } /* unescape: reverse escape */ static const char *unescape(const char *data) { char *buf=imc_getsbuf(IMC_DATA_LENGTH); char *p; char ch; for (p=buf; *data && (p-buf < IMC_DATA_LENGTH-1); data++, p++) { if (*data == '\\') { ch = *(++data); switch (ch) { case 'n': *p='\n'; break; case 'r': *p='\r'; break; case '\\': *p='\\'; break; default: *p=ch; break; } } else *p=*data; } *p=0; imc_shrinksbuf(buf); return buf; } /* * mail */ /* new_mail: create a new mail structure */ static imc_mail *new_mail(void) { imc_mail *p; p=imc_malloc(sizeof(*p)); p->from = NULL; p->to = NULL; p->subject = NULL; p->date = NULL; p->text = NULL; p->id = NULL; p->received = 0; p->usage = 0; p->next = NULL; return p; } /* free_mail: free a mail structure */ static void free_mail(imc_mail *p) { if (!p) { imc_logerror("BUG: free_mail: freeing NULL pointer"); return; } if (p->usage) { imc_logerror("BUG: free_mail: freeing mail at %p with usage=%d", p, p->usage); return; } if (p->from) imc_strfree(p->from); if (p->to) imc_strfree(p->to); if (p->id) imc_strfree(p->id); if (p->text) imc_strfree(p->text); if (p->subject) imc_strfree(p->subject); if (p->date) imc_strfree(p->date); imc_cancel_event(NULL, p); imc_free(p, sizeof(*p)); } /* write_mail: write a single mail to a file */ static void write_mail(imc_mail *p, FILE *out) { if (!p) { imc_logerror("BUG: write_mail: NULL pointer"); return; } fprintf(out, "From %s\n", escape(p->from)); fprintf(out, "To %s\n", escape(p->to)); fprintf(out, "Subject %s\n", escape(p->subject)); fprintf(out, "Date %s\n", escape(p->date)); fprintf(out, "Text %s\n", escape(p->text)); fprintf(out, "ID %s\n", escape(p->id)); fprintf(out, "Received %ld\n", p->received); } /* read_mail: read a single mail from a file, NULL on EOF */ static imc_mail *read_mail(FILE *in) { imc_mail *p; char line[IMC_DATA_LENGTH]; char temp[IMC_DATA_LENGTH]; fgets(line, IMC_DATA_LENGTH, in); if (ferror(in) || feof(in)) return NULL; p=new_mail(); sscanf(line, "From %[^\n]", temp); p->from=imc_strdup(unescape(temp)); fgets(line, IMC_DATA_LENGTH, in); sscanf(line, "To %[^\n]", temp); p->to=imc_strdup(unescape(temp)); fgets(line, IMC_DATA_LENGTH, in); sscanf(line, "Subject %[^\n]", temp); p->subject=imc_strdup(unescape(temp)); fgets(line, IMC_DATA_LENGTH, in); sscanf(line, "Date %[^\n]", temp); p->date=imc_strdup(unescape(temp)); fgets(line, IMC_DATA_LENGTH, in); sscanf(line, "Text %[^\n]", temp); p->text=imc_strdup(unescape(temp)); fgets(line, IMC_DATA_LENGTH, in); sscanf(line, "ID %[^\n]", temp); p->id=imc_strdup(unescape(temp)); fgets(line, IMC_DATA_LENGTH, in); sscanf(line, "Received %ld", &p->received); p->usage=0; return p; } /* * maillist - a list of imc_mail entries */ imc_mail *imc_ml_head; /* init_ml: init the maillist */ static void init_ml(void) { imc_ml_head = new_mail(); imc_ml_head->to = imc_strdup(""); imc_ml_head->from = imc_strdup(""); imc_ml_head->date = imc_strdup(""); imc_ml_head->subject = imc_strdup(""); imc_ml_head->text = imc_strdup(""); imc_ml_head->id = imc_strdup(""); } /* free_ml: free the maillist */ static void free_ml(void) { imc_mail *p, *p_next; for (p=imc_ml_head; p; p=p_next) { p_next=p->next; p->usage=0; /* suppress warnings */ free_mail(p); } imc_ml_head=NULL; } /* add_ml: add an item to the maillist */ static void add_ml(imc_mail *p) { if (!p) { imc_logerror("BUG: add_ml: adding NULL pointer"); return; } p->next=imc_ml_head->next; imc_ml_head->next=p; } /* find_ml: find a given ID in the mail list */ static imc_mail *find_ml(const char *id) { imc_mail *p; if (!id) { imc_logerror("BUG: find_ml: NULL id"); return NULL; } for (p=imc_ml_head->next; p; p=p->next) if (!strcasecmp(p->id, id)) return p; return NULL; } /* delete_ml: delete a given node in the mail list */ static void delete_ml(imc_mail *node) { imc_mail *last, *p; if (!node) { imc_logerror("BUG: delete_ml: NULL node"); return; } for (last=imc_ml_head, p=last->next; p && p != node; p=p->next) ; if (p) { last->next = p->next; free_mail(p); } else imc_logerror("BUG: delete_ml: node at %p not on list", node); } /* save_ml: save maillist */ static void save_ml(void) { FILE *out; imc_mail *p; char name[200]; imc_sncpy(name, imc_prefix, 190); strcat(name, "mail-list"); out=fopen(name, "w"); if (!out) { imc_lerror("save_ml: fopen"); return; } for (p=imc_ml_head->next; p; p=p->next) write_mail(p, out); fclose(out); } /* load_ml: load maillist, assumes init_ml done */ static void load_ml(void) { FILE *in; imc_mail *p; char name[200]; imc_sncpy(name, imc_prefix, 190); strcat(name, "mail-list"); in=fopen(name, "r"); if (!in) return; p=read_mail(in); while (p) { add_ml(p); p=read_mail(in); } fclose(in); } /* * qnode - an entry in the 'mail to send' queue, referencing a particular * piece of mail, and the mud that this entry needs to send to */ /* new_qnode: get a new qnode */ static imc_qnode *new_qnode(void) { imc_qnode *p; p=imc_malloc(sizeof(*p)); p->data = NULL; p->next = NULL; p->tomud = NULL; return p; } /* free_qnode: free a qnode */ static void free_qnode(imc_qnode *q) { if (!q) { imc_logerror("BUG: free_qnode: freeing NULL pointer"); return; } if (q->tomud) imc_strfree(q->tomud); if (q->data && !--q->data->usage) delete_ml(q->data); imc_cancel_event(NULL, q); imc_free(q, sizeof(*q)); } /* write_qnode: write a qnode to a file */ static void write_qnode(imc_qnode *q, FILE *out) { if (!q) { imc_logerror("BUG: write_qnode: NULL pointer"); return; } fprintf(out, "%s %s\n", q->data->id, q->tomud); } /* read_qnode: read a qnode from a file */ static imc_qnode *read_qnode(FILE *in) { imc_qnode *p; imc_mail *m; char line[IMC_DATA_LENGTH]; char temp1[IMC_DATA_LENGTH], temp2[IMC_DATA_LENGTH]; fgets(line, IMC_DATA_LENGTH, in); if (ferror(in) || feof(in)) return NULL; sscanf(line, "%[^ ] %[^\n]", temp1, temp2); m=find_ml(temp1); if (!m) { imc_logerror("read_qnode: ID %s not in mail queue", temp1); return NULL; } p=new_qnode(); m->usage++; p->data = m; p->tomud = imc_strdup(temp2); return p; } /* * mailqueue - a list of active qnodes */ imc_qnode *imc_mq_head, *imc_mq_tail; /* init_mq: init mailqueue */ static void init_mq(void) { imc_mq_head = new_qnode(); imc_mq_tail = imc_mq_head; imc_mq_head->data = NULL; imc_mq_head->next = NULL; } /* free_mq: delete mailqueue */ static void free_mq(void) { imc_qnode *p, *p_next; for (p=imc_mq_head; p; p=p_next) { p_next=p->next; free_qnode(p); } imc_mq_head=imc_mq_tail=NULL; } /* add_mq: add a queue of items to the tail of the mq */ static void add_mq(imc_qnode *p) { imc_mq_tail->next=p; while (p->next) p=p->next; imc_mq_tail=p; } #if 0 /* get_mq: extract the head of the mail queue */ static imc_qnode *get_mq(void) { imc_qnode *p; if (imc_mq_head == imc_mq_tail) /* empty queue */ return NULL; p=imc_mq_head->next; imc_mq_head->next=p->next; if (p == imc_mq_tail) imc_mq_tail=imc_mq_head; p->next=NULL; /* Just In Case */ return p; } #endif /* find_mq: find the item with the given ID/tomud values */ static imc_qnode *find_mq(const char *id, const char *tomud) { imc_qnode *p; for (p=imc_mq_head->next; p; p=p->next) if (!strcmp(id, p->data->id) && !strcasecmp(tomud, p->tomud)) return p; return NULL; } /* delete_mq: delete the item with the given ID/tomud values */ static void delete_mq(const char *id, const char *tomud) { imc_qnode *p, *last; for (last=imc_mq_head, p=last->next; p; last=p, p=p->next) if (!strcmp(id, p->data->id) && !strcasecmp(tomud, p->tomud)) { last->next=p->next; if (p == imc_mq_tail) imc_mq_tail=last; free_qnode(p); return; } } /* save mailqueue */ static void save_mq(void) { FILE *out; imc_qnode *p; char name[200]; imc_sncpy(name, imc_prefix, 189); strcat(name, "mail-queue"); out=fopen(name, "w"); if (!out) { imc_lerror("save_mq: fopen"); return; } for (p=imc_mq_head->next; p; p=p->next) write_qnode(p, out); fclose(out); } /* load mailqueue, assumes init_mq done */ static void load_mq(void) { FILE *in; imc_qnode *p; char name[200]; int when=10; imc_sncpy(name, imc_prefix, 189); strcat(name, "mail-queue"); in=fopen(name, "r"); if (!in) return; p=read_qnode(in); while (!feof(in) && !ferror(in)) { if (p) { add_mq(p); imc_add_event(when, ev_qnode_send, p, 1); when += rand()%30+30; } p=read_qnode(in); } fclose(in); } /* * mailid - a single mail ID that has been received */ /* new_mailid: get a new mailid */ static imc_mailid *new_mailid(void) { imc_mailid *p; p=imc_malloc(sizeof(*p)); p->id = NULL; p->received = 0; p->next = NULL; return p; } /* free_mailid: free a mailid */ static void free_mailid(imc_mailid *p) { if (!p) { imc_logerror("BUG: free_mailid: freeing NULL pointer"); return; } if (p->id) imc_strfree(p->id); imc_cancel_event(NULL, p); imc_free(p, sizeof(*p)); } /* generate_mailid: generate a new mailid (string) */ static char *generate_mailid(void) { char *buffer=imc_getsbuf(200); sprintf(buffer, "%d-%ld@%s", rand(), imc_sequencenumber++, imc_name); imc_shrinksbuf(buffer); return buffer; } /* write_mailid: write a mailid to a file */ static void write_mailid(imc_mailid *p, FILE * out) { fprintf(out, "%s %ld\n", p->id, p->received); } /* read_mailid: read a mailid from a file, NULL on EOF */ static imc_mailid *read_mailid(FILE *in) { imc_mailid *p; char line[IMC_DATA_LENGTH]; char temp[IMC_DATA_LENGTH]; time_t r; fgets(line, IMC_DATA_LENGTH, in); if (ferror(in) || feof(in)) return NULL; sscanf(line, "%[^ ] %ld", temp, &r); p=new_mailid(); p->id = imc_strdup(temp); p->received = r; return p; } /* * idlist - a list of mail IDs received over the last 24 hours */ imc_mailid *imc_idlist; /* init_idlist: init the ID list */ static void init_idlist(void) { imc_idlist=new_mailid(); } /* free_idlist: free the idlist */ static void free_idlist(void) { imc_mailid *p, *p_next; for (p=imc_idlist; p; p=p_next) { p_next=p->next; free_mailid(p); } imc_idlist=NULL; } /* add_idlist: add an ID to the idlist */ static void add_idlist(imc_mailid *p) { p->next=imc_idlist->next; imc_idlist->next=p; } /* find_id: check if an ID is in the ID list */ static imc_mailid *find_id(const char *id) { imc_mailid *p; for (p=imc_idlist->next; p; p=p->next) if (!strcmp(p->id, id)) return p; return NULL; } /* flush_idlist: flush old entries from the mailseen list */ static void flush_idlist(time_t at) { imc_mailid *p, *last; for (last=imc_idlist, p=last->next; p; p=p->next) if (p->received < at) /* delete this entry */ { last->next=p->next; free_mailid(p); p=last; } } /* save_idlist: save idlist */ static void save_idlist(void) { FILE *out; imc_mailid *p; char name[200]; imc_sncpy(name, imc_prefix, 191); strcat(name, "mail-ids"); out=fopen(name, "w"); if (!out) { imc_lerror("save_idlist: fopen"); return; } for (p=imc_idlist->next; p; p=p->next) write_mailid(p, out); fclose(out); } /* load_idlist: load idlist, assumes init_idlist done */ static void load_idlist(void) { FILE *in; imc_mailid *p; char name[200]; imc_sncpy(name, imc_prefix, 191); strcat(name, "mail-ids"); in=fopen(name, "r"); if (!in) return; p=read_mailid(in); while (p) { add_idlist(p); p=read_mailid(in); } fclose(in); } /* datestring: generate a date string for the current time */ static char *datestring(void) { char *buf=imc_getsbuf(100); strcpy(buf, ctime(&imc_now)); buf[strlen(buf)-1]=0; imc_shrinksbuf(buf); return buf; } /* bounce: generate a local bounce note */ static void bounce(imc_mail *item, const char *source, const char *reason) { char temp[IMC_DATA_LENGTH]; sprintf(temp, "Your mail of %s:\n\r" " to: %s\n\r" " re: %s\n\r" "was undeliverable for the following reason:\n\r" "\n\r" "%s: %s\n\r", item->date, item->to, item->subject, source, reason); imc_mail_arrived("Mail-daemon", imc_nameof(item->from), datestring(), "Bounced mail", temp); } /* expire old entries in the mailid list; called once an hour */ void ev_mailid_expire(void *data) { flush_idlist(imc_now + 24*3600); imc_add_event(3600, ev_mailid_expire, NULL, 1); } /* give up sending a given qnode */ void ev_qnode_expire(void *data) { char temp[200]; imc_qnode *p=(imc_qnode *)data; sprintf(temp, "Unable to send to %s after 12 hours, giving up", p->tomud); bounce(p->data, imc_name, temp); delete_mq(p->data->id, p->tomud); save_ml(); save_mq(); } /* try sending a qnode */ void ev_qnode_send(void *data) { imc_qnode *p=(imc_qnode *)data; imc_packet out; save_ml(); save_mq(); /* send it.. */ imc_initdata(&out.data); sprintf(out.to, "Mail-daemon@%s", p->tomud); strcpy(out.from, "Mail-daemon"); strcpy(out.type, "mail"); imc_addkey(&out.data, "to", p->data->to); imc_addkey(&out.data, "from", p->data->from); imc_addkey(&out.data, "subject", p->data->subject); imc_addkey(&out.data, "date", p->data->date); imc_addkey(&out.data, "text", p->data->text); imc_addkey(&out.data, "id", p->data->id); imc_send(&out); imc_freedata(&out.data); /* try resending it in an hour */ imc_add_event(3600, ev_qnode_send, data, 1); } /* imc_recv_mailok: a mail-ok packet was received */ void imc_recv_mailok(const char *from, const char *id) { delete_mq(id, imc_mudof(from)); save_mq(); save_ml(); /* we might have removed the mail if usage==0 */ } /* imc_recv_mailrej: a mail-reject packet was received */ void imc_recv_mailrej(const char *from, const char *id, const char *reason) { imc_qnode *p; p = find_mq(id, imc_mudof(from)); if (!p) return; bounce(p->data, from, reason); delete_mq(id, imc_mudof(from)); save_mq(); save_ml(); } /* addrtomud: convert a 'to' list to the local mud format (ie strip @mudname * when it matches imc_name) */ static void addrtomud(const char *list, char *output) { char arg[IMC_NAME_LENGTH]; output[0]=0; list=imc_getarg(list, arg, IMC_NAME_LENGTH); while (*arg) { if (!strcasecmp(imc_name, imc_mudof(arg))) sprintf(output + strlen(output), "%s ", imc_nameof(arg)); else sprintf(output + strlen(output), "%s ", arg); list=imc_getarg(list, arg, IMC_NAME_LENGTH); } } /* mudtoaddr: add the @mudname to a 'to' list for unqualified names */ static void mudtoaddr(const char *list, char *output) { char arg[IMC_NAME_LENGTH]; output[0]=0; list=imc_getarg(list, arg, IMC_NAME_LENGTH); while (*arg) { if (strchr(arg, '@') == NULL) sprintf(output + strlen(output), "%s@%s ", arg, imc_name); else sprintf(output + strlen(output), "%s ", arg); list=imc_getarg(list, arg, IMC_NAME_LENGTH); } /* chop final space */ if (arg[0] && arg[strlen(arg) - 1] == ' ') arg[strlen(arg) - 1] = 0; } /* imc_recv_mail: a mail packet was received */ void imc_recv_mail(const char *from, const char *to, const char *date, const char *subject, const char *id, const char *text) { imc_mailid *mid; imc_packet out; char *reason; char temp[IMC_DATA_LENGTH]; imc_initdata(&out.data); sprintf(out.to, "Mail-daemon@%s", imc_mudof(from)); strcpy(out.from, "Mail-daemon"); /* check if we've already seen it */ mid=find_id(id); if (mid) { strcpy(out.type, "mail-ok"); imc_addkey(&out.data, "id", id); imc_send(&out); imc_freedata(&out.data); mid->received = imc_now; return; } /* check for rignores */ if (imc_isignored(from)) { strcpy(out.type, "mail-reject"); imc_addkey(&out.data, "id", id); imc_addkey(&out.data, "reason", "You are being ignored."); imc_send(&out); imc_freedata(&out.data); return; } /* forward it to the mud */ addrtomud(to, temp); if ((reason=imc_mail_arrived(from, temp, date, subject, text)) == NULL) { /* it was OK */ strcpy(out.type, "mail-ok"); imc_addkey(&out.data, "id", id); imc_send(&out); imc_freedata(&out.data); mid=new_mailid(); mid->id=imc_strdup(id); mid->received=imc_now; add_idlist(mid); save_idlist(); return; } /* mud rejected the mail */ strcpy(out.type, "mail-reject"); imc_addkey(&out.data, "id", id); imc_addkey(&out.data, "reason", reason); imc_send(&out); imc_freedata(&out.data); } /* imc_send_mail: called by the mud to add a piece of mail to the queue */ void imc_send_mail(const char *from, const char *to, const char *date, const char *subject, const char *text) { char temp[IMC_DATA_LENGTH]; imc_mail *m; imc_qnode *qroot, *q; char arg[IMC_NAME_LENGTH]; const char *mud; int when=10; /* set up the entry for the mail list */ m=new_mail(); mudtoaddr(to, temp); /* qualify local addresses */ m->to = imc_strdup(temp); sprintf(temp, "%s@%s", from, imc_name); /* qualify sender */ m->from = imc_strdup(temp); m->date = imc_strdup(date); m->subject = imc_strdup(subject); m->id = imc_strdup(generate_mailid()); m->text = imc_strdup(text); m->received = imc_now; qroot=NULL; /* initialise the local list */ to=imc_getarg(to, arg, IMC_NAME_LENGTH); while (*arg) { /* get a mudname and check if we've already added a queue entry for that * mud. If not, add it */ if (strchr(arg, '@') != NULL && (mud = imc_mudof(arg))[0] != 0 && strcasecmp(mud, imc_name)) { if (!strcmp(mud, "*")) q=NULL; /* catch the @* case - not yet implemented */ else for (q=qroot; q; q=q->next) if (!strcasecmp(q->tomud, mud)) break; if (!q) /* not seen yet */ { /* add to the top of our mini-queue */ q=new_qnode(); q->tomud=imc_strdup(mud); q->data=m; q->next=qroot; m->usage++; qroot=q; imc_add_event(when, ev_qnode_send, q, 1); when += rand()%30+30; } } /* get the next address */ to=imc_getarg(to, arg, IMC_NAME_LENGTH); } if (!qroot) /* boggle, no foreign addresses?? */ { free_mail(m); return; } /* add mail to mail list, add mini-queue to mail queue */ add_ml(m); add_mq(qroot); save_ml(); save_mq(); } /* imc_mail_startup: start up the mail subsystem */ void imc_mail_startup(void) { init_mq(); init_ml(); init_idlist(); /* order is important here: we need the maillist to resolve the ID refs in * the mailqueue */ load_ml(); load_mq(); load_idlist(); /* queue an expiry event */ imc_add_event(24*3600, ev_mailid_expire, NULL, 0); } /* imc_mail_shutdown: shut down the mailer */ void imc_mail_shutdown(void) { save_mq(); save_ml(); save_idlist(); free_mq(); free_ml(); free_idlist(); imc_cancel_event(ev_mailid_expire, NULL); } /* imc_mail_showqueue: returns the current mail queue * buffer handling here is pretty ugly, oh well */ char *imc_mail_showqueue(void) { char *buf=imc_getsbuf(IMC_DATA_LENGTH); char temp[100]; imc_qnode *p; int left = IMC_DATA_LENGTH; sprintf(buf, "%-15s %-45s %-10s %s\n\r", "From", "To", "Via", "Time"); for (p=imc_mq_head->next; p && left > 160; p = p->next) { int m, s; m=imc_next_event(ev_qnode_send, p); if (m<0) sprintf(temp, "%-15.15s %-45.45s %-10.10s --:--\n\r", p->data->from, p->data->to, p->tomud); else { s=m%60; m/=60; sprintf(temp, "%-15.15s %-45.45s %-10.10s %2d:%02d\n\r", p->data->from, p->data->to, p->tomud, m, s); } left -= strlen(temp); strcat(buf, temp); } if (p) { int count; for (count=0; p; p=p->next, count++) ; sprintf(temp, "[%d further entries omitted]\n\r", count); strcat(buf, temp); } imc_shrinksbuf(buf); return buf; }