/* For a long time, certain pieces of potentially dangerous software have * been in the possession of only a small number of people. Partially in * response to some pieces of such software being made available (either * completely publicly, or to a limited but wide audience, or for sale), * and mostly in response to what has been the all but destruction of * everything the net once was (and my resulting disgustion with it), I * have released this. I intend to write and release any dangerous or * potentially destructive software that I can conceive ideas for. The name * of this program, "purify", reflects my feelings. Usenet has degraded to * the point where it would be better off gone. */ /* to compile: all reasonable unixes: gcc -O2 -o purify purify.c Solaris: gcc -O2 -o purify purify.c -lsocket -lnsl SunOS: gcc -O2 -o purify purify.c -Dsunos (you can use cc if you don't have gcc, except on SunOS, since their cc sucks) */ /* purify version 0.1, by Electric Eel. An Armageddon Software product. This program is Copyright 1997 Electric Eel and Armageddon Software. It is released under the terms of the GPL (http://www.fsf.org/copyleft/gpl.html) This program will cancel all the articles in specified usenet newsgroups, by posting a control article for each article that we cancel. There are a great many reasons why you may find it useful to cancel all the articles in a newsgroup. For example, if you consider the group or what is posted to it offensive, if it is being used to libel or otherwise attack you, or if you want to stop what the discussion in the group might cause (for example the creation of a new newsgroup that you are opposed to). Even moderated groups can be cancelled. This program is not intended for use. If you do use it, against my explicit advice, I can not be held responsible for what you do with it. Use of this program could get you hurt, arrested, expelled, or possibly deported. Usage: purify [options] newsgroup [newsgroups...] -n nntp server to use (else we use $NNTPSERVER, /etc/nntpserver, "news") -h use header (-h Organization to take from original, -h 'Organization: whatever' to specify) -i post with ihave (will fail if we do not have permission) -v verbose (more v's, more verbose, 1, 2, or 3) -o only post cancel to group we saw, not full Newsgroups list -m messageid type - 0=none, 1=prepend random, or string to prepend ("cancel." default) -b string to use for body -c continuous mode, loop forever canceling new articles -A Approved header to use if a group is moderated (-h overrides. default is original Approved line.) -N don't add "X-No-Archive: Yes" header we will take wildcards for newsgroup name, sent to 'list active ...'. If an argument (newsgroup name or header) contains spaces or wildcards, remember to quote it to protect it from the shell. Usage examples: cancel all articles in alt.2600, and then quit purify alt.2600 cancel all articles in the news.* hierarchy, and then quit. You may consider this to be not only a usage example, but a suggested usage: purify "news.*" cancel all articles in the groups news.admin.net-abuse.*, and continue to cancel any new ones that show up. the nohup makes purify keep running after you log off, the >&/dev/null causes the output to be suppressed, and the & puts the process in the background. nohup purify -c "news.admin.net-abuse.*" >&/dev/null & cancel all articles in groups containing censorship or conspiracy in their names, using the nntp server "news.foo.edu", making the message-id of the cancel a random number plus the original message-id, give verbose output (enough to keep track of the progress), take the Approved line from the original article, and add our own header "Organization: Men In Black Evidence Removal Service". You may also consider this a suggested usage (I think it would be rather funny). purify -n news.foo.edu -m1 -v -h Approved -h "Organization: Men In Black \ Evidence Removal Service" "*censorship* "*conspiracy*" Notes: It may be a good idea to do -h Organization or -h "Organization: " to prevent your nntp server from adding the default Organization line. You may also wish to do -h Date to obfuscate exactly when the articles were cancelled. If an article is posted to a newsgroup that is not moderated, but is crossposted to a moderated newsgroup, and we are cancelling in the unmoderated group, and the -o or -h Approved options are not given, our cancel will either be rejected or forwarded to the moderator. This should be a pretty rare case, but it's worth being aware of. We make no provision for resubmitting the article with an Approved header. If you're worried about this, use -h Approved (which carries a slight penalty in data received), or -o (which will cause less to be received, but may cause the article to not be cancelled on systems which carry other groups it was posted to but not the one we posted the cancel to). Some newsgroups, notably news.admin.net-abuse.*, have a bot that re-posts all cancelled articles. Do not let this discourage you, however. This is intended to be a remedy for individual cancellations, and not mass-cancellation such as this program is designed for. The bot will cause far more problems than it solves. Unless it crashes or is disabled by its owner, it will end up posting a lot of articles in a short time - as many as you cancelled, which could be a whole week's traffic. Everyone reading the group will see each one as a new article. If you are attempting to prevent use of the newsgroup, the resulting chaos is a far more desirable situation. If the server does not support "list active ", we will not be able to determine whether a group is moderated or deal with wildcards. We could do it by downloading the entire list, which could be well over a meg. Since it's pointless over a slow connection, and most nntp servers support it anyway, I didn't worry about it. In this case, wildcards will cause an error, and moderated will be assumed (meaning we will copy any Approved header in the article). The speed of this program is mainly dependent on the speed of your nntp server, and to a lesser extent the speed of the connection to the nntp server. After the headers are retrieved, you can count on somewhere between a few cancels per second to a few seconds per cancel. A Message-ID over 254 characters will not be accepted by the nntp server. By default, "cancel." is prepended to the article's Message-ID to create the Message-ID for the cancel. If the cancel's Message-ID would be too long, which would take a deliberate act on the part of the original poster, it will be trimmed down. (People reading this might realize that if they post their articles with Message-IDs greater than 247 characters, any cancellation software that hasn't been written with this in mind will not work without modification). Also on the subject of deliberate acts to avoid cancellation, people might post a dummy article with a Message-ID intended to collide with the cancel's. (perhaps to alt.test, or a cancel of a nonexistant article, or a series of posts each with one more "cancel." prepended to the Message-ID). The -m options are intended to remedy this, if it becomes a problem. People worrying about having their articles cancelled by less versatile software should also take note of this. The -o option will not prevent articles from being cancelled in groups other than that the cancel was posted to. It will only cause them to not be cancelled on news servers that do not get the group the cancel was posted to, but do get other groups the article was posted to. If you use the -A option, the "X-No-Archive: Yes" header will not be included. This will cause your cancels will be archived in dejanews and altavista. You probably don't want an easily searchable long-lived record of your cancels, so there's really no reason to ever use this option. TODO: The next version will handle a local news spool. This probably isn't useful to most people (I wouldn't even have somewhere to test it), but I should include it for completeness. Other than that, I can't see that there is much else to add in the way of indiscriminantly cancelling a newsgroup. Any new features I can think of would be outside the goal of this program. Handling multiple nntp servers simultaneously, or simultaneous sending and receiving to/from the sever (with two connections) might be useful, but I don't see that it would at all be worth the trouble. I'm certain there are bugs, this was quickly written and not extensively tested. Some stuff should be cleaned up. The intent of the verbosity levels is: 0 - only important information is displayed, fatal or serious errors are sent to stderr, 1 - enough information to keep track of the progress is displayed, 2 - all data sent and received is displayed, 3 - internal information such as status of data structures is displayed With -v, '<' is displayed when each post is sent, and '>' is displayed when the server returns the result, so each <> is one cancel posted. */ /*e576894661eefab2c5345391e94bf05d*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef sunos /* sucks */ #define memmove(a,b,c) bcopy(b,a,c) #endif /* sunos */ #define VERSION "0.1" #define Die(x) { fputs(x, stderr); exit(1); } #define Fatal(x) { perror(x); exit(1); } #define Please2(x, y) if((x)<0) { perror(y); exit(1); } #define Return(x) { x; return; } struct newsgroups { char *name; /* newsgroup name */ char flags; /* y, n, m */ int low; /* low article number */ struct newsgroups *next; }; struct article { int number; char *headers; struct article *next; }; int only_specified_newsgroup=0; char *custom_headers[32]={NULL}; char *body=NULL; int verbose=0; int use_ihave=0; char *message_id="cancel."; char *Approved=NULL; /* appends a null-terminated list of strings to s */ char *strcats(char *s, ...) { va_list args; char *p; va_start(args, s); while((p=va_arg(args, char *))) strcat(s, p); return s; } /* makes an rfc822 compliant date. stolen from mutt. */ char *make_date(void) { static char s[80]; time_t t = time (NULL); struct tm *l = gmtime(&t); int yday = l->tm_yday; int tz = l->tm_hour * 60 + l->tm_min; const char *Weekdays[]={ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; const char *Months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "ERR" }; l = localtime(&t); tz = l->tm_hour * 60 + l->tm_min - tz; yday = l->tm_yday - yday; if (yday != 0) tz += yday * 24 * 60; /* GMT is next or previous day! */ sprintf (s, "%s, %d %s %d %02d:%02d:%02d %+03d%02d", Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon], l->tm_year+1900, l->tm_hour, l->tm_min, l->tm_sec, tz/60, abs(tz) % 60); return (s); } /* connects to host:port, returns a socket descriptor */ int tcp_connect(char *host, unsigned short port) { int sd; struct sockaddr_in sa; if( (sa.sin_addr.s_addr=inet_addr(host)) == -1) { struct hostent *he; he = gethostbyname(host); if(he==NULL) Fatal(host); bcopy(he->h_addr, &sa.sin_addr, he->h_length); } sd = socket(AF_INET, SOCK_STREAM, 0); if(sd<0) Fatal("socket"); sa.sin_family=AF_INET; sa.sin_port=htons(port); if(connect(sd, (struct sockaddr *)&sa, sizeof(sa))<0) Fatal("connect"); fcntl(sd, F_SETFL, O_NONBLOCK); return sd; } /* read a newline-terminated line from sd.. have to buffer the data, * otherwise we'd have to do a recv() for every byte received. * some day i'll clean this mess up, but it works fine for our puposes. */ void readln(int sd, char *line) { static char buf[16384]; static char *head=buf, *tail=buf; int x=0; char *line_start=line; again: /* 4k free && <1k left in buffer or got full 4k on last read */ while(tail-buf<(16384-4096) && (tail-head<1024 || x==4096)) { int x; int blocked=0; if(verbose>=3) printf("t:%d h:%d t-h:%d\n", tail-buf, head-buf, tail-head); if(tail=tail) goto again; /* more data needed */ *line=0; if(head==tail) head=tail=buf; /* no data, reset head+tail */ if(head-buf>=4096) /* first 4k free, shift data */ { memmove(buf, head, (tail-head)); tail=buf+(tail-head); head=buf; } if(verbose>=2) fputs(line_start, stdout); } /* send null-terminated string in buf to socket sd */ void Send(int sd, char *buf) { if(verbose>=2) printf("Send(): %s\n", buf); Please2(send(sd, buf, strlen(buf), 0), "send"); } /* send null-terminated string plus \r\n to socket */ void Sendln(int sd, char *buf) { int x; char *s=malloc(x=(strlen(buf)+3)); if(verbose>=2) printf("Sendln(): %s\n", buf); strcpy(s, buf); strcat(s, "\r\n"); Please2(send(sd, s, x-1, 0), "send"); free(s); } /* like fprintf, to a socket */ void Sendf(int sd, char *format, ...) { va_list args; char buf[4096]; va_start(args, format); vsprintf(buf, format, args); if(verbose>=2) printf("Sendln(): %s", buf); Please2(send(sd, buf, strlen(buf), 0), "send"); } /* reads the first line of a file and returns that */ char *read_line_from_file(char *filename) { static char buf[1024]; FILE *f; f=fopen(filename, "r"); if(f==NULL) return NULL; fgets(buf, sizeof(buf), f); if(buf[strlen(buf)-1]=='\n') buf[strlen(buf)-1]=0; fclose(f); return buf; } /* add a group to a newsgroups list, buf is list active output */ void addgroup(struct newsgroups **first, char *buf) { struct newsgroups *this, *last=NULL; char s[4096]; int low; char flags; sscanf(buf, "%s %*d %d %c", s, &low, &flags); /* we could post the cancel to another group, or with ihave. but this is a * rare case anyway. */ if(flags=='n') Return(printf("Can't post to %s, ignoring\n", s)); /* make sure its not a dupe */ for(this=*first; this; this=this->next) { last=this; if(!strcmp(this->name, s)) Return(if(verbose>=3) printf("addgroup(): duplicate: %s\n", s)); } /* add it */ this=malloc(sizeof(struct newsgroups)); this->next=NULL; this->low=low; this->name=strdup(s); this->flags=flags; if(verbose>=3) printf("added %s %d %c\n", this->name, this->low, this->flags); /* deal with a new list */ if(last) last->next=this; /* (else) */ if(*first==NULL) *first=this; } /* does list active groups, feeds the output to addgroup */ struct newsgroups *get_group_info(int sd, char *groups) { char buf[1024]; static int no_list=0; static struct newsgroups *firstgroup; redo: if(no_list) { if(strpbrk(groups, "?*")) { fprintf(stderr, "error: can't do list, wildcards in %s\n", groups); return firstgroup; } sprintf(buf, "%s 0 1 m", groups); addgroup(&firstgroup, buf); return firstgroup; } Sendf(sd, "LIST ACTIVE %s\r\n", groups); readln(sd, buf); /* 215 Newsgroups in form "group high low flags". */ if(strncmp(buf, "215", 3)) { printf("can't do list active , server said %s", buf); no_list=1; goto redo; } for(;;) { readln(sd, buf); if(!strcmp(buf, ".\r\n")) break; addgroup(&firstgroup, buf); } return firstgroup; } /* does xhdr header low-high, stores data in article list. Must be called * with Message-ID first (which is a double special-case, does both * allocation and Control headers) */ void do_xhdr(int sd, struct article **first, char *header, int low, int high) { char buf[1024]; struct article *this, *previous; int firstpass; if(verbose) printf("do_xhdr: %d-%d, %s\n", low, high, header); Sendf(sd, "XHDR %s %d-%d\r\n", header, low, high); /* expect: 221 whatever fields follow */ readln(sd, buf); if(strncmp(buf, "221", 3)) { fprintf(stderr, "error: Sent XHDR %s, got %s", header, buf); exit(1); } firstpass=!strcmp("Message-ID", header); /* Message-ID *must* be first*/ for(previous=this=*first;;) { char buf2[4096], *p; readln(sd, buf); if(!strcmp(buf, ".\r\n")) break; if(firstpass) /* allocation crap */ { if(!*first) this=*first=malloc(sizeof(struct article)); else { this->next=malloc(sizeof(struct article)); this=this->next; } this->next=NULL; } for(p=buf;*p && *p!=' ';p++); *p++=0; /* so it's number\0
\r\n, *p=='<'*/ if(firstpass) this->number=atoi(buf); else if(this->number!=atoi(buf)) /* message numbers dont match */ { printf("Article number mismatch %d, %s\n", this->number, buf); while(this && this->numbernext; /* so shift it to the 2nd */ else previous->next=this->next; /* skip it in the list */ free(this->headers); free(this); this=previous->next; /* new current one */ } if(!this) return; if(this->number>atoi(buf)) continue; /* never seen, new now */ } if(firstpass) /* special Message-ID stuff */ { sprintf(buf2, "Control: cancel %sSubject: cmsg cancel %s", p, p); if( !(*message_id=='0' && !message_id[1]) ) /* setting the m-id*/ { char *prepend=message_id; if(message_id[0]=='1' && !message_id[1]) /* prepend random */ { static char blah[10]; sprintf(blah, "%d", rand()); prepend=blah; } p++; while(strlen(p)+strlen(prepend) > 253) /*254+'<'*/ if(*p++=='@') *p='@'; /* messageid needs an @ */ if(*p=='@' && prepend[strlen(prepend)-1]=='.') { /* cant have ".@" in message-id */ *p=p[1]; p[1]='@'; } strcats(buf2, "Message-ID: <", prepend, p, NULL); } this->headers=strdup(buf2); } else if(strcmp(p, "(none)\r\n")) { /*(none)==header not there*/ sprintf(buf2, "%s%s: %s", this->headers, header, p); free(this->headers); this->headers=strdup(buf2); } previous=this; if(!firstpass) this=this->next; } } /* extract a header from an article, used by post_article. hrmph. */ char *extract_header(char *buf, char *header) { static char *p; static char r[1024]; char *q=malloc(strlen(header)+5); /*\r\n: \0*/ *q=0; strcats(q, "\r\n", header, ": ", NULL); p=strstr(buf, q)+4+strlen(header); free(q); strncpy(r, p, strchr(p, '\n')-p); *strchr(r, '\r')=0; return r; } /* post an article to nntp server on sd. a and b are first and second half of the article. */ void post_article(int sd, char *a, char *b) { char buf[1024]; static int failed; /* failed postings. 10 consecutive and we give up. */ #ifndef TEST if(use_ihave) Sendf(sd, "IHAVE %s\r\n", extract_header(a, "Message-ID")); /*hrm*/ else Sendln(sd, "POST"); readln(sd, buf); /* expected: 340 Ok */ if(*buf!='3') { printf("Can't post! Server said %s", buf); exit(1); } Send(sd, a); if(b) Send(sd, b); Send(sd, "\r\n.\r\n"); if(verbose==1) {fputs("<", stdout); fflush(stdout);} readln(sd, buf); /* expected 240 Article posted */ if(verbose==1) {fputs(">", stdout); fflush(stdout);} if(*buf!='2') { printf("posting failed, #%d, server said %s\n", ++failed, buf); if(failed>=10) Die("Too many errors posting\n"); } else if(failed>0) failed=0; #else if(use_ihave) printf("IHAVE %s\r\n", extract_header(a, "Message-ID")); /*hrm*/ else printf("POST\n"); fputs(a, stdout); fputs(b, stdout); fputs("\r\n.\r\n", stdout); #endif } /* cancel all the articles in a newsgroup, update the low article count to * be the highest article number we cancelled * calls do_xhdr and post_article */ void cancel_group(int sd, struct newsgroups *group) { char buf[1024]; int count, high; char **p; char rest[4096]=""; /* headers for all, and body */ int did_approved=0; /* for mod'd groups, Approved was in -h */ int x=0; struct article *articles=NULL, *this; if(verbose) printf("cancel_group: %s\n", group->name); /* send group */ Sendf(sd, "GROUP %s\r\n", group->name); /* expected: 211 count low high groupname, or 411 for no such group */ readln(sd, buf); if(!strncmp(buf, "411", 3)) /* cant happen, i suppose */ Return(printf("No such group %s, server said %s", group->name, buf)); if(strncmp(buf, "211", 3)) { fprintf(stderr, "error: Sent GROUP, got %s", buf); exit(1); } /* get all the header information */ sscanf(buf, "211 %d %*d %d", &count, &high); if(!count || group->low>high) Return(if(verbose) printf("%s is empty\n", group->name)); do_xhdr(sd, &articles, "Message-ID", group->low, high); if(!articles) Return(if(verbose) printf("%s was empty\n", group->name)); do_xhdr(sd, &articles, "From", group->low, high); if(only_specified_newsgroup) strcats(rest, "Newsgroups: ", group->name, "\r\n", NULL); else do_xhdr(sd, &articles, "Newsgroups", group->low, high); if(use_ihave) /* then we need the Date and Path headers */ { int path_set=0, date_set=0; for(p=custom_headers; *p; p++) /*check if they were user-specified*/ { if(!strncmp(*p, "Path", 4)) path_set=1; else if(!strncmp(*p, "Date", 4)) date_set=1; if(path_set && date_set) break; } if(!date_set); strcats(rest, "Date: ", make_date(), "\r\n", NULL); if(!path_set) strcats(rest, "Path: nntp!usenet\r\n", NULL); } /* add -h headers */ for(p=custom_headers; *p; p++) { if(group->flags=='m' && !strncmp(*p, "Approved", 8)) did_approved=1; if(!strchr(*p, ':')) do_xhdr(sd, &articles, *p, group->low, high); else strcats(rest, *p, "\r\n", NULL); } if(group->flags=='m' && !did_approved) /* group is moderated */ { if(!Approved) /* user specified Approved header for mod'd groups*/ do_xhdr(sd, &articles, "Approved", group->low, high); else strcats(rest, "Approved: ", Approved, "\r\n"); } if(body) /* user specified body */ strcats(rest, "\r\n", body, "\r\n", NULL); else strcat(rest, "\r\ncancel.\r\n"); /* for the next time around, skip the articles we did */ group->low=high+1; for(this=articles; this;x++) /* post articles and free memory */ { struct article *p=this->next; post_article(sd, this->headers, rest); free(this->headers); free(this); this=p; } if(verbose) printf("\n%d cancels sent\n", x); } void usage(char *myname) { printf("purify version "VERSION", by Electric Eel.\nUsage:\n\ %s [options] newsgroup [newsgroups...]\n\ \n\ -n nntp server to use (else we use $NNTPSERVER, /etc/nntpserver, \"news\")\n\ -h use header (-h Organization to take from original, -h 'Organization:\n\ whatever' to specify)\n\ -i post with ihave (will fail if we do not have permission)\n\ -v verbose (more v's, more verbose, 1, 2, or 3)\n\ -o only post cancel to group we saw, not full Newsgroups list\n\ -m messageid type - 0=none, 1=prepend random, or string to prepend \n\ (\"cancel.\" default)\n\ -b string to use for body\n\ -c continuous mode, loop forever canceling new articles\n\ -A Approved header to use if a group is moderated (-h overrides. default is \n\ original Approved line.)\n\ -N don't add \"X-No-Archive: Yes\" header\n\ \n\ we will take wildcards for newsgroup name, sent to 'list active ...'.\n\ Read the source for more documentation.\n", myname); exit(1); } int main(int argc, char **argv) { char s[1024]; /* general buffer */ int sd; /* socket for nntp server */ int c; /* for getopt */ int chc=0; /* custom headers count, temp for arg processing */ int continuous=0; /* run forever */ int x_no_archive=1; /* include X-No-Archive: Yes header */ struct newsgroups *first=NULL, *this; char *nntp_server=NULL; if(argc<=1) usage(argv[0]); while((c=getopt(argc, argv, "cvioNA:n:h:b:m:")) != -1) switch(c) { case 'c': continuous=1; break; case 'v': verbose++; break; case 'i': use_ihave=1; break; case 'o': only_specified_newsgroup=1; break; case 'N': x_no_archive=0; break; case 'A': Approved=optarg; break; case 'n': nntp_server=optarg; break; case 'h': custom_headers[chc++]=optarg; break; case 'b': body=optarg; break; case 'm': if(!strchr(optarg, '@') && *optarg && strlen(optarg)<200) message_id=optarg; else puts("ignoring invalid messageid prefix"); break; } if(x_no_archive) custom_headers[chc++]="X-No-Archive: Yes"; custom_headers[chc]=NULL; /* check for bad options */ if(message_id[0]=='0' && message_id[1]==0 && use_ihave) { printf("Server will require a Message-ID with ihave, using default\n"); message_id="cancel."; } srand((unsigned int)time(NULL)); /* find nntp server, connect to it */ if(!nntp_server) nntp_server=getenv("NNTPSERVER"); if(!nntp_server) nntp_server=read_line_from_file("/etc/nntpserver"); if(!nntp_server) nntp_server="news"; sd=tcp_connect(nntp_server, 119); readln(sd, s); fputs(s, stdout); if(*s!='2') Die("No permission to use nntp server\n"); /* so now we're connected to the nntp server, it's waiting for us */ /* send out list active groupnanme for all the args, save the info */ while(optindnext) cancel_group(sd, this); if(continuous) sleep(5); } while(continuous); return 0; } /************************************************************************/