From: Ian Jackson Date: Tue, 1 Jun 2010 13:22:13 +0000 (+0100) Subject: Initial checkin as found. X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ijackson/git?a=commitdiff_plain;h=55f0e5df04720ada605edb8755a49251a0c56de0;p=nntp-merge-chiark.git Initial checkin as found. --- 55f0e5df04720ada605edb8755a49251a0c56de0 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); +}