diff --git a/src/sbbs3/sbbsecho.c b/src/sbbs3/sbbsecho.c index 232bee97cdd90a2ada83d10d72f3748673fc5a37..570b25881e65b589462c3ff33981d7b2177fc1b9 100644 --- a/src/sbbs3/sbbsecho.c +++ b/src/sbbs3/sbbsecho.c @@ -106,6 +106,7 @@ bool terminated=false; str_list_t locked_bso_nodes; int mv(const char *insrc, const char *indest, bool copy); +time32_t fmsgtime(const char *str); void export_echomail(const char *sub_code, const nodecfg_t*, bool rescan); const char default_domain[] = "fidonet"; @@ -138,6 +139,287 @@ const char* zone_root_outbound(uint16_t zone) return cfg.outbound; } +/* Allocates the buffer and returns the data (or NULL) */ +char* parse_control_line(const char* fmsgbuf, const char* kludge) +{ + char* p; + char str[128]; + + if(fmsgbuf == NULL) + return NULL; + sprintf(str, "\1%s", kludge); + p = strstr(fmsgbuf, str); + if(p == NULL) + return NULL; + if(p != fmsgbuf && *(p-1) != '\r' && *(p-1) != '\n') /* Kludge must start new-line */ + return NULL; + SAFECOPY(str, p); + + /* Terminate at the CR */ + p = strchr(str, '\r'); + if(p == NULL) + return NULL; + *p = 0; + + /* Only return the data portion */ + p = str + strlen(kludge) + 1; + SKIP_WHITESPACE(p); + return strdup(p); +} + +typedef struct echostat_msg { + char msg_id[128]; + char reply_id[128]; + char pid[128]; + char tid[128]; + char to[FIDO_NAME_LEN]; + char from[FIDO_NAME_LEN]; + char subj[FIDO_SUBJ_LEN]; + time_t msgtime; + time_t localtime; + size_t length; + fidoaddr_t origaddr; + fidoaddr_t pkt_orig; +} echostat_msg_t; + +enum echostat_msg_type { + ECHOSTAT_MSG_RECEIVED, + ECHOSTAT_MSG_IMPORTED, + ECHOSTAT_MSG_EXPORTED, + ECHOSTAT_MSG_DUPLICATE, + ECHOSTAT_MSG_CIRCULAR, + ECHOSTAT_MSG_TYPES +}; + +const char* echostat_msg_type[ECHOSTAT_MSG_TYPES] = { + "Received", + "Imported", + "Exported", + "Duplicate", + "Circular", +}; + +typedef struct echostat { + char tag[FIDO_AREATAG_LEN + 1]; + echostat_msg_t last[ECHOSTAT_MSG_TYPES]; + echostat_msg_t first[ECHOSTAT_MSG_TYPES]; + ulong total[ECHOSTAT_MSG_TYPES]; +} echostat_t; + +echostat_t* echostat; +size_t echostat_count; + +echostat_msg_t parse_echostat_msg(str_list_t ini, const char* section, const char* prefix) +{ + char str[128]; + char key[128]; + echostat_msg_t msg = {0}; + + sprintf(key, "%s.to", prefix), iniGetString(ini, section, key, NULL, msg.to); + sprintf(key, "%s.from", prefix), iniGetString(ini, section, key, NULL, msg.from); + sprintf(key, "%s.subj", prefix), iniGetString(ini, section, key, NULL, msg.subj); + sprintf(key, "%s.msg_id", prefix), iniGetString(ini, section, key, NULL, msg.msg_id); + sprintf(key, "%s.reply_id", prefix), iniGetString(ini, section, key, NULL, msg.reply_id); + sprintf(key, "%s.pid", prefix), iniGetString(ini, section, key, NULL, msg.pid); + sprintf(key, "%s.tid", prefix), iniGetString(ini, section, key, NULL, msg.tid); + sprintf(key, "%s.msgtime", prefix), msg.msgtime = iniGetDateTime(ini, section, key, 0); + sprintf(key, "%s.localtime", prefix), msg.localtime = iniGetDateTime(ini, section, key, 0); + sprintf(key, "%s.length", prefix), msg.length = (size_t)iniGetBytes(ini, section, key, 1, 0); + sprintf(key, "%s.origaddr", prefix), iniGetString(ini, section, key, NULL, str); + if(str[0]) msg.origaddr = atofaddr(str); + sprintf(key, "%s.pkt_orig", prefix), iniGetString(ini, section, key, NULL, str); + if(str[0]) msg.pkt_orig = atofaddr(str); + + return msg; +} + +echostat_msg_t fidomsg_to_echostat_msg(fmsghdr_t fmsghdr, fidoaddr_t* pkt_orig, const char* fmsgbuf) +{ + char* p; + echostat_msg_t msg = {0}; + + SAFECOPY(msg.to , fmsghdr.to); + SAFECOPY(msg.from , fmsghdr.from); + SAFECOPY(msg.subj , fmsghdr.subj); + msg.msgtime = fmsgtime(fmsghdr.time); + msg.localtime = time(NULL); + msg.origaddr.zone = fmsghdr.origzone; + msg.origaddr.net = fmsghdr.orignet; + msg.origaddr.node = fmsghdr.orignode; + msg.origaddr.point = fmsghdr.origpoint; + if(pkt_orig != NULL) + msg.pkt_orig = *pkt_orig; + if((p = parse_control_line(fmsgbuf, "MSGID:")) != NULL) { + SAFECOPY(msg.msg_id, p); + free(p); + } + if((p = parse_control_line(fmsgbuf, "REPLY:")) != NULL) { + SAFECOPY(msg.reply_id, p); + free(p); + } + if((p = parse_control_line(fmsgbuf, "PID:")) != NULL) { + SAFECOPY(msg.pid, p); + free(p); + } + if((p = parse_control_line(fmsgbuf, "TID:")) != NULL) { + SAFECOPY(msg.tid, p); + free(p); + } + if(fmsgbuf != NULL) + msg.length = strlen(fmsgbuf); + + return msg; +} + +echostat_msg_t smsg_to_echostat_msg(smbmsg_t smsg, size_t msglen) +{ + char* p; + echostat_msg_t emsg = {0}; + + SAFECOPY(emsg.to , smsg.to); + SAFECOPY(emsg.from , smsg.from); + SAFECOPY(emsg.subj , smsg.subj); + emsg.msgtime = smsg.hdr.when_written.time; + emsg.localtime = time(NULL); + if(smsg.from_net.type == NET_FIDO && smsg.from_net.addr != NULL) + emsg.origaddr = atofaddr(smsg.from_net.addr); + if((p = smsg.ftn_msgid) != NULL) + SAFECOPY(emsg.msg_id, p); + if((p = smsg.ftn_reply) != NULL) + SAFECOPY(emsg.reply_id, p); + if((p = smsg.ftn_pid) != NULL) + SAFECOPY(emsg.pid, p); + if((p = smsg.ftn_tid) != NULL) + SAFECOPY(emsg.tid, p); + emsg.length = msglen; + + return emsg; +} + +void new_echostat_msg(echostat_t* stat, enum echostat_msg_type type, echostat_msg_t msg) +{ + stat->last[type] = msg; + if(stat->first[type].localtime == 0) + stat->first[type] = msg; + stat->total[type]++; +} + +size_t read_echostats(const char* fname, echostat_t **echostat) +{ + str_list_t ini; + FILE* fp = iniOpenFile(fname, FALSE); + if(fp == NULL) + return 0; + + ini = iniReadFile(fp); + iniCloseFile(fp); + + str_list_t echoes = iniGetSectionList(ini, NULL); + if(echoes == NULL) { + iniFreeStringList(ini); + return 0; + } + + size_t echo_count = strListCount(echoes); + *echostat = malloc(sizeof(echostat_t) * echo_count); + if(*echostat == NULL) { + iniFreeStringList(echoes); + iniFreeStringList(ini); + return 0; + } + for(unsigned i = 0; i < echo_count; i++) { + echostat_t* stat = &(*echostat)[i]; + SAFECOPY(stat->tag, echoes[i]); + for(int type = 0; type < ECHOSTAT_MSG_TYPES; type++) { + char prefix[32]; + sprintf(prefix, "First%s", echostat_msg_type[type]) + ,stat->first[type] = parse_echostat_msg(ini, stat->tag, prefix); + sprintf(prefix, "Last%s", echostat_msg_type[type]) + ,stat->last[type] = parse_echostat_msg(ini, stat->tag, prefix); + sprintf(prefix, "Total%s", echostat_msg_type[type]) + ,stat->total[type] = iniGetLongInt(ini, stat->tag, prefix, 0); + } + } + iniFreeStringList(echoes); + iniFreeStringList(ini); + + return echo_count; +} + +/* Mimic iniSetDateTime() */ +const char* iniTimeStr(time_t t) +{ + static char tstr[32]; + char tmp[32]; + char* p; + + if(t == 0) + return "Never"; + if((p = ctime_r(&t, tmp)) == NULL) + sprintf(tstr, "0x%lx", t); + else + sprintf(tstr, "%.3s %.2s %.4s %.8s", p+4, p+8, p+20, p+11); + + return tstr; +} + +void write_echostat_msg(FILE* fp, echostat_msg_t msg, const char* prefix) +{ + echostat_msg_t zero = {0}; + if(memcmp(&msg, &zero, sizeof(msg)) == 0) + return; + if(msg.to[0]) fprintf(fp, "%s.to = %s\n" , prefix, msg.to); + if(msg.from[0]) fprintf(fp, "%s.from = %s\n" , prefix, msg.from); + if(msg.subj[0]) fprintf(fp, "%s.subj = %s\n" , prefix, msg.subj); + if(msg.msg_id[0]) fprintf(fp, "%s.msg_id = %s\n" , prefix, msg.msg_id); + if(msg.reply_id[0]) fprintf(fp, "%s.reply_id = %s\n" , prefix, msg.reply_id); + if(msg.pid[0]) fprintf(fp, "%s.pid = %s\n" , prefix, msg.pid); + if(msg.tid[0]) fprintf(fp, "%s.tid = %s\n" , prefix, msg.tid); + fprintf(fp, "%s.length = %lu\n" , prefix, msg.length); + fprintf(fp, "%s.msgtime = %s\n" , prefix, iniTimeStr(msg.msgtime)); + fprintf(fp, "%s.localtime = %s\n" , prefix, iniTimeStr(msg.localtime)); + fprintf(fp, "%s.origaddr = %s\n" , prefix, faddrtoa(&msg.origaddr)); + fprintf(fp, "%s.pkt_orig = %s\n" , prefix, faddrtoa(&msg.pkt_orig)); +} + +bool write_echostats(const char* fname, echostat_t* echostat, size_t echo_count) +{ + FILE* fp; + + if((fp=fopen(fname, "w"))==NULL) + return false; + + for(unsigned i = 0; i < echo_count; i++) { + echostat_t* stat = &echostat[i]; + fprintf(fp, "[%s]\n" , stat->tag); + for(int type = 0; type < ECHOSTAT_MSG_TYPES; type++) { + char prefix[32]; + sprintf(prefix, "First%s", echostat_msg_type[type]) , write_echostat_msg(fp, stat->first[type], prefix); + sprintf(prefix, "Last%s", echostat_msg_type[type]) , write_echostat_msg(fp, stat->last[type], prefix); + fprintf(fp, "Total%s = %lu\n" , echostat_msg_type[type], stat->total[type]); + } + fprintf(fp, "\n"); + } + fclose(fp); + return true; +} + +echostat_t* get_echostat(const char* tag) +{ + for(unsigned int i = 0; i < echostat_count; i++) { + if(stricmp(echostat[i].tag, tag) == 0) + return &echostat[i]; + } + echostat_t* new_echostat = realloc(echostat, sizeof(echostat_t) * (echostat_count + 1)); + if(new_echostat == NULL) + return NULL; + echostat = new_echostat; + memset(&echostat[echostat_count], 0, sizeof(echostat_t)); + SAFECOPY(echostat[echostat_count].tag, tag); + + return &echostat[echostat_count++]; +} + /* FTN-compliant "Program Identifier"/PID (also used as a "Tosser Identifier"/TID) */ const char* sbbsecho_pid(void) { @@ -2346,7 +2628,7 @@ const char* area_desc(const char* areatag) char tag[FIDO_AREATAG_LEN+1]; static char desc[128]; - for(int i=0; i<cfg.listcfgs; i++) { + for(uint i=0; i<cfg.listcfgs; i++) { FILE* fp = fopen(cfg.listcfg[i].listpath, "r"); if(fp == NULL) { lprintf(LOG_ERR, "ERROR %d (%s) opening %s", errno, strerror(errno), cfg.listcfg[i].listpath); @@ -4065,6 +4347,12 @@ void export_echomail(const char* sub_code, const nodecfg_t* nodecfg, bool rescan FREE_AND_NULL(post); continue; } + + echostat_t* stat = get_echostat(tag); + if(stat == NULL) { + lprintf(LOG_CRIT, "Failed to allocated memory for echostats!"); + break; + } for(m=exp=0;m<posts && !terminated;m++) { printf("\r%*s %5lu of %-5"PRIu32" " @@ -4269,17 +4557,18 @@ void export_echomail(const char* sub_code, const nodecfg_t* nodecfg, bool rescan strcat((char *)fmsgbuf,str); } - for(k=0;k<cfg.areas;k++) - if(cfg.area[k].sub==i) { - cfg.area[k].exported++; - pkt_to_pkt(fmsgbuf,cfg.area[k] - ,nodecfg ? &nodecfg->addr : NULL,hdr,msg_seen - ,msg_path); + for(uint u=0; u < cfg.areas; u++) { + if(cfg.area[u].sub == i) { + cfg.area[u].exported++; + pkt_to_pkt(fmsgbuf, cfg.area[u] + ,nodecfg ? &nodecfg->addr : NULL, hdr, msg_seen, msg_path); break; } - FREE_AND_NULL(fmsgbuf); + } exported++; exp++; + new_echostat_msg(stat, ECHOSTAT_MSG_EXPORTED, smsg_to_echostat_msg(msg, strlen(fmsgbuf))); + FREE_AND_NULL(fmsgbuf); printf("Exp: %lu ",exp); smb_unlockmsghdr(&smb[cur_smb],&msg); smb_freemsgmem(&msg); @@ -4294,11 +4583,11 @@ void export_echomail(const char* sub_code, const nodecfg_t* nodecfg, bool rescan printf("\r%*s\r", 70, ""); - for(i=0;i<cfg.areas;i++) - if(cfg.area[i].exported) + for(unsigned u=0; u<cfg.areas; u++) + if(cfg.area[u].exported) lprintf(LOG_INFO,"Exported: %5u msgs %*s -> %s" - ,cfg.area[i].exported,LEN_EXTCODE,scfg.sub[cfg.area[i].sub]->code - ,cfg.area[i].name); + ,cfg.area[u].exported,LEN_EXTCODE,scfg.sub[cfg.area[u].sub]->code + ,cfg.area[u].name); if(exported) lprintf(LOG_INFO,"Exported: %5lu msgs total", exported); @@ -4881,6 +5170,18 @@ void import_packets(const char* inbound, nodecfg_t* inbox, bool secure) continue; } + p+=5; /* Skip "AREA:" */ + SKIP_WHITESPACE(p); /* Skip any white space */ + printf("%21s: ",p); /* Show areaname: */ + SAFECOPY(areatag,p); + + echostat_t* stat = get_echostat(areatag); + if(stat == NULL) { + lprintf(LOG_CRIT, "Failed to allocate memory for echostats!"); + break; + } + new_echostat_msg(stat, ECHOSTAT_MSG_RECEIVED, fidomsg_to_echostat_msg(hdr, &pkt_orig, NULL)); + if(!opt_import_echomail) { printf("EchoMail Ignored"); seektonull(fidomsg); @@ -4888,11 +5189,6 @@ void import_packets(const char* inbound, nodecfg_t* inbox, bool secure) continue; } - p+=5; /* Skip "AREA:" */ - SKIP_WHITESPACE(p); /* Skip any white space */ - printf("%21s: ",p); /* Show areaname: */ - SAFECOPY(areatag,p); - if(!secure && (nodecfg == NULL || nodecfg->pktpwd[0] == 0)) { lprintf(LOG_WARNING, "Unauthenticated %s EchoMail from %s ignored" ,areatag, smb_faddrtoa(&pkt_orig, NULL)); @@ -4932,7 +5228,6 @@ void import_packets(const char* inbound, nodecfg_t* inbox, bool secure) if(!parse_origin(fmsgbuf, &hdr)) lprintf(LOG_WARNING, "%s: Failed to parse Origin Line in message from %s (%s) in packet from %s: %s" ,areatag, hdr.from, fmsghdr_srcaddr_str(&hdr), smb_faddrtoa(&pkt_orig,NULL), packet); - if(cfg.secure_echomail && cfg.area[i].sub!=INVALID_SUB) { if(!area_is_linked(i,&pkt_orig)) { lprintf(LOG_WARNING, "%s: Security violation - %s not in AREAS.BBS" @@ -4967,6 +5262,8 @@ void import_packets(const char* inbound, nodecfg_t* inbox, bool secure) lprintf(LOG_NOTICE, "%s: Circular path detected for %s in message from %s (%s)" ,areatag,smb_faddrtoa(&scfg.faddr[j],NULL), hdr.from, fmsghdr_srcaddr_str(&hdr)); printf("\n"); + new_echostat_msg(stat, ECHOSTAT_MSG_CIRCULAR + ,fidomsg_to_echostat_msg(hdr, &pkt_orig, fmsgbuf)); continue; } } @@ -5039,6 +5336,8 @@ void import_packets(const char* inbound, nodecfg_t* inbox, bool secure) lprintf(LOG_NOTICE, "%s Duplicate message from %s (%s) to %s, subject: %s" ,areatag, hdr.from, fmsghdr_srcaddr_str(&hdr), hdr.to, hdr.subj); cfg.area[i].dupes++; + new_echostat_msg(stat, ECHOSTAT_MSG_DUPLICATE + ,fidomsg_to_echostat_msg(hdr, &pkt_orig, fmsgbuf)); } else if(result==IMPORT_SUCCESS || (result==IMPORT_FILTERED_AGE && cfg.relay_filtered_msgs)) { /* Not a dupe */ strip_psb(fmsgbuf); @@ -5060,10 +5359,13 @@ void import_packets(const char* inbound, nodecfg_t* inbox, bool secure) } echomail++; cfg.area[i].imported++; + new_echostat_msg(stat, ECHOSTAT_MSG_IMPORTED + ,fidomsg_to_echostat_msg(hdr, &pkt_orig, fmsgbuf)); } printf("\n"); } fclose(fidomsg); + FREE_AND_NULL(fmsgbuf); if(bad_packet) rename_bad_packet(packet); @@ -5336,9 +5638,9 @@ int main(int argc, char **argv) SAFECOPY(cfg.outbound, outbound); free(outbound); } - for(i=0; i<cfg.nodecfgs; i++) { - if(cfg.nodecfg[i].inbox[0]) - backslash(cfg.nodecfg[i].inbox); + for(uint u=0; u<cfg.nodecfgs; u++) { + if(cfg.nodecfg[u].inbox[0]) + backslash(cfg.nodecfg[u].inbox); } truncsp(cmdline); @@ -5501,6 +5803,8 @@ int main(int argc, char **argv) gen_notify_list(); } + echostat_count = read_echostats("../data/echostats.ini", &echostat); + find_stray_packets(); if(opt_import_packets) { @@ -5511,13 +5815,13 @@ int main(int argc, char **argv) /* to take care of any packets that may already be hanging around for some */ /* reason or another (thus the do/while loop) */ - for(i=0; i<cfg.nodecfgs && !terminated; i++) { - if(cfg.nodecfg[i].inbox[0] == 0) + for(uint u=0; u<cfg.nodecfgs && !terminated; u++) { + if(cfg.nodecfg[u].inbox[0] == 0) continue; - printf("Scanning %s inbox: %s\n", faddrtoa(&cfg.nodecfg[i].addr), cfg.nodecfg[i].inbox); + printf("Scanning %s inbox: %s\n", faddrtoa(&cfg.nodecfg[u].addr), cfg.nodecfg[u].inbox); do { - import_packets(cfg.nodecfg[i].inbox, &cfg.nodecfg[i], /* secure: */true); - } while(unpack_bundle(cfg.nodecfg[i].inbox)); + import_packets(cfg.nodecfg[u].inbox, &cfg.nodecfg[u], /* secure: */true); + } while(unpack_bundle(cfg.nodecfg[u].inbox)); } if(cfg.secure_inbound[0] && !terminated) { printf("Scanning secure inbound: %s\n", cfg.secure_inbound); @@ -5538,21 +5842,21 @@ int main(int argc, char **argv) /******* END OF IMPORT PKT ROUTINE *******/ - for(i=0;i<cfg.areas;i++) { - if(cfg.area[i].imported) + for(uint u=0; u<cfg.areas; u++) { + if(cfg.area[u].imported) lprintf(LOG_INFO,"Imported: %5u msgs %*s <- %s" - ,cfg.area[i].imported,LEN_EXTCODE,scfg.sub[cfg.area[i].sub]->code - ,cfg.area[i].name); + ,cfg.area[u].imported,LEN_EXTCODE,scfg.sub[cfg.area[u].sub]->code + ,cfg.area[u].name); } - for(i=0;i<cfg.areas;i++) { - if(cfg.area[i].circular) + for(uint u=0; u<cfg.areas; u++) { + if(cfg.area[u].circular) lprintf(LOG_INFO,"Circular: %5u detected in %s" - ,cfg.area[i].circular,cfg.area[i].name); + ,cfg.area[u].circular, cfg.area[u].name); } - for(i=0;i<cfg.areas;i++) { - if(cfg.area[i].dupes) + for(uint u=0; u<cfg.areas; u++) { + if(cfg.area[u].dupes) lprintf(LOG_INFO,"Duplicate: %4u detected in %s" - ,cfg.area[i].dupes,cfg.area[i].name); + ,cfg.area[u].dupes, cfg.area[u].name); } if(echomail) @@ -5625,17 +5929,17 @@ int main(int argc, char **argv) lprintf(LOG_DEBUG,"Updating Message Pointers to Last Posted Message..."); - for(j=0; j<cfg.areas; j++) { + for(uint u=0; u<cfg.areas; u++) { uint32_t lastmsg; - if(j==cfg.badecho) /* Don't scan the bad-echo area */ + if(u==cfg.badecho) /* Don't scan the bad-echo area */ continue; - i=cfg.area[j].sub; + i=cfg.area[u].sub; if(i<0 || i>=scfg.total_subs) /* Don't scan pass-through areas */ continue; lprintf(LOG_DEBUG,"\n%-*.*s -> %s" - ,LEN_EXTCODE, LEN_EXTCODE, scfg.sub[i]->code, cfg.area[j].name); + ,LEN_EXTCODE, LEN_EXTCODE, scfg.sub[i]->code, cfg.area[u].name); if(getlastmsg(i,&lastmsg,0) >= 0) - write_export_ptr(i, lastmsg, cfg.area[j].name); + write_export_ptr(i, lastmsg, cfg.area[u].name); } } @@ -5643,6 +5947,8 @@ int main(int argc, char **argv) if(nodecfg != NULL) attachment(NULL,nodecfg->addr,ATTACHMENT_NETMAIL); + write_echostats("../data/echostats.ini", echostat, echostat_count); + if(email->shd_fp) smb_close(email);