Newer
Older
/* Synchronet message retrieval functions */
/****************************************************************************
* @format.tab-size 4 (Plain Text/Source Code File Header) *
* @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) *
* *
* Copyright Rob Swindell - http://www.synchro.net/copyright.html *
* *
* 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. *
* See the GNU General Public License for more details: gpl.txt or *
* http://www.fsf.org/copyleft/gpl.html *
* *
* For Synchronet coding style and modification guidelines, see *
* http://www.synchro.net/source.html *
* *
* Note: If this box doesn't appear square, then you need to fix your tabs. *
****************************************************************************/
/***********************************************************************/
/* Functions that do i/o with messages (posts/mail/auto) or their data */
/***********************************************************************/
#include "sbbs.h"
#include "utf8.h"
/****************************************************************************/
/* Loads an SMB message from the open msg base the fastest way possible */
/* first by offset, and if that's the wrong message, then by number. */
/* Returns >=0 if the message was loaded and left locked, otherwise < 0. */
/* !WARNING!: If you're going to write the msg index back to disk, you must */
/* Call this function with a msg->idx.offset of 0 (so msg->offset will be */
/* initialized correctly) */
/****************************************************************************/
int sbbs_t::loadmsg(smbmsg_t *msg, uint number)
if (msg->idx.offset) { /* Load by offset if specified */
if ((i = smb_lockmsghdr(&smb, msg)) != SMB_SUCCESS) {
errormsg(WHERE, ERR_LOCK, smb.file, i, smb.last_error);
return i;
i = smb_getmsghdr(&smb, msg);
if (i == SMB_SUCCESS) {
if (msg->hdr.number == number)
return msg->total_hfields;
/* Wrong offset */
smb_unlockmsghdr(&smb, msg);
msg->hdr.number = number;
if ((i = smb_getmsgidx(&smb, msg)) != SMB_SUCCESS) /* Message is deleted */
return i;
if ((i = smb_lockmsghdr(&smb, msg)) != SMB_SUCCESS) {
errormsg(WHERE, ERR_LOCK, smb.file, i, smb.last_error);
return i;
if ((i = smb_getmsghdr(&smb, msg)) != SMB_SUCCESS) {
SAFEPRINTF4(str, "(%06" PRIX32 ") #%" PRIu32 "/%u %s", msg->idx.offset, msg->idx.number
, number, smb.file);
smb_unlockmsghdr(&smb, msg);
errormsg(WHERE, ERR_READ, str, i, smb.last_error);
return i;
return msg->total_hfields;
void sbbs_t::show_msgattr(const smbmsg_t* msg)
uint16_t poll = attr & MSG_POLL_VOTE_MASK;
uint32_t netattr = msg->hdr.netattr;
safe_snprintf(attr_str, sizeof(attr_str), "%s%s%s%s%s%s%s%s%s%s%s%s%s%s"
, attr & MSG_PRIVATE ? "Private " :nulstr
, attr & MSG_SPAM ? "SPAM " :nulstr
, attr & MSG_READ ? "Read " :nulstr
, attr & MSG_DELETE ? "Deleted " :nulstr
, attr & MSG_KILLREAD ? "Kill " :nulstr
, attr & MSG_ANONYMOUS ? "Anonymous " :nulstr
, attr & MSG_LOCKED ? "Locked " :nulstr
, attr & MSG_PERMANENT ? "Permanent " :nulstr
, attr & MSG_MODERATED ? "Moderated " :nulstr
, attr & MSG_VALIDATED ? "Validated " :nulstr
, attr & MSG_REPLIED ? "Replied " :nulstr
, attr & MSG_NOREPLY ? "NoReply " :nulstr
, poll == MSG_POLL ? "Poll " :nulstr
, poll == MSG_POLL && auxattr & POLL_CLOSED ? "(Closed) " :nulstr
);
char auxattr_str[64];
safe_snprintf(auxattr_str, sizeof(auxattr_str), "%s%s%s%s%s%s%s"
, auxattr & MSG_FILEREQUEST? "FileRequest " :nulstr
, auxattr & MSG_FILEATTACH ? "FileAttach " :nulstr
, auxattr & MSG_MIMEATTACH ? "MimeAttach " :nulstr
, auxattr & MSG_KILLFILE ? "KillFile " :nulstr
, auxattr & MSG_RECEIPTREQ ? "ReceiptReq " :nulstr
, auxattr & MSG_CONFIRMREQ ? "ConfirmReq " :nulstr
, auxattr & MSG_NODISP ? "DontDisplay " :nulstr
);
char netattr_str[64];
safe_snprintf(netattr_str, sizeof(netattr_str), "%s%s%s%s%s%s%s%s"
, netattr & NETMSG_LOCAL ? "Local " :nulstr
, netattr & NETMSG_INTRANSIT ? "InTransit " :nulstr
, netattr & NETMSG_SENT ? "Sent " :nulstr
, netattr & NETMSG_KILLSENT ? "KillSent " :nulstr
, netattr & NETMSG_HOLD ? "Hold " :nulstr
, netattr & NETMSG_CRASH ? "Crash " :nulstr
, netattr & NETMSG_IMMEDIATE ? "Immediate " :nulstr
, netattr & NETMSG_DIRECT ? "Direct " :nulstr
);
bprintf(text[MsgAttr], attr_str, auxattr_str, netattr_str
, nulstr, nulstr, nulstr, nulstr, nulstr, nulstr, nulstr, nulstr, nulstr, nulstr, nulstr
, nulstr, nulstr, nulstr, nulstr, nulstr, nulstr, nulstr, nulstr, nulstr, nulstr, nulstr
, nulstr, nulstr, nulstr, nulstr, nulstr, nulstr, nulstr, nulstr, nulstr, nulstr, nulstr);
/* Returns a CP437 text.dat string converted to UTF-8, when appropriate */
const char* sbbs_t::msghdr_text(const smbmsg_t* msg, uint index)
{
if (msg == NULL || !(msg->hdr.auxattr & MSG_HFIELDS_UTF8))
return text[index];
if (cp437_to_utf8_str(text[index], msghdr_utf8_text, sizeof(msghdr_utf8_text), /* min-char-val: */ '\x80') < 1)
return text[index];
return msghdr_utf8_text;
}
// Returns a CP437 version of a message header field or UTF-8 if can_utf8 is true
// Doesn't do CP437->UTF-8 conversion
const char* sbbs_t::msghdr_field(const smbmsg_t* msg, const char* str, char* buf, bool can_utf8)
{
if (msg == NULL || !(msg->hdr.auxattr & MSG_HFIELDS_UTF8))
strncpy(buf, str, sizeof(msgghdr_field_cp437_str) - 1);
utf8_to_cp437_inplace(buf);
/****************************************************************************/
/* Displays a message header to the screen */
/****************************************************************************/
void sbbs_t::show_msghdr(smb_t* smb, const smbmsg_t* msg, const char* subject, const char* from, const char* to)
char str[MAX_PATH + 1];
char age[64];
char *sender = NULL;
int i;
smb_t saved_smb = this->smb;
int pmode = 0;
if (smb != NULL)
this->smb = *smb; // Needed for @-codes and JS bbs.smb_* properties
current_msg = msg; // Needed for @-codes and JS bbs.msg_* properties
current_msg_subj = msg->subj;
current_msg_from = msg->from;
current_msg_to = msg->to;
if (msg->hdr.auxattr & MSG_HFIELDS_UTF8)
if (row != 0) {
if (useron.misc & CLRSCRN)
if (!menu("msghdr", P_NOERROR)) {
bprintf(pmode, msghdr_text(msg, MsgSubj), current_msg_subj);
if (msg->tags && *msg->tags)
bprintf(text[MsgTags], msg->tags);
if (msg->hdr.attr || msg->hdr.netattr || (msg->hdr.auxattr & ~MSG_HFIELDS_UTF8))
show_msgattr(msg);
if (current_msg_to != NULL && *current_msg_to != 0) {
bprintf(pmode, msghdr_text(msg, MsgTo), current_msg_to);
if (msg->to_net.addr != NULL)
bprintf(text[MsgToNet], smb_netaddrstr(&msg->to_net, str));
if (msg->to_ext)
bprintf(text[MsgToExt], msg->to_ext);
}
if (msg->cc_list != NULL)
bprintf(text[MsgCarbonCopyList], msg->cc_list);
if (current_msg_from != NULL && (!(msg->hdr.attr & MSG_ANONYMOUS) || SYSOP)) {
bprintf(pmode, msghdr_text(msg, MsgFrom), current_msg_from);
if (msg->from_ext)
bprintf(text[MsgFromExt], msg->from_ext);
if (msg->from_net.addr != NULL)
bprintf(text[MsgFromNet], smb_netaddrstr(&msg->from_net, str));
}
if (!(msg->hdr.attr & MSG_POLL) && (msg->upvotes || msg->downvotes))
bprintf(text[MsgVotes]
, msg->upvotes, msg->user_voted == 1 ? text[PollAnswerChecked] : nulstr
, msg->downvotes, msg->user_voted == 2 ? text[PollAnswerChecked] : nulstr
, msg->upvotes - msg->downvotes);
time_t t = smb_time(msg->hdr.when_written);
bprintf(text[MsgDate]
, timestr(t)
, smb_zonestr(msg->hdr.when_written.zone, NULL)
, age_of_posted_item(age, sizeof(age), t - (smb_tzutc(msg->hdr.when_written.zone) * 60)));
}
for (i = 0; i < msg->total_hfields; i++) {
if (msg->hfield[i].type == SENDER)
sender = (char *)msg->hfield_dat[i];
if (msg->hfield[i].type == FORWARDED && sender)
bprintf(text[ForwardedFrom], sender
, timestr(*(time32_t *)msg->hfield_dat[i]));
current_msg_subj = NULL;
current_msg_from = NULL;
current_msg_to = NULL;
}
/****************************************************************************/
/* Displays message header and text (if not deleted) */
/****************************************************************************/
bool sbbs_t::show_msg(smb_t* smb, smbmsg_t* msg, int p_mode, post_t* post)
char* txt;
BOOL is_sub = subnum_is_valid(smb->subnum);
if (is_sub) {
if ((msg->hdr.type == SMB_MSG_TYPE_NORMAL && post != NULL && (post->upvotes || post->downvotes))
|| msg->hdr.type == SMB_MSG_TYPE_POLL)
msg->user_voted = smb_voted_already(smb, msg->hdr.number
, cfg.sub[smb->subnum]->misc & SUB_NAME ? useron.name : useron.alias, NET_NONE, NULL);
int comments = 0;
for (int i = 0; i < msg->total_hfields; i++)
if (msg->hfield[i].type == SMB_COMMENT) {
bprintf("%s\r\n", (char*)msg->hfield_dat[i]);
comments++;
}
CRLF;
if (msg->hdr.type == SMB_MSG_TYPE_POLL && post != NULL && is_sub) {
int longest_answer = 0;
for (int i = 0; i < msg->total_hfields; i++) {
if (msg->hfield[i].type != SMB_POLL_ANSWER)
continue;
answer = (char*)msg->hfield_dat[i];
int len = strlen(answer);
if (len > longest_answer)
longest_answer = len;
}
unsigned answers = 0;
for (int i = 0; i < msg->total_hfields; i++) {
if (msg->hfield[i].type != SMB_POLL_ANSWER)
continue;
answer = (char*)msg->hfield_dat[i];
float pct = post->total_votes ? ((float)post->votes[answers] / post->total_votes) * 100.0F : 0.0F;
char str[128];
int width = longest_answer;
if (width < cols / 3)
width = cols / 3;
else if (width > cols - 20)
width = cols - 20;
bprintf(text[PollAnswerNumber], answers + 1);
if ((msg->hdr.auxattr & POLL_RESULTS_MASK) == POLL_RESULTS_OPEN)
else if ((msg->from_net.type == NET_NONE && sub_op(smb->subnum))
|| smb_msg_is_from(msg, cfg.sub[smb->subnum]->misc & SUB_NAME ? useron.name : useron.alias, NET_NONE, NULL))
else if ((msg->hdr.auxattr & POLL_RESULTS_MASK) == POLL_RESULTS_CLOSED)
results_visible = (msg->hdr.auxattr & POLL_CLOSED) ? true : false;
else if ((msg->hdr.auxattr & POLL_RESULTS_MASK) != POLL_RESULTS_SECRET)
results_visible = msg->user_voted ? true : false;
if (results_visible) {
safe_snprintf(str, sizeof(str), text[PollAnswerFmt]
, width, width, answer, post->votes[answers], pct);
backfill(str, pct, cfg.color[clr_votes_full], cfg.color[clr_votes_empty]);
if (msg->user_voted & (1 << answers))
bputs(text[PollAnswerChecked]);
} else {
attr(cfg.color[clr_votes_empty]);
bputs(answer);
}
if (!msg->user_voted && !(useron.misc & EXPERT) && !(msg->hdr.auxattr & POLL_CLOSED) && !(useron.rest & FLAG('V')))
if ((txt = smb_getmsgtxt(smb, msg, GETMSGTXT_BODY_ONLY)) == NULL)
if (!(console & CON_RAW_IN)) {
if (strstr(txt, "\x1b[") == NULL) // Don't word-wrap raw ANSI text
p_mode |= P_WORDWRAP;
bprintf(text[MIMEDecodedPlainTextFmt]
, msg->text_charset == NULL ? "unspecified (US-ASCII)" : msg->text_charset
, msg->text_subtype == NULL ? "plain" : msg->text_subtype);
if (smb_msg_is_utf8(msg)) {
if (!term_supports(UTF8))
utf8_normalize_str(txt);
p_mode |= P_UTF8;
}
p_mode |= cfg.sub[smb->subnum]->pmode;
p_mode &= ~cfg.sub[smb->subnum]->n_pmode;
}
if (console & CON_RAW_IN)
p_mode = P_NOATCODES;
putmsg(p, p_mode, msg->columns);
smb_freemsgtxt(txt);
if ((txt = smb_getmsgtxt(smb, msg, GETMSGTXT_TAIL_ONLY)) == NULL)
putmsg(txt, p_mode & (~P_WORDWRAP));
void sbbs_t::download_msg_attachments(smb_t* smb, smbmsg_t* msg, bool del, bool use_default_prot)
{
char str[256];
char keys[128];
char fpath[MAX_PATH + 1];
char* txt;
int attachment_index = 0;
bool found = true;
while (found && (txt = smb_getmsgtxt(smb, msg, 0)) != NULL) {
char filename[MAX_PATH + 1] = {0};
uint32_t filelen = 0;
uint8_t* filedata;
if ((filedata = smb_getattachment(msg, txt, filename, sizeof(filename), &filelen, attachment_index++)) != NULL
&& filename[0] != 0 && filelen > 0) {
char tmp[32];
SAFEPRINTF2(str, text[DownloadAttachedFileQ], filename, ultoac(filelen, tmp));
if (!noyes(str)) {
SAFEPRINTF2(fpath, "%s%s", cfg.temp_dir, filename);
FILE* fp = fopen(fpath, "wb");
errormsg(WHERE, ERR_OPEN, fpath, 0);
else {
int result = fwrite(filedata, filelen, 1, fp);
fclose(fp);
errormsg(WHERE, ERR_WRITE, fpath, filelen);
else
sendfile(fpath, use_default_prot ? useron.prot : 0, "attachment");
}
}
} else
found = false;
smb_freemsgtxt(txt);
}
if (msg->hdr.auxattr & MSG_FILEATTACH) { /* Attached file */
char subj[FIDO_SUBJ_LEN];
int result = smb_getmsgidx(smb, msg);
if (result != SMB_SUCCESS) {
errormsg(WHERE, ERR_READ, "index", result, smb->last_error);
return;
}
SAFECOPY(subj, msg->subj); /* filenames (multiple?) in title */
char *p, *tp, ch;
tp = subj;
while (online) {
p = strchr(tp, ' ');
tp = getfname(tp);
if (strcspn(tp, ILLEGAL_FILENAME_CHARS) == strlen(tp)) {
SAFEPRINTF3(fpath, "%sfile/%04u.in/%s" /* path is path/fname */
, cfg.data_dir, msg->idx.to, tp);
if (!fexistcase(fpath) && msg->idx.from)
SAFEPRINTF3(fpath, "%sfile/%04u.out/%s" /* path is path/fname */
, cfg.data_dir, msg->idx.from, tp);
off_t length = flength(fpath);
if (length < 1)
bprintf(text[FileDoesNotExist], tp);
else if (!(useron.exempt & FLAG('T')) && cur_cps && !SYSOP
&& (ulong)(length / cur_cps) > timeleft)
bputs(text[NotEnoughTimeToDl]);
else {
char tmp[512];
int i;
SAFEPRINTF2(str, text[DownloadAttachedFileQ]
, getfname(fpath), u64toac(length, tmp));
if (length > 0L && text[DownloadAttachedFileQ][0] && yesno(str)) {
{ /* Remote User */
xfer_prot_menu(XFER_DOWNLOAD, &useron, keys, sizeof keys);
SAFECAT(keys, quit_key(str));
mnemonics(text[ProtocolOrQuit]);
ch = (char)getkeys(keys, 0);
i = protnum(ch, XFER_DOWNLOAD);
if (i < cfg.total_prots) {
time_t elapsed = 0;
int error = protocol(cfg.prot[i], XFER_DOWNLOAD, fpath, nulstr, /* cid: */ false, /* autohang: */ true, &elapsed);
if (checkprotresult(cfg.prot[i], error, fpath)) {
if (del)
(void)remove(fpath);
logon_dlb += length; /* Update stats */
logon_dls++;
useron.dls = (ushort)adjustuserval(&cfg, useron.number
, USER_DLS, 1);
useron.dlb = adjustuserval(&cfg, useron.number
, USER_DLB, length);
downloadedbytes(length, elapsed);
bprintf(text[FileNBytesSent]
, getfname(fpath), u64toac(length, tmp));
SAFEPRINTF(str
, "downloaded attached file: %s"
, getfname(fpath));
logline("D-", str);
}
autohangup();
}
}
}
}
}
break;
tp = p + 1;
while (*tp == ' ') tp++;
}
// Remove the *.in directory, only if its empty
SAFEPRINTF2(fpath, "%sfile/%04u.in", cfg.data_dir, msg->idx.to);
rmdir(fpath);
}
}
/****************************************************************************/
/* Writes message header and text data to a text file */
/****************************************************************************/
bool sbbs_t::msgtotxt(smb_t* smb, smbmsg_t* msg, const char *fname, bool header, uint gettxt_mode)
char *buf;
char tmp[128];
int i;
FILE *out;
if ((out = fnopen(&i, fname, O_WRONLY | O_CREAT | O_APPEND)) == NULL) {
errormsg(WHERE, ERR_OPEN, fname, 0);
return false;
if (header) {
fprintf(out, "\r\n");
fprintf(out, "Subj : %s\r\n", msg->subj);
fprintf(out, "To : %s", msg->to);
if (msg->to_net.addr)
fprintf(out, " (%s)", smb_netaddrstr(&msg->to_net, tmp));
if (msg->to_ext)
fprintf(out, " #%s", msg->to_ext);
fprintf(out, "\r\nFrom : %s", msg->from);
if (msg->from_ext && !(msg->hdr.attr & MSG_ANONYMOUS))
fprintf(out, " #%s", msg->from_ext);
if (msg->from_net.addr)
fprintf(out, " (%s)", smb_netaddrstr(&msg->from_net, tmp));
fprintf(out, "\r\nDate : %.24s %s"
, timestr(smb_time(msg->hdr.when_written))
, smb_zonestr(msg->hdr.when_written.zone, NULL));
fprintf(out, "\r\n\r\n");
buf = smb_getmsgtxt(smb, msg, gettxt_mode);
if (buf != NULL) {
smb_freemsgtxt(buf);
} else if (smb_getmsgdatlen(msg) > 2)
errormsg(WHERE, ERR_READ, smb->file, smb_getmsgdatlen(msg));
}
/****************************************************************************/
/* Returns message number posted at or after time */
/****************************************************************************/

Rob Swindell
committed
int sbbs_t::getmsgnum(int subnum, time_t t)
int i;
smb_t smb;
idxrec_t idx;
SAFEPRINTF2(smb.file, "%s%s", cfg.sub[subnum]->data_dir, cfg.sub[subnum]->code);
smb.retry_time = cfg.smb_retry_time;
smb.subnum = subnum;
if ((i = smb_open_index(&smb)) != SMB_SUCCESS) {
errormsg(WHERE, ERR_OPEN, smb.file, i, smb.last_error);
return 0;
int result = smb_getmsgidx_by_time(&smb, &idx, t);
if (result >= SMB_SUCCESS)
return idx.number - 1;
return ~0;
}
/****************************************************************************/
/* Returns the time of the message number pointed to by 'ptr' */
/****************************************************************************/

Rob Swindell
committed
time_t sbbs_t::getmsgtime(int subnum, uint ptr)
int i;
smb_t smb;
smbmsg_t msg;
idxrec_t lastidx;
SAFEPRINTF2(smb.file, "%s%s", cfg.sub[subnum]->data_dir, cfg.sub[subnum]->code);
smb.retry_time = cfg.smb_retry_time;
smb.subnum = subnum;
if ((i = smb_open(&smb)) != 0) {
errormsg(WHERE, ERR_OPEN, smb.file, i, smb.last_error);
if (!filelength(fileno(smb.sid_fp))) { /* Empty base */
msg.idx_offset = 0;
msg.hdr.number = 0;
if (smb_getmsgidx(&smb, &msg)) { /* Get first message index */
if (!ptr || msg.idx.number >= ptr) { /* ptr is before first message */
return msg.idx.time; /* so return time of first msg */
if (smb_getlastidx(&smb, &lastidx)) { /* Get last message index */
if (lastidx.number < ptr) { /* ptr is after last message */
return lastidx.time; /* so return time of last msg */
msg.idx.time = 0;
msg.hdr.number = ptr;
if (!smb_getmsgidx(&smb, &msg)) {
if (ptr - msg.idx.number < lastidx.number - ptr) {
msg.idx_offset = 0;
msg.idx.number = 0;
while (msg.idx.number < ptr) {
msg.hdr.number = 0;
if (smb_getmsgidx(&smb, &msg) || msg.idx.number >= ptr)
msg.idx_offset++;
while (ptr) {
msg.hdr.number = ptr;
if (!smb_getmsgidx(&smb, &msg))
}
/****************************************************************************/
/* Returns the total number of msgs in the sub-board and sets 'ptr' to the */
/* number of the last message in the sub (0) if no messages. */
/****************************************************************************/

Rob Swindell
committed
uint sbbs_t::getlastmsg(int subnum, uint32_t *ptr, time_t *t)
int i;
uint total;
smb_t smb;
idxrec_t idx;
if (ptr)
(*ptr) = 0;
if (t)
(*t) = 0;
if (!subnum_is_valid(subnum))
SAFEPRINTF2(smb.file, "%s%s", cfg.sub[subnum]->data_dir, cfg.sub[subnum]->code);
smb.retry_time = cfg.smb_retry_time;
smb.subnum = subnum;
if ((i = smb_open(&smb)) != 0) {
errormsg(WHERE, ERR_OPEN, smb.file, i, smb.last_error);
if (!filelength(fileno(smb.sid_fp))) { /* Empty base */
if ((i = smb_locksmbhdr(&smb)) != 0) {
errormsg(WHERE, ERR_LOCK, smb.file, i, smb.last_error);
if ((i = smb_getlastidx(&smb, &idx)) != 0) {
errormsg(WHERE, ERR_READ, smb.file, i, smb.last_error);
if (cfg.sub[subnum]->misc & SUB_NOVOTING)
total = (int)filelength(fileno(smb.sid_fp)) / sizeof(idxrec_t);

Rob Swindell
committed
else
total = smb_msg_count(&smb, (1 << SMB_MSG_TYPE_NORMAL) | (1 << SMB_MSG_TYPE_POLL));
smb_unlocksmbhdr(&smb);
smb_close(&smb);
if (ptr)
(*ptr) = idx.number;
if (t)
(*t) = idx.time;