/******************************************************** * File: handle.c * Created at Sun Jan 28 22:10:29 MSK 2001 by raorn // raorn@binec.ru * Message handling * $Id: handle.c,v 1.23 2002/05/12 22:18:36 raorn Exp $ *******************************************************/ #include "crashecho.h" bool WriteBad(struct MemMessage * mm, uchar * reason); bool HandleMessage(struct MemMessage *mm, bool no_echomail) { LogWrite(6, DEBUG, "Is in HandleMessage()"); if (nowdoing == DOING_TOSS || nowdoing == DOING_TOSSBAD) toss_total++; if (mm->Area[0] == 0) return HandleNetmail(mm); else if (mm->no_security || !no_echomail) return HandleEchomail(mm); else { LogWrite(3, TOSSINGERR, "Refusing echomail message from insecure inbound"); toss_bad++; return WriteBad(mm, "Echomail message from insecure inbound"); } } /**************************** auto-add *****************************/ bool GetDescription(uchar * area, struct ConfigNode * node, uchar * desc) { struct Arealist *arealist; uchar buf[200]; ulong c, d; FILE *fh; for (arealist = (struct Arealist *) config.ArealistList.First; arealist; arealist = arealist->Next) { if (arealist->Node == node && (arealist->Flags & AREALIST_DESC)) { if ((fh = fopen(arealist->AreaFile, "rt"))) { while (fgets(buf, 199, fh)) { for (c = 0; buf[c] > 32; c++); if (buf[c] != 0) { buf[c] = 0; if (stricmp(buf, area) == 0) { c++; while (buf[c] <= 32 && buf[c] != 0) c++; if (buf[c] != 0) { d = 0; while (buf[c] != 0 && buf[c] != 10 && buf[c] != 13 && d < 77) desc[d++] = buf[c++]; desc[d] = 0; fclose(fh); return (TRUE); } } } } fclose(fh); } else { LogWrite(1, SYSTEMERR, "Failed to open file \"%s\"\n", arealist->AreaFile); LogWrite(1, SYSTEMERR, "Error: %s", strerror(errno)); } } } return (FALSE); } bool AddTossNode(struct Area * area, struct ConfigNode * cnode, ushort flags) { struct TossNode *tnode; /* Check if it already exists */ for (tnode = (struct TossNode *) area->TossNodes.First; tnode; tnode = tnode->Next) if (tnode->ConfigNode == cnode) return (TRUE); if (!(tnode = (struct TossNode *) calloc(1, sizeof(struct TossNode)))) { nomem = TRUE; return (FALSE); } jbAddNode((struct jbList *) &area->TossNodes, (struct jbNode *) tnode), tnode->ConfigNode = cnode; tnode->Flags = flags; return (TRUE); } static time_t lastt=0; void MakeDirectory(uchar * dest, ulong destsize, uchar * defdir, uchar * areaname) { ulong c, d; uchar lowercase[200], shortname[50]; /* Convert to lower case */ mystrncpy(lowercase, areaname, 200); for (c = 0; lowercase[c] != 0; c++) lowercase[c] = tolower(lowercase[c]); /* Make 8 digit serial number */ if (lastt == 0) lastt = time(NULL); else lastt++; snprintf(shortname, 50, "%08lx", lastt); d = 0; for (c = 0; c < strlen(defdir) && d != destsize - 1; c++) { if (defdir[c] == '%' && (defdir[c + 1] | 32) == 'a') { strncpy(&dest[d], areaname, (size_t) (destsize - 1 - d)); dest[destsize - 1] = 0; d = strlen(dest); c++; } else if (defdir[c] == '%' && (defdir[c + 1] | 32) == 'l') { strncpy(&dest[d], lowercase, (size_t) (destsize - 1 - d)); dest[destsize - 1] = 0; d = strlen(dest); c++; } else if (defdir[c] == '%' && (defdir[c + 1] | 32) == '8') { strncpy(&dest[d], shortname, (size_t) (destsize - 1 - d)); dest[destsize - 1] = 0; d = strlen(dest); c++; } else dest[d++] = defdir[c]; } dest[d] = 0; } struct Area *AddArea(uchar * name, struct Node4D *node, struct Node4D *mynode, ulong active, ulong forcepassthru) { struct Area *temparea, *defarea; struct Aka *tempaka; struct ConfigNode *tempcnode; if (!(temparea = (struct Area *) calloc(1, sizeof(struct Area)))) { nomem = TRUE; return (NULL); } jbNewList(&temparea->TossNodes); jbNewList(&temparea->BannedNodes); jbAddNode(&config.AreaList, (struct jbNode *) temparea); for (tempaka = (struct Aka *) config.AkaList.First; tempaka; tempaka = tempaka->Next) if (Compare4D(&tempaka->Node, mynode) == 0) break; if (!tempaka) tempaka = (struct Aka *) config.AkaList.First; for (tempcnode = (struct ConfigNode *) config.CNodeList.First; tempcnode; tempcnode = tempcnode->Next) if (Compare4D(&tempcnode->Node, node) == 0) break; /* Find default area to use */ defarea = NULL; /* First we try to find one for specific groups */ if (tempcnode && tempcnode->DefaultGroup) { uchar groups[100]; for (defarea = (struct Area *) config.AreaList.First; defarea; defarea = defarea->Next) if (strnicmp(defarea->Tagname, "DEFAULT_", 8) == 0) { mystrncpy(groups, &defarea->Tagname[8], 50); if (MatchFlags(tempcnode->DefaultGroup, groups)) break; } } /* If not found, we try to find the general default area */ if (!defarea) { for (defarea = (struct Area *) config.AreaList.First; defarea; defarea = defarea->Next) if (stricmp(defarea->Tagname, "DEFAULT") == 0) break; } if (defarea) { struct TossNode *tnode; ulong c; uchar *forbiddenchars = "\"#'`()*,/:;<>|"; uchar buf[100], buf2[200]; mystrncpy(buf, name, 100); for (c = 0; buf[c] != 0; c++) if (buf[c] < 33 || buf[c] > 126 || strchr(forbiddenchars, buf[c])) buf[c] = '_'; /* * Cannot create directory directly into temparea->Path. * MakeDirectory checks for duplicate area names in the * AreaList and would get confused * * * * * * * * * * * * * * * * * * * * * * * * * * * */ MakeDirectory(buf2, 200, defarea->Path, buf); mystrncpy(temparea->Path, buf2, 200); if (!forcepassthru) temparea->Messagebase = defarea->Messagebase; strcpy(temparea->Description, defarea->Description); if (defarea->Flags & AREA_MANDATORY) temparea->Flags |= AREA_MANDATORY; if (defarea->Flags & AREA_DEFREADONLY) temparea->Flags |= AREA_DEFREADONLY; if (defarea->Flags & AREA_IGNOREDUPES) temparea->Flags |= AREA_IGNOREDUPES; if (defarea->Flags & AREA_IGNORESEENBY) temparea->Flags |= AREA_IGNORESEENBY; if (defarea->Flags & AREA_IMPORTSEENBY) temparea->Flags |= AREA_IMPORTSEENBY; temparea->KeepDays = defarea->KeepDays; temparea->KeepNum = defarea->KeepNum; for (tnode = (struct TossNode *) defarea->TossNodes.First; tnode; tnode = tnode->Next) AddTossNode(temparea, tnode->ConfigNode, tnode->Flags); } GetDescription(name, tempcnode, temparea->Description); if (!active) temparea->Flags |= AREA_UNCONFIRMED; mystrncpy(temparea->Tagname, name, 80); temparea->Aka = tempaka; temparea->AreaType = AREATYPE_ECHOMAIL; if (tempcnode) { temparea->Group = tempcnode->DefaultGroup; AddTossNode(temparea, tempcnode, TOSSNODE_FEED); for (tempcnode = (struct ConfigNode *) config.CNodeList.First; tempcnode; tempcnode = tempcnode->Next) if (MatchFlags(temparea->Group, tempcnode->AddGroups)) { ushort flags; flags = 0; if ((temparea->Flags & AREA_DEFREADONLY) || MatchFlags(temparea->Group, tempcnode->ReadOnlyGroups)) flags = TOSSNODE_READONLY; AddTossNode(temparea, tempcnode, flags); } } config.changed = TRUE; temparea->changed = TRUE; return (temparea); } /**************************** Echomail *****************************/ bool FindNodes2D(struct jbList * list, struct Node4D * node) { struct Nodes2D *tmp; ushort c; for (tmp = (struct Nodes2D *) list->First; tmp; tmp = tmp->Next) for (c = 0; c < tmp->Nodes; c++) if (tmp->Net[c] == node->Net && tmp->Node[c] == node->Node) return (TRUE); return (FALSE); } bool WriteBad(struct MemMessage * mm, uchar * reason) { struct Area *temparea; /* struct TextChunk *chunk;*/ uchar buf[200]; LogWrite(6, DEBUG, "Is in WriteBad(..., \"%s\")", reason); for (temparea = (struct Area *) config.AreaList.First; temparea; temparea = temparea->Next) if (temparea->AreaType == AREATYPE_BAD) break; if (!temparea) { LogWrite(2, TOSSINGERR, "No BAD area configured, message lost"); return (TRUE); } /* Insert a new textchunk with information first in the message */ /* if (!(chunk = (struct TextChunk *) malloc(sizeof(struct TextChunk)))) {*/ /* nomem = TRUE;*/ /* return (FALSE);*/ /* }*/ /* chunk->Next = (struct TextChunk *) mm->TextChunks.First;*/ /* mm->TextChunks.First = (struct jbNode *) chunk;*/ /* if (!mm->TextChunks.Last)*/ /* mm->TextChunks.Last = (struct jbNode *) chunk;*/ /* Lines must be inserted in reverse order */ snprintf(buf, 200, "\001REASON: %s\r", reason); mmAddLine(mm, buf, MM_HEAD); snprintf(buf, 200, "\001FROM: %u:%u/%u.%u\r", mm->PktOrig.Zone, mm->PktOrig.Net, mm->PktOrig.Node, mm->PktOrig.Point); mmAddLine(mm, buf, MM_HEAD); snprintf(buf, 200, "\001AREA:%s\r", mm->Area); mmAddLine(mm, buf, MM_HEAD); if (temparea->Messagebase) { if (!((*temparea->Messagebase->importfunc) (mm, temparea))) return (FALSE); } temparea->NewTexts++; return (TRUE); } bool AddNodePath(struct jbList * list, struct Node4D * node) { uchar buf[40]; struct Path *path; struct Node4D n4d; ushort lastnet, num; bool lastok; ulong jbcpos; lastok = FALSE; lastnet = 0; /* Find last entry in Path */ path = (struct Path *) list->Last; if (path && path->Paths != 0) { num = path->Paths - 1; jbcpos = 0; while (jbstrcpy(buf, path->Path[num], 40, &jbcpos)) { if (Parse4D(buf, &n4d)) { if (n4d.Net == 0) n4d.Net = lastnet; else lastnet = n4d.Net; lastok = TRUE; } else { lastok = FALSE; } } } /* Are we already in the PATH line? */ if (lastok) { if (n4d.Net == node->Net && n4d.Node == node->Node && n4d.Point == node->Point) return (TRUE); } /* Make address */ if (lastok && n4d.Net == node->Net) snprintf(buf, 40, "%u", node->Node); else snprintf(buf, 40, "%u/%u", node->Net, node->Node); /* Add new */ path = (struct Path *) list->Last; if (path) { if (path->Paths != 0) { if (strlen(buf) + strlen(path->Path[path->Paths - 1]) <= 70) { /* Add to old path */ strcat(path->Path[path->Paths - 1], " "); strcat(path->Path[path->Paths - 1], buf); return (TRUE); } } } if (path && path->Paths == PKT_NUMPATH) path = NULL; /* Chunk is full */ if (!path) { /* Alloc new path */ if (!(path = (struct Path *) malloc(sizeof(struct Path)))) { nomem = TRUE; return (FALSE); } jbAddNode(list, (struct jbNode *) path); path->Next = NULL; path->Paths = 0; } /* Always net/node when a new line */ snprintf(path->Path[path->Paths], 100, "%u/%u", node->Net, node->Node); path->Paths++; return (TRUE); } uchar *StripRe(uchar * str) { for (;;) { if (strnicmp(str, "Re:", 3) == 0) { str += 3; if (*str == ' ') str++; } else if (strnicmp(str, "Re^", 3) == 0 && str[4] == ':') { str += 5; if (*str == ' ') str++; } else if (strnicmp(str, "Re[", 3) == 0 && str[4] == ']' && str[5] == ':') { str += 6; if (*str == ' ') str++; } else break; } return (str); } bool HandleEchomail(struct MemMessage * mm) { struct Area *temparea; struct TossNode *temptnode; struct AddNode *tmpaddnode; struct RemNode *tmpremnode; struct ConfigNode *tempcnode; struct Carbon *tempcarbon; uchar buf[200]; LogWrite(6, DEBUG, "Is in HandleEchomail()"); mm->Type = PKTS_ECHOMAIL; mm->Attr = 0; /* Find orignode */ for (tempcnode = (struct ConfigNode *) config.CNodeList.First; tempcnode; tempcnode = tempcnode->Next) if (Compare4D(&mm->PktOrig, &tempcnode->Node) == 0) break; if (!stricmp(mm->Area, "NETMAIL")) { LogWrite(1, TOSSINGERR, "Echomail message to NETMAIL area. CrashEcho confused :-/", mm->Area); toss_bad++; if (!WriteBad(mm, "Echomail message to \"NETMAIL\" area.")) return (FALSE); } /* Find area */ for (temparea = (struct Area *) config.AreaList.First; temparea; temparea = temparea->Next) if (stricmp(temparea->Tagname, mm->Area) == 0) break; /* Auto-add */ if (!temparea) { if (tempcnode) temparea = AddArea(mm->Area, &mm->PktOrig, &mm->PktDest, tempcnode->Flags & NODE_AUTOADD, FALSE); else temparea = AddArea(mm->Area, &mm->PktOrig, &mm->PktDest, FALSE, FALSE); if (!temparea) return (FALSE); if (temparea->Flags & AREA_UNCONFIRMED) LogWrite(3, TOSSINGERR, "Unknown area %s", mm->Area); else LogWrite(3, TOSSINGINFO, "Unknown area %s -- auto-adding", mm->Area); } /* Don't toss in auto-added areas */ if (temparea->Flags & AREA_UNCONFIRMED) { toss_bad++; if (!WriteBad(mm, "Unknown area tag")) return (FALSE); return (TRUE); } /* Don't toss in removed areas */ if (temparea->AreaType == AREATYPE_DELETED) { LogWrite(1, TOSSINGERR, "Posting not allowed to removed area %s", mm->Area); toss_bad++; if (!WriteBad(mm, "Posting not allowed to removed area")) return (FALSE); return (TRUE); } /* Check if the node receives this area */ if ((nowdoing == DOING_TOSS || nowdoing == DOING_TOSSBAD) && !mm->no_security) { for (temptnode = (struct TossNode *) temparea->TossNodes.First; temptnode; temptnode = temptnode->Next) if (Compare4D(&temptnode->ConfigNode->Node, &mm->PktOrig) == 0) break; if (!temptnode) { LogWrite(1, TOSSINGERR, "%u:%u/%u.%u not active for %s", mm->PktOrig.Zone, mm->PktOrig.Net, mm->PktOrig.Node, mm->PktOrig.Point, mm->Area); toss_bad++; if (!WriteBad(mm, "Sender not active for this area")) return (FALSE); return (TRUE); } if (temptnode->Flags & TOSSNODE_READONLY || (temparea->Expires && !(temptnode->Flags & TOSSNODE_FEED))) { LogWrite(1, TOSSINGERR, "%u:%u/%u.%u not allowed to post in %s", mm->PktOrig.Zone, mm->PktOrig.Net, mm->PktOrig.Node, mm->PktOrig.Point, mm->Area); toss_bad++; if (!WriteBad(mm, "Sender not allowed to post in this area")) return (FALSE); return (TRUE); } } /* We got message, so clear the EXPIRES field */ if (temparea->Expires) { /* FIXME check if message comes from FEED */ temparea->Expires = 0; config.changed = TRUE; temparea->changed = TRUE; } /* Remove all SEEN-BY:s if the message comes from an other zone */ if (temparea->Aka->Node.Zone != mm->PktOrig.Zone && mm->SeenBy.First) jbFreeList(&mm->SeenBy); /* Check if a node already is in seen-by */ if ((config.cfg_Flags & CFG_CHECKSEENBY) && !(temparea->Flags & AREA_IGNORESEENBY)) { for (temptnode = (struct TossNode *) temparea->TossNodes.First; temptnode; temptnode = temptnode->Next) { temptnode->ConfigNode->IsInSeenBy = FALSE; if (temptnode->ConfigNode->Node.Zone == temparea->Aka->Node.Zone) if (temptnode->ConfigNode->Node.Point == 0) if (FindNodes2D(&mm->SeenBy, &temptnode->ConfigNode->Node)) temptnode->ConfigNode->IsInSeenBy = TRUE; } } /* Add nodes to seen-by */ for (temptnode = (struct TossNode *) temparea->TossNodes.First; temptnode; temptnode = temptnode->Next) if (temptnode->ConfigNode->Node.Point == 0 && temparea->Aka->Node.Zone == temptnode->ConfigNode->Node.Zone) if (!(temptnode->ConfigNode->Flags & NODE_PASSIVE)) if (!(temptnode->Flags & TOSSNODE_WRITEONLY)) if (!mmAddNodes2DList (&mm->SeenBy, temptnode->ConfigNode->Node.Net, temptnode->ConfigNode->Node.Node)) return (FALSE); /* Remove nodes specified in config from seen-by and path */ for (tmpremnode = (struct RemNode *) temparea->Aka->RemList.First; tmpremnode; tmpremnode = tmpremnode->Next) mmRemNodes2DListPat(&mm->SeenBy, &tmpremnode->NodePat); /* Add nodes specified in config to seen-by */ for (tmpaddnode = (struct AddNode *) temparea->Aka->AddList.First; tmpaddnode; tmpaddnode = tmpaddnode->Next) mmAddNodes2DList(&mm->SeenBy, tmpaddnode->Node.Net, tmpaddnode->Node.Node); /* Add own node to seen-by */ if (temparea->Aka->Node.Point == 0) { if (!mmAddNodes2DList (&mm->SeenBy, temparea->Aka->Node.Net, temparea->Aka->Node.Node)) return (FALSE); } /* Add own node to path */ if (temparea->Aka->Node.Point == 0) { if (!AddNodePath(&mm->Path, &temparea->Aka->Node)) return (FALSE); } /* Dupecheck */ if (config.cfg_DupeMode != DUPE_IGNORE && (nowdoing == DOING_TOSS || nowdoing == DOING_TOSSBAD) && !(temparea->Flags & AREA_IGNOREDUPES)) { if (CheckDupe(mm)) { LogWrite(4, TOSSINGERR, "Duplicate message in %s", mm->Area); toss_dupes++; temparea->NewDupes++; if (nowdoing == DOING_TOSS && tempcnode) tempcnode->Dupes++; if (config.cfg_DupeMode == DUPE_BAD) { if (!WriteBad(mm, "Duplicate message")) return (FALSE); } return (TRUE); } } temparea->NewTexts++; if (!mmSortNodes2D(&mm->SeenBy)) return (FALSE); /* Write to all nodes */ if (!mm->Rescanned) /* not rescanned */ for (temptnode = (struct TossNode *) temparea->TossNodes.First; temptnode; temptnode = temptnode->Next) /* is not sender of packet */ if (Compare4D(&mm->PktOrig, &temptnode->ConfigNode->Node) != 0) /* is not passive */ if (!(temptnode->ConfigNode->Flags & NODE_PASSIVE)) /* is not write-only */ if (!(temptnode->Flags & TOSSNODE_WRITEONLY)) /* is not already in seen-by */ if (!(temptnode->ConfigNode->IsInSeenBy == TRUE && (config.cfg_Flags & CFG_CHECKSEENBY))) if (!WriteEchoMail(mm, temptnode->ConfigNode, temparea->Aka)) return (FALSE); if (nowdoing == DOING_TOSS || nowdoing == DOING_TOSSBAD) { if (temparea->Messagebase) { toss_import++; if (config.cfg_Flags & CFG_STRIPRE) strcpy(mm->Subject, StripRe(mm->Subject)); if (!(*temparea->Messagebase->importfunc) (mm, temparea)) return (FALSE); } } if ((nowdoing == DOING_TOSS || nowdoing == DOING_TOSSBAD) || (nowdoing == DOING_SCAN && (config.cfg_Flags & CFG_CCONSCAN))) { /* Add ^aAREA */ snprintf(buf, 200, "\001AREA:%s\r", mm->Area); mmAddLine(mm, buf, MM_HEAD); for (tempcarbon = (struct Carbon *) config.CarbonList.First; tempcarbon; tempcarbon = tempcarbon->Next) if (MatchPattern(tempcarbon->FromArea, mm->Area) && MatchPattern(tempcarbon->Pattern, tempcarbon->CCType == CC_SUBJ ? mm->Subject : tempcarbon->CCType == CC_FROM ? mm->From : mm->To)) { for (temparea = (struct Area *) config.AreaList.First; temparea; temparea = temparea->Next) if (stricmp(temparea->Tagname, tempcarbon->ToArea) == 0) break; /* Copy message */ if (temparea && temparea->Messagebase) { toss_cc++; if (!(*temparea->Messagebase->importfunc) (mm, temparea)) return (FALSE); } } } return (TRUE); } /**************************** Netmail *****************************/ /* Main netmail handling */ bool HandleNetmail(struct MemMessage * mm) { struct ConfigNode *pktcnode; struct TextChunk *tmpchunk, *chunk; uchar buf[400]; ulong size; LogWrite(6, DEBUG, "Is in HandleNetmail()"); /* Find orignode */ for (pktcnode = (struct ConfigNode *) config.CNodeList.First; pktcnode; pktcnode = pktcnode->Next) if (Compare4D(&mm->PktOrig, &pktcnode->Node) == 0) break; /* Calculate size */ size = 0; for (tmpchunk = (struct TextChunk *) mm->TextChunks.First; tmpchunk; tmpchunk = tmpchunk->Next) if (tmpchunk->Data) size += strlen(tmpchunk->Data); /* Statistics */ if (nowdoing == DOING_TOSS && pktcnode) { pktcnode->GotNetmails++; pktcnode->GotNetmailBytes += size; } /* Set zones if they are zero */ if (mm->DestNode.Zone == 0) mm->DestNode.Zone = mm->PktDest.Zone; if (mm->OrigNode.Zone == 0) mm->OrigNode.Zone = mm->PktOrig.Zone; /* Add CR if last line doesn't end with CR */ chunk = (struct TextChunk *) mm->TextChunks.First; if (chunk && chunk->Data) { if (chunk->Data[strlen(chunk->Data) - 1] != 13 && chunk->Data[strlen(chunk->Data) - 1]) mmAddBuf(&mm->TextChunks, "\015", 1, MM_ADD); } if (nowdoing == DOING_TOSS) { mm->Attr &= FLAG_IMPORT; mm->Attr |= FLAG_PVT; } else if (nowdoing == DOING_SCAN) { mm->Attr &= FLAG_EXPORT; mm->Attr |= (FLAG_PVT | FLAG_KILLSENT); } /* Import netmail */ if (config.cfg_Flags & CFG_STRIPRE) strcpy(mm->Subject, StripRe(mm->Subject)); if (nowdoing == DOING_TOSS) { Print4D(&mm->OrigNode, buf, 400); LogWrite(4, TOSSINGINFO, "Importing message from %s at %s", mm->From, buf); } config.Netmail.NewTexts++; if (config.Netmail.Messagebase) { toss_import++; if (config.cfg_Flags & CFG_STRIPRE) strcpy(mm->Subject, StripRe(mm->Subject)); if (!(config.Netmail.Messagebase->importfunc) (mm, &config.Netmail)) return (FALSE); } return (TRUE); } /******************************* end netmail **********************************/ /********************************** Rescan *******************************/ bool HandleRescan(struct MemMessage * mm) { struct Area *temparea; rescan_total++; /* Find area */ for (temparea = (struct Area *) config.AreaList.First; temparea; temparea = temparea->Next) if (stricmp(temparea->Tagname, mm->Area) == 0) break; /* Add own node to seen-by to be on the safe side */ if (temparea->Aka->Node.Point == 0) { if (!mmAddNodes2DList (&mm->SeenBy, temparea->Aka->Node.Net, temparea->Aka->Node.Node)) return (FALSE); } /* Add destination node to seen-by to be on the safe side */ if (RescanNode->Node.Point == 0) { if (!mmAddNodes2DList (&mm->SeenBy, RescanNode->Node.Net, RescanNode->Node.Node)) return (FALSE); } /* Add own node to path */ if (temparea->Aka->Node.Point == 0) { if (!AddNodePath(&mm->Path, &temparea->Aka->Node)) return (FALSE); } if (!mmSortNodes2D(&mm->SeenBy)) return (FALSE); mm->Rescanned = TRUE; if (!WriteEchoMail(mm, RescanNode, temparea->Aka)) return (FALSE); return (TRUE); } /********************************** TossBad ******************************/ bool HandleBad(struct MemMessage * mm) { if (HandleEchomail(mm)) { mm->Changed = TRUE; mm->Deleted = TRUE; return TRUE; } return FALSE; }