From 55f0e5df04720ada605edb8755a49251a0c56de0 Mon Sep 17 00:00:00 2001 From: Ian Jackson Date: Tue, 1 Jun 2010 14:22:13 +0100 Subject: [PATCH] Initial checkin as found. --- .gitignore | 6 + GPL | 339 +++++++++++ Makefile | 23 + README | 38 ++ config.c | 360 +++++++++++ main.c | 1430 +++++++++++++++++++++++++++++++++++++++++++ md5.c | 236 ++++++++ md5.h | 27 + nnrpwrap.c | 139 +++++ nntp-merge-serv | 3 + nntp-merge.conf | 151 +++++ nntp-merge.h | 71 +++ nyxpost | 45 ++ nyxpostrun.c | 10 + post.c | 294 +++++++++ post.c.orig | 288 +++++++++ rfc977.txt | 1539 +++++++++++++++++++++++++++++++++++++++++++++++ sharedsecret.c | 101 ++++ 18 files changed, 5100 insertions(+) create mode 100644 .gitignore create mode 100644 GPL create mode 100644 Makefile create mode 100644 README create mode 100644 config.c create mode 100644 main.c create mode 100644 md5.c create mode 100644 md5.h create mode 100644 nnrpwrap.c create mode 100755 nntp-merge-serv create mode 100644 nntp-merge.conf create mode 100644 nntp-merge.h create mode 100755 nyxpost create mode 100644 nyxpostrun.c create mode 100644 post.c create mode 100644 post.c.orig create mode 100644 rfc977.txt create mode 100644 sharedsecret.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..92ff319 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*~ +*.o +md5cookie1way +nnrpwrap +nntp-merge +nyxpostrun diff --git a/GPL b/GPL new file mode 100644 index 0000000..a43ea21 --- /dev/null +++ b/GPL @@ -0,0 +1,339 @@ + 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. + + 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.) + +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. + + 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. + + 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 + + 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. + + + Copyright (C) 19yy + + 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. + + , 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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..644accb --- /dev/null +++ b/Makefile @@ -0,0 +1,23 @@ +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 diff --git a/README b/README new file mode 100644 index 0000000..97866ad --- /dev/null +++ b/README @@ -0,0 +1,38 @@ +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 (), or possibly +at . + +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 + 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 . + + - Ian Jackson 16.09.1997 diff --git a/config.c b/config.c new file mode 100644 index 0000000..87128c9 --- /dev/null +++ b/config.c @@ -0,0 +1,360 @@ +/**/ + +/* 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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]; +} diff --git a/main.c b/main.c new file mode 100644 index 0000000..cd1314e --- /dev/null +++ b/main.c @@ -0,0 +1,1430 @@ +/**/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 : "", + gi->readonlyto && gi->readonlyto->name ? gi->readonlyto->name : ""); +} + +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 .\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"); + } + } +} diff --git a/md5.c b/md5.c new file mode 100644 index 0000000..46cdbe8 --- /dev/null +++ b/md5.c @@ -0,0 +1,236 @@ +/* + * 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 /* 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<>(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 diff --git a/md5.h b/md5.h new file mode 100644 index 0000000..245ecc9 --- /dev/null +++ b/md5.h @@ -0,0 +1,27 @@ +/* + * 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 */ diff --git a/nnrpwrap.c b/nnrpwrap.c new file mode 100644 index 0000000..8b144bf --- /dev/null +++ b/nnrpwrap.c @@ -0,0 +1,139 @@ +/**/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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); +} diff --git a/nntp-merge-serv b/nntp-merge-serv new file mode 100755 index 0000000..7406a1c --- /dev/null +++ b/nntp-merge-serv @@ -0,0 +1,3 @@ +#!/bin/sh +cd /u/iwj10/nntp-merge +exec ./nntp-merge chiark.chu.cam.ac.uk 2>>err-log diff --git a/nntp-merge.conf b/nntp-merge.conf new file mode 100644 index 0000000..139bf93 --- /dev/null +++ b/nntp-merge.conf @@ -0,0 +1,151 @@ +# Consists of +# command +# group/pattern +# group/pattern +# ... + +# Commands are: + +# myfqdn +# xref +# server + +# permit +# *=all, -=none +# fetch ... +# read +# post [] +# missing server-name means posting not allowed +# minreaddays +# extrarc + +# when doing ARTICLE &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 + * diff --git a/nntp-merge.h b/nntp-merge.h new file mode 100644 index 0000000..b9a4737 --- /dev/null +++ b/nntp-merge.h @@ -0,0 +1,71 @@ +/**/ + +#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 */ diff --git a/nyxpost b/nyxpost new file mode 100755 index 0000000..cb06569 --- /dev/null +++ b/nyxpost @@ -0,0 +1,45 @@ +#!/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 $/; $_=

