1 /*
2 * hptkill - areas killer for high Portable Tosser (hpt)
3 * by Serguei Revtov 2:5021/11.10 || 2:5021/19.1
4 * Some code was taken from hpt/src/areafix.c
5 *
6 * This file is part of HPT.
7 *
8 * HPT is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation; either version 2, or (at your option) any
11 * later version.
12 *
13 * HPT is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with HPT; see the file COPYING. If not, write to the Free
20 * Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
21 *****************************************************************************
22 * $Id$
23 */
24
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <ctype.h>
28 #include <fcntl.h>
29 #include <errno.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <time.h>
33 #include <string.h>
34
35 #include <huskylib/compiler.h>
36
37 #ifdef HAS_PROCESS_H
38 # include <process.h>
39 #endif
40
41 #ifdef HAS_UNISTD_H
42 # include <unistd.h>
43 # include <strings.h>
44 #endif
45
46 #ifdef HAS_IO_H
47 # include <io.h>
48 #endif
49
50 #ifdef HAS_SHARE_H
51 # include <share.h>
52 #endif
53
54 #ifdef HAS_DOS_H
55 # include <dos.h>
56 #endif
57
58 #include <smapi/msgapi.h>
59 #include <fidoconf/fidoconf.h>
60 #include <fidoconf/common.h>
61 #include <huskylib/xstr.h>
62 #include <huskylib/log.h>
63 #include <fidoconf/afixcmd.h>
64
65 #include "version.h"
66
67 s_fidoconfig *config;
68 s_robot *robot;
69
70 FILE *outlog;
71 char *versionStr;
72
73 typedef enum senduns { eNobody, eFirstLink, eNodes, eAll} e_senduns;
74
75 e_senduns sendUnSubscribe = eNodes;
76 int delFromConfig = 0;
77 int eraseBase = 1;
78 int killPass = 0;
79 int createDupe = 0;
80 static time_t tnow;
81 const long secInDay = 3600*24;
82
83
usage(void)84 void usage(void) {
85
86 printf(
87 "hptkill is a tool for removing echoareas\n"
88 "Usage: hptkill [options] [areaNameMask ...]\n"
89 "Options:"
90 "\t -c config-file - specify alternate config file\n"
91 "\t -1 - send unsubscribe message to the first link only\n"
92 "\t -n - don't send unsubscribe messages\n"
93 "\t -a - send unsubscribe messages to all subscribed links\n"
94 "\t -d - delete area from config file\n"
95 "\t -s - save (don't erase) message & dupe bases\n"
96 "\t -f file - read areas list from file in addition to args\n"
97 "\t -f - - read areas list from stdin in addition to args\n"
98 "\t -p - find & kill passthrough echoareas with <=1 links\n"
99 "\t -pp - same as -p including paused links\n"
100 "\t -y - find & kill ALL echoareas with <=1 links\n"
101 "\t -yp - same as -y including paused links\n"
102 "\t -o num - kill passthrough areas with dupebases older than"
103 "\t\t\t'num' days\n"
104 "\t -O num - same as -o but kill areas without dupebases\n"
105 "\t -l file - with -o/-O write out file with list of areas"
106 "\t\t\twithout dupebases\n"
107 "\t -C - create empty dupebase if it doesn't exist\n"
108 "\nDefault settings:\n"
109 " - send unsubscribe message only to subcribed links\n"
110 " - leave config unchanged\n"
111 " - erase message & dupe bases\n");
112 exit(-1);
113
114 }
115
changeconfig(char * fileName,s_area * area)116 int changeconfig(char *fileName, s_area *area) {
117 char *cfgline=NULL, *token=NULL, *tmpPtr=NULL, *line=NULL;
118 long strbeg = 0, strend = -1;
119
120 char *areaName = area->areaName;
121
122 w_log(LL_FUNC, __FILE__ "::changeconfig(%s,...)", fileName);
123
124 if (init_conf(fileName))
125 return -1;
126
127 while ((cfgline = configline()) != NULL) {
128 line = sstrdup(cfgline);
129 line = trimLine(line);
130 line = stripComment(line);
131 if (line[0] != 0) {
132 line = shell_expand(line);
133 line = tmpPtr = vars_expand(line);
134 token = strseparate(&tmpPtr, " \t");
135 if (stricmp(token, "echoarea")==0) {
136 token = strseparate(&tmpPtr, " \t");
137 if (*token=='\"' && token[strlen(token)-1]=='\"' && token[1]) {
138 token++;
139 token[strlen(token)-1]='\0';
140 }
141 if (stricmp(token, areaName)==0) {
142 fileName = sstrdup(getCurConfName());
143 strend = get_hcfgPos();
144 if(strbeg > strend) strbeg = 0;
145 break;
146 }
147 }
148 }
149 strbeg = get_hcfgPos();
150 nfree(line);
151 nfree(cfgline);
152 }
153 close_conf();
154 nfree(line);
155 if (strend == -1) { /* "Never happens" */
156 nfree(cfgline);
157 nfree(fileName);
158 return -1;
159 }
160 nfree(cfgline);
161 InsertCfgLine(fileName, cfgline, strbeg, strend);
162 nfree(fileName);
163 return 0;
164 }
165
166
167
putMsgInArea(s_area * echo,s_message * msg)168 int putMsgInArea(s_area *echo, s_message *msg)
169 {
170 char *ctrlBuff, *textStart, *textWithoutArea;
171 UINT textLength;
172 HAREA harea;
173 HMSG hmsg;
174 XMSG msgHeader;
175 int rc = 0;
176
177 harea = MsgOpenArea((UCHAR *) echo->fileName, MSGAREA_CRIFNEC, (word)(echo->msgbType));
178 if (harea != NULL) {
179 hmsg = MsgOpenMsg(harea, MOPEN_CREATE, 0);
180 if (hmsg != NULL) {
181
182 textWithoutArea = msg->text;
183 textLength = strlen(textWithoutArea);
184
185 ctrlBuff = (char *) CopyToControlBuf((UCHAR *) textWithoutArea,
186 (UCHAR **) &textStart,
187 &textLength);
188
189 msgHeader = createXMSG(config,msg, NULL, MSGLOCAL ,NULL);
190
191 MsgWriteMsg(hmsg, 0, &msgHeader, (UCHAR*)textStart, (dword)textLength, (dword)textLength, (dword)strlen(ctrlBuff), (byte *)ctrlBuff);
192
193 MsgCloseMsg(hmsg);
194 nfree(ctrlBuff);
195 rc = 1;
196
197 } else {
198 fprintf(outlog, "Unable to create new message in %s!", echo->fileName);
199 } /* endif */
200
201 MsgCloseArea(harea);
202
203 } else {
204 fprintf(outlog, "Unable to open echoarea %s!", echo->fileName);
205 } /* endif */
206 return rc;
207 }
208
209
makeRequestToLink(char * areatag,s_link * link)210 int makeRequestToLink (char *areatag, s_link *link) {
211 s_message *msg;
212
213 if (link->msg == NULL)
214 {
215 msg = makeMessage(link->ourAka, &(link->hisAka), config->sysop,
216 link->areafix.name ? link->areafix.name : "areafix",
217 link->areafix.pwd ? link->areafix.pwd : "\x00", 1,
218 link->areafix.reportsAttr ? link->areafix.reportsAttr : robot->reportsAttr);
219 msg->text = createKludges(config, NULL, link->ourAka, &(link->hisAka),
220 "hptkill");
221 if (link->areafix.reportsFlags)
222 xstrscat(&(msg->text), "\001FLAGS ", link->areafix.reportsFlags, "\r",NULL);
223 else if (robot->reportsFlags)
224 xstrscat(&(msg->text), "\001FLAGS ", robot->reportsFlags, "\r",NULL);
225 link->msg = msg;
226 } else {
227 msg = link->msg;
228 }
229 xscatprintf(&(msg->text), "-%s\r", areatag);
230 return 0;
231 }
232
createDupeFileName(s_area * area)233 char *createDupeFileName(s_area *area) {
234 char *name=NULL, *ptr, *retname=NULL;
235
236 if (!area->DOSFile) {
237 name = makeMsgbFileName(config, area->areaName);
238 } else {
239 if (area->fileName) xstrcat(&name, (ptr = strrchr(area->fileName,PATH_DELIM))
240 ? ptr+1 : area->fileName);
241 else xscatprintf(&name, "%X", strcrc32(area->areaName,0xFFFFFFFFUL) );
242 }
243
244 switch (config->typeDupeBase) {
245 case hashDupes:
246 xstrcat(&name,".dph");
247 break;
248 case hashDupesWmsgid:
249 xstrcat(&name,".dpd");
250 break;
251 case textDupes:
252 xstrcat(&name,".dpt");
253 break;
254 case commonDupeBase:
255 break;
256 }
257
258 if (config->areasFileNameCase == eUpper)
259 name = strUpper(name);
260 else
261 name = strLower(name);
262
263 xstrscat(&retname, config->dupeHistoryDir, name, NULL);
264 nfree(name);
265
266 return retname;
267 }
268
update_queue(s_area * area)269 void update_queue(s_area *area)
270 {
271 char *line = NULL;
272 FILE *queryFile;
273 char seps[] = " \t\n";
274 char upDate = 0;
275
276 if(!robot->queueFile)
277 return;
278
279 if ( !(queryFile = fopen(robot->queueFile,"a+b")) ) /* can't open query file */
280 {
281 w_log(LL_ERR, "Unable to open areafixQueueFile %s: %s", robot->queueFile, strerror(errno) );
282 return;
283 }
284 rewind(queryFile);
285 while ((line = readLine(queryFile)) != NULL)
286 {
287 char* token = strtok( line, seps );
288 if(strcasecmp(token,area->areaName) == 0)
289 {
290 upDate = 1;
291 break;
292 }
293 nfree(line);
294 }
295 nfree(line)
296
297 if (upDate == 0)
298 {
299 time_t eTime;
300 struct tm t1,t2;
301 eTime = tnow + robot->killedRequestTimeout*secInDay;
302 t1 = *localtime( &tnow );
303 t2 = *localtime( &eTime );
304 xscatprintf( &line , "%s %s %d-%02d-%02d@%02d:%02d\t%d-%02d-%02d@%02d:%02d" ,
305 area->areaName,
306 "kill",
307 t1.tm_year + 1900,
308 t1.tm_mon + 1,
309 t1.tm_mday,
310 t1.tm_hour,
311 t1.tm_min,
312 t2.tm_year + 1900,
313 t2.tm_mon + 1,
314 t2.tm_mday,
315 t2.tm_hour,
316 t2.tm_min );
317
318 xstrscat(&line, " ", aka2str(*(area->useAka)), "\n", NULL);
319 fputs(line , queryFile);
320 }
321 fclose(queryFile);
322 }
323
delete_area(s_area * area)324 void delete_area(s_area *area)
325 {
326 char *an = area->areaName;
327 unsigned int i;
328 int rc;
329
330 fprintf(outlog, "Killing area %s\n", an);
331
332 switch (sendUnSubscribe) {
333
334 case eNobody:
335 break;
336
337 case eFirstLink:
338 makeRequestToLink(an, area->downlinks[0]->link);
339 break;
340
341 case eNodes:
342 for (i=0; i<area->downlinkCount; i++) {
343 if (area->downlinks[i]->link->hisAka.point == 0)
344 makeRequestToLink(an, area->downlinks[i]->link);
345 }
346 break;
347
348 case eAll:
349 for (i=0; i<area->downlinkCount; i++) {
350 makeRequestToLink(an, area->downlinks[i]->link);
351 }
352 break;
353 }
354
355
356 /* delete msgbase and dupebase for the area */
357 if (eraseBase) {
358
359 if (area->msgbType!=MSGTYPE_PASSTHROUGH) {
360 fprintf(outlog, " Removing base of %s...", an);
361 rc=MsgDeleteBase(area->fileName, (word) area->msgbType);
362 fprintf(outlog, "%s\n", rc ? "ok" : "failed");
363 }
364
365
366 if (area->dupeCheck != dcOff) {
367 char *dupename = createDupeFileName(area);
368 if (dupename) {
369 fprintf(outlog, " Removing dupebase for %s...", an);
370 rc=unlink(dupename);
371 fprintf(outlog, "%s\n", rc==0 ? "ok" : "failed");
372 nfree(dupename);
373 }
374 }
375 }
376
377 /* remove area from config-file */
378 if (delFromConfig) {
379
380 update_queue(area);
381
382 fprintf(outlog, " deleting from config");
383 if (changeconfig (getConfigFileName(), area) != 0)
384 fprintf(outlog, " ERROR!\n");
385 else
386 fprintf(outlog, " ok\n");
387
388 /* delete the area from in-core config */
389 for (i=0; i<area->downlinkCount; i++)
390 nfree(area->downlinks[i]);
391 nfree(area->downlinks);
392 area->downlinkCount = 0;
393 for (i=0; i<config->echoAreaCount; i++)
394 if (stricmp(config->echoAreas[i].areaName, an)==0)
395 break;
396 if (i<config->echoAreaCount && area==&(config->echoAreas[i])) {
397 nfree(area->areaName);
398 nfree(area->fileName);
399 nfree(area->description);
400 nfree(area->group);
401 for (; i<config->echoAreaCount-1; i++)
402 memcpy(&(config->echoAreas[i]), &(config->echoAreas[i+1]),
403 sizeof(s_area));
404 config->echoAreaCount--;
405 }
406 }
407 fprintf(outlog, "done\n");
408 }
409
410
main(int argc,char ** argv)411 int main(int argc, char **argv) {
412
413 int i, j;
414 UINT k;
415 struct _minf m;
416 char **areas = NULL;
417 char *needfree = NULL;
418 char *line = NULL;
419 int nareas=0;
420 int found = 0;
421 FILE *f = NULL;
422 s_link *link = NULL;
423 int killed = 0;
424 int checkPaused = 0;
425 int killNoLink = 0;
426 int killLowLink = 0;
427 int killOld = 0;
428 int killWithoutDupes = 0;
429 int delArea = 0;
430 time_t oldest = 0;
431 s_area *area = NULL;
432 struct stat stbuf;
433 char *listNoDupeFile = NULL;
434 FILE *fNoDupe = NULL;
435 char *dupename = NULL;
436 char *cfgfile = NULL;
437
438
439 outlog=stdout;
440 setbuf(outlog, NULL);
441
442 versionStr = GenVersionStr( "hptkill", VER_MAJOR, VER_MINOR, VER_PATCH,
443 VER_BRANCH, cvs_date );
444 fprintf(outlog,"%s\n\n", versionStr);
445
446 for (i=1; i<argc; i++) {
447 if ( argv[i][0] == '-' ) {
448 switch (argv[i][1])
449 {
450 case 'c': /* alternate config file */
451 if ( ++i<argc ) {
452 cfgfile = argv[i];
453 } else {
454 fprintf( stderr, "Option -c requires config file name\n");
455 usage();
456 }
457 break;
458 case '1': /* send unsubscribe message to first link only */
459 sendUnSubscribe = eFirstLink;
460 break;
461
462 case 'n': /* don't send unsubscribe message */
463 case 'N':
464 sendUnSubscribe = eNobody;
465 break;
466
467 case 'a': /* send unsubscribe message all links */
468 case 'A':
469 sendUnSubscribe = eAll;
470 break;
471
472 case 'd': /* delete area from config */
473 case 'D':
474 delFromConfig = 1;
475 break;
476
477 case 's': /* save (don't erase) message & dupe bases */
478 case 'S':
479 eraseBase = 0;
480 break;
481
482 case 'f': /* read areas list from file */
483 case 'F':
484 i++;
485
486 if ( argv[i] == NULL || argv[i][0] == '\0') {
487 usage();
488 exit(-1);
489 }
490
491 if (strcmp(argv[i], "-") == 0) {
492 f=stdin;
493 } else {
494 if ( !(f=fopen(argv[i], "r"))) {
495 fprintf(outlog, "%s: Unable to open file '%s'\n", argv[0], argv[i]);
496 exit(-1);
497 }
498 }
499
500 while (!feof(f)) {
501 line = readLine(f);
502
503 if (line) {
504 char *spacep=strchr(line+1,' ');
505 if(spacep) { /* Format FILEBONE.NA: "areatag comment" */
506 *spacep=0; /* First char should be alphanumberic */
507 spacep=strdup(line);
508 free(line);
509 line=spacep;
510 }
511 if( line[0]==0 ) continue; /* skip empty line */
512 nareas++;
513 areas = (char **)srealloc ( areas, nareas*sizeof(char *));
514 areas[nareas-1] = line;
515 needfree = (char *)srealloc ( needfree, nareas*sizeof(char));
516 needfree[nareas-1] = 1;
517 }
518
519 }
520
521 if (f != stdin) fclose(f);
522 break;
523
524 case 'p': /* kill passthrough areas with 1 link*/
525 case 'P':
526 killNoLink++;
527 killPass++;
528 if (argv[i][2]=='p' || argv[i][2]=='P') checkPaused++;
529 break;
530
531 case 'y': /* kill ANY areas with <=1 link*/
532 case 'Y':
533 killLowLink++;
534 if (argv[i][2]=='p' || argv[i][2]=='P') checkPaused++;
535 break;
536
537 case 'o': /* kill passthrough area with dupebase older 'days' days */
538 case 'O':
539 if (argv[i][1]=='O') killWithoutDupes++;
540 i++;
541 if ( argv[i] == NULL || argv[i][0] == '\0') {
542 usage();
543 exit(-1);
544 }
545 killPass++;
546 killOld++;
547 oldest = time(NULL) - atoi(argv[i]) * 60*60*24;
548 break;
549
550 case 'l': /* write list of areas without dupebase to file */
551 case 'L':
552 i++;
553 if ( argv[i] == NULL || argv[i][0] == '\0') {
554 usage();
555 exit(-1);
556 }
557 listNoDupeFile = argv[i];
558 break;
559
560 case 'C': /* create empty dupebase if it doesn't exist */
561 createDupe++;
562 break;
563
564 default:
565 usage();
566 exit(-1);
567 }
568 } else {
569 /* AreaName(s) specified by args */
570 nareas++;
571 areas = (char **)srealloc ( areas, nareas*sizeof(char *));
572 areas[nareas-1] = argv[i];
573 needfree = (char *)srealloc ( needfree, nareas*sizeof(char));
574 needfree[nareas-1] = 0;
575 }
576 }
577
578 if (nareas == 0) {
579 if (killPass || killLowLink) {
580 nareas++;
581 areas = (char **)srealloc ( areas, nareas*sizeof(char *));
582 areas[nareas-1] = "*";
583 needfree = (char *)srealloc ( needfree, nareas*sizeof(char));
584 needfree[nareas-1] = 0;
585 } else {
586 if (!createDupe) {
587 usage();
588 exit (-1);
589 }
590 }
591 }
592
593 if( cfgfile && cfgfile[0] )
594 config = readConfig(cfgfile);
595 else config = readConfig(getConfigFileName());
596
597 if (!config) {
598 fprintf(outlog, "Unable to read fido config\n");
599 return (1);
600 }
601
602 robot = getRobot(config, "areafix", -1);
603 if (!robot) {
604 fprintf(outlog, "Unable to find robot 'areafix' in config\n");
605 return (1);
606 }
607
608 time( &tnow );
609
610 m.req_version = 0;
611 m.def_zone = (UINT16) config->addr[0].zone;
612 if (MsgOpenApi(&m) != 0) fprintf(outlog, "MsgApiOpen Error");
613
614 for ( j=0; j<nareas; j++) {
615 found = 0;
616
617 for (i=0, area = config->echoAreas; (unsigned int)i < config->echoAreaCount; i++, area++) {
618 if (patimat(area->areaName, areas[j])==1){
619
620 delArea = 0;
621 if (killPass==0 && killLowLink==0) delArea++;
622 else if ((area->msgbType & MSGTYPE_PASSTHROUGH) == MSGTYPE_PASSTHROUGH) {
623 if (killNoLink) {
624 if (area->downlinkCount <= 1) delArea++;
625 else if (checkPaused) {
626 delArea = 2; /* if two links w/o pause - leave untouched */
627 for (k=0; (unsigned int)k < area->downlinkCount && delArea; k++) {
628 if ((area->downlinks[k]->link->Pause & ECHOAREA)!= ECHOAREA) delArea--;
629 }
630 }
631 }
632 if (killOld && !delArea && area->dupeCheck != dcOff) {
633 dupename = createDupeFileName(area);
634 if (dupename) {
635 if (stat(dupename, &stbuf)==0) {
636 if (stbuf.st_mtime < oldest) delArea++;
637 } else {
638 if (killWithoutDupes) {
639 delArea++;
640 }
641 if (listNoDupeFile) {
642 if (!fNoDupe) {
643 if (!(fNoDupe=fopen (listNoDupeFile, "a"))) {
644 fprintf (stderr, "Unable to open file '%s' for appending\n", listNoDupeFile);
645 }
646 }
647 if (fNoDupe) fprintf (fNoDupe, "%s\n", area->areaName);
648
649 }
650 }
651 nfree(dupename);
652 }
653 }
654 }
655 if (killLowLink) {
656 if (area->downlinkCount <= 1) delArea++;
657 else if (checkPaused) {
658 delArea = 2; /* if two links w/o pause - leave untouched */
659 for (k=0; k < area->downlinkCount && delArea; k++) {
660 if ((area->downlinks[k]->link->Pause & ECHOAREA)!= ECHOAREA) delArea--;
661 }
662 }
663 }
664 if (delArea) {
665 delete_area(area);
666 killed++;
667 found++;
668 if (delFromConfig) { /* Area is removed from areas array! */
669 i--;
670 area--;
671 }
672
673 }
674 }
675 }
676
677 if (killPass==0 && killLowLink==0) {
678 for (i=0, area=config->localAreas; (unsigned int)i < config->localAreaCount; i++, area++) {
679 if (patimat(area->areaName, areas[j])==1){
680 delete_area(area);
681 killed++;
682 found++;
683 if (delFromConfig) { /* Area is removed from areas array! */
684 i--;
685 area--;
686 }
687 }
688 }
689
690 if (!found) fprintf(outlog, "Unable to find area \"%s\"\n", areas[j]);
691 }
692 }
693
694 if (fNoDupe) fclose (fNoDupe);
695
696 if (killed) fprintf(outlog, "\n");
697 /* Put mail for links to netmail */
698 for (i=0; (unsigned int)i < config->linkCount; i++) {
699 if (config->links[i]->msg) {
700 link = config->links[i];
701 if (link->hisAka.point)
702 fprintf(outlog, "Wrote message for %u:%u/%u.%u...",
703 link->hisAka.zone ,
704 link->hisAka.net ,
705 link->hisAka.node ,
706 link->hisAka.point);
707 else
708 fprintf(outlog, "Wrote message for %u:%u/%u...",
709 link->hisAka.zone ,
710 link->hisAka.net ,
711 link->hisAka.node);
712
713 putMsgInArea(&(config->netMailAreas[0]), config->links[i]->msg);
714 nfree(link->msg);
715 fprintf(outlog, "done\n");
716 }
717 }
718
719 for ( j=0; j<nareas; j++) if (needfree[nareas-1]) nfree(areas[j]);
720 if (needfree) nfree(needfree);
721 if (areas) nfree(areas);
722
723 if (killed && config->echotosslog) {
724 f=fopen(config->echotosslog, "a");
725 if (f==NULL) {
726 fprintf(outlog, "\nUnable to open or create EchoTossLogFile\n");
727 } else {
728 fprintf(f,"%s\n", config->netMailAreas[0].areaName);
729 fclose(f);
730 }
731 }
732
733 if (createDupe) {
734 for (i=0, area = config->echoAreas; (unsigned int)i < config->echoAreaCount; i++, area++) {
735 dupename = createDupeFileName(area);
736 if (stat(dupename, &stbuf)!=0) {
737 fprintf (outlog, "Creating %s\n", dupename);
738 f=fopen(dupename, "a");
739 if (f) {
740 fclose (f);
741 } else {
742 fprintf (outlog, "Unable to create %s\n", dupename);
743 }
744 }
745 nfree(dupename);
746 }
747 }
748
749 /* deinit SMAPI */
750 MsgCloseApi();
751
752 disposeConfig(config);
753 fprintf(outlog,"Done\n");
754 return (0);
755 }
756