--- /dev/null
+*~
+*.o
+md5cookie1way
+nnrpwrap
+nntp-merge
+nyxpostrun
--- /dev/null
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 675 Mass Ave, Cambridge, MA 02139, USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+\f
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+\f
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+\f
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+\f
+ Appendix: How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) 19yy <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) 19yy name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
--- /dev/null
+CFLAGS= -Wall -Wwrite-strings -Wpointer-arith -Wnested-externs \
+ -Wmissing-declarations -Wmissing-prototypes -O3 -g
+# -DDEBUG
+LDFLAGS= -s
+LIBS= -lident
+
+all: nntp-merge md5cookie1way nnrpwrap nyxpostrun
+
+nntp-merge: main.o post.o config.o md5.o
+ $(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
+
+md5cookie1way: sharedsecret.o md5.o
+ $(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
+
+nnrpwrap: nnrpwrap.o md5.o
+ $(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
+
+nyxpostrun: nyxpostrun.o
+ $(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
+
+main.o post.o config.o: nntp-merge.h
+main.o: md5.h
+md5.o: md5.h
--- /dev/null
+This is nntp-merge, a program which acts as an NNTP server and passes
+requests to one or more other NNTP servers, towards which it presents a
+client interface. Selection of which server to use is based on the group
+being read or posted to. The merger supports XAUTHINFO GENERIC
+authentication with its own shared-secret MD5 challenge-response protocol;
+other authentication methods could be added in principle.
+
+I wrote it for my own system, chiark.greenend.org.uk, where it has proved
+to be very useful; it may also be useful to you. If you like it and patch
+it please do send me the patches as I may someday turn this into a real
+free software product with documentation, version numbers, features and
+everything.
+
+There may be some binaries in the tarfile; these are Linux/i386 libc5
+ELF. There is no configure script and no documentation, but there is
+a Makefile and a sample config file.
+
+If there are any new versions or anything I might put them up on my main
+WWW page (<URL:http://www.chiark.greenend.org.uk/~ijackson/>), or possibly
+at <URL:http://www.chiark.greenend.org.uk/~ian/nntp-merge/>.
+
+nntp-merge is Copyright (C)1995-1997 Ian Jackson. It is free software; you
+can redistribute it and/or modify it under the terms of the GNU General
+Public License as published by the Free Software Foundation; either version
+2 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+more details.
+
+You should have received a copy of the GNU General Public License along
+with this program as the file GPL; if not, email Ian Jackson
+<ian@chiark.greenend.org.uk> or write to the Free Software Foundation,
+Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA, or consult
+the Free Software Foundation's WWW page at <URL:http://www.fsf.org/>.
+
+ - Ian Jackson 16.09.1997
--- /dev/null
+/**/
+
+/* Algorithm for finding a group:
+ * OUT OF DATE !
+ * Start at listentry 0.
+ * For each character,
+ * keep going through list until one of the following:
+ * if character in string matches in list entry,
+ * then jump to that listentry's `p' and go on to the next character
+ * if we encounter a null character (ie, default, end of this list)
+ * then we go to the listentry's `p' and use the `gi' member of
+ * the union as the group info struct
+ * If we run out of characters,
+ * keep going through the list until we find a default or an if-terminate-here
+ * use the listentry's `p', and the `gi' member of that entry's union.
+ *
+ * The listentry pointingto's are indices into the array of listentry unions.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include "nntp-merge.h"
+
+/* Conceptually we have one array with all the stuff in it. However,
+ * this is wasteful of space (we want the lookup algorithm and its
+ * data to fit in 1K). Conceptually the array is one of
+ * struct {
+ * char c;
+ * unsigned short n_to_go_to_if_char_matches_p;
+ * struct groupinfo *groupinfo_to_use_if_char_does_not_match;
+ * };
+ * The groupinfo pointer is in the array element corresponding to the
+ * null character at the end of the list.
+ * Conceptually `end of string' is just another character, 1, except
+ * that when you find it in the table you use the groupinfo member rather
+ * then n_to_go member.
+ */
+
+struct tnode {
+ unsigned short lip;
+ struct tlistentry {
+ struct tlistentry *next;
+ char c;
+ struct tnode *p;
+ } *l;
+ struct groupinfo *ifdef, *ifterm;
+};
+
+void *xmalloc(size_t s) {
+ void *r;
+ r= malloc(s);
+ if (!r) die("malloc failed");
+ return r;
+}
+
+char *xstrdup(const char *s) {
+ char *r;
+ r= xmalloc(strlen(s)+1);
+ strcpy(r,s);
+ return r;
+}
+
+struct permission *permissions;
+struct permission *restrictpost;
+struct serverinfo *servers;
+static struct serverinfo **lastserver;
+
+static char *lisc=0;
+static unsigned short *lisp=0;
+static struct groupinfo **lisg=0;
+
+static int line;
+static int nrequired;
+
+static struct tnode *newemptytnode(void) {
+ static struct tnode *r;
+
+ r= xmalloc(sizeof(struct tnode));
+ r->l= 0;
+ r->ifdef= 0;
+ r->ifterm= 0;
+ return r;
+}
+
+static void setmissingdefaults(struct tnode *n, struct groupinfo *gi) {
+ struct tlistentry *tle;
+ if (n->ifdef) gi= n->ifdef; else n->ifdef= gi;
+ for (tle= n->l; tle; tle= tle->next) setmissingdefaults(tle->p,gi);
+}
+
+static void syntax(const char *m) {
+ printf("400 syntax error in config file, line %d: %s\r\n",line,m);
+ exit(1);
+}
+
+static void countup(struct tnode *n) {
+ struct tlistentry *tle;
+
+ assert(n->ifdef);
+ n->lip= nrequired;
+ for (tle= n->l; tle; tle= tle->next) nrequired++;
+ if (n->ifterm) nrequired++;
+ nrequired++;
+
+ for (tle= n->l; tle; tle= tle->next) countup(tle->p);
+}
+
+static void fillin(struct tnode *n) {
+ struct tlistentry *tle;
+ int i;
+
+ i= n->lip;
+ for (tle= n->l; tle; tle= tle->next) {
+ lisc[i]= tle->c;
+ lisp[i]= tle->p->lip;
+ fillin(tle->p);
+ i++;
+ }
+ if (n->ifterm) {
+ lisc[i]= '\1';
+ lisg[i]= n->ifterm;
+ i++;
+ }
+ lisc[i]= 0;
+ lisg[i]= n->ifdef;
+}
+
+static void posscopy(int *needcopyp, struct groupinfo **gip) {
+ struct groupinfo *gi;
+ if (!*needcopyp) return;
+ gi= xmalloc(sizeof(struct groupinfo));
+ *gi= **gip;
+ *needcopyp= 0;
+ *gip= gi;
+ return;
+}
+
+struct permission *findpermit(const char *name) {
+ struct permission *pi;
+
+ for (pi= permissions; pi && strcmp(name,pi->name); pi= pi->next);
+ return pi;
+}
+
+static struct permission *findmakepermit(const char *name) {
+ struct permission *pi;
+
+ if (!strcmp(name,"*")) return 0;
+ pi= findpermit(name); if (pi) return pi;
+
+ pi= xmalloc(sizeof(struct permission));
+ pi->name= xstrdup(name);
+ pi->authd= 0;
+ pi->next= permissions;
+ permissions= pi;
+ return pi;
+}
+
+static struct serverinfo *findserver(const char *name) {
+ struct serverinfo *search;
+
+ for (search= servers;
+ search && strcmp(search->nickname,name);
+ search= search->next);
+
+ if (!search) syntax("unknown server nickname");
+ return search;
+}
+
+static struct tnode *ttree;
+
+void readconfig(void) {
+ FILE *file;
+ struct groupinfo *gi;
+ int needcopy= 0;
+ struct tnode *tcur;
+ struct tlistentry *tle;
+ char buf[MAX_COMMAND], *p, *q;
+ struct serverinfo *si;
+ int c, i, insearchser;
+ unsigned long portt;
+
+ file= fopen("nntp-merge.conf","r");
+ if (!file) die("unable to open config file");
+
+ gi= xmalloc(sizeof(struct groupinfo));
+ gi->readfrom= 0;
+ gi->postto[0]= 0;
+ gi->offset= 0;
+ gi->restrictto= 0;
+ gi->readonlyto= 0;
+ needcopy= 0;
+
+ ttree= newemptytnode();
+ line= 0;
+ servers= 0;
+ lastserver= &servers;
+ while ((c= getc(file)) != EOF) {
+ line++;
+ if (c == '\n') continue;
+ if (c == '#') {
+ while ((c= getc(file)) != EOF && c != '\n');
+ continue;
+ }
+ if (isspace(c)) {
+ if (!gi->readfrom) syntax("config file has groups before readfrom specs");
+ do { c= getc(file); } while (c != EOF && c != '\n' && isspace(c));
+ if (c == '\n') continue;
+ ungetc(c,file);
+ tcur= ttree;
+ while ((c= getc(file)) != EOF && !isspace(c) && c != '*') {
+ if (c == '\1') syntax("group name in config file has ^A character");
+ for (tle= tcur->l; tle && tle->c != c; tle= tle->next);
+ if (!tle) {
+ tle= xmalloc(sizeof(struct tlistentry));
+ tle->next= tcur->l;
+ tle->c= c;
+ tle->p= newemptytnode();
+ tcur->l= tle;
+ }
+ tcur= tle->p;
+ }
+ if (c == '*') {
+ tcur->ifdef= gi;
+ needcopy= 1;
+ c= getc(file);
+ } else {
+ while (c != EOF && c != '\n' && isspace(c)) c= getc(file);
+ if (c != EOF && (isdigit(c) || c == '-')) {
+ posscopy(&needcopy,&gi);
+ ungetc(c,file);
+ if (fscanf(file,"%d",&gi->offset) != 1) die("fscanf in group offset");
+ } else if (gi->offset) {
+ posscopy(&needcopy,&gi);
+ gi->offset= 0;
+ }
+ tcur->ifterm= gi;
+ needcopy= 1;
+ }
+ while (c != EOF && c != '\n') {
+ c= getc(file);
+ if (!isspace(c)) syntax("stuff after * or space (and offset?) in group");
+ }
+ } else {
+ ungetc(c,file);
+ if (!fgets(buf,sizeof(buf),file)) break;
+ p= strchr(buf,'\n'); if (p) *p= 0;
+ p= strtok(buf," \t");
+ if (!strcmp(p,"read")) {
+ p= strtok(0," \t"); if (!p) syntax("missing server after read");
+ posscopy(&needcopy,&gi);
+ gi->readfrom= findserver(p);
+ } else if (!strcmp(p,"post")) {
+ posscopy(&needcopy,&gi);
+ for (i=0; (p= strtok(0," \t")); i++) {
+ if (i >= sizeof(gi->postto)/sizeof(gi->postto[0])-1)
+ syntax("too many servers after post");
+ gi->postto[i]= findserver(p);
+ }
+ gi->postto[i]= 0;
+ } else if (!strcmp(p,"server") || !strcmp(p,"server-nosearch")) {
+ insearchser= !strcmp(p,"server");
+ p= strtok(0," \t"); if (!p) syntax("missing server name");
+ for (si= servers; si && strcmp(si->nickname,p); si= si->next);
+ if (si) syntax("server nickname redefined");
+ si= xmalloc(sizeof(struct serverinfo));
+ si->next= 0;
+ si->searchthis= insearchser;
+ si->nickname= xstrdup(p);
+ si->send= 0;
+ p= strtok(0," \t"); if (!p) syntax("missing server host:port/command");
+ if (*p == '/') {
+ si->hostname= xstrdup(p);
+ si->port= 0;
+ si->program= 1;
+ } else {
+ q= strchr(p,':'); if (!q || !*q) syntax("missing :port in server");
+ *q++= 0;
+ si->hostname= xstrdup(p);
+ portt= strtoul(q,&q,10);
+ if (portt > USHRT_MAX || *q) syntax("bad :port in server");
+ si->port= portt;
+ si->program= 0;
+ }
+ while ((p= strtok(0," \t"))) {
+ if (strcmp(p,"mode-reader")) syntax("bad option on server");
+ si->send= "MODE READER";
+ }
+ si->rfile= si->wfile= 0;
+ *lastserver= si;
+ lastserver= &si->next;
+ } else if (!strcmp(p,"permit")) {
+ posscopy(&needcopy,&gi);
+ p= strtok(0," \t");
+ if (!p) syntax("missing read-only group after permit");
+ gi->readonlyto= findmakepermit(p);
+ p= strtok(0," \t");
+ if (!p) syntax("missing read-write group after permit");
+ gi->restrictto= findmakepermit(p);
+ } else if (!strcmp(p,"myfqdn")) {
+ posscopy(&needcopy,&gi);
+ p= strtok(0," \t");
+ if (!p) syntax("missing fqdn after myfqdn");
+ free(myfqdn);
+ myfqdn= xstrdup(p);
+ } else if (!strcmp(p,"xref")) {
+ posscopy(&needcopy,&gi);
+ p= strtok(0," \t");
+ if (!p) syntax("missing fqdn after myfqdn");
+ free(myxref);
+ myxref= xstrdup(p);
+ } else if (!strcmp(p,"fetch") ||
+ !strcmp(p,"believe") ||
+ !strcmp(p,"minreaddays") ||
+ !strcmp(p,"maxperuser") ||
+ !strcmp(p,"extrarc") ||
+ !strcmp(p,"ignorerc")) {
+ /* These aren't for us, we ignore them. */
+ } else {
+ syntax("unknown thing in file");
+ }
+ }
+ }
+ if (ferror(file)) die("failed to read config file");
+ if (!ttree->ifdef) syntax("missing default newsgroup entry");
+ fclose(file);
+
+ setmissingdefaults(ttree,0);
+
+ /* Now we count the total number of entries we need in our table. */
+ nrequired= 0;
+ countup(ttree);
+ lisc= xmalloc(nrequired);
+ lisp= xmalloc(nrequired*sizeof(unsigned short));
+ lisg= xmalloc(nrequired*sizeof(struct groupinfo*));
+ fillin(ttree);
+}
+
+struct groupinfo *findgroup(const char *name) {
+ /* Null or ':' terminated */
+ int c, d;
+ unsigned short cnode;
+
+ cnode= 0;
+ while ((c= *name++) && c != ':' && !isspace(c)) {
+ while ((d= lisc[cnode]) && d != c) cnode++;
+ if (d) { cnode= lisp[cnode]; continue; }
+ return lisg[cnode];
+ }
+ while ((d= lisc[cnode]) && d != '\1') cnode++;
+ return lisg[cnode];
+}
--- /dev/null
+/**/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <assert.h>
+#include <time.h>
+#include <errno.h>
+#include <fnmatch.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include "nntp-merge.h"
+#include "md5.h"
+
+char *myfqdn, *myxref, *lastdoneauth= 0;
+const char *theirfqdn= 0;
+char currentgroupname[MAX_COMMAND+3];
+struct groupinfo *currentgroup;
+struct sockaddr_in peername;
+
+int stripcommand(char *buf) {
+ int n;
+
+ n= strlen(buf);
+ if (n == 0) return 0;
+ if (buf[n-1] != '\n') return 0;
+ while (n>0 && isspace(buf[n-1])) n--;
+ buf[n]= 0;
+ return n;
+}
+
+void die(const char *msg) {
+ int e;
+ e= errno;
+ printf("400 server failure: %s - %s\r\n",msg,strerror(errno));
+ exit(0);
+}
+
+static int parsehex(char *p, unsigned char *up, int maxlen) {
+ int nused, v, n;
+
+ nused= 0;
+ while (nused < MAX_SECRET && (n=-1, sscanf(p,"%x%n",&v,&n) >0) && n>0) {
+ p+= n;
+ *up++= v;
+ nused++;
+ if (*p == ':') p++;
+ }
+ return nused;
+}
+
+enum { scf_norecover=001, scf_nogroup };
+
+void closeserver(struct serverinfo *server) {
+ fclose(server->rfile); server->rfile= 0;
+ fclose(server->wfile); server->wfile= 0;
+}
+
+int decoderesponse(char response[MAX_RESPONSE+3], unsigned long *rvp,
+ struct serverinfo *server) {
+ char temp[MAX_RESPONSE+3];
+ if (!isdigit(response[0]) || !isdigit(response[1]) || !isdigit(response[2]) ||
+ !(isspace(response[3]) || !response[3])) {
+ strcpy(temp,response);
+ sprintf(response,"503 server %.100s produced garbage: %.100s",
+ server->hostname,temp);
+ closeserver(server); return 0;
+ }
+ *rvp= strtol(response,0,16);
+ return 1;
+}
+
+#ifdef DEBUG
+static int realservercommand(struct serverinfo *server,
+ const char command[], char response[MAX_RESPONSE+3],
+ int flags);
+#endif
+int servercommand(struct serverinfo *server,
+ const char command[], char response[MAX_RESPONSE+3],
+ int flags) {
+#ifdef DEBUG
+ int rcode;
+ fprintf(stderr,"[%03o]>>>%s: %s\n",flags,server->nickname,command);
+ rcode= realservercommand(server,command,response,flags);
+ fprintf(stderr,"[%03x]<<<%s: %s\n",rcode,server->nickname,response);
+ return rcode;
+}
+static int realservercommand(struct serverinfo *server,
+ const char command[], char response[MAX_RESPONSE+3],
+ int flags) {
+#endif
+ struct hostent *he;
+ char **alist;
+ struct sockaddr_in sin;
+ pid_t c1;
+ int rpipefds[2], wpipefds[2], rfd, wfd, l, n;
+ unsigned long rv;
+ char temp[MAX_RESPONSE+3], *p;
+ unsigned char message[16+MAX_SECRET], cryptresponse[16];
+ struct MD5Context md5ctx;
+ FILE *file;
+
+ /* response will be null-terminated and have trailing whitespace
+ * removed including \r\n
+ * (but indeed the extra two spaces used by this routine are for \r\n)
+ */
+ if (server->rfile) {
+ if (fprintf(server->wfile,"%s\r\n",command) == EOF ||
+ fflush(server->wfile) == EOF) goto conn_broken;
+ if (!fgets(response,MAX_RESPONSE+3,server->rfile)) goto conn_broken;
+ if (!stripcommand(response)) goto conn_broken;
+ if (!strncmp(response,"400",3)) goto conn_broken;
+ goto message_sent_response_ok;
+ conn_broken:
+ closeserver(server);
+ if (flags & scf_norecover) {
+ strcpy(response,"205 it had already gone"); return 0x205;
+ }
+ }
+ if (server->program) {
+ if (pipe(rpipefds) || pipe(wpipefds)) die("unable to create pipe");
+ if ((c1= fork()) == -1) die("unable to fork");
+ if (!c1) {
+ close(0); close(1); dup(wpipefds[0]); dup(rpipefds[1]);
+ close(wpipefds[0]); close(wpipefds[1]);
+ close(rpipefds[0]); close(rpipefds[1]);
+ signal(SIGPIPE,SIG_DFL);
+ if (lastdoneauth) setenv("NNTPMERGE_AUTHD_AS",lastdoneauth,1);
+ execl(server->hostname,server->hostname,(char*)0);
+ printf("400 exec %s: %s\r\n",server->hostname,strerror(errno));
+ exit(1);
+ }
+ rfd= rpipefds[0]; close(rpipefds[1]);
+ wfd= wpipefds[1]; close(wpipefds[0]);
+ } else {
+ he= gethostbyname(server->hostname);
+ if (!he) {
+ sprintf(response,"503 unable to find address of %.100s.",server->hostname);
+ return 0x503;
+ }
+ if (he->h_addrtype != AF_INET) {
+ sprintf(response,"503 address of %.100s is of unknown type 0x%x.",
+ server->hostname,he->h_addrtype);
+ return 0x503;
+ }
+ for (alist= he->h_addr_list; *alist; alist++) {
+ rfd= socket(PF_INET,SOCK_STREAM,0);
+ if (rfd == -1) die("unable to create TCP socket");
+ memset(&sin,0,sizeof(sin));
+ sin.sin_family= AF_INET;
+ sin.sin_addr= *(struct in_addr*)*alist;
+ sin.sin_port= htons(server->port);
+ if (!connect(rfd,(struct sockaddr*)&sin,sizeof(sin))) break;
+ sprintf(response,"503 unable to connect to %.100s (%.50s:%u): %.100s.",
+ server->hostname, inet_ntoa(sin.sin_addr), server->port, strerror(errno));
+ close(rfd);
+ }
+ if (!*alist) return 0x503;
+ wfd= dup(rfd);
+ if (wfd < 0) die("failed to dup socket fd");
+ }
+ server->rfile= fdopen(rfd,"r");
+ server->wfile= fdopen(wfd,"w");
+ if (!server->rfile || !server->wfile) die("failed to fdopen");
+ if (setvbuf(server->rfile,0,_IOLBF,0) || setvbuf(server->wfile,0,_IOLBF,0))
+ die("failed to setvbuf linebuf");
+ if (!fgets(response,MAX_RESPONSE+3,server->rfile)) {
+ sprintf(response,"503 failed to read from server %.100s: %.100s.",
+ server->hostname,strerror(errno));
+ }
+ if (!stripcommand(response)) {
+ sprintf(response,"503 server %.100s is spouting null garbage.",server->hostname);
+ closeserver(server); return 0x503;
+ }
+ if (!strncmp(response,"480",3) &&
+ response[3] == ' ' && (p= strchr(response+4,' ')) &&
+ ((*p=0), (parsehex(response+4,message,17) == 16)) &&
+ (file= fopen(SESAMEFILE,"r"))) {
+ do {
+ if (!fgets(temp,sizeof(temp),file)) {
+ if (ferror(file)) {
+ sprintf(response,"503 error reading sesame file: %.100s",strerror(errno));
+ } else {
+ sprintf(response,"503 no sesame for server nicknamed %.100s",server->nickname);
+ }
+ fclose(file); closeserver(server); return 0x503;
+ }
+ } while (!stripcommand(temp) || !*temp || *temp == '#' ||
+ !(p= strchr(temp,' ')) || ((*p++= 0), strcmp(temp,server->nickname)));
+ fclose(file); l= strlen(p);
+ if (l>MAX_SECRET) {
+ sprintf(response,"503 sesame file secret for %.100s is too long",server->nickname);
+ closeserver(server); return 0x503;
+ }
+ memcpy(message+16,p,l);
+
+ MD5Init(&md5ctx);
+ MD5Update(&md5ctx,message,16+l);
+ MD5Final(cryptresponse,&md5ctx);
+
+ fprintf(server->wfile,"PASS ");
+ for (n=0; n<16; n++) {
+ fprintf(server->wfile,"%s%02x", n?":":"", cryptresponse[n]);
+ }
+ fprintf(server->wfile,"\r\n");
+ if (fflush(server->wfile) == EOF || ferror(server->wfile)) {
+ sprintf(response,"503 error sending sesame response to %.100s: %.100s",
+ server->hostname,strerror(errno));
+ closeserver(server); return 0x503;
+ }
+ if (!fgets(response,MAX_RESPONSE+3,server->rfile)) {
+ sprintf(response,"503 error reading from %.100s after sesame: %.100s.",
+ server->hostname,strerror(errno));
+ closeserver(server); return 0x503;
+ }
+ }
+ if (!strncmp(response,"400",3)) goto server_is_unavailable;
+ if (response[0] != '2') {
+ strcpy(temp,response);
+ stripcommand(temp);
+ sprintf(response,"503 server %.100s refuses to talk: %.100s",server->hostname,temp);
+ closeserver(server); return 0x503;
+ }
+ if (server->send) {
+ if (fprintf(server->wfile,"%s\r\n",server->send) == EOF ||
+ fflush(server->wfile) == EOF) {
+ sprintf(response,"503 error sending %.50s to %.100s: %.100s",
+ server->send,server->hostname,strerror(errno));
+ closeserver(server); return 0x503;
+ }
+ if (!fgets(response,MAX_RESPONSE+3,server->rfile)) {
+ sprintf(response,"503 error reading from %.100s after %.50s: %.100s.",
+ server->hostname,server->send,strerror(errno));
+ closeserver(server); return 0x503;
+ }
+ }
+ if (!(flags & scf_nogroup) && currentgroup && currentgroup->readfrom == server) {
+ if (fprintf(server->wfile,"GROUP %s\r\n",currentgroupname) == EOF ||
+ fflush(server->wfile) == EOF) {
+ sprintf(response,"503 error sending GROUP to %.100s: %.100s",
+ server->hostname,strerror(errno));
+ closeserver(server); return 0x503;
+ }
+ if (!fgets(response,MAX_RESPONSE+3,server->rfile)) {
+ sprintf(response,"503 error reading from %.100s after GROUP: %.100s.",
+ server->hostname,strerror(errno));
+ closeserver(server); return 0x503;
+ }
+ }
+ if (fprintf(server->wfile,"%s\r\n",command) == EOF ||
+ fflush(server->wfile) == EOF) {
+ sprintf(response,"503 error sending to %.100s: %.100s",
+ server->hostname,strerror(errno));
+ closeserver(server); return 0x503;
+ }
+ if (!fgets(response,MAX_RESPONSE+3,server->rfile)) {
+ sprintf(response,"503 error reading from %.100s: %.100s.",server->hostname,
+ strerror(errno));
+ closeserver(server); return 0x503;
+ }
+ if (!stripcommand(response)) {
+ sprintf(response,"503 server %.100s is replying with null garbage.",
+ server->hostname);
+ closeserver(server); return 0x503;
+ }
+ message_sent_response_ok:
+ if (!decoderesponse(response,&rv,server)) return 0x503;
+ if (rv != 0x400) return rv;
+ server_is_unavailable:
+ strcpy(temp,response);
+ sprintf(response,"503 server %.100s unavailable: %.200s",
+ server->hostname,temp);
+ closeserver(server); return 0x503;
+}
+
+static int adjust(int original, int offset) {
+ if (offset < 0 && original < -offset) return 0;
+ return original+offset;
+}
+
+void serverdataerr(struct serverinfo *si) {
+ if (ferror(si->rfile)) {
+ printf("\r\n\r\n*** problem detected by nntp-merge:\r\n"
+ "error reading data from server %s: %s\r\n",
+ si->hostname, strerror(errno));
+ } else {
+ printf("\r\n\r\n*** problem detected by nntp-merge:\r\n"
+ "server %s closed connection while sending data\r\n",
+ si->hostname);
+ }
+ printf("closing connection on you, sorry.\r\n");
+ exit(0);
+}
+
+static int noargs(const char *arg) {
+ if (!*arg) return 1;
+ printf("501 no arguments please.\r\n");
+ return 0;
+}
+
+static void cmd_unimplemented(char *arg, const struct cmdinfo *cip) { \
+ printf("500 %s not implemented.\r\n",cip->command); \
+}
+
+static void cmd_quit(char *arg, const struct cmdinfo *cip) {
+ char responsebuf[MAX_RESPONSE+3];
+ struct serverinfo *si;
+
+ if (!noargs(arg)) return;
+ for (si= servers; si; si= si->next) {
+ if (!si->rfile) continue;
+ servercommand(si,"QUIT",responsebuf,scf_norecover);
+ fclose(si->wfile); fclose(si->rfile); /* we're not interested in the response */
+ }
+ printf("205 goodbye.\r\n");
+ exit(0);
+}
+
+static void cmd_date(char *arg, const struct cmdinfo *cip) {
+ char responsebuf[MAX_RESPONSE+3];
+ struct tm *brokendown;
+ char buf[100];
+ int n, rcode;
+ time_t now;
+ struct serverinfo *si;
+
+ if (!noargs(arg)) return;
+ time(&now);
+ brokendown= gmtime(&now); if (!brokendown) die("unable to break down date/time");
+ n= strftime(buf,sizeof(buf)-1,"%Y%m%d%H%M%S",brokendown);
+ if (n <= 0 || n >= sizeof(buf)-1 || strlen(buf) != 14)
+ die("unable to format date/time");
+ for (si= servers; si; si= si->next) {
+ if (!si->searchthis) continue;
+ rcode= servercommand(si,"DATE",responsebuf,0);
+ if (rcode == 0x111 && responsebuf[3]==' ' &&
+ strspn(responsebuf+4,"0123456789")==14 &&
+ responsebuf[18]==0 &&
+ strcmp(responsebuf+4,buf) < 0)
+ strcpy(buf,responsebuf+4);
+ }
+ printf("111 %s\r\n",buf);
+}
+
+static void cmd_noop(char *arg, const struct cmdinfo *cip) {
+ if (!noargs(arg)) return;
+ printf("200 doing that has no effect.\r\n");
+}
+
+static void cmd_slave(char *arg, const struct cmdinfo *cip) {
+ if (!noargs(arg)) return;
+ printf("202 bind me, whip me.\r\n");
+}
+
+static void cmd_ihave(char *arg, const struct cmdinfo *cip) {
+ printf("502 please don't feed me.\r\n");
+}
+
+static void cmd_newnews(char *arg, const struct cmdinfo *cip) {
+ printf("502 get a proper feed, slurping through this merger is silly.\r\n");
+}
+
+static void cmd_mode(char *arg, const struct cmdinfo *cip) {
+ if (!strcasecmp(arg,"READER")) {
+ printf("200 %s that was uncalled-for.\r\n",myfqdn);
+ } else {
+ printf("501 you want me to do *what* ?\r\n");
+ }
+}
+
+static void authrequired(void) {
+ if (lastdoneauth) {
+ printf("480 the sight of %s is but dim.\r\n",lastdoneauth);
+ } else {
+ printf("480 identify yourself ! friend or foe ?\r\n");
+ }
+}
+
+int stillrestricted(struct permission **pip) {
+ if (!*pip) return 0;
+ if (!(*pip)->authd) return 1;
+ *pip= 0; return 0;
+}
+
+static int groupreadable(struct groupinfo *gi) {
+ if (!stillrestricted(&gi->restrictto) ||
+ !stillrestricted(&gi->readonlyto)) return 1;
+ authrequired();
+ return 0;
+}
+
+static void cmd_group(char *arg, const struct cmdinfo *cip) {
+ struct groupinfo *gi;
+ int estcount, first, last, startfrom;
+ char commandbuf[MAX_COMMAND+3];
+ char responsebuf[MAX_RESPONSE+3];
+ int rcode;
+
+ if (strlen(arg) > MAX_COMMAND-12) {
+ printf("501 too long.\r\n");
+ return;
+ }
+ if (*arg) {
+ gi= findgroup(arg); if (!groupreadable(gi)) return;
+ } else {
+ printf("501 which group, then ?\r\n");
+ return;
+ }
+
+ sprintf(commandbuf,"%s %s",cip->command,arg);
+ rcode= servercommand(gi->readfrom,commandbuf,responsebuf,scf_nogroup);
+ if ((rcode & 0xf00) != 0x200) {
+ printf("%s\r\n",responsebuf);
+ return;
+ } else if (gi->offset) {
+ startfrom= -1;
+ if (sscanf(responsebuf,"211 %d %d %d %n", &estcount,&first,&last,&startfrom) != 3 ||
+ startfrom == -1) {
+ printf("503 %s said (after GROUP): %s\r\n",gi->readfrom->hostname,responsebuf);
+ closeserver(gi->readfrom); return;
+ }
+ printf("211 %d %d %d %s\r\n",
+ estcount, adjust(first,gi->offset), adjust(last,gi->offset),
+ responsebuf + startfrom);
+ } else {
+ printf("%s\r\n",responsebuf);
+ }
+ currentgroup= gi;
+ strcpy(currentgroupname,arg);
+}
+
+int copydatafile(FILE *from, FILE *to) {
+ /* `to' may be null */
+ int stopstate, c;
+
+ stopstate= 1; /* 0: middle of line; 1: start of line; 2: after `.' */
+ while ((c= getc(from)) != EOF) {
+ if (to) putc(c,to);
+ if (c == '\r') continue;
+ switch (stopstate) {
+ case 0:
+ if (c == '\n') stopstate= 1;
+ continue;
+ case 1:
+ stopstate= (c == '.') ? 2 : (c == '\n') ? 1 : 0;
+ continue;
+ case 2:
+ if (c == '\n') return 1;
+ stopstate= 0;
+ continue;
+ }
+ }
+ return 0;
+}
+
+static void copydata(struct serverinfo *from, FILE *to) {
+ if (!copydatafile(from->rfile,to)) serverdataerr(from);
+}
+
+static void cmd_listgroup(char *arg, const struct cmdinfo *cip) {
+ struct groupinfo *gi;
+ char commandbuf[MAX_COMMAND+3];
+ char responsebuf[MAX_RESPONSE+3];
+ long an;
+ char *p;
+ int rcode;
+
+ if (strlen(arg) > MAX_COMMAND-12) {
+ printf("501 too long.\r\n");
+ return;
+ }
+ if (*arg) {
+ gi= findgroup(arg); if (!groupreadable(gi)) return;
+ } else if (!currentgroup) {
+ printf("412 list of articles in which group ?\r\n");
+ return;
+ } else {
+ gi= currentgroup;
+ }
+
+ sprintf(commandbuf,"%s %s",cip->command,arg);
+ rcode= servercommand(gi->readfrom,commandbuf,responsebuf,scf_nogroup);
+ if ((rcode & 0xf00) != 0x200) {
+ printf("%s\r\n",responsebuf);
+ return;
+ }
+ if (rcode != 0x211) {
+ printf("503 %s said (after %s): %s\r\n",
+ gi->readfrom->hostname,cip->command,responsebuf);
+ return;
+ }
+ printf("211 article list:\r\n");
+ if (!gi->offset) {
+ copydata(gi->readfrom,stdout);
+ } else {
+ for (;;) {
+ if (!fgets(responsebuf,MAX_RESPONSE+3,gi->readfrom->rfile))
+ serverdataerr(gi->readfrom);
+ if (!strcmp(responsebuf,".\r\n") || !strcmp(responsebuf,".\n"))
+ break;
+ an= strtol(responsebuf,&p,10);
+ printf("%d%s",adjust(an,gi->offset),p);
+ }
+ printf(".\r\n");
+ }
+ currentgroup= gi;
+}
+
+static void pserver(struct serverinfo *si) {
+ printf(" %s= %s:%u %s (%s)\r\n",
+ si->nickname,
+ si->hostname, si->port,
+ si->rfile ? "open" : "closed",
+ si->send ? si->send : "-");
+}
+
+static void cmd_xmergeinfo(char *arg, const struct cmdinfo *cip) {
+ struct groupinfo *gi;
+ int i;
+
+ if (strlen(arg) > MAX_COMMAND-6) {
+ printf("501 too long.\r\n");
+ return;
+ }
+ gi= findgroup(arg);
+ printf("100 %s\r\n read:\r\n",arg);
+ pserver(gi->readfrom);
+ printf(" post:\r\n");
+ for (i= 0; gi->postto[i]; i++) pserver(gi->postto[i]);
+ printf(" offset %d\r\n"
+ " auth %s / read-auth %s\r\n"
+ ".\r\n",
+ gi->offset,
+ gi->restrictto && gi->restrictto->name ? gi->restrictto->name : "<none>",
+ gi->readonlyto && gi->readonlyto->name ? gi->readonlyto->name : "<none>");
+}
+
+static void cmd_help(char *arg, const struct cmdinfo *cip);
+
+static int articleselectresponse(char responsebuf[MAX_RESPONSE+3], char *typecharp,
+ struct serverinfo *si, const struct cmdinfo *cip,
+ char modifiedresponse[MAX_RESPONSE+3]) {
+ int startfrom, ran;
+
+ startfrom= -1;
+ if (sscanf(responsebuf,"22%c %d %n",typecharp,&ran,&startfrom) != 2 ||
+ startfrom == -1 || !strchr("0123",*typecharp)) {
+ printf("503 %s said (after %s): %s\r\n",
+ si->hostname, cip->command, responsebuf);
+ return 0;
+ }
+ ran= (currentgroup && si == currentgroup->readfrom)
+ ? adjust(ran,currentgroup->offset) : 0;
+ sprintf(modifiedresponse,"22%c %d %s",*typecharp,ran,responsebuf+startfrom);
+ return 1;
+}
+
+static void cmd_last_next(char *arg, const struct cmdinfo *cip) {
+ int rcode;
+ char responsebuf[MAX_RESPONSE+3];
+ char modresponse[MAX_RESPONSE+3];
+ char dummy;
+
+ if (!noargs(arg)) return;
+ if (!currentgroup) {
+ printf("412 use GROUP and ARTICLE first.\r\n");
+ return;
+ }
+ rcode= servercommand(currentgroup->readfrom,cip->command,responsebuf,0);
+ if ((rcode & 0xf00) != 0x200) {
+ printf("%s\r\n",responsebuf); return;
+ }
+ articleselectresponse(responsebuf,&dummy,currentgroup->readfrom,cip,modresponse);
+ printf("%s\r\n",modresponse);
+}
+
+static void processxref(char *linebufplus6, int n, FILE *writeto,
+ struct serverinfo *gotfrom) {
+ char *space, *cpos, *colon;
+ struct groupinfo *gi;
+ unsigned long an;
+
+ linebufplus6[n++]= ' ';
+ linebufplus6[n]= 0;
+ cpos= linebufplus6;
+ space= strchr(cpos,' ');
+ if (!space) return;
+ *space++= 0;
+ fprintf(writeto,"%s",myxref);
+ cpos= space;
+ while ((space= strchr(cpos,' '))) {
+ *space++= 0;
+ colon= strrchr(cpos,':');
+ if (colon) {
+ *colon++= 0;
+ gi= findgroup(cpos);
+ if (gi->readfrom != gotfrom) {
+ cpos= space; continue;
+ }
+ fprintf(writeto," %s:",cpos);
+ if (gi->offset && isdigit(*colon)) {
+ an= strtol(colon,&colon,10);
+ fprintf(writeto,"%d", adjust(an,gi->offset));
+ }
+ fputs(colon,writeto);
+ } else {
+ if (*cpos) putc(' ',writeto);
+ fputs(cpos,writeto);
+ }
+ cpos= space;
+ }
+}
+
+static void cmd_article(char *arg, const struct cmdinfo *cip) {
+ struct serverinfo *si;
+ struct groupinfo *gi;
+ char commandbuf[MAX_COMMAND+40+3];
+ char responsebuf[MAX_RESPONSE+3];
+ char modresponse[MAX_RESPONSE+3];
+ char linebuf[MAX_XREFLINE+3];
+ char typechar;
+ unsigned long an;
+ int rcode, n, c, realeinfo;
+ char *p;
+ const char *realcommand;
+ int checkpermission= 0;
+ FILE *file;
+
+ realcommand= cip->command;
+ if (*arg == '<') {
+ checkpermission= 1;
+ switch (cip->einfo) {
+ case 0: realcommand= "HEAD"; break;
+ case 2: realcommand= "ARTICLE"; break;
+ }
+ sprintf(commandbuf,"%s %s",realcommand,arg);
+ rcode= 0x423;
+ for (si= servers; si; si= si->next) {
+ if (!si->searchthis) continue;
+ rcode= servercommand(si,commandbuf,responsebuf,0);
+ if ((rcode & 0xf00) == 0x200) break;
+ }
+ } else if (isdigit(*arg) || !*arg) {
+ if (!currentgroup) {
+ printf("412 use GROUP first.\r\n");
+ return;
+ }
+ if (*arg) {
+ an= strtol(arg,&p,10);
+ if (*p) { printf("501 bad article number.\r\n"); return; }
+ if (an < currentgroup->offset)
+ printf("423 offset makes number negative, so no such article.\r\n");
+ sprintf(commandbuf,"%s %lu",cip->command,an - currentgroup->offset);
+ } else {
+ sprintf(commandbuf,"%s",cip->command);
+ }
+ si= currentgroup->readfrom;
+ rcode= servercommand(si,commandbuf,responsebuf,0);
+ } else {
+ printf("501 optional arg must be message-id or article number.\r\n");
+ return;
+ }
+ if ((rcode & 0xff0) != 0x220) {
+ printf("%s\r\n",responsebuf);
+ return;
+ }
+ switch (rcode & 0x00f) {
+ case 0: realeinfo= 03; break;
+ case 1: realeinfo= 01; break;
+ case 2: realeinfo= 02; break;
+ case 3: realeinfo= 00; break;
+ default: realeinfo= -1;
+ }
+ if (realeinfo == (cip->einfo | 01)) {
+ switch (cip->einfo) {
+ case 0: responsebuf[2]= '3'; break;
+ case 2: responsebuf[2]= '2'; break;
+ }
+ } else if (realeinfo != cip->einfo) {
+ printf("503 %s gave bad code (in response to %s): %s\r\n",
+ si->hostname,realcommand,responsebuf);
+ closeserver(si);
+ return;
+ }
+
+ if (!articleselectresponse(responsebuf,&typechar,si,cip,modresponse)) return;
+ if (checkpermission) {
+ file= tmpfile(); if (!file) die("failed to create temp file");
+ } else {
+ printf("%s\r\n",modresponse);
+ if (!realeinfo) return;
+ file= stdout;
+ }
+ if (realeinfo & 01) {
+ for (;;) {
+ if (!fgets(linebuf,MAX_XREFLINE+3,si->rfile)) serverdataerr(si);
+ n= strlen(linebuf);
+ if (n==0) continue;
+ while (n>0 && ((c= linebuf[n-1]) == '\n' || c == '\r')) n--;
+ linebuf[n]= 0;
+ if (!strcmp(linebuf,".")) { fputs(".\r\n",file); break; }
+ if ((cip->einfo & 01) && !strncasecmp(linebuf,"Xref: ",6)) {
+ fprintf(file,"Xref: ");
+ processxref(linebuf+6,n-6,file,si);
+ fputs("\r\n",file);
+ continue;
+ }
+ if (cip->einfo & 01) fprintf(file,"%s\r\n",linebuf);
+ if (checkpermission && !strncasecmp(linebuf,"Newsgroups: ",12)) {
+ p= strtok(linebuf," "); assert(p);
+ while ((p= strtok(0,","))) {
+ gi= findgroup(p);
+ if (!stillrestricted(&gi->restrictto) ||
+ !stillrestricted(&gi->readonlyto)) break;
+ }
+ if (!p) checkpermission= -1; /* Don't return, we must clean up &c */
+ }
+ if (n == 0 && realeinfo == 03) break; /* end of header, go on to body */
+ continue;
+ }
+ }
+ if (realeinfo & 02) {
+ /* process body */
+ copydata(si,file);
+ }
+
+ switch (checkpermission) {
+ case 0:
+ return;
+ case 1:
+ if (ferror(file) && fflush(file)) die("error writing article temp file");
+ printf("%s\r\n",modresponse);
+ if (!cip->einfo) { fclose(file); return; }
+ if (fseek(file,0,SEEK_SET)) die("unable to rewind article temp file");
+ while ((c= getc(file)) != EOF) putchar(c);
+ if (ferror(file)) die("unable to read article temp file");
+ fclose(file);
+ return;
+ case -1:
+ fclose(file);
+ authrequired();
+ return;
+ }
+}
+
+struct listwhatinfo {
+ const char *name;
+ void (*call)(const struct listwhatinfo *lwi, const char *cachename);
+ int mustall;
+ const char *what;
+ int einfo;
+};
+
+static void lwc_bynewsgroup(const struct listwhatinfo *lwi, const char *cachename) {
+ struct serverinfo *si;
+ struct groupinfo *gi;
+ char fnbuf[250];
+ char linebuf[MAX_RESPONSE+3];
+ FILE *file;
+ long an1, an2;
+ char *space1, *space2, *space3;
+
+ for (si= servers; si; si= si->next) {
+ if (!si->searchthis) continue;
+ sprintf(fnbuf,"%.100s:%.100s",si->nickname,cachename);
+ file= fopen(fnbuf,"r");
+ if (!file) {
+ if (lwi->mustall) die("unable to open list file when spouting");
+ continue;
+ }
+ while (fgets(linebuf,MAX_RESPONSE+3,file)) {
+ gi= findgroup(linebuf);
+ if (gi->readfrom != si) continue;
+ if (lwi->einfo && gi->offset &&
+ (space1= strchr(linebuf,' ')) &&
+ ((an1= strtol(space1+1,&space2,10)), *space2 == ' ') &&
+ ((an2= strtol(space2+1,&space3,10)), *space3 == ' ')) {
+ *space1= 0;
+ printf("%s %d %d %s",linebuf,
+ adjust(an1,gi->offset),adjust(an2,gi->offset),space3+1);
+ } else {
+ fputs(linebuf,stdout);
+ }
+ }
+ if (ferror(file)) die("read error on list file");
+ fclose(file);
+ }
+ return;
+}
+
+static void lwc_fixed(const struct listwhatinfo *lwi, const char *cachename) {
+ char *pathname;
+ pathname=
+ FILE *f= fopen(
+ char *p, tc;
+ struct disdone { struct disdone *next; char *name; } *done, *search, *tmp;
+ struct serverinfo *si;
+ char fnbuf[250];
+ char linebuf[MAX_RESPONSE+3];
+ FILE *file;
+
+ done= 0;
+ for (si= servers; si; si= si->next) {
+ if (!si->searchthis) continue;
+ sprintf(fnbuf,"%.100s:%.100s",si->nickname,cachename);
+ file= fopen(fnbuf,"r");
+ if (!file) continue;
+ while (fgets(linebuf,MAX_RESPONSE+3,file)) {
+ p= linebuf; while (*p && !isspace(*p)) p++;
+ tc= *p; *p= 0;
+ for (search= done;
+ search && strcasecmp(linebuf,search->name);
+ search= search->next);
+ if (search) continue;
+ tmp= xmalloc(sizeof(struct disdone));
+ tmp->name= xstrdup(linebuf);
+ tmp->next= done;
+ done= tmp;
+ *p= tc;
+ fputs(linebuf,stdout);
+ }
+ if (ferror(file)) die("read error on list file");
+ fclose(file);
+ }
+ for (search= done; search; search= tmp) {
+ tmp= search->next; free(search->name); free(search);
+ }
+ return;
+}
+
+static void lwc_overview(const struct listwhatinfo *lwi, const char *cachename) {
+ fputs("Subject:\r\n"
+ "From:\r\n"
+ "Date:\r\n"
+ "Message-ID:\r\n"
+ "References:\r\n"
+ "Bytes:\r\n"
+ "Lines:\r\n"
+ "Xref:full\r\n", stdout);
+}
+
+static const struct listwhatinfo listwhatinfos[]= {
+ { "", lwc_bynewsgroup, 1, "active file", 1 },
+ { "active", lwc_bynewsgroup, 1, "active file", 1 },
+ { "active.times", lwc_bynewsgroup, 1, "newsgroups' creation info", 0 },
+ { "newsgroups", lwc_bynewsgroup, 0, "newsgroup titles", 0 },
+ { "subscriptions", lwc_bynewsgroup, 0, "default subscription list", 0 },
+ { "distributions", lwc_distributions, 0, "distributions available" },
+ { "overview.fmt", lwc_overview, -1, "field list" },
+ { 0 }
+};
+
+static int copydatanodot(struct serverinfo *si, FILE *file) {
+ int c;
+
+ while ((c= getc(si->rfile)) != EOF) {
+ if (c == '.') {
+ c= getc(si->rfile);
+ if (c == '\r') {
+ c= getc(si->rfile);
+ if (c == '\n')
+ return 1;
+ fputs(".\r",file);
+ } else if (c == '\n') {
+ return 1;
+ } else {
+ putc('.',file);
+ }
+ }
+ while (c != EOF && c != '\r' && c != '\n') {
+ putc(c,file);
+ c= getc(si->rfile);
+ }
+ if (c == EOF) break;
+ putc(c,file);
+ }
+ if (ferror(si->rfile)) {
+ printf("503 error getting data from %s: %s\r\n",si->hostname,strerror(errno));
+ } else {
+ printf("503 connection closed during data transfer from %s\r\n",si->hostname);
+ }
+ return 0;
+}
+
+static void cmd_list(char *arg, const struct cmdinfo *cip) {
+ const struct listwhatinfo *lwi;
+ int rcode, c;
+ char commandbuf[MAX_COMMAND+40+3];
+ char responsebuf[MAX_RESPONSE+3];
+ char ufnbuf[250], nfnbuf[250], fnbuf[250];
+ struct stat stab;
+ FILE *file;
+ struct serverinfo *si;
+ const char *cachename;
+
+ for (lwi= listwhatinfos; lwi->name && strcasecmp(arg,lwi->name); lwi++);
+ if (!lwi->name) {
+ printf("501 LIST %s not available, sorry.\r\n",arg);
+ return;
+ }
+ if (lwi->mustall >= 0) {
+ cachename= lwi->name; if (!*cachename) cachename= (lwi+1)->name;
+ } else {
+ cachename= 0;
+ }
+ if (lwi->mustall > 0) {
+ sprintf(ufnbuf,"unavailable:%.100s",arg);
+ file= fopen(ufnbuf,"r");
+ if (file) {
+ printf("501 %s not available because not supported by ",arg);
+ while ((c= getc(file)) != EOF) { if (!isspace(c)) putchar(c); }
+ printf(".\r\n");
+ fclose(file);
+ return;
+ }
+ }
+ for (si= servers; lwi->mustall >= 0 && si; si= si->next) {
+ if (!si->searchthis) continue;
+ sprintf(commandbuf,"LIST %.100s",lwi->name);
+ rcode= servercommand(si,commandbuf,responsebuf,0);
+ sprintf(fnbuf,"%.100s:%.100s",si->nickname,cachename);
+ if (rcode == 0x500 || rcode == 0x501) {
+ if (lwi->mustall) {
+ printf("501 just discovered: %s doesn't support it - it says: %s\r\n",
+ si->hostname,responsebuf);
+ sprintf(nfnbuf,"~%s~%ld",ufnbuf,(long)getpid());
+ file= fopen(nfnbuf,"w"); if (!file) die("unable to create tmp unsup list file");
+ if (fprintf(file,"%s\n",si->hostname) == EOF)
+ die("unable to write unsup list file");
+ if (fclose(file)) die("unable to close unsup list file");
+ if (rename(nfnbuf,ufnbuf)) die("unable to install unsup list file");
+ return;
+ } else {
+ if (unlink(fnbuf) && errno != ENOENT) die("unable to remove now unsup list");
+ continue;
+ }
+ }
+ if (rcode == 0x215) {
+ sprintf(nfnbuf,"~%s~%ld",fnbuf,(long)getpid());
+ file= fopen(nfnbuf,"w"); if (!file) die("unable to create tmp list file");
+ if (!copydatanodot(si,file)) {
+ fclose(file); unlink(nfnbuf); return;
+ }
+ if (ferror(file) || fclose(file)) die("unable to write tmp list file");
+ if (rename(nfnbuf,fnbuf)) die("unable to install new list file");
+ } else if (rcode == 0x503) {
+ if (lwi->mustall && stat(fnbuf,&stab)) {
+ printf("501 no can do: need all, but file for %s not accessible: %s\r\n",
+ si->hostname,strerror(errno));
+ }
+ } else {
+ printf("503 %s said (after LIST): %s\r\n",si->hostname,responsebuf);
+ return;
+ }
+ }
+ printf("215 %s follows:\r\n",lwi->what);
+ lwi->call(lwi,cachename);
+ printf(".\r\n");
+}
+
+static int unadjustnumber(char *appendto, char *from, int offset) {
+ char *p;
+ int n;
+ long an;
+
+ an= strtol(from,&p,10);
+ if (!*from || *p) {
+ printf("503 bad article range.\r\n");
+ return 0;
+ }
+ if (an > offset)
+ an-= offset;
+ else
+ an= 0;
+ n= strlen(appendto);
+ sprintf(appendto+n,"%lu",an);
+ return 1;
+}
+
+static int unadjustrange(char *appendto, char *from, int offset) {
+ char *p;
+
+ p= strchr(from,'-');
+ if (p) {
+ *p++= 0;
+ if (!unadjustnumber(appendto,from,offset)) return 0;
+ from= p;
+ strcat(appendto,"-");
+ }
+ return unadjustnumber(appendto,from,offset);
+}
+
+static void cmd_xover(char *arg, const struct cmdinfo *cipa) {
+ char commandbuf[MAX_COMMAND+40+3];
+ char responsebuf[MAX_RESPONSE+3];
+ char xrefbuf[MAX_XREFLINE+3];
+ char *p;
+ int rcode;
+ struct serverinfo *si;
+ int an, n, fieldn, c;
+
+ if (!currentgroup) {
+ printf("412 overview of which group ?\r\n");
+ return;
+ }
+ strcpy(commandbuf,"XOVER ");
+ if (*arg && !unadjustrange(commandbuf,arg,currentgroup->offset)) return;
+ si= currentgroup->readfrom;
+ rcode= servercommand(si,commandbuf,responsebuf,0);
+ if ((rcode & 0xf00) != 0x200) {
+ printf("%s\r\n",responsebuf);
+ return;
+ } else if (rcode != 0x224) {
+ printf("503 %s said (after XOVER): %s\r\n",si->hostname,responsebuf);
+ return;
+ }
+ printf("%s\r\n",responsebuf);
+ for (;;) {
+ c= getc(si->rfile);
+ if (c == EOF) serverdataerr(si);
+ if (c == '.') {
+ c= getc(si->rfile);
+ if (c == '\r') c= getc(si->rfile);
+ if (c == EOF) serverdataerr(si);
+ if (c == '\n') break;
+ /* Oh, well, it's clearly bozoid, so what if we dropped a `.' ... */
+ putchar('.');
+ putchar(c);
+ }
+ ungetc(c,si->rfile);
+ if (fscanf(si->rfile,"%d",&an) != 1) {
+ while ((c= getc(si->rfile)) != EOF && c != '\n');
+ if (c == EOF) serverdataerr(si);
+ continue;
+ }
+ printf("%d",adjust(an,currentgroup->offset));
+ p= xrefbuf; n= 0; fieldn= 0;
+ for (;;) {
+ c= getc(si->rfile);
+ if (c == EOF) serverdataerr(si);
+ if (c == '\r') continue;
+ if (c != '\t' && c != '\n' && n < MAX_XREFLINE) {
+ *p++= c; n++;
+ continue;
+ }
+ if (c == '\t' || c == '\n') fieldn++;
+ *p++= 0;
+ if (fieldn >= 8 && !strncasecmp("Xref: ",xrefbuf,6)) {
+ printf("Xref: ");
+ processxref(xrefbuf+6,strlen(xrefbuf)-6,stdout,si);
+ } else {
+ fputs(xrefbuf,stdout);
+ }
+ if (c == '\n') break;
+ putchar('\t');
+ p= xrefbuf; n= 0;
+ }
+ printf("\r\n");
+ }
+ printf(".\r\n");
+ return;
+}
+
+static void cmd_xhdr(char *arg, const struct cmdinfo *cipa) {
+ char commandbuf[MAX_COMMAND+40+3];
+ char responsebuf[MAX_RESPONSE+3];
+ char linebuf[MAX_XREFLINE+40+3];
+ char *p, *q;
+ int rcode, isxref;
+ struct serverinfo *si;
+ long an;
+
+ if (!currentgroup) {
+ printf("412 headers in which group ?\r\n");
+ return;
+ }
+ p= strchr(arg,' ');
+ if (!p) {
+ printf("501 need header and range.\r\n");
+ return;
+ }
+ *p++= 0;
+ sprintf(commandbuf,"XHDR %s ",arg);
+ if (!unadjustrange(commandbuf,p,currentgroup->offset)) return;
+ si= currentgroup->readfrom;
+ rcode= servercommand(si,commandbuf,responsebuf,0);
+ if ((rcode & 0xf00) != 0x200) {
+ printf("%s\r\n",responsebuf);
+ return;
+ } else if (rcode != 0x221) {
+ printf("503 %s said (after XHDR): %s\r\n",si->hostname,responsebuf);
+ return;
+ }
+ printf("%s\r\n",responsebuf);
+ isxref= !strcasecmp(arg,"Xref");
+ if (!isxref && !currentgroup->offset) {
+ copydata(si,stdout);
+ } else {
+ for (;;) {
+ if (!fgets(linebuf,MAX_XREFLINE+40,si->rfile)) serverdataerr(si);
+ if (!stripcommand(linebuf)) continue;
+ if (!strcmp(linebuf,".")) break;
+ q= linebuf;
+ if (currentgroup->offset) {
+ an= strtol(linebuf,&q,10);
+ printf("%d",adjust(an,currentgroup->offset));
+ }
+ if (isxref && (p= strchr(q,' ')) && strcmp(p+1,"(none)")) {
+ *p++= 0;
+ printf("%s ",q);
+ processxref(p,strlen(p),stdout,si);
+ } else {
+ fputs(q,stdout);
+ }
+ fputs("\r\n",stdout);
+ }
+ printf(".\r\n");
+ }
+}
+
+static void cmd_newgroups_xgtitle(char *arg, const struct cmdinfo *cip) {
+ char commandbuf[MAX_COMMAND+40+3];
+ char responsebuf[MAX_RESPONSE+3];
+ char linebuf[MAX_XREFLINE+40+3];
+ int rcode, okrcode;
+ struct serverinfo *si, *gsi;
+
+ sprintf(commandbuf,"%s %s",cip->command,arg);
+
+ okrcode= cip->einfo ? 0x282 : 0x231;
+ if (cip->einfo && !strchr(arg,'*') && !strchr(arg,'?')) {
+ if (!*arg) {
+ if (!currentgroup) {
+ printf("412 title of which group ?\r\n");
+ return;
+ }
+ si= currentgroup->readfrom;
+ } else {
+ si= findgroup(arg)->readfrom;
+ }
+ rcode= servercommand(si,commandbuf,responsebuf,0);
+ printf("%s\r\n",responsebuf);
+ if ((rcode & 0xf00) == 0x200) copydata(si,stdout);
+ return;
+ }
+
+ for (si= servers; si; si= si->next) {
+ if (!si->searchthis) continue;
+ si->tempfile= 0;
+ }
+ for (si= servers; si; si= si->next) {
+ if (!si->searchthis) continue;
+ rcode= servercommand(si,commandbuf,responsebuf,0);
+ if (rcode != okrcode) {
+ if ((rcode & 0xf00) == 0x200) closeserver(si);
+ if (cip->einfo) continue;
+ printf("503 NEWGROUPS not available - %s: %s\r\n",si->hostname,responsebuf);
+ goto close_files_and_abort;
+ }
+ si->tempfile= tmpfile();
+ if (!si->tempfile) die("unable to create tempfile");
+ if (!copydatanodot(si,si->tempfile)) goto close_files_and_abort;
+ if (ferror(si->tempfile) || fflush(si->tempfile) || fseek(si->tempfile,0,SEEK_SET))
+ die("unable to write temp file");
+ }
+ printf("%x here you are:\r\n",okrcode);
+ for (si= servers; si; si= si->next) {
+ if (!si->searchthis) continue;
+ if (!si->tempfile) continue;
+ while (fgets(linebuf,MAX_XREFLINE+3,si->tempfile)) {
+ gsi= findgroup(linebuf)->readfrom;
+ if (gsi != si) continue;
+ fputs(linebuf,stdout);
+ }
+ if (ferror(si->tempfile)) die("read error on temp file");
+ fclose(si->tempfile); si->tempfile= 0;
+ }
+ printf(".\r\n");
+ return;
+
+ close_files_and_abort:
+ for (si= servers; si; si= si->next) {
+ if (!si->searchthis) continue;
+ if (si->tempfile) fclose(si->tempfile);
+ }
+ return;
+}
+
+static void cmd_authinfo(char *arg, const struct cmdinfo *cip) {
+ struct authdas { struct authdas *next; char *name; };
+
+ static struct authdas *alreadydone= 0;
+
+ struct authdas *asearch;
+ struct MD5Context md5ctx;
+ char *p, *claim, *here, *what;
+ FILE *file;
+ char buf[500], buf2[MAX_RESPONSE+3];
+ unsigned char message[16+MAX_SECRET], expect[16], reply[16];
+ int n, nused, matching, ifmatch;
+ struct timeval timevab;
+ unsigned long ul;
+ unsigned short us;
+ struct permission *pi;
+
+ if (strcasecmp(strtok(arg," "),"generic") ||
+ strcmp(strtok(0," "),"md5cookie1way")) {
+ printf("501 please use AUTHINFO GENERIC md5cookie1way <claim>.\r\n");
+ return;
+ }
+ claim= strtok(0," ");
+ if (strtok(0," ")) {
+ printf("501 something after claim.\r\n");
+ return;
+ }
+ for (asearch= alreadydone;
+ asearch && strcmp(asearch->name,claim);
+ asearch= asearch->next);
+ if (asearch) {
+ printf("502 you are already user/group %s; need other group for more access.\r\n",
+ claim);
+ return;
+ }
+ file= fopen("md5cookies","r");
+ if (!file) {
+ printf("503 couldn't open md5cookies file: %s\r\n",strerror(errno));
+ return;
+ }
+ matching= 0;
+ for (;;) {
+ if (!fgets(buf,sizeof(buf),file)) {
+ if (ferror(file)) {
+ printf("503 error reading md5cookies file: %s\r\n",strerror(errno));
+ } else {
+ printf("502 who did you say you were ?\r\n");
+ }
+ fclose(file);
+ return;
+ }
+ if (!stripcommand(buf) || *buf == '#' || !*buf) continue;
+ here= strtok(buf," \t");
+ if (!strcmp(buf,"@")) {
+ what= strtok(0," \t");
+ if (!strcmp(what,"allow")) {
+ ifmatch= 1;
+ } else if (!strcmp(what,"refuse")) {
+ ifmatch= 0;
+ } else {
+ continue;
+ }
+ what= strtok(0,"");
+ if (!what) continue;
+ if (fnmatch(what,theirfqdn ? theirfqdn : "_unknown_",0)) continue;
+ matching= ifmatch;
+ } else if (!strcmp(here,claim)) {
+ if (matching) break;
+ }
+ }
+ fclose(file);
+
+ if (gettimeofday(&timevab,(void*)0)) die("unable to gettimeofday for nonce");
+ memcpy(message,&timevab.tv_sec,4);
+ ul= timevab.tv_usec; memcpy(message+4,&ul,4);
+ memcpy(message+8,&peername.sin_addr,4);
+ memcpy(message+12,&peername.sin_port,2);
+ us= getpid(); memcpy(message+14,&us,2);
+
+ p= strtok(0," \t");
+ if (!p) {
+ printf("502 md5cookies file is missing secret for you.\r\n");
+ return;
+ }
+ nused= parsehex(p,message+16,MAX_SECRET);
+
+ MD5Init(&md5ctx);
+ MD5Update(&md5ctx,message,16+nused);
+ MD5Final(expect,&md5ctx);
+
+ printf("100 ");
+ for (n=0; n<16; n++) {
+ printf("%s%02x", n?":":"", message[n]);
+ }
+ printf("\r\n");
+ if (ferror(stdout) || fflush(stdout)) die("unable to write auth challenge");
+
+ if (!fgets(buf2,MAX_RESPONSE,stdin)) {
+ if (ferror(stdin)) die("client connection failed during crypto");
+ exit(1);
+ }
+ if (strncasecmp(buf2,"MD5 ",4)) {
+ printf("502 expecting MD5.\r\n");
+ return;
+ }
+ nused= parsehex(buf2+4,reply,17);
+ if (nused != 16) {
+ printf("502 expecting 16 pairs (got %d).\r\n",nused);
+ return;
+ }
+ if (memcmp(reply,expect,16)) {
+ printf("502 pull the other one, it's got bells on.\r\n");
+ return;
+ }
+ asearch= xmalloc(sizeof(struct authdas));
+ asearch->name= xstrdup(claim);
+ asearch->next= alreadydone;
+ alreadydone= asearch;
+ printf("281 ok");
+ while ((p= strtok(0," \t"))) {
+ printf(" %s",p);
+ for (pi= permissions; pi; pi= pi->next) {
+ if (strcmp(pi->name,p)) continue;
+ if (!pi->authd) {
+ putchar('+');
+ pi->authd= 1;
+ }
+ }
+ }
+ lastdoneauth= asearch->name;
+ printf("\r\n");
+}
+
+const struct cmdinfo cmdinfos[]= {
+ { "ARTICLE", cmd_article, 3 },
+ { "AUTHINFO", cmd_authinfo },
+ { "XAUTHINFO", cmd_authinfo },
+ { "HEAD", cmd_article, 1 },
+ { "BODY", cmd_article, 2 },
+ { "STAT", cmd_article, 0 },
+ { "GROUP", cmd_group },
+ { "HELP", cmd_help },
+ { "IHAVE", cmd_ihave },
+ { "LAST", cmd_last_next },
+ { "LIST", cmd_list },
+ { "NEWGROUPS", cmd_newgroups_xgtitle, 0 },
+ { "NEWNEWS", cmd_newnews },
+ { "NEXT", cmd_last_next },
+ { "NOOP", cmd_noop },
+ { "POST", cmd_post },
+ { "QUIT", cmd_quit },
+ { "SLAVE", cmd_slave },
+ { "DATE", cmd_date },
+ { "LISTGROUP", cmd_listgroup },
+ { "XLISTGROUP", cmd_listgroup },
+ { "MODE", cmd_mode },
+ { "XMODE", cmd_mode },
+ { "XMERGEINFO", cmd_xmergeinfo },
+ { "XGTITLE", cmd_newgroups_xgtitle, 1 },
+ { "XHDR", cmd_xhdr },
+ { "XOVER", cmd_xover },
+
+ { "XPAT", cmd_unimplemented },
+ { "XPATH", cmd_unimplemented },
+ { 0 }
+};
+
+static void cmd_help(char *arg, const struct cmdinfo *cipa) {
+ /* close outstanding channels */
+ const struct cmdinfo *cip;
+ if (!noargs(arg)) return;
+ printf("100 commands are (* = implemented)\r\n");
+ for (cip= cmdinfos; cip->command; cip++) {
+ printf(" %s %s\r\n",
+ cip->call == cmd_unimplemented ? " " : "*",
+ cip->command);
+ }
+ printf(".\r\n");
+}
+
+int main(int argc, char **argv) {
+ char cmdbuf[1000];
+ int n;
+ unsigned int nu;
+ const struct cmdinfo *cip;
+ struct hostent *he;
+
+ cmdbuf[sizeof(cmdbuf)-1]= 0;
+ if (gethostname(cmdbuf,sizeof(cmdbuf)-1)) die("gethostname");
+ if (cmdbuf[sizeof(cmdbuf)-1]) die("gethostname overflow");
+ if (!(he= gethostbyname(cmdbuf))) die("gethostbyname");
+ myfqdn= xstrdup(he->h_name);
+ myxref= xstrdup(he->h_name);
+
+ if (chdir("/var/lib/news/merge")) die("chdir");
+ readconfig();
+
+ setvbuf(stdin,0,_IOLBF,0);
+ setvbuf(stdout,0,_IOFBF,10240);
+ signal(SIGPIPE,SIG_IGN);
+ nu= sizeof(peername);
+ memset(&peername,0,nu);
+ if (getpeername(0,(struct sockaddr*)&peername,&nu)) {
+ theirfqdn= "_direct_";
+ } else {
+ he= gethostbyaddr((void*)&peername.sin_addr,sizeof(peername.sin_addr),AF_INET);
+ if (he && he->h_name) theirfqdn= xstrdup(he->h_name);
+ }
+
+ printf("200 %s nntp-merge ready (posting might be ok).\r\n",myfqdn);
+
+ for (;;) {
+ if (fflush(stdout)) die("flush stdout");
+ if (ferror(stdout) || ferror(stdin)) die("client connection died");
+ waitpid(-1,0,WNOHANG); /* ignore all children */
+ errno=0; if (!fgets(cmdbuf,sizeof(cmdbuf),stdin)) {
+ if (ferror(stdin)) die("client closed"); else exit(0);
+ }
+ if (!stripcommand(cmdbuf)) continue;
+ for (cip= cmdinfos; cip->command; cip++) {
+ n= strlen(cip->command);
+ if (!strncasecmp(cip->command,cmdbuf,n) &&
+ (!cmdbuf[n] || isspace(cmdbuf[n])))
+ break;
+ }
+ if (cip->command) {
+ while (isspace(cmdbuf[n])) n++;
+ cip->call(cmdbuf+n,cip);
+ } else {
+ printf("500 huh?\r\n");
+ }
+ }
+}
--- /dev/null
+/*
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest. This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ *
+ * Modified by Ian Jackson in 1995 so as not to use Colin Plumb's
+ * `usuals.h'. Arranged for byteSwap to be compiled and called
+ * even on little-endian machines, as performance isn't critical
+ * here.
+ */
+
+#include <string.h> /* for memcpy() */
+#include "md5.h"
+
+/*
+ * Note: this code is harmless but does nothing on little-endian machines.
+ */
+void
+byteSwap(UINT32 *buf, unsigned words)
+{
+ UINT8 *p = (UINT8 *)buf;
+
+ do {
+ *buf++ = (UINT32)((unsigned)p[3] << 8 | p[2]) << 16 |
+ ((unsigned)p[1] << 8 | p[0]);
+ p += 4;
+ } while (--words);
+}
+
+/*
+ * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
+ * initialization constants.
+ */
+void
+MD5Init(struct MD5Context *ctx)
+{
+ ctx->buf[0] = 0x67452301;
+ ctx->buf[1] = 0xefcdab89;
+ ctx->buf[2] = 0x98badcfe;
+ ctx->buf[3] = 0x10325476;
+
+ ctx->bytes[0] = 0;
+ ctx->bytes[1] = 0;
+}
+
+/*
+ * Update context to reflect the concatenation of another buffer full
+ * of bytes.
+ */
+void
+MD5Update(struct MD5Context *ctx, UINT8 const *buf, unsigned len)
+{
+ UINT32 t;
+
+ /* Update byte count */
+
+ t = ctx->bytes[0];
+ if ((ctx->bytes[0] = t + len) < t)
+ ctx->bytes[1]++; /* Carry from low to high */
+
+ t = 64 - (t & 0x3f); /* Space available in ctx->in (at least 1) */
+ if (t > len) {
+ memcpy((UINT8 *)ctx->in + 64 - t, buf, len);
+ return;
+ }
+ /* First chunk is an odd size */
+ memcpy((UINT8 *)ctx->in + 64 - t, buf, t);
+ byteSwap(ctx->in, 16);
+ MD5Transform(ctx->buf, ctx->in);
+ buf += t;
+ len -= t;
+
+ /* Process data in 64-byte chunks */
+ while (len >= 64) {
+ memcpy(ctx->in, buf, 64);
+ byteSwap(ctx->in, 16);
+ MD5Transform(ctx->buf, ctx->in);
+ buf += 64;
+ len -= 64;
+ }
+
+ /* Handle any remaining bytes of data. */
+ memcpy(ctx->in, buf, len);
+}
+
+/*
+ * Final wrapup - pad to 64-byte boundary with the bit pattern
+ * 1 0* (64-bit count of bits processed, MSB-first)
+ */
+void
+MD5Final(UINT8 digest[16], struct MD5Context *ctx)
+{
+ int count = ctx->bytes[0] & 0x3f; /* Number of bytes in ctx->in */
+ UINT8 *p = (UINT8 *)ctx->in + count;
+
+ /* Set the first char of padding to 0x80. There is always room. */
+ *p++ = 0x80;
+
+ /* Bytes of padding needed to make 56 bytes (-8..55) */
+ count = 56 - 1 - count;
+
+ if (count < 0) { /* Padding forces an extra block */
+ memset(p, 0, count + 8);
+ byteSwap(ctx->in, 16);
+ MD5Transform(ctx->buf, ctx->in);
+ p = (UINT8 *)ctx->in;
+ count = 56;
+ }
+ memset(p, 0, count);
+ byteSwap(ctx->in, 14);
+
+ /* Append length in bits and transform */
+ ctx->in[14] = ctx->bytes[0] << 3;
+ ctx->in[15] = ctx->bytes[1] << 3 | ctx->bytes[0] >> 29;
+ MD5Transform(ctx->buf, ctx->in);
+
+ byteSwap(ctx->buf, 4);
+ memcpy(digest, ctx->buf, 16);
+ memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */
+}
+
+#ifndef ASM_MD5
+
+/* The four core functions - F1 is optimized somewhat */
+
+/* #define F1(x, y, z) (x & y | ~x & z) */
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+/* This is the central step in the MD5 algorithm. */
+#define MD5STEP(f,w,x,y,z,in,s) \
+ (w += f(x,y,z) + in, w = (w<<s | w>>(32-s)) + x)
+
+/*
+ * The core of the MD5 algorithm, this alters an existing MD5 hash to
+ * reflect the addition of 16 longwords of new data. MD5Update blocks
+ * the data and converts bytes into longwords for this routine.
+ */
+void
+MD5Transform(UINT32 buf[4], UINT32 const in[16])
+{
+ register UINT32 a, b, c, d;
+
+ a = buf[0];
+ b = buf[1];
+ c = buf[2];
+ d = buf[3];
+
+ MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
+ MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
+ MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
+ MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
+ MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
+ MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
+ MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
+ MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
+ MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
+ MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
+ MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
+ MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
+ MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
+ MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
+ MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
+ MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
+
+ MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
+ MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
+ MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
+ MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
+ MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
+ MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
+ MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
+ MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
+ MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
+ MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
+ MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
+ MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
+ MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
+ MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
+ MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
+ MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+
+ MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
+ MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
+ MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
+ MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
+ MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
+ MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
+ MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
+ MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
+ MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
+ MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
+ MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
+ MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
+ MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
+ MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
+ MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
+ MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
+
+ MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
+ MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
+ MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
+ MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
+ MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
+ MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
+ MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
+ MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
+ MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
+ MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
+ MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
+ MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
+ MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
+ MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
+ MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
+ MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
+
+ buf[0] += a;
+ buf[1] += b;
+ buf[2] += c;
+ buf[3] += d;
+}
+
+#endif
--- /dev/null
+/*
+ * Header file for Colin Plumb's MD5 implementation.
+ * Modified by Ian Jackson so as not to use Colin Plumb's `usuals.h'.
+ *
+ * This file is in the public domain.
+ */
+
+#ifndef MD5_H
+#define MD5_H
+
+#define UINT8 unsigned char
+#define UINT32 unsigned long
+
+struct MD5Context {
+ UINT32 buf[4];
+ UINT32 bytes[2];
+ UINT32 in[16];
+};
+
+void MD5Init(struct MD5Context *context);
+void MD5Update(struct MD5Context *context, UINT8 const *buf, unsigned len);
+void MD5Final(unsigned char digest[16], struct MD5Context *context);
+void MD5Transform(UINT32 buf[4], UINT32 const in[16]);
+
+void byteSwap(UINT32 *buf, unsigned words);
+
+#endif /* !MD5_H */
--- /dev/null
+/**/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include "md5.h"
+
+#define MAX_SECRET 64
+#define NNRPDREAL "/usr/sbin/nnrpd.real"
+
+static void chopspaces(char *buf) {
+ int n;
+ n= strlen(buf);
+ while (n > 0 && isspace(buf[n-1])) n--;
+ buf[n]= 0;
+}
+
+int main(int argc, char **argv) {
+ char *p, *q, **argvs;
+ unsigned char message[16+MAX_SECRET], expect[16];
+ char responsebuf[300];
+ char sesamebuf[MAX_SECRET*2+100];
+ struct sockaddr_in peername;
+ int c, namesize, n, l;
+ struct MD5Context md5ctx;
+ FILE *file;
+ unsigned long ul;
+ unsigned short us;
+ struct timeval timevab;
+
+ argvs= argv;
+ while ((p= *++argvs)) {
+ if (*p++ != '-') break;
+ c= *p++;
+ if (!c) break;
+ do {
+ if (c == 'r') {
+ if (*++argvs) {
+ printf("400 %s\r\n",*argvs); exit(1);
+ }
+ --argvs;
+ break;
+ } else if (c == 's' || c == 'S') {
+ if (!*++argvs) --argvs;
+ break;
+ }
+ c= *p++;
+ } while (c);
+ }
+
+ if (gettimeofday(&timevab,(void*)0)) {
+ printf("400 what year is it: %s\r\n",strerror(errno));
+ exit(1);
+ }
+ memcpy(message,&timevab.tv_sec,4);
+ ul= timevab.tv_usec; memcpy(message+4,&ul,4);
+ namesize= sizeof(peername);
+ if (getsockname(fileno(stdin),(struct sockaddr*)&peername,&namesize) &&
+ namesize == sizeof(peername) &&
+ peername.sin_family == AF_INET) {
+ memcpy(message+8,&peername.sin_addr,4);
+ memcpy(message+12,&peername.sin_port,2);
+ } else {
+ memset(message+8,'x',6);
+ }
+ us= getpid(); memcpy(message+14,&us,2);
+
+ printf("480 ");
+ for (n=0; n<16; n++) {
+ printf("%s%02x", n?":":"", message[n]);
+ }
+ printf(" halt ! who goes there ?\r\n");
+ if (fflush(stdout) || ferror(stdout)) { perror("stdout challenge"); exit(1); }
+ if (!fgets(responsebuf,sizeof(responsebuf),stdin)) {
+ if (ferror(stdin)) perror("stdin response");
+ else fprintf(stderr,"stdin response EOF\n");
+ exit(1);
+ }
+ chopspaces(responsebuf);
+ p= strchr(responsebuf,' ');
+ if (p) *p++= 0;
+ if (!strcasecmp(responsebuf,"QUIT")) {
+ printf("205 please remember your papers next time.\r\n");
+ exit(0);
+ }
+ if (strcasecmp(responsebuf,"PASS")) {
+ printf("500 guards ! guards !\r\n"
+ "400 this client was just leaving.\r\n");
+ exit(1);
+ }
+ if (!p) {
+ printf("501 fail.\r\n"
+ "400 we don't want failures here.\r\n");
+ exit(1);
+ }
+ file= fopen("/etc/news/sesame","r");
+ if (!file) {
+ printf("400 suddenly i can't see anything: %s\r\n",strerror(errno));
+ exit(1);
+ }
+ do {
+ if (!fgets(sesamebuf,sizeof(sesamebuf),file)) {
+ if (ferror(file)) {
+ printf("400 i'm not sure about that: %s\r\n",strerror(errno));
+ } else {
+ printf("400 happiness is mandatory.\r\n");
+ }
+ exit(1);
+ }
+ chopspaces(sesamebuf);
+ } while (!*sesamebuf || *sesamebuf == '#' || !(q= strchr(sesamebuf,' ')));
+ *q++= 0;
+ l= strlen(q);
+ if (l>MAX_SECRET) { printf("400 i feel all bloated.\r\n"); exit(1); }
+ memcpy(message+16,q,l);
+
+ MD5Init(&md5ctx);
+ MD5Update(&md5ctx,message,16+l);
+ MD5Final(expect,&md5ctx);
+
+ for (n=0; n<16; n++) sprintf(sesamebuf+n*3,"%02x:",expect[n]);
+ sesamebuf[47]= 0;
+ if (!strcasecmp(p,sesamebuf)) {
+ execvp(NNRPDREAL,argv);
+ printf("400 o master, i have failed you: %s\r\n",strerror(errno));
+ exit(1);
+ }
+ printf("502 impostor !\r\n"
+ "400 do not darken my door again.\r\n");
+ exit(1);
+}
--- /dev/null
+#!/bin/sh
+cd /u/iwj10/nntp-merge
+exec ./nntp-merge chiark.chu.cam.ac.uk 2>>err-log
--- /dev/null
+# Consists of
+# command
+# group/pattern
+# group/pattern
+# ...
+
+# Commands are:
+
+# myfqdn <merge-server-fqdn>
+# xref <xref-name>
+# server <server-name> <host:port>
+
+# permit <group-of-users-read-only> <group-of-users-posting>
+# *=all, -=none
+# fetch <method-for-fetch> <method-for-fetch> ...
+# read <server-name>
+# post [<server-name>]
+# missing server-name means posting not allowed
+# minreaddays <n>
+# extrarc <filename> <user>
+
+# when doing ARTICLE <mid> &c, the first server listed is tried first
+myfqdn chiark.chu.cam.ac.uk
+xref nntp-merge.chiark
+server chiark chiark.chu.cam.ac.uk:118
+server lyra nntp-serv.cam.ac.uk:119
+server-nosearch nyxpost /usr/local/src/nntp-merge/nyxpost
+
+minreaddays 21
+maxperuser 250
+ignorerc guest
+
+# Chiark's local groups
+fetch nowhere
+read chiark
+post chiark
+
+# ... private mailing lists
+permit debian -
+ chiark.mail.debian.private
+ chiark.mail.debian.team
+permit camlbg -
+ chiark.mail.cam-lbg
+# ... the rest
+permit - -
+ chiark.test
+permit * *
+ chiark.*
+ local.*
+ control*
+ junk
+
+# Regional hierarchies near Nyx
+fetch nyx
+read chiark
+post nyxpost
+permit * nyxers
+ co.*
+ du.*
+ nyx.*
+permit * posters
+
+# Groups censored at UKnet, and in the `blocked' area at Nyx
+fetch lyra derwent demon
+read chiark
+post chiark
+permit - censor
+ alt.binaries.pictures.erotica
+ alt.binaries.pictures.erotica.*
+ alt.binaries.pictures.tasteless
+permit * posters
+
+# Don't bother with these at sol
+fetch lyra demon
+read chiark
+post chiark
+ demon.*
+
+# Groups likely to have bad propagation
+fetch lyra derwent demon nyx
+read chiark
+post lyra chiark
+ alt.anonymous.messages
+
+# Groups censored at UKnet, but available elsewhere
+fetch lyra derwent demon
+read chiark
+post chiark
+permit - censor
+ alt.drugs
+ alt.evil
+ alt.lycra
+ alt.personals
+ alt.personals.*
+ alt.psychoactives
+ alt.sex
+ alt.sex.*
+ alt.tasteless
+ rec.arts.erotica
+permit * posters
+
+# Groups on chiark anyway
+read chiark
+post chiark
+ alt.fan.linus-torvalds
+ alt.support.divorce
+ alt.support.personality
+ sci.psychology.*
+ alt.med.cfs
+ alt.security.pgp
+ cam.misc
+ comp.lang.perl.announce
+ comp.org.cpsr.announce
+ comp.os.linux.advocacy
+ comp.os.linux.announce
+ comp.os.linux.answers
+ comp.os.linux.development.apps
+ comp.os.linux.development.system
+ comp.os.linux.hardware
+ comp.os.linux.misc
+ comp.os.linux.networking
+ comp.sources.hp48
+ demon.announce
+ demon.dev
+ demon.sales.d
+ demon.security
+ demon.security.keys
+ demon.tickets
+ gnu.utils.bug
+ news.announce.newusers
+ news.software.nntp
+ rec.games.computer.doom.announce
+ sci.crypt
+ sci.crypt.research
+ soc.bi
+ talk.politics.crypto
+ uk.gay-lesbian-bi
+
+# UK and Cambridge hierarchies
+fetch nowhere
+read lyra
+post lyra chiark
+ cam.*
+ ucam.*
+ uk.*
+
+# Default
+fetch nowhere
+read lyra
+post lyra
+ *
--- /dev/null
+/**/
+
+#ifndef NNTP_MERGE_H
+#define NNTP_MERGE_H
+
+#define MAX_COMMAND 2048
+#define MAX_RESPONSE 2048
+#define MAX_XREFLINE 5120
+#define MAX_SECRET 64
+#define SESAMEFILE "/etc/news/sesame"
+
+struct permission {
+ struct permission *next;
+ int authd;
+ char *name;
+};
+
+struct serverinfo {
+ struct serverinfo *next; /* only used during setup */
+ int searchthis;
+ int program;
+ const char *nickname;
+ const char *hostname;
+ unsigned short port;
+ const char *send;
+ FILE *rfile, *wfile, *tempfile;
+};
+
+struct groupinfo {
+ struct serverinfo *readfrom;
+ struct serverinfo *postto[10];
+ int offset;
+ struct permission *restrictto, *readonlyto;
+};
+
+struct cmdinfo {
+ const char *command;
+ void (*call)(char *arg, const struct cmdinfo*);
+ int einfo;
+};
+
+void cmd_post(char *arg, const struct cmdinfo *cip);
+
+void die(const char *msg);
+
+void readconfig(void);
+struct groupinfo *findgroup(const char *groupname);
+struct permission *findpermit(const char *name);
+
+void *xmalloc(size_t);
+char *xstrdup(const char *);
+
+void closeserver(struct serverinfo *server);
+int stripcommand(char *buf);
+int decoderesponse(char response[MAX_RESPONSE+3], unsigned long *rvp,
+ struct serverinfo *server);
+int servercommand(struct serverinfo *server,
+ const char command[], char response[MAX_RESPONSE+3],
+ int flags);
+void serverdataerr(struct serverinfo *si);
+int copydatafile(FILE *from, FILE *to);
+int stillrestricted(struct permission **pip);
+
+extern struct permission *permissions, *restrictpost;
+extern struct serverinfo *servers;
+struct sockaddr_in peername;
+
+extern char *myfqdn, *myxref, *lastdoneauth;
+extern const char *theirfqdn;
+
+#endif /* NNTP_MERGE_H */
--- /dev/null
+#!/usr/bin/perl
+
+defined($la= $ENV{'NNTPMERGE_AUTHD_AS'}) || die "no la";
+$la.= "\@chiark";
+$outnews= "/usr/local/lib/exchange/outnews";
+$route= "nyx";
+
+$|=1;
+print "200 nyxpost\r\n";
+while (<>) {
+ s/\s*\n$//;
+ if (m/^POST$/i) {
+ print "340 mmmm\r\n";
+ $art= '';
+ for (;;) {
+ length($_=<>) || exit(1);
+ last if m/^\.\r?\n$/;
+ s/^\.//; s/\r\n$/\n/; $art.= $_;
+ }
+ defined($c= open(P,"-|")) || die "fork: $!";
+ if (!$c) {
+ open(STDERR,">&STDOUT");
+ defined($c2= open(Q,"|-")) || die "fork: $!";
+ if (!$c2) {
+ exec($outnews,"--sender",$la,$route);
+ die "exec: $!";
+ }
+ print(Q $art) || die "write: $!";
+ close(Q); $? && die "outnews: $?";
+ exit(0);
+ }
+ undef $/; $_= <P>; $/= "\n"; s/\n/ /g;
+ close(P);
+ if ($?) {
+ print "441 $_\r\n";
+ } else {
+ print "240 $_\r\n";
+ }
+ } elsif (m/^QUIT$/i) {
+ print "205 ttfn\r\n";
+ exit(0);
+ } else {
+ print "500 too stupid\r\n";
+ }
+}
--- /dev/null
+#include <unistd.h>
+#include <stdio.h>
+int main(int argc, char **argv) {
+ int g;
+ g= getegid();
+ setregid(g,g);
+ execv("/usr/local/lib/news/nyxpost",argv);
+ perror("nyxpostrun: exec");
+ exit(1);
+}
--- /dev/null
+/**/
+
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <time.h>
+#include <unistd.h>
+#include <netinet/in.h>
+
+#include <ident.h>
+
+#include "nntp-merge.h"
+
+static int output64(FILE *file, unsigned long v, int minn) {
+ static const char *const b64=
+ "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789" "+-";
+ int i= 0;
+ while (minn>0 || v) {
+ putc(b64[v&077],file);
+ v >>= 6;
+ i++; minn--;
+ }
+ return i;
+}
+
+static void eatpost() {
+ if (!copydatafile(stdin,0)) die("failed read from client during POST");
+}
+
+static void badpost(const char *why) {
+ eatpost();
+ printf("441 not acceptable: %s\r\n",why);
+}
+
+static int attemptposting(struct serverinfo *si, FILE *file,
+ char response[MAX_RESPONSE+3]) {
+ int rcode, c;
+ unsigned long rv;
+
+ if (fseek(file,0,SEEK_SET)) {
+ sprintf(response,"503 rewind article failed: %.100s",strerror(errno));
+ return 0x503;
+ }
+ rcode= servercommand(si,"POST",response,0);
+ if (rcode != 0x340) return rcode;
+ while ((c= getc(file)) != EOF) {
+ if (putc(c,si->wfile) == EOF) {
+ sprintf(response,"503 server write failed: %.100s",strerror(errno));
+ closeserver(si); return 0x503;
+ }
+ }
+ if (fflush(si->wfile) == EOF || ferror(file)) {
+ sprintf(response,"503 read article failed: %.100s",strerror(errno));
+ closeserver(si); return 0x503;
+ }
+ if (!fgets(response,MAX_RESPONSE+3,si->rfile)) {
+ sprintf(response,"503 server read failed: %.100s",strerror(errno));
+ closeserver(si); return 0x503;
+ }
+ if (!stripcommand(response)) {
+ sprintf(response,"503 null garbage in reply to article");
+ closeserver(si); return 0x503;
+ }
+ if (!decoderesponse(response,&rv,si)) return 0x503;
+ return rv;
+}
+
+void cmd_post(char *arg, const struct cmdinfo *cip) {
+ char linebuf[MAX_XREFLINE+3];
+ FILE *file;
+ int n, m, hadmessageid, haddate, hadnewsgroups, moderated, zonediff, i, j, rcode;
+ int zonediffc, hadpermprob, c;
+ struct groupinfo *gi;
+ char *p, *colon, *space, *comma;
+ struct serverinfo *postto[20], *si;
+ char response[MAX_RESPONSE+3];
+ char commandbuf[MAX_COMMAND+3];
+ char buf[1000];
+ IDENT *id;
+ time_t now;
+ struct tm local, gm, *tmtmp;
+
+ file= tmpfile(); if (!file) die("unable to make tmpfile for POST");
+ if (lastdoneauth)
+ fputs("340 go ahead.\r\n",stdout);
+ else
+ fputs("340 make my day, punk.\r\n",stdout);
+ if (ferror(stdout) || fflush(stdout)) die("unable to say go ahead to POST");
+ hadmessageid= haddate= hadnewsgroups= 0; hadpermprob= 0;
+ postto[0]= 0;
+ for (si= servers; si; si= si->next) si->tempfile= 0;
+ for (;;) {
+ errno= 0; if (!fgets(linebuf,MAX_XREFLINE,stdin)) die("error reading data in POST");
+ m= n= strlen(linebuf); if (!n) die("null data line in POST");
+ while (n > 0 && (linebuf[n-1] == '\n' || linebuf[n-1] == '\r')) n--;
+ if (m == n) { badpost("line in POST too long"); fclose(file); return; }
+ if (n==0) break;
+ linebuf[n]= 0;
+ if (n == 1 && linebuf[0] == '.') {
+ printf("441 message must have a body.\r\n"); fclose(file); return;
+ }
+ colon= strchr(linebuf,':');
+ if (!colon) { badpost("header line with no colon"); fclose(file); return; }
+ *colon++= 0;
+ while (isspace(*colon)) colon++;
+ for (p=linebuf; *p; p++)
+ if (isspace(*p)) { badpost("space in header name"); fclose(file); return; }
+ if (!strcasecmp(linebuf,"originator")) continue;
+ if (!strcasecmp(linebuf,"nntp-posting-host")) continue;
+ if (!strcasecmp(linebuf,"path")) continue;
+ fprintf(file,"%s: %s\r\n",linebuf,colon);
+ if (!strcasecmp(linebuf,"message-id")) {
+ if (hadmessageid++) {
+ badpost("two (or more) Message-ID headers"); fclose(file); return;
+ }
+ } else if (!strcasecmp(linebuf,"date")) {
+ if (haddate++) {
+ badpost("two (or more) Date headers"); fclose(file); return;
+ }
+ } else if (!strcasecmp(linebuf,"newsgroups")) {
+ if (hadnewsgroups++) {
+ badpost("two (or more) Newsgroups headers"); fclose(file); return;
+ }
+ p= colon;
+ while (p) {
+ comma= strchr(p,',');
+ if (comma) *comma++= 0;
+ gi= findgroup(p);
+ if (stillrestricted(&gi->restrictto)) {
+ hadpermprob= 1; p= comma; continue;
+ } else if (!gi->postto[0]) {
+ p= comma; continue;
+ }
+ for (i=0;
+ i<(sizeof(gi->postto)/sizeof(gi->postto[0])) &&
+ gi->postto[i];
+ i++) {
+ for (j=0;
+ j<(sizeof(postto)/sizeof(postto[0])) && postto[j] &&
+ postto[j] != gi->postto[i];
+ j++);
+ if (j >= (sizeof(postto)/sizeof(postto[0]))) {
+ badpost("would have to post to too many servers"); fclose(file); return;
+ }
+ if (!postto[j]) {
+ postto[j]= gi->postto[i];
+ if (j+1 < (sizeof(postto)/sizeof(postto[0]))) postto[j+1]= 0;
+ }
+ }
+ /* We don't bother checking for moderation status if we're only
+ * posting to one group, and that group is only on one server.
+ */
+ if (p == colon && !comma && !gi->postto[1]) break;
+ /* Try undocumented nnrp feature, falling back on getting whole active file. */
+ snprintf(commandbuf, sizeof(commandbuf), "LIST ACTIVE %s", p);
+ rcode= servercommand(gi->postto[0],commandbuf,response,0);
+ if (rcode != 0x215) {
+ rcode= servercommand(gi->postto[0],"LIST",response,0);
+ if (rcode != 0x215) {
+ eatpost(); fclose(file);
+ printf("441 couldn't LIST to check moderation status of %s - %s: %s\r\n",
+ p,gi->postto[0]->hostname,response);
+ return;
+ }
+ }
+ moderated= 0;
+ for (;;) {
+ if (!fgets(response,sizeof(response),gi->postto[0]->rfile))
+ serverdataerr(gi->postto[0]);
+ if (!strcmp(response,".\r\n") || !strcmp(response,".\n")) break;
+ space= strchr(response,' ');
+ if (!space) continue;
+ *space++= 0;
+ if (strcmp(response,p)) continue;
+ space= strchr(space,' ');
+ if (!space) continue;
+ space= strchr(++space,' ');
+ if (!space) continue;
+ ++space;
+ if (*space == 'm') moderated= 1;
+ }
+ if (moderated) {
+ postto[1]= 0;
+ break;
+ }
+ p= comma;
+ }
+ } else {
+ for (;;) {
+ c= getchar();
+ if (c != ' ' && c != '\t') break;
+ for (;;) {
+ putc(c,file);
+ if (c == '\n') break;
+ c= getchar();
+ if (c == EOF) break;
+ }
+ }
+ ungetc(c,stdin);
+ }
+ }
+ /* Right, we've copied the header into tmpfile, and we've read
+ * but not yet copied the blank line. We must add the Originator
+ * field.
+ */
+ if (!hadnewsgroups) { badpost("missing Newsgroups header"); fclose(file); return; }
+ if (!postto[0]) {
+ if (hadpermprob) {
+ eatpost();
+ if (lastdoneauth) {
+ printf("480 the power of %s is but weak.\r\n",lastdoneauth);
+ } else {
+ printf("480 thou must prove thine strength.\r\n");
+ }
+ } else {
+ badpost("no server(s) for those groups, cannot post.\r\n");
+ }
+ return;
+ }
+ id= ident_lookup(0,30);
+ fprintf(file,"Originator: %s@", id && id->identifier ? id->identifier : "");
+ if (theirfqdn) fprintf(file,"%s ([%s])",
+ strcmp(theirfqdn,"localhost") ? theirfqdn : myfqdn,
+ inet_ntoa(peername.sin_addr));
+ else fprintf(file,"[%s]", inet_ntoa(peername.sin_addr));
+ fputs("\r\n",file);
+ /* Forms of Originator line are:
+ * Originator: <identifier>@<hostname> ([<addr>])
+ * Originator: <identifier>@[<addr>]
+ * Originator: @<hostname> ([<addr>])
+ * Originator: @[addr]
+ * <opsys>, <charset> are empty strings if not available.
+ */
+
+ if (!haddate || !hadmessageid) {
+ time(&now);
+ }
+ if (!haddate) {
+ tmtmp= gmtime(&now); if (!tmtmp) die("can't get Greenwich Mean Time");
+ gm= *tmtmp;
+ tmtmp= localtime(&now); if (!tmtmp) die("can't get local time");
+ local= *tmtmp;
+ zonediff= (local.tm_hour*60 + local.tm_min) - (gm.tm_hour*60 + gm.tm_min);
+ zonediff += 2880;
+ zonediff %= 1440;
+ zonediffc= '+';
+ if (zonediff > 720) { zonediff= 1440-zonediff; zonediffc= '-'; }
+ assert(sprintf(buf,"Date: %%d %%b %%Y %%H:%%M:%%S %c%02d%02d (%%Z)\r\n",
+ zonediffc,zonediff/60,zonediff%60) < sizeof(buf));
+ if (strftime(response,sizeof(response),buf,&local) == sizeof(response))
+ die("date is too long for buffer");
+ fputs(response,file);
+ }
+ if (!hadmessageid) {
+ unsigned long pid= getpid();
+ fputs("Message-ID: <",file);
+ output64(file,(now&03)|(pid<<2),0);
+ putc('*',file);
+ output64(file,now>>2,5);
+ fprintf(file,"@%s>\r\n",myfqdn);
+ sleep(2);
+ }
+
+ fputs("\r\n",file);
+ if (!copydatafile(stdin,file)) die("failed read from client during POST");
+
+ if (ferror(file) || fflush(file) || fseek(file,0,SEEK_SET))
+ die("error writing tmp posting file");
+
+ rcode= attemptposting(postto[0],file,response);
+ if (rcode != 0x240) {
+ fclose(file);
+ printf("441 POST failed - %s: %s\r\n",postto[0]->hostname,response);
+ return;
+ }
+ printf("240 %s ok",postto[0]->hostname); fflush(stdout);
+ for (i=1; i<sizeof(postto)/sizeof(postto[0]) && postto[i]; i++) {
+ rcode= attemptposting(postto[i],file,response);
+ if (rcode != 0x240) {
+ printf("; %s err %.30s",postto[i]->hostname,response);
+ } else {
+ printf("; %s ok",postto[i]->hostname);
+ }
+ fflush(stdout);
+ }
+ printf(" - posted.\r\n");
+ fclose(file);
+ return;
+}
--- /dev/null
+/**/
+
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <time.h>
+#include <unistd.h>
+#include <netinet/in.h>
+
+#include <ident.h>
+
+#include "nntp-merge.h"
+
+static int output64(FILE *file, unsigned long v, int minn) {
+ static const char *const b64=
+ "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789" "+-";
+ int i= 0;
+ while (minn>0 || v) {
+ putc(b64[v&077],file);
+ v >>= 6;
+ i++; minn--;
+ }
+ return i;
+}
+
+static void eatpost() {
+ if (!copydatafile(stdin,0)) die("failed read from client during POST");
+}
+
+static void badpost(const char *why) {
+ eatpost();
+ printf("441 not acceptable: %s\r\n",why);
+}
+
+static int attemptposting(struct serverinfo *si, FILE *file,
+ char response[MAX_RESPONSE+3]) {
+ int rcode, c;
+ unsigned long rv;
+
+ if (fseek(file,0,SEEK_SET)) {
+ sprintf(response,"503 rewind article failed: %.100s",strerror(errno));
+ return 0x503;
+ }
+ rcode= servercommand(si,"POST",response,0);
+ if (rcode != 0x340) return rcode;
+ while ((c= getc(file)) != EOF) {
+ if (putc(c,si->wfile) == EOF) {
+ sprintf(response,"503 server write failed: %.100s",strerror(errno));
+ closeserver(si); return 0x503;
+ }
+ }
+ if (fflush(si->wfile) == EOF || ferror(file)) {
+ sprintf(response,"503 read article failed: %.100s",strerror(errno));
+ closeserver(si); return 0x503;
+ }
+ if (!fgets(response,MAX_RESPONSE+3,si->rfile)) {
+ sprintf(response,"503 server read failed: %.100s",strerror(errno));
+ closeserver(si); return 0x503;
+ }
+ if (!stripcommand(response)) {
+ sprintf(response,"503 null garbage in reply to article");
+ closeserver(si); return 0x503;
+ }
+ if (!decoderesponse(response,&rv,si)) return 0x503;
+ return rv;
+}
+
+void cmd_post(char *arg, const struct cmdinfo *cip) {
+ char linebuf[MAX_XREFLINE+3];
+ FILE *file;
+ int n, m, hadmessageid, haddate, hadnewsgroups, moderated, zonediff, i, j, rcode;
+ int zonediffc, hadpermprob, c;
+ struct groupinfo *gi;
+ char *p, *colon, *space, *comma;
+ struct serverinfo *postto[20], *si;
+ char response[MAX_RESPONSE+3];
+ char buf[1000];
+ IDENT *id;
+ time_t now;
+ struct tm local, gm, *tmtmp;
+
+ file= tmpfile(); if (!file) die("unable to make tmpfile for POST");
+ if (lastdoneauth)
+ fputs("340 go ahead.\r\n",stdout);
+ else
+ fputs("340 make my day, punk.\r\n",stdout);
+ if (ferror(stdout) || fflush(stdout)) die("unable to say go ahead to POST");
+ hadmessageid= haddate= hadnewsgroups= 0; hadpermprob= 0;
+ postto[0]= 0;
+ for (si= servers; si; si= si->next) si->tempfile= 0;
+ for (;;) {
+ errno= 0; if (!fgets(linebuf,MAX_XREFLINE,stdin)) die("error reading data in POST");
+ m= n= strlen(linebuf); if (!n) die("null data line in POST");
+ while (n > 0 && (linebuf[n-1] == '\n' || linebuf[n-1] == '\r')) n--;
+ if (m == n) { badpost("line in POST too long"); fclose(file); return; }
+ if (n==0) break;
+ linebuf[n]= 0;
+ if (n == 1 && linebuf[0] == '.') {
+ printf("441 message must have a body.\r\n"); fclose(file); return;
+ }
+ colon= strchr(linebuf,':');
+ if (!colon) { badpost("header line with no colon"); fclose(file); return; }
+ *colon++= 0;
+ while (isspace(*colon)) colon++;
+ for (p=linebuf; *p; p++)
+ if (isspace(*p)) { badpost("space in header name"); fclose(file); return; }
+ if (!strcasecmp(linebuf,"originator")) continue;
+ if (!strcasecmp(linebuf,"nntp-posting-host")) continue;
+ if (!strcasecmp(linebuf,"path")) continue;
+ fprintf(file,"%s: %s\r\n",linebuf,colon);
+ if (!strcasecmp(linebuf,"message-id")) {
+ if (hadmessageid++) {
+ badpost("two (or more) Message-ID headers"); fclose(file); return;
+ }
+ } else if (!strcasecmp(linebuf,"date")) {
+ if (haddate++) {
+ badpost("two (or more) Date headers"); fclose(file); return;
+ }
+ } else if (!strcasecmp(linebuf,"newsgroups")) {
+ if (hadnewsgroups++) {
+ badpost("two (or more) Newsgroups headers"); fclose(file); return;
+ }
+ p= colon;
+ while (p) {
+ comma= strchr(p,',');
+ if (comma) *comma++= 0;
+ gi= findgroup(p);
+ if (stillrestricted(&gi->restrictto)) {
+ hadpermprob= 1; p= comma; continue;
+ } else if (!gi->postto[0]) {
+ p= comma; continue;
+ }
+ for (i=0;
+ i<(sizeof(gi->postto)/sizeof(gi->postto[0])) &&
+ gi->postto[i];
+ i++) {
+ for (j=0;
+ j<(sizeof(postto)/sizeof(postto[0])) && postto[j] &&
+ postto[j] != gi->postto[i];
+ j++);
+ if (j >= (sizeof(postto)/sizeof(postto[0]))) {
+ badpost("would have to post to too many servers"); fclose(file); return;
+ }
+ if (!postto[j]) {
+ postto[j]= gi->postto[i];
+ if (j+1 < (sizeof(postto)/sizeof(postto[0]))) postto[j+1]= 0;
+ }
+ }
+ /* We don't bother checking for moderation status if we're only
+ * posting to one group, and that group is only on one server.
+ */
+ if (p == colon && !comma && !gi->postto[1]) break;
+ rcode= servercommand(gi->postto[0],"LIST",response,0);
+ if (rcode != 0x215) {
+ eatpost(); fclose(file);
+ printf("441 couldn't LIST to check moderation status of %s - %s: %s\r\n",
+ p,gi->postto[0]->hostname,response);
+ return;
+ }
+ moderated= 0;
+ for (;;) {
+ if (!fgets(response,sizeof(response),gi->postto[0]->rfile))
+ serverdataerr(gi->postto[0]);
+ if (!strcmp(response,".\r\n") || !strcmp(response,".\n")) break;
+ space= strchr(response,' ');
+ if (!space) continue;
+ *space++= 0;
+ if (strcmp(response,p)) continue;
+ space= strchr(space,' ');
+ if (!space) continue;
+ space= strchr(++space,' ');
+ if (!space) continue;
+ ++space;
+ if (*space == 'm') moderated= 1;
+ }
+ if (moderated) {
+ postto[1]= 0;
+ break;
+ }
+ p= comma;
+ }
+ } else {
+ for (;;) {
+ c= getchar();
+ if (c != ' ' && c != '\t') break;
+ for (;;) {
+ putc(c,file);
+ if (c == '\n') break;
+ c= getchar();
+ if (c == EOF) break;
+ }
+ }
+ ungetc(c,stdin);
+ }
+ }
+ /* Right, we've copied the header into tmpfile, and we've read
+ * but not yet copied the blank line. We must add the Originator
+ * field.
+ */
+ if (!hadnewsgroups) { badpost("missing Newsgroups header"); fclose(file); return; }
+ if (!postto[0]) {
+ if (hadpermprob) {
+ eatpost();
+ if (lastdoneauth) {
+ printf("480 the power of %s is but weak.\r\n",lastdoneauth);
+ } else {
+ printf("480 thou must prove thine strength.\r\n");
+ }
+ } else {
+ badpost("no server(s) for those groups, cannot post.\r\n");
+ }
+ return;
+ }
+ id= ident_lookup(0,30);
+ fprintf(file,"Originator: %s@", id && id->identifier ? id->identifier : "");
+ if (theirfqdn) fprintf(file,"%s ([%s])",
+ strcmp(theirfqdn,"localhost") ? theirfqdn : myfqdn,
+ inet_ntoa(peername.sin_addr));
+ else fprintf(file,"[%s]", inet_ntoa(peername.sin_addr));
+ fputs("\r\n",file);
+ /* Forms of Originator line are:
+ * Originator: <identifier>@<hostname> ([<addr>])
+ * Originator: <identifier>@[<addr>]
+ * Originator: @<hostname> ([<addr>])
+ * Originator: @[addr]
+ * <opsys>, <charset> are empty strings if not available.
+ */
+
+ if (!haddate || !hadmessageid) {
+ time(&now);
+ }
+ if (!haddate) {
+ tmtmp= gmtime(&now); if (!tmtmp) die("can't get Greenwich Mean Time");
+ gm= *tmtmp;
+ tmtmp= localtime(&now); if (!tmtmp) die("can't get local time");
+ local= *tmtmp;
+ zonediff= (local.tm_hour*60 + local.tm_min) - (gm.tm_hour*60 + gm.tm_min);
+ zonediff += 2880;
+ zonediff %= 1440;
+ zonediffc= '+';
+ if (zonediff > 720) { zonediff= 1440-zonediff; zonediffc= '-'; }
+ assert(sprintf(buf,"Date: %%d %%b %%Y %%H:%%M:%%S %c%02d%02d (%%Z)\r\n",
+ zonediffc,zonediff/60,zonediff%60) < sizeof(buf));
+ if (strftime(response,sizeof(response),buf,&local) == sizeof(response))
+ die("date is too long for buffer");
+ fputs(response,file);
+ }
+ if (!hadmessageid) {
+ unsigned long pid= getpid();
+ fputs("Message-ID: <",file);
+ output64(file,(now&03)|(pid<<2),0);
+ putc('*',file);
+ output64(file,now>>2,5);
+ fprintf(file,"@%s>\r\n",myfqdn);
+ sleep(2);
+ }
+
+ fputs("\r\n",file);
+ if (!copydatafile(stdin,file)) die("failed read from client during POST");
+
+ if (ferror(file) || fflush(file) || fseek(file,0,SEEK_SET))
+ die("error writing tmp posting file");
+
+ rcode= attemptposting(postto[0],file,response);
+ if (rcode != 0x240) {
+ fclose(file);
+ printf("441 POST failed - %s: %s\r\n",postto[0]->hostname,response);
+ return;
+ }
+ printf("240 %s ok",postto[0]->hostname); fflush(stdout);
+ for (i=1; i<sizeof(postto)/sizeof(postto[0]) && postto[i]; i++) {
+ rcode= attemptposting(postto[i],file,response);
+ if (rcode != 0x240) {
+ printf("; %s err %.30s",postto[i]->hostname,response);
+ } else {
+ printf("; %s ok",postto[i]->hostname);
+ }
+ fflush(stdout);
+ }
+ printf(" - posted.\r\n");
+ fclose(file);
+ return;
+}
--- /dev/null
+
+
+Network Working Group Brian Kantor (U.C. San Diego)
+Request for Comments: 977 Phil Lapsley (U.C. Berkeley)
+ February 1986
+
+ Network News Transfer Protocol
+
+ A Proposed Standard for the Stream-Based
+ Transmission of News
+
+Status of This Memo
+
+ NNTP specifies a protocol for the distribution, inquiry, retrieval,
+ and posting of news articles using a reliable stream-based
+ transmission of news among the ARPA-Internet community. NNTP is
+ designed so that news articles are stored in a central database
+ allowing a subscriber to select only those items he wishes to read.
+ Indexing, cross-referencing, and expiration of aged messages are also
+ provided. This RFC suggests a proposed protocol for the ARPA-Internet
+ community, and requests discussion and suggestions for improvements.
+ Distribution of this memo is unlimited.
+
+1. Introduction
+
+ For many years, the ARPA-Internet community has supported the
+ distribution of bulletins, information, and data in a timely fashion
+ to thousands of participants. We collectively refer to such items of
+ information as "news". Such news provides for the rapid
+ dissemination of items of interest such as software bug fixes, new
+ product reviews, technical tips, and programming pointers, as well as
+ rapid-fire discussions of matters of concern to the working computer
+ professional. News is very popular among its readers.
+
+ There are popularly two methods of distributing such news: the
+ Internet method of direct mailing, and the USENET news system.
+
+1.1. Internet Mailing Lists
+
+ The Internet community distributes news by the use of mailing lists.
+ These are lists of subscriber's mailbox addresses and remailing
+ sublists of all intended recipients. These mailing lists operate by
+ remailing a copy of the information to be distributed to each
+ subscriber on the mailing list. Such remailing is inefficient when a
+ mailing list grows beyond a dozen or so people, since sending a
+ separate copy to each of the subscribers occupies large quantities of
+ network bandwidth, CPU resources, and significant amounts of disk
+ storage at the destination host. There is also a significant problem
+ in maintenance of the list itself: as subscribers move from one job
+ to another; as new subscribers join and old ones leave; and as hosts
+ come in and out of service.
+
+
+
+
+Kantor & Lapsley [Page 1]
+\f
+
+
+RFC 977 February 1986
+Network News Transfer Protocol
+
+
+1.2. The USENET News System
+
+ Clearly, a worthwhile reduction of the amount of these resources used
+ can be achieved if articles are stored in a central database on the
+ receiving host instead of in each subscriber's mailbox. The USENET
+ news system provides a method of doing just this. There is a central
+ repository of the news articles in one place (customarily a spool
+ directory of some sort), and a set of programs that allow a
+ subscriber to select those items he wishes to read. Indexing,
+ cross-referencing, and expiration of aged messages are also provided.
+
+1.3. Central Storage of News
+
+ For clusters of hosts connected together by fast local area networks
+ (such as Ethernet), it makes even more sense to consolidate news
+ distribution onto one (or a very few) hosts, and to allow access to
+ these news articles using a server and client model. Subscribers may
+ then request only the articles they wish to see, without having to
+ wastefully duplicate the storage of a copy of each item on each host.
+
+1.4. A Central News Server
+
+ A way to achieve these economies is to have a central computer system
+ that can provide news service to the other systems on the local area
+ network. Such a server would manage the collection of news articles
+ and index files, with each person who desires to read news bulletins
+ doing so over the LAN. For a large cluster of computer systems, the
+ savings in total disk space is clearly worthwhile. Also, this allows
+ workstations with limited disk storage space to participate in the
+ news without incoming items consuming oppressive amounts of the
+ workstation's disk storage.
+
+ We have heard rumors of somewhat successful attempts to provide
+ centralized news service using IBIS and other shared or distributed
+ file systems. While it is possible that such a distributed file
+ system implementation might work well with a group of similar
+ computers running nearly identical operating systems, such a scheme
+ is not general enough to offer service to a wide range of client
+ systems, especially when many diverse operating systems may be in use
+ among a group of clients. There are few (if any) shared or networked
+ file systems that can offer the generality of service that stream
+ connections using Internet TCP provide, particularly when a wide
+ range of host hardware and operating systems are considered.
+
+ NNTP specifies a protocol for the distribution, inquiry, retrieval,
+ and posting of news articles using a reliable stream (such as TCP)
+ server-client model. NNTP is designed so that news articles need only
+
+
+Kantor & Lapsley [Page 2]
+\f
+
+
+RFC 977 February 1986
+Network News Transfer Protocol
+
+
+ be stored on one (presumably central) host, and subscribers on other
+ hosts attached to the LAN may read news articles using stream
+ connections to the news host.
+
+ NNTP is modelled upon the news article specifications in RFC 850,
+ which describes the USENET news system. However, NNTP makes few
+ demands upon the structure, content, or storage of news articles, and
+ thus we believe it easily can be adapted to other non-USENET news
+ systems.
+
+ Typically, the NNTP server runs as a background process on one host,
+ and would accept connections from other hosts on the LAN. This works
+ well when there are a number of small computer systems (such as
+ workstations, with only one or at most a few users each), and a large
+ central server.
+
+1.5. Intermediate News Servers
+
+ For clusters of machines with many users (as might be the case in a
+ university or large industrial environment), an intermediate server
+ might be used. This intermediate or "slave" server runs on each
+ computer system, and is responsible for mediating news reading
+ requests and performing local caching of recently-retrieved news
+ articles.
+
+ Typically, a client attempting to obtain news service would first
+ attempt to connect to the news service port on the local machine. If
+ this attempt were unsuccessful, indicating a failed server, an
+ installation might choose to either deny news access, or to permit
+ connection to the central "master" news server.
+
+ For workstations or other small systems, direct connection to the
+ master server would probably be the normal manner of operation.
+
+ This specification does not cover the operation of slave NNTP
+ servers. We merely suggest that slave servers are a logical addition
+ to NNTP server usage which would enhance operation on large local
+ area networks.
+
+1.6. News Distribution
+
+ NNTP has commands which provide a straightforward method of
+ exchanging articles between cooperating hosts. Hosts which are well
+ connected on a local area or other fast network and who wish to
+ actually obtain copies of news articles for local storage might well
+ find NNTP to be a more efficient way to distribute news than more
+ traditional transfer methods (such as UUCP).
+
+
+Kantor & Lapsley [Page 3]
+\f
+
+
+RFC 977 February 1986
+Network News Transfer Protocol
+
+
+ In the traditional method of distributing news articles, news is
+ propagated from host to host by flooding - that is, each host will
+ send all its new news articles on to each host that it feeds. These
+ hosts will then in turn send these new articles on to other hosts
+ that they feed. Clearly, sending articles that a host already has
+ obtained a copy of from another feed (many hosts that receive news
+ are redundantly fed) again is a waste of time and communications
+ resources, but for transport mechanisms that are single-transaction
+ based rather than interactive (such as UUCP in the UNIX-world <1>),
+ distribution time is diminished by sending all articles and having
+ the receiving host simply discard the duplicates. This is an
+ especially true when communications sessions are limited to once a
+ day.
+
+ Using NNTP, hosts exchanging news articles have an interactive
+ mechanism for deciding which articles are to be transmitted. A host
+ desiring new news, or which has new news to send, will typically
+ contact one or more of its neighbors using NNTP. First it will
+ inquire if any new news groups have been created on the serving host
+ by means of the NEWGROUPS command. If so, and those are appropriate
+ or desired (as established by local site-dependent rules), those new
+ newsgroups can be created.
+
+ The client host will then inquire as to which new articles have
+ arrived in all or some of the newsgroups that it desires to receive,
+ using the NEWNEWS command. It will receive a list of new articles
+ from the server, and can request transmission of those articles that
+ it desires and does not already have.
+
+ Finally, the client can advise the server of those new articles which
+ the client has recently received. The server will indicate those
+ articles that it has already obtained copies of, and which articles
+ should be sent to add to its collection.
+
+ In this manner, only those articles which are not duplicates and
+ which are desired are transferred.
+
+
+
+
+
+
+
+
+
+
+
+
+
+Kantor & Lapsley [Page 4]
+\f
+
+
+RFC 977 February 1986
+Network News Transfer Protocol
+
+
+2. The NNTP Specification
+
+2.1. Overview
+
+ The news server specified by this document uses a stream connection
+ (such as TCP) and SMTP-like commands and responses. It is designed
+ to accept connections from hosts, and to provide a simple interface
+ to the news database.
+
+ This server is only an interface between programs and the news
+ databases. It does not perform any user interaction or presentation-
+ level functions. These "user-friendly" functions are better left to
+ the client programs, which have a better understanding of the
+ environment in which they are operating.
+
+ When used via Internet TCP, the contact port assigned for this
+ service is 119.
+
+2.2. Character Codes
+
+ Commands and replies are composed of characters from the ASCII
+ character set. When the transport service provides an 8-bit byte
+ (octet) transmission channel, each 7-bit character is transmitted
+ right justified in an octet with the high order bit cleared to zero.
+
+2.3. Commands
+
+ Commands consist of a command word, which in some cases may be
+ followed by a parameter. Commands with parameters must separate the
+ parameters from each other and from the command by one or more space
+ or tab characters. Command lines must be complete with all required
+ parameters, and may not contain more than one command.
+
+ Commands and command parameters are not case sensitive. That is, a
+ command or parameter word may be upper case, lower case, or any
+ mixture of upper and lower case.
+
+ Each command line must be terminated by a CR-LF (Carriage Return -
+ Line Feed) pair.
+
+ Command lines shall not exceed 512 characters in length, counting all
+ characters including spaces, separators, punctuation, and the
+ trailing CR-LF (thus there are 510 characters maximum allowed for the
+ command and its parameters). There is no provision for continuation
+ command lines.
+
+
+
+
+Kantor & Lapsley [Page 5]
+\f
+
+
+RFC 977 February 1986
+Network News Transfer Protocol
+
+
+2.4. Responses
+
+ Responses are of two kinds, textual and status.
+
+2.4.1. Text Responses
+
+ Text is sent only after a numeric status response line has been sent
+ that indicates that text will follow. Text is sent as a series of
+ successive lines of textual matter, each terminated with CR-LF pair.
+ A single line containing only a period (.) is sent to indicate the
+ end of the text (i.e., the server will send a CR-LF pair at the end
+ of the last line of text, a period, and another CR-LF pair).
+
+ If the text contained a period as the first character of the text
+ line in the original, that first period is doubled. Therefore, the
+ client must examine the first character of each line received, and
+ for those beginning with a period, determine either that this is the
+ end of the text or whether to collapse the doubled period to a single
+ one.
+
+ The intention is that text messages will usually be displayed on the
+ user's terminal whereas command/status responses will be interpreted
+ by the client program before any possible display is done.
+
+2.4.2. Status Responses
+
+ These are status reports from the server and indicate the response to
+ the last command received from the client.
+
+ Status response lines begin with a 3 digit numeric code which is
+ sufficient to distinguish all responses. Some of these may herald
+ the subsequent transmission of text.
+
+ The first digit of the response broadly indicates the success,
+ failure, or progress of the previous command.
+
+ 1xx - Informative message
+ 2xx - Command ok
+ 3xx - Command ok so far, send the rest of it.
+ 4xx - Command was correct, but couldn't be performed for
+ some reason.
+ 5xx - Command unimplemented, or incorrect, or a serious
+ program error occurred.
+
+
+
+
+
+
+Kantor & Lapsley [Page 6]
+\f
+
+
+RFC 977 February 1986
+Network News Transfer Protocol
+
+
+ The next digit in the code indicates the function response category.
+
+ x0x - Connection, setup, and miscellaneous messages
+ x1x - Newsgroup selection
+ x2x - Article selection
+ x3x - Distribution functions
+ x4x - Posting
+ x8x - Nonstandard (private implementation) extensions
+ x9x - Debugging output
+
+ The exact response codes that should be expected from each command
+ are detailed in the description of that command. In addition, below
+ is listed a general set of response codes that may be received at any
+ time.
+
+ Certain status responses contain parameters such as numbers and
+ names. The number and type of such parameters is fixed for each
+ response code to simplify interpretation of the response.
+
+ Parameters are separated from the numeric response code and from each
+ other by a single space. All numeric parameters are decimal, and may
+ have leading zeros. All string parameters begin after the separating
+ space, and end before the following separating space or the CR-LF
+ pair at the end of the line. (String parameters may not, therefore,
+ contain spaces.) All text, if any, in the response which is not a
+ parameter of the response must follow and be separated from the last
+ parameter by a space. Also, note that the text following a response
+ number may vary in different implementations of the server. The
+ 3-digit numeric code should be used to determine what response was
+ sent.
+
+ Response codes not specified in this standard may be used for any
+ installation-specific additional commands also not specified. These
+ should be chosen to fit the pattern of x8x specified above. (Note
+ that debugging is provided for explicitly in the x9x response codes.)
+ The use of unspecified response codes for standard commands is
+ prohibited.
+
+ We have provided a response pattern x9x for debugging. Since much
+ debugging output may be classed as "informative messages", we would
+ expect, therefore, that responses 190 through 199 would be used for
+ various debugging outputs. There is no requirement in this
+ specification for debugging output, but if such is provided over the
+ connected stream, it must use these response codes. If appropriate
+ to a specific implementation, other x9x codes may be used for
+ debugging. (An example might be to use e.g., 290 to acknowledge a
+ remote debugging request.)
+
+
+Kantor & Lapsley [Page 7]
+\f
+
+
+RFC 977 February 1986
+Network News Transfer Protocol
+
+
+2.4.3. General Responses
+
+ The following is a list of general response codes that may be sent by
+ the NNTP server. These are not specific to any one command, but may
+ be returned as the result of a connection, a failure, or some unusual
+ condition.
+
+ In general, 1xx codes may be ignored or displayed as desired; code
+ 200 or 201 is sent upon initial connection to the NNTP server
+ depending upon posting permission; code 400 will be sent when the
+ NNTP server discontinues service (by operator request, for example);
+ and 5xx codes indicate that the command could not be performed for
+ some unusual reason.
+
+ 100 help text
+ 190
+ through
+ 199 debug output
+
+ 200 server ready - posting allowed
+ 201 server ready - no posting allowed
+
+ 400 service discontinued
+
+ 500 command not recognized
+ 501 command syntax error
+ 502 access restriction or permission denied
+ 503 program fault - command not performed
+
+3. Command and Response Details
+
+ On the following pages are descriptions of each command recognized by
+ the NNTP server and the responses which will be returned by those
+ commands.
+
+ Each command is shown in upper case for clarity, although case is
+ ignored in the interpretation of commands by the NNTP server. Any
+ parameters are shown in lower case. A parameter shown in [square
+ brackets] is optional. For example, [GMT] indicates that the
+ triglyph GMT may present or omitted.
+
+ Every command described in this section must be implemented by all
+ NNTP servers.
+
+
+
+
+
+
+Kantor & Lapsley [Page 8]
+\f
+
+
+RFC 977 February 1986
+Network News Transfer Protocol
+
+
+ There is no prohibition against additional commands being added;
+ however, it is recommended that any such unspecified command begin
+ with the letter "X" to avoid conflict with later revisions of this
+ specification.
+
+ Implementors are reminded that such additional commands may not
+ redefine specified status response codes. Using additional
+ unspecified responses for standard commands is also prohibited.
+
+3.1. The ARTICLE, BODY, HEAD, and STAT commands
+
+ There are two forms to the ARTICLE command (and the related BODY,
+ HEAD, and STAT commands), each using a different method of specifying
+ which article is to be retrieved. When the ARTICLE command is
+ followed by a message-id in angle brackets ("<" and ">"), the first
+ form of the command is used; when a numeric parameter or no parameter
+ is supplied, the second form is invoked.
+
+ The text of the article is returned as a textual response, as
+ described earlier in this document.
+
+ The HEAD and BODY commands are identical to the ARTICLE command
+ except that they respectively return only the header lines or text
+ body of the article.
+
+ The STAT command is similar to the ARTICLE command except that no
+ text is returned. When selecting by message number within a group,
+ the STAT command serves to set the current article pointer without
+ sending text. The returned acknowledgement response will contain the
+ message-id, which may be of some value. Using the STAT command to
+ select by message-id is valid but of questionable value, since a
+ selection by message-id does NOT alter the "current article pointer".
+
+3.1.1. ARTICLE (selection by message-id)
+
+ ARTICLE <message-id>
+
+ Display the header, a blank line, then the body (text) of the
+ specified article. Message-id is the message id of an article as
+ shown in that article's header. It is anticipated that the client
+ will obtain the message-id from a list provided by the NEWNEWS
+ command, from references contained within another article, or from
+ the message-id provided in the response to some other commands.
+
+ Please note that the internally-maintained "current article pointer"
+ is NOT ALTERED by this command. This is both to facilitate the
+ presentation of articles that may be referenced within an article
+
+
+Kantor & Lapsley [Page 9]
+\f
+
+
+RFC 977 February 1986
+Network News Transfer Protocol
+
+
+ being read, and because of the semantic difficulties of determining
+ the proper sequence and membership of an article which may have been
+ posted to more than one newsgroup.
+
+3.1.2. ARTICLE (selection by number)
+
+ ARTICLE [nnn]
+
+ Displays the header, a blank line, then the body (text) of the
+ current or specified article. The optional parameter nnn is the
+
+ numeric id of an article in the current newsgroup and must be chosen
+ from the range of articles provided when the newsgroup was selected.
+ If it is omitted, the current article is assumed.
+
+ The internally-maintained "current article pointer" is set by this
+ command if a valid article number is specified.
+
+ [the following applies to both forms of the article command.] A
+ response indicating the current article number, a message-id string,
+ and that text is to follow will be returned.
+
+ The message-id string returned is an identification string contained
+ within angle brackets ("<" and ">"), which is derived from the header
+ of the article itself. The Message-ID header line (required by
+ RFC850) from the article must be used to supply this information. If
+ the message-id header line is missing from the article, a single
+ digit "0" (zero) should be supplied within the angle brackets.
+
+ Since the message-id field is unique with each article, it may be
+ used by a news reading program to skip duplicate displays of articles
+ that have been posted more than once, or to more than one newsgroup.
+
+3.1.3. Responses
+
+ 220 n <a> article retrieved - head and body follow
+ (n = article number, <a> = message-id)
+ 221 n <a> article retrieved - head follows
+ 222 n <a> article retrieved - body follows
+ 223 n <a> article retrieved - request text separately
+ 412 no newsgroup has been selected
+ 420 no current article has been selected
+ 423 no such article number in this group
+ 430 no such article found
+
+
+
+
+
+Kantor & Lapsley [Page 10]
+\f
+
+
+RFC 977 February 1986
+Network News Transfer Protocol
+
+
+3.2. The GROUP command
+
+3.2.1. GROUP
+
+ GROUP ggg
+
+ The required parameter ggg is the name of the newsgroup to be
+ selected (e.g. "net.news"). A list of valid newsgroups may be
+ obtained from the LIST command.
+
+ The successful selection response will return the article numbers of
+ the first and last articles in the group, and an estimate of the
+ number of articles on file in the group. It is not necessary that
+ the estimate be correct, although that is helpful; it must only be
+ equal to or larger than the actual number of articles on file. (Some
+ implementations will actually count the number of articles on file.
+ Others will just subtract first article number from last to get an
+ estimate.)
+
+ When a valid group is selected by means of this command, the
+ internally maintained "current article pointer" is set to the first
+ article in the group. If an invalid group is specified, the
+ previously selected group and article remain selected. If an empty
+ newsgroup is selected, the "current article pointer" is in an
+ indeterminate state and should not be used.
+
+ Note that the name of the newsgroup is not case-dependent. It must
+ otherwise match a newsgroup obtained from the LIST command or an
+ error will result.
+
+3.2.2. Responses
+
+ 211 n f l s group selected
+ (n = estimated number of articles in group,
+ f = first article number in the group,
+ l = last article number in the group,
+ s = name of the group.)
+ 411 no such news group
+
+
+
+
+
+
+
+
+
+
+
+Kantor & Lapsley [Page 11]
+\f
+
+
+RFC 977 February 1986
+Network News Transfer Protocol
+
+
+3.3. The HELP command
+
+3.3.1. HELP
+
+ HELP
+
+ Provides a short summary of commands that are understood by this
+ implementation of the server. The help text will be presented as a
+ textual response, terminated by a single period on a line by itself.
+
+ 3.3.2. Responses
+
+ 100 help text follows
+
+3.4. The IHAVE command
+
+3.4.1. IHAVE
+
+ IHAVE <messageid>
+
+ The IHAVE command informs the server that the client has an article
+ whose id is <messageid>. If the server desires a copy of that
+ article, it will return a response instructing the client to send the
+ entire article. If the server does not want the article (if, for
+ example, the server already has a copy of it), a response indicating
+ that the article is not wanted will be returned.
+
+ If transmission of the article is requested, the client should send
+ the entire article, including header and body, in the manner
+ specified for text transmission from the server. A response code
+ indicating success or failure of the transferral of the article will
+ be returned.
+
+ This function differs from the POST command in that it is intended
+ for use in transferring already-posted articles between hosts.
+ Normally it will not be used when the client is a personal
+ newsreading program. In particular, this function will invoke the
+ server's news posting program with the appropriate settings (flags,
+ options, etc) to indicate that the forthcoming article is being
+ forwarded from another host.
+
+ The server may, however, elect not to post or forward the article if
+ after further examination of the article it deems it inappropriate to
+ do so. The 436 or 437 error codes may be returned as appropriate to
+ the situation.
+
+ Reasons for such subsequent rejection of an article may include such
+
+
+Kantor & Lapsley [Page 12]
+\f
+
+
+RFC 977 February 1986
+Network News Transfer Protocol
+
+
+ problems as inappropriate newsgroups or distributions, disk space
+ limitations, article lengths, garbled headers, and the like. These
+ are typically restrictions enforced by the server host's news
+ software and not necessarily the NNTP server itself.
+
+3.4.2. Responses
+
+ 235 article transferred ok
+ 335 send article to be transferred. End with <CR-LF>.<CR-LF>
+ 435 article not wanted - do not send it
+ 436 transfer failed - try again later
+ 437 article rejected - do not try again
+
+ An implementation note:
+
+ Because some host news posting software may not be able to decide
+ immediately that an article is inappropriate for posting or
+ forwarding, it is acceptable to acknowledge the successful transfer
+ of the article and to later silently discard it. Thus it is
+ permitted to return the 235 acknowledgement code and later discard
+ the received article. This is not a fully satisfactory solution to
+ the problem. Perhaps some implementations will wish to send mail to
+ the author of the article in certain of these cases.
+
+3.5. The LAST command
+
+3.5.1. LAST
+
+ LAST
+
+ The internally maintained "current article pointer" is set to the
+ previous article in the current newsgroup. If already positioned at
+ the first article of the newsgroup, an error message is returned and
+ the current article remains selected.
+
+ The internally-maintained "current article pointer" is set by this
+ command.
+
+ A response indicating the current article number, and a message-id
+ string will be returned. No text is sent in response to this
+ command.
+
+3.5.2. Responses
+
+ 223 n a article retrieved - request text separately
+ (n = article number, a = unique article id)
+
+
+
+Kantor & Lapsley [Page 13]
+\f
+
+
+RFC 977 February 1986
+Network News Transfer Protocol
+
+
+ 412 no newsgroup selected
+ 420 no current article has been selected
+ 422 no previous article in this group
+
+3.6. The LIST command
+
+3.6.1. LIST
+
+ LIST
+
+ Returns a list of valid newsgroups and associated information. Each
+ newsgroup is sent as a line of text in the following format:
+
+ group last first p
+
+ where <group> is the name of the newsgroup, <last> is the number of
+ the last known article currently in that newsgroup, <first> is the
+ number of the first article currently in the newsgroup, and <p> is
+ either 'y' or 'n' indicating whether posting to this newsgroup is
+ allowed ('y') or prohibited ('n').
+
+ The <first> and <last> fields will always be numeric. They may have
+ leading zeros. If the <last> field evaluates to less than the
+ <first> field, there are no articles currently on file in the
+ newsgroup.
+
+ Note that posting may still be prohibited to a client even though the
+ LIST command indicates that posting is permitted to a particular
+ newsgroup. See the POST command for an explanation of client
+ prohibitions. The posting flag exists for each newsgroup because
+ some newsgroups are moderated or are digests, and therefore cannot be
+ posted to; that is, articles posted to them must be mailed to a
+ moderator who will post them for the submitter. This is independent
+ of the posting permission granted to a client by the NNTP server.
+
+ Please note that an empty list (i.e., the text body returned by this
+ command consists only of the terminating period) is a possible valid
+ response, and indicates that there are currently no valid newsgroups.
+
+3.6.2. Responses
+
+ 215 list of newsgroups follows
+
+
+
+
+
+
+
+Kantor & Lapsley [Page 14]
+\f
+
+
+RFC 977 February 1986
+Network News Transfer Protocol
+
+
+3.7. The NEWGROUPS command
+
+3.7.1. NEWGROUPS
+
+ NEWGROUPS date time [GMT] [<distributions>]
+
+ A list of newsgroups created since <date and time> will be listed in
+ the same format as the LIST command.
+
+ The date is sent as 6 digits in the format YYMMDD, where YY is the
+ last two digits of the year, MM is the two digits of the month (with
+ leading zero, if appropriate), and DD is the day of the month (with
+ leading zero, if appropriate). The closest century is assumed as
+ part of the year (i.e., 86 specifies 1986, 30 specifies 2030, 99 is
+ 1999, 00 is 2000).
+
+ Time must also be specified. It must be as 6 digits HHMMSS with HH
+ being hours on the 24-hour clock, MM minutes 00-59, and SS seconds
+ 00-59. The time is assumed to be in the server's timezone unless the
+ token "GMT" appears, in which case both time and date are evaluated
+ at the 0 meridian.
+
+ The optional parameter "distributions" is a list of distribution
+ groups, enclosed in angle brackets. If specified, the distribution
+ portion of a new newsgroup (e.g, 'net' in 'net.wombat') will be
+ examined for a match with the distribution categories listed, and
+ only those new newsgroups which match will be listed. If more than
+ one distribution group is to be listed, they must be separated by
+ commas within the angle brackets.
+
+ Please note that an empty list (i.e., the text body returned by this
+ command consists only of the terminating period) is a possible valid
+ response, and indicates that there are currently no new newsgroups.
+
+3.7.2. Responses
+
+ 231 list of new newsgroups follows
+
+
+
+
+
+
+
+
+
+
+
+
+Kantor & Lapsley [Page 15]
+\f
+
+
+RFC 977 February 1986
+Network News Transfer Protocol
+
+
+3.8. The NEWNEWS command
+
+3.8.1. NEWNEWS
+
+ NEWNEWS newsgroups date time [GMT] [<distribution>]
+
+ A list of message-ids of articles posted or received to the specified
+ newsgroup since "date" will be listed. The format of the listing will
+ be one message-id per line, as though text were being sent. A single
+ line consisting solely of one period followed by CR-LF will terminate
+ the list.
+
+ Date and time are in the same format as the NEWGROUPS command.
+
+ A newsgroup name containing a "*" (an asterisk) may be specified to
+ broaden the article search to some or all newsgroups. The asterisk
+ will be extended to match any part of a newsgroup name (e.g.,
+ net.micro* will match net.micro.wombat, net.micro.apple, etc). Thus
+ if only an asterisk is given as the newsgroup name, all newsgroups
+ will be searched for new news.
+
+ (Please note that the asterisk "*" expansion is a general
+ replacement; in particular, the specification of e.g., net.*.unix
+ should be correctly expanded to embrace names such as net.wombat.unix
+ and net.whocares.unix.)
+
+ Conversely, if no asterisk appears in a given newsgroup name, only
+ the specified newsgroup will be searched for new articles. Newsgroup
+ names must be chosen from those returned in the listing of available
+ groups. Multiple newsgroup names (including a "*") may be specified
+ in this command, separated by a comma. No comma shall appear after
+ the last newsgroup in the list. [Implementors are cautioned to keep
+ the 512 character command length limit in mind.]
+
+ The exclamation point ("!") may be used to negate a match. This can
+ be used to selectively omit certain newsgroups from an otherwise
+ larger list. For example, a newsgroups specification of
+ "net.*,mod.*,!mod.map.*" would specify that all net.<anything> and
+ all mod.<anything> EXCEPT mod.map.<anything> newsgroup names would be
+ matched. If used, the exclamation point must appear as the first
+ character of the given newsgroup name or pattern.
+
+ The optional parameter "distributions" is a list of distribution
+ groups, enclosed in angle brackets. If specified, the distribution
+ portion of an article's newsgroup (e.g, 'net' in 'net.wombat') will
+ be examined for a match with the distribution categories listed, and
+ only those articles which have at least one newsgroup belonging to
+
+
+Kantor & Lapsley [Page 16]
+\f
+
+
+RFC 977 February 1986
+Network News Transfer Protocol
+
+
+ the list of distributions will be listed. If more than one
+ distribution group is to be supplied, they must be separated by
+ commas within the angle brackets.
+
+ The use of the IHAVE, NEWNEWS, and NEWGROUPS commands to distribute
+ news is discussed in an earlier part of this document.
+
+ Please note that an empty list (i.e., the text body returned by this
+ command consists only of the terminating period) is a possible valid
+ response, and indicates that there is currently no new news.
+
+3.8.2. Responses
+
+ 230 list of new articles by message-id follows
+
+3.9. The NEXT command
+
+3.9.1. NEXT
+
+ NEXT
+
+ The internally maintained "current article pointer" is advanced to
+ the next article in the current newsgroup. If no more articles
+ remain in the current group, an error message is returned and the
+ current article remains selected.
+
+ The internally-maintained "current article pointer" is set by this
+ command.
+
+ A response indicating the current article number, and the message-id
+ string will be returned. No text is sent in response to this
+ command.
+
+3.9.2. Responses
+
+ 223 n a article retrieved - request text separately
+ (n = article number, a = unique article id)
+ 412 no newsgroup selected
+ 420 no current article has been selected
+ 421 no next article in this group
+
+
+
+
+
+
+
+
+
+Kantor & Lapsley [Page 17]
+\f
+
+
+RFC 977 February 1986
+Network News Transfer Protocol
+
+
+3.10. The POST command
+
+3.10.1. POST
+
+ POST
+
+ If posting is allowed, response code 340 is returned to indicate that
+ the article to be posted should be sent. Response code 440 indicates
+ that posting is prohibited for some installation-dependent reason.
+
+ If posting is permitted, the article should be presented in the
+ format specified by RFC850, and should include all required header
+ lines. After the article's header and body have been completely sent
+ by the client to the server, a further response code will be returned
+ to indicate success or failure of the posting attempt.
+
+ The text forming the header and body of the message to be posted
+ should be sent by the client using the conventions for text received
+ from the news server: A single period (".") on a line indicates the
+ end of the text, with lines starting with a period in the original
+ text having that period doubled during transmission.
+
+ No attempt shall be made by the server to filter characters, fold or
+ limit lines, or otherwise process incoming text. It is our intent
+ that the server just pass the incoming message to be posted to the
+ server installation's news posting software, which is separate from
+ this specification. See RFC850 for more details.
+
+ Since most installations will want the client news program to allow
+ the user to prepare his message using some sort of text editor, and
+ transmit it to the server for posting only after it is composed, the
+ client program should take note of the herald message that greeted it
+ when the connection was first established. This message indicates
+ whether postings from that client are permitted or not, and can be
+ used to caution the user that his access is read-only if that is the
+ case. This will prevent the user from wasting a good deal of time
+ composing a message only to find posting of the message was denied.
+ The method and determination of which clients and hosts may post is
+ installation dependent and is not covered by this specification.
+
+3.10.2. Responses
+
+ 240 article posted ok
+ 340 send article to be posted. End with <CR-LF>.<CR-LF>
+ 440 posting not allowed
+ 441 posting failed
+
+
+
+Kantor & Lapsley [Page 18]
+\f
+
+
+RFC 977 February 1986
+Network News Transfer Protocol
+
+
+ (for reference, one of the following codes will be sent upon initial
+ connection; the client program should determine whether posting is
+ generally permitted from these:) 200 server ready - posting allowed
+ 201 server ready - no posting allowed
+
+3.11. The QUIT command
+
+3.11.1. QUIT
+
+ QUIT
+
+ The server process acknowledges the QUIT command and then closes the
+ connection to the client. This is the preferred method for a client
+ to indicate that it has finished all its transactions with the NNTP
+ server.
+
+ If a client simply disconnects (or the connection times out, or some
+ other fault occurs), the server should gracefully cease its attempts
+ to service the client.
+
+3.11.2. Responses
+
+ 205 closing connection - goodbye!
+
+3.12. The SLAVE command
+
+3.12.1. SLAVE
+
+ SLAVE
+
+ Indicates to the server that this client connection is to a slave
+ server, rather than a user.
+
+ This command is intended for use in separating connections to single
+ users from those to subsidiary ("slave") servers. It may be used to
+ indicate that priority should therefore be given to requests from
+ this client, as it is presumably serving more than one person. It
+ might also be used to determine which connections to close when
+ system load levels are exceeded, perhaps giving preference to slave
+ servers. The actual use this command is put to is entirely
+ implementation dependent, and may vary from one host to another. In
+ NNTP servers which do not give priority to slave servers, this
+ command must nonetheless be recognized and acknowledged.
+
+3.12.2. Responses
+
+ 202 slave status noted
+
+
+Kantor & Lapsley [Page 19]
+\f
+
+
+RFC 977 February 1986
+Network News Transfer Protocol
+
+
+4. Sample Conversations
+
+ These are samples of the conversations that might be expected with
+ the news server in hypothetical sessions. The notation C: indicates
+ commands sent to the news server from the client program; S: indicate
+ responses received from the server by the client.
+
+4.1. Example 1 - relative access with NEXT
+
+ S: (listens at TCP port 119)
+
+ C: (requests connection on TCP port 119)
+ S: 200 wombatvax news server ready - posting ok
+
+ (client asks for a current newsgroup list)
+ C: LIST
+ S: 215 list of newsgroups follows
+ S: net.wombats 00543 00501 y
+ S: net.unix-wizards 10125 10011 y
+ (more information here)
+ S: net.idiots 00100 00001 n
+ S: .
+
+ (client selects a newsgroup)
+ C: GROUP net.unix-wizards
+ S: 211 104 10011 10125 net.unix-wizards group selected
+ (there are 104 articles on file, from 10011 to 10125)
+
+ (client selects an article to read)
+ C: STAT 10110
+ S: 223 10110 <23445@sdcsvax.ARPA> article retrieved - statistics
+ only (article 10110 selected, its message-id is
+ <23445@sdcsvax.ARPA>)
+
+ (client examines the header)
+ C: HEAD
+ S: 221 10110 <23445@sdcsvax.ARPA> article retrieved - head
+ follows (text of the header appears here)
+ S: .
+
+ (client wants to see the text body of the article)
+ C: BODY
+ S: 222 10110 <23445@sdcsvax.ARPA> article retrieved - body
+ follows (body text here)
+ S: .
+
+ (client selects next article in group)
+
+
+Kantor & Lapsley [Page 20]
+\f
+
+
+RFC 977 February 1986
+Network News Transfer Protocol
+
+
+ C: NEXT
+ S: 223 10113 <21495@nudebch.uucp> article retrieved - statistics
+ only (article 10113 was next in group)
+
+ (client finishes session)
+ C: QUIT
+ S: 205 goodbye.
+
+4.2. Example 2 - absolute article access with ARTICLE
+
+ S: (listens at TCP port 119)
+
+ C: (requests connection on TCP port 119)
+ S: 201 UCB-VAX netnews server ready -- no posting allowed
+
+ C: GROUP msgs
+ S: 211 103 402 504 msgs Your new group is msgs
+ (there are 103 articles, from 402 to 504)
+
+ C: ARTICLE 401
+ S: 423 No such article in this newsgroup
+
+ C: ARTICLE 402
+ S: 220 402 <4105@ucbvax.ARPA> Article retrieved, text follows
+ S: (article header and body follow)
+ S: .
+
+ C: HEAD 403
+ S: 221 403 <3108@mcvax.UUCP> Article retrieved, header follows
+ S: (article header follows)
+ S: .
+
+ C: QUIT
+ S: 205 UCB-VAX news server closing connection. Goodbye.
+
+4.3. Example 3 - NEWGROUPS command
+
+ S: (listens at TCP port 119)
+
+ C: (requests connection on TCP port 119)
+ S: 200 Imaginary Institute News Server ready (posting ok)
+
+ (client asks for new newsgroups since April 3, 1985)
+ C: NEWGROUPS 850403 020000
+
+ S: 231 New newsgroups since 03/04/85 02:00:00 follow
+
+
+
+Kantor & Lapsley [Page 21]
+\f
+
+
+RFC 977 February 1986
+Network News Transfer Protocol
+
+
+ S: net.music.gdead
+ S: net.games.sources
+ S: .
+
+ C: GROUP net.music.gdead
+ S: 211 0 1 1 net.music.gdead Newsgroup selected
+ (there are no articles in that newsgroup, and
+ the first and last article numbers should be ignored)
+
+ C: QUIT
+ S: 205 Imaginary Institute news server ceasing service. Bye!
+
+4.4. Example 4 - posting a news article
+
+ S: (listens at TCP port 119)
+
+ C: (requests connection on TCP port 119)
+ S: 200 BANZAIVAX news server ready, posting allowed.
+
+ C: POST
+ S: 340 Continue posting; Period on a line by itself to end
+ C: (transmits news article in RFC850 format)
+ C: .
+ S: 240 Article posted successfully.
+
+ C: QUIT
+ S: 205 BANZAIVAX closing connection. Goodbye.
+
+4.5. Example 5 - interruption due to operator request
+
+ S: (listens at TCP port 119)
+
+ C: (requests connection on TCP port 119)
+ S: 201 genericvax news server ready, no posting allowed.
+
+ (assume normal conversation for some time, and
+ that a newsgroup has been selected)
+
+ C: NEXT
+ S: 223 1013 <5734@mcvax.UUCP> Article retrieved; text separate.
+
+ C: HEAD
+ C: 221 1013 <5734@mcvax.UUCP> Article retrieved; head follows.
+
+ S: (sends head of article, but halfway through is
+ interrupted by an operator request. The following
+ then occurs, without client intervention.)
+
+
+Kantor & Lapsley [Page 22]
+\f
+
+
+RFC 977 February 1986
+Network News Transfer Protocol
+
+
+ S: (ends current line with a CR-LF pair)
+ S: .
+ S: 400 Connection closed by operator. Goodbye.
+ S: (closes connection)
+
+4.6. Example 6 - Using the news server to distribute news between
+ systems.
+
+ S: (listens at TCP port 119)
+
+ C: (requests connection on TCP port 119)
+ S: 201 Foobar NNTP server ready (no posting)
+
+ (client asks for new newsgroups since 2 am, May 15, 1985)
+ C: NEWGROUPS 850515 020000
+ S: 235 New newsgroups since 850515 follow
+ S: net.fluff
+ S: net.lint
+ S: .
+
+ (client asks for new news articles since 2 am, May 15, 1985)
+ C: NEWNEWS * 850515 020000
+ S: 230 New news since 850515 020000 follows
+ S: <1772@foo.UUCP>
+ S: <87623@baz.UUCP>
+ S: <17872@GOLD.CSNET>
+ S: .
+
+ (client asks for article <1772@foo.UUCP>)
+ C: ARTICLE <1772@foo.UUCP>
+ S: 220 <1772@foo.UUCP> All of article follows
+ S: (sends entire message)
+ S: .
+
+ (client asks for article <87623@baz.UUCP>
+ C: ARTICLE <87623@baz.UUCP>
+ S: 220 <87623@baz.UUCP> All of article follows
+ S: (sends entire message)
+ S: .
+
+ (client asks for article <17872@GOLD.CSNET>
+ C: ARTICLE <17872@GOLD.CSNET>
+ S: 220 <17872@GOLD.CSNET> All of article follows
+ S: (sends entire message)
+ S: .
+
+
+
+
+Kantor & Lapsley [Page 23]
+\f
+
+
+RFC 977 February 1986
+Network News Transfer Protocol
+
+
+ (client offers an article it has received recently)
+ C: IHAVE <4105@ucbvax.ARPA>
+ S: 435 Already seen that one, where you been?
+
+ (client offers another article)
+ C: IHAVE <4106@ucbvax.ARPA>
+ S: 335 News to me! <CRLF.CRLF> to end.
+ C: (sends article)
+ C: .
+ S: 235 Article transferred successfully. Thanks.
+
+ (or)
+
+ S: 436 Transfer failed.
+
+ (client is all through with the session)
+ C: QUIT
+ S: 205 Foobar NNTP server bids you farewell.
+
+4.7. Summary of commands and responses.
+
+ The following are the commands recognized and responses returned by
+ the NNTP server.
+
+4.7.1. Commands
+
+ ARTICLE
+ BODY
+ GROUP
+ HEAD
+ HELP
+ IHAVE
+ LAST
+ LIST
+ NEWGROUPS
+ NEWNEWS
+ NEXT
+ POST
+ QUIT
+ SLAVE
+ STAT
+
+4.7.2. Responses
+
+ 100 help text follows
+ 199 debug output
+
+
+
+Kantor & Lapsley [Page 24]
+\f
+
+
+RFC 977 February 1986
+Network News Transfer Protocol
+
+
+ 200 server ready - posting allowed
+ 201 server ready - no posting allowed
+ 202 slave status noted
+ 205 closing connection - goodbye!
+ 211 n f l s group selected
+ 215 list of newsgroups follows
+ 220 n <a> article retrieved - head and body follow 221 n <a> article
+ retrieved - head follows
+ 222 n <a> article retrieved - body follows
+ 223 n <a> article retrieved - request text separately 230 list of new
+ articles by message-id follows
+ 231 list of new newsgroups follows
+ 235 article transferred ok
+ 240 article posted ok
+
+ 335 send article to be transferred. End with <CR-LF>.<CR-LF>
+ 340 send article to be posted. End with <CR-LF>.<CR-LF>
+
+ 400 service discontinued
+ 411 no such news group
+ 412 no newsgroup has been selected
+ 420 no current article has been selected
+ 421 no next article in this group
+ 422 no previous article in this group
+ 423 no such article number in this group
+ 430 no such article found
+ 435 article not wanted - do not send it
+ 436 transfer failed - try again later
+ 437 article rejected - do not try again.
+ 440 posting not allowed
+ 441 posting failed
+
+ 500 command not recognized
+ 501 command syntax error
+ 502 access restriction or permission denied
+ 503 program fault - command not performed
+
+4.8. A Brief Word about the USENET News System
+
+ In the UNIX world, which traditionally has been linked by 1200 baud
+ dial-up telephone lines, the USENET News system has evolved to handle
+ central storage, indexing, retrieval, and distribution of news. With
+ the exception of its underlying transport mechanism (UUCP), USENET
+ News is an efficient means of providing news and bulletin service to
+ subscribers on UNIX and other hosts worldwide. The USENET News
+
+
+
+
+Kantor & Lapsley [Page 25]
+\f
+
+
+RFC 977 February 1986
+Network News Transfer Protocol
+
+
+ system is discussed in detail in RFC 850. It runs on most versions
+ of UNIX and on many other operating systems, and is customarily
+ distributed without charge.
+
+ USENET uses a spooling area on the UNIX host to store news articles,
+ one per file. Each article consists of a series of heading text,
+ which contain the sender's identification and organizational
+ affiliation, timestamps, electronic mail reply paths, subject,
+ newsgroup (subject category), and the like. A complete news article
+ is reproduced in its entirety below. Please consult RFC 850 for more
+ details.
+
+ Relay-Version: version B 2.10.3 4.3bsd-beta 6/6/85; site
+ sdcsvax.UUCP
+ Posting-Version: version B 2.10.1 6/24/83 SMI; site unitek.uucp
+ Path:sdcsvax!sdcrdcf!hplabs!qantel!ihnp4!alberta!ubc-vision!unitek
+ !honman
+ From: honman@unitek.uucp (Man Wong)
+ Newsgroups: net.unix-wizards
+ Subject: foreground -> background ?
+ Message-ID: <167@unitek.uucp>
+ Date: 25 Sep 85 23:51:52 GMT
+ Date-Received: 29 Sep 85 09:54:48 GMT
+ Reply-To: honman@unitek.UUCP (Hon-Man Wong)
+ Distribution: net.all
+ Organization: Unitek Technologies Corporation
+ Lines: 12
+
+ I have a process (C program) which generates a child and waits for
+ it to return. What I would like to do is to be able to run the
+ child process interactively for a while before kicking itself into
+ the background so I can return to the parent process (while the
+ child process is RUNNING in the background). Can it be done? And
+ if it can, how?
+
+ Please reply by E-mail. Thanks in advance.
+
+ Hon-Man Wong
+
+
+
+
+
+
+
+
+
+
+
+Kantor & Lapsley [Page 26]
+\f
+
+
+RFC 977 February 1986
+Network News Transfer Protocol
+
+
+5. References
+
+ [1] Crocker, D., "Standard for the Format of ARPA Internet Text
+ Messages", RFC-822, Department of Electrical Engineering,
+ University of Delaware, August, 1982.
+
+ [2] Horton, M., "Standard for Interchange of USENET Messages",
+ RFC-850, USENET Project, June, 1983.
+
+ [3] Postel, J., "Transmission Control Protocol- DARPA Internet
+ Program Protocol Specification", RFC-793, USC/Information
+ Sciences Institute, September, 1981.
+
+ [4] Postel, J., "Simple Mail Transfer Protocol", RFC-821,
+ USC/Information Sciences Institute, August, 1982.
+
+6. Acknowledgements
+
+ The authors wish to express their heartfelt thanks to those many
+ people who contributed to this specification, and especially to Erik
+ Fair and Chuq von Rospach, without whose inspiration this whole thing
+ would not have been necessary.
+
+7. Notes
+
+ <1> UNIX is a trademark of Bell Laboratories.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Kantor & Lapsley [Page 27]
+\f
--- /dev/null
+/**/
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "md5.h"
+#define MAX_SECRET 100
+
+static void ohshit(const char *p) {
+ fprintf(stderr,"md5cookie1way: fatal error: %s\n",p); exit(1);
+}
+
+static void ohshite(const char *p) {
+ fprintf(stderr,"md5cookie1way: fatal system error: %s: %s\n",p,strerror(errno));
+ exit(1);
+}
+
+static void try(FILE **fp, const char *fn) {
+ if (*fp) return;
+ *fp= fopen(fn,"r");
+}
+
+static int parsehex(char *p, unsigned char *up, int maxlen) {
+ int nused, v, n;
+
+ nused= 0;
+ while (nused < maxlen && (n=-1, sscanf(p,"%x%n",&v,&n) >0) && n>0) {
+ p+= n;
+ *up++= v;
+ nused++;
+ if (*p == ':') p++;
+ }
+ return nused;
+}
+
+int main(int argc, char **argv) {
+ char *authfds, *claim, *here, *p;
+ int rfd, wfd, n, nused;
+ char buf[200];
+ unsigned char message[16+MAX_SECRET], reply[16];
+ struct MD5Context md5ctx;
+ FILE *file;
+
+ if (argc != 2) ohshit("bad args");
+ claim= argv[1];
+
+ authfds= getenv("NNTP_AUTH_FDS"); if (!authfds) ohshit("no NNTP_AUTH_FDS");
+ p= strchr(authfds,'.'); if (!p) ohshit("no . in NNTP_AUTH_FDS");
+ rfd= atoi(authfds);
+ wfd= atoi(p+1);
+ dup2(rfd,0); dup2(wfd,1);
+
+ file= 0;
+ if ((p= getenv("HOME"))) {
+ sprintf(buf,"%.180s/News/md5cookies",p); try(&file,buf);
+ sprintf(buf,"%.180s/.newscookies",p); try(&file,buf);
+ }
+ try(&file,"/etc/news/md5cookies.read");
+ try(&file,"/etc/news/md5cookies");
+ if (!file) ohshite("no cookies file");
+
+ for (;;) {
+ errno= 0; if (!(fgets(buf,sizeof(buf),file))) ohshite("no line for claim");
+ if (!*buf || *buf == '\n' || *buf == '#' || *buf == '@') continue;
+ here= strtok(buf," \t\n");
+ if (!here) continue;
+ if (!strcmp(here,claim)) break;
+ }
+
+ p= strtok(0," \t\n");
+ if (!p) ohshit("no cookie on line");
+
+ nused= parsehex(p,message+16,MAX_SECRET);
+
+ errno= 0; if (!fgets(buf,sizeof(buf),stdin)) ohshite("no comm with server");
+ if (strncmp(buf,"100 ",4)) {
+ fprintf(stderr,"\nunable to authenticate - server sent:\n %s",buf);
+ exit(1);
+ }
+
+ if (parsehex(buf+4,message,17) != 16) ohshit("server sent wrong amount of hex");
+
+ MD5Init(&md5ctx);
+ MD5Update(&md5ctx,message,16+nused);
+ MD5Final(reply,&md5ctx);
+
+ printf("MD5 ");
+ for (n=0; n<16; n++) {
+ printf("%s%02x", n?":":"", reply[n]);
+ }
+ printf("\r\n");
+ fflush(stdout);
+
+ errno= 0; if (!fgets(buf,sizeof(buf),stdin)) ohshite("no reply from server");
+ if (strncmp(buf,"281",3)) ohshit("server didn't send 281");
+
+ exit(0);
+}