; $/= "\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"; + } +} diff --git a/nyxpostrun.c b/nyxpostrun.c new file mode 100644 index 0000000..b7e0c31 --- /dev/null +++ b/nyxpostrun.c @@ -0,0 +1,10 @@ +#include +#include +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); +} diff --git a/post.c b/post.c new file mode 100644 index 0000000..986fb60 --- /dev/null +++ b/post.c @@ -0,0 +1,294 @@ +/**/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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: @ ([]) + * Originator: @[] + * Originator: @ ([]) + * Originator: @[addr] + * , 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; ihostname,response); + } else { + printf("; %s ok",postto[i]->hostname); + } + fflush(stdout); + } + printf(" - posted.\r\n"); + fclose(file); + return; +} diff --git a/post.c.orig b/post.c.orig new file mode 100644 index 0000000..af2a7ee --- /dev/null +++ b/post.c.orig @@ -0,0 +1,288 @@ +/**/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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: @ ([]) + * Originator: @[] + * Originator: @ ([]) + * Originator: @[addr] + * , 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; ihostname,response); + } else { + printf("; %s ok",postto[i]->hostname); + } + fflush(stdout); + } + printf(" - posted.\r\n"); + fclose(file); + return; +} diff --git a/rfc977.txt b/rfc977.txt new file mode 100644 index 0000000..046a270 --- /dev/null +++ b/rfc977.txt @@ -0,0 +1,1539 @@ + + +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] + + + +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] + + + +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] + + + +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] + + + +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] + + + +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] + + + +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] + + + +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] + + + +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 + + 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] + + + +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 article retrieved - head and body follow + (n = article number, = message-id) + 221 n article retrieved - head follows + 222 n article retrieved - body follows + 223 n 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] + + + +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] + + + +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 + + The IHAVE command informs the server that the client has an article + whose id is . 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] + + + +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 . + 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] + + + +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 is the name of the newsgroup, is the number of + the last known article currently in that newsgroup, is the + number of the first article currently in the newsgroup, and

is + either 'y' or 'n' indicating whether posting to this newsgroup is + allowed ('y') or prohibited ('n'). + + The and fields will always be numeric. They may have + leading zeros. If the field evaluates to less than the + 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] + + + +RFC 977 February 1986 +Network News Transfer Protocol + + +3.7. The NEWGROUPS command + +3.7.1. NEWGROUPS + + NEWGROUPS date time [GMT] [] + + A list of newsgroups created since 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] + + + +RFC 977 February 1986 +Network News Transfer Protocol + + +3.8. The NEWNEWS command + +3.8.1. NEWNEWS + + NEWNEWS newsgroups date time [GMT] [] + + 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. and + all mod. EXCEPT mod.map. 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] + + + +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] + + + +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 . + 440 posting not allowed + 441 posting failed + + + +Kantor & Lapsley [Page 18] + + + +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] + + + +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] + + + +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] + + + +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] + + + +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] + + + +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! 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] + + + +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 article retrieved - head and body follow 221 n article + retrieved - head follows + 222 n article retrieved - body follows + 223 n 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 . + 340 send article to be posted. End with . + + 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] + + + +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] + + + +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] + diff --git a/sharedsecret.c b/sharedsecret.c new file mode 100644 index 0000000..b264749 --- /dev/null +++ b/sharedsecret.c @@ -0,0 +1,101 @@ +/**/ + +#include +#include +#include +#include +#include + +#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); +} -- 2.30.2