+/**/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <assert.h>
+#include <time.h>
+#include <errno.h>
+#include <fnmatch.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include "nntp-merge.h"
+#include "md5.h"
+
+char *myfqdn, *myxref, *lastdoneauth= 0;
+const char *theirfqdn= 0;
+char currentgroupname[MAX_COMMAND+3];
+struct groupinfo *currentgroup;
+struct sockaddr_in peername;
+
+int stripcommand(char *buf) {
+ int n;
+
+ n= strlen(buf);
+ if (n == 0) return 0;
+ if (buf[n-1] != '\n') return 0;
+ while (n>0 && isspace(buf[n-1])) n--;
+ buf[n]= 0;
+ return n;
+}
+
+void die(const char *msg) {
+ int e;
+ e= errno;
+ printf("400 server failure: %s - %s\r\n",msg,strerror(errno));
+ exit(0);
+}
+
+static int parsehex(char *p, unsigned char *up, int maxlen) {
+ int nused, v, n;
+
+ nused= 0;
+ while (nused < MAX_SECRET && (n=-1, sscanf(p,"%x%n",&v,&n) >0) && n>0) {
+ p+= n;
+ *up++= v;
+ nused++;
+ if (*p == ':') p++;
+ }
+ return nused;
+}
+
+enum { scf_norecover=001, scf_nogroup };
+
+void closeserver(struct serverinfo *server) {
+ fclose(server->rfile); server->rfile= 0;
+ fclose(server->wfile); server->wfile= 0;
+}
+
+int decoderesponse(char response[MAX_RESPONSE+3], unsigned long *rvp,
+ struct serverinfo *server) {
+ char temp[MAX_RESPONSE+3];
+ if (!isdigit(response[0]) || !isdigit(response[1]) || !isdigit(response[2]) ||
+ !(isspace(response[3]) || !response[3])) {
+ strcpy(temp,response);
+ sprintf(response,"503 server %.100s produced garbage: %.100s",
+ server->hostname,temp);
+ closeserver(server); return 0;
+ }
+ *rvp= strtol(response,0,16);
+ return 1;
+}
+
+#ifdef DEBUG
+static int realservercommand(struct serverinfo *server,
+ const char command[], char response[MAX_RESPONSE+3],
+ int flags);
+#endif
+int servercommand(struct serverinfo *server,
+ const char command[], char response[MAX_RESPONSE+3],
+ int flags) {
+#ifdef DEBUG
+ int rcode;
+ fprintf(stderr,"[%03o]>>>%s: %s\n",flags,server->nickname,command);
+ rcode= realservercommand(server,command,response,flags);
+ fprintf(stderr,"[%03x]<<<%s: %s\n",rcode,server->nickname,response);
+ return rcode;
+}
+static int realservercommand(struct serverinfo *server,
+ const char command[], char response[MAX_RESPONSE+3],
+ int flags) {
+#endif
+ struct hostent *he;
+ char **alist;
+ struct sockaddr_in sin;
+ pid_t c1;
+ int rpipefds[2], wpipefds[2], rfd, wfd, l, n;
+ unsigned long rv;
+ char temp[MAX_RESPONSE+3], *p;
+ unsigned char message[16+MAX_SECRET], cryptresponse[16];
+ struct MD5Context md5ctx;
+ FILE *file;
+
+ /* response will be null-terminated and have trailing whitespace
+ * removed including \r\n
+ * (but indeed the extra two spaces used by this routine are for \r\n)
+ */
+ if (server->rfile) {
+ if (fprintf(server->wfile,"%s\r\n",command) == EOF ||
+ fflush(server->wfile) == EOF) goto conn_broken;
+ if (!fgets(response,MAX_RESPONSE+3,server->rfile)) goto conn_broken;
+ if (!stripcommand(response)) goto conn_broken;
+ if (!strncmp(response,"400",3)) goto conn_broken;
+ goto message_sent_response_ok;
+ conn_broken:
+ closeserver(server);
+ if (flags & scf_norecover) {
+ strcpy(response,"205 it had already gone"); return 0x205;
+ }
+ }
+ if (server->program) {
+ if (pipe(rpipefds) || pipe(wpipefds)) die("unable to create pipe");
+ if ((c1= fork()) == -1) die("unable to fork");
+ if (!c1) {
+ close(0); close(1); dup(wpipefds[0]); dup(rpipefds[1]);
+ close(wpipefds[0]); close(wpipefds[1]);
+ close(rpipefds[0]); close(rpipefds[1]);
+ signal(SIGPIPE,SIG_DFL);
+ if (lastdoneauth) setenv("NNTPMERGE_AUTHD_AS",lastdoneauth,1);
+ execl(server->hostname,server->hostname,(char*)0);
+ printf("400 exec %s: %s\r\n",server->hostname,strerror(errno));
+ exit(1);
+ }
+ rfd= rpipefds[0]; close(rpipefds[1]);
+ wfd= wpipefds[1]; close(wpipefds[0]);
+ } else {
+ he= gethostbyname(server->hostname);
+ if (!he) {
+ sprintf(response,"503 unable to find address of %.100s.",server->hostname);
+ return 0x503;
+ }
+ if (he->h_addrtype != AF_INET) {
+ sprintf(response,"503 address of %.100s is of unknown type 0x%x.",
+ server->hostname,he->h_addrtype);
+ return 0x503;
+ }
+ for (alist= he->h_addr_list; *alist; alist++) {
+ rfd= socket(PF_INET,SOCK_STREAM,0);
+ if (rfd == -1) die("unable to create TCP socket");
+ memset(&sin,0,sizeof(sin));
+ sin.sin_family= AF_INET;
+ sin.sin_addr= *(struct in_addr*)*alist;
+ sin.sin_port= htons(server->port);
+ if (!connect(rfd,(struct sockaddr*)&sin,sizeof(sin))) break;
+ sprintf(response,"503 unable to connect to %.100s (%.50s:%u): %.100s.",
+ server->hostname, inet_ntoa(sin.sin_addr), server->port, strerror(errno));
+ close(rfd);
+ }
+ if (!*alist) return 0x503;
+ wfd= dup(rfd);
+ if (wfd < 0) die("failed to dup socket fd");
+ }
+ server->rfile= fdopen(rfd,"r");
+ server->wfile= fdopen(wfd,"w");
+ if (!server->rfile || !server->wfile) die("failed to fdopen");
+ if (setvbuf(server->rfile,0,_IOLBF,0) || setvbuf(server->wfile,0,_IOLBF,0))
+ die("failed to setvbuf linebuf");
+ if (!fgets(response,MAX_RESPONSE+3,server->rfile)) {
+ sprintf(response,"503 failed to read from server %.100s: %.100s.",
+ server->hostname,strerror(errno));
+ }
+ if (!stripcommand(response)) {
+ sprintf(response,"503 server %.100s is spouting null garbage.",server->hostname);
+ closeserver(server); return 0x503;
+ }
+ if (!strncmp(response,"480",3) &&
+ response[3] == ' ' && (p= strchr(response+4,' ')) &&
+ ((*p=0), (parsehex(response+4,message,17) == 16)) &&
+ (file= fopen(SESAMEFILE,"r"))) {
+ do {
+ if (!fgets(temp,sizeof(temp),file)) {
+ if (ferror(file)) {
+ sprintf(response,"503 error reading sesame file: %.100s",strerror(errno));
+ } else {
+ sprintf(response,"503 no sesame for server nicknamed %.100s",server->nickname);
+ }
+ fclose(file); closeserver(server); return 0x503;
+ }
+ } while (!stripcommand(temp) || !*temp || *temp == '#' ||
+ !(p= strchr(temp,' ')) || ((*p++= 0), strcmp(temp,server->nickname)));
+ fclose(file); l= strlen(p);
+ if (l>MAX_SECRET) {
+ sprintf(response,"503 sesame file secret for %.100s is too long",server->nickname);
+ closeserver(server); return 0x503;
+ }
+ memcpy(message+16,p,l);
+
+ MD5Init(&md5ctx);
+ MD5Update(&md5ctx,message,16+l);
+ MD5Final(cryptresponse,&md5ctx);
+
+ fprintf(server->wfile,"PASS ");
+ for (n=0; n<16; n++) {
+ fprintf(server->wfile,"%s%02x", n?":":"", cryptresponse[n]);
+ }
+ fprintf(server->wfile,"\r\n");
+ if (fflush(server->wfile) == EOF || ferror(server->wfile)) {
+ sprintf(response,"503 error sending sesame response to %.100s: %.100s",
+ server->hostname,strerror(errno));
+ closeserver(server); return 0x503;
+ }
+ if (!fgets(response,MAX_RESPONSE+3,server->rfile)) {
+ sprintf(response,"503 error reading from %.100s after sesame: %.100s.",
+ server->hostname,strerror(errno));
+ closeserver(server); return 0x503;
+ }
+ }
+ if (!strncmp(response,"400",3)) goto server_is_unavailable;
+ if (response[0] != '2') {
+ strcpy(temp,response);
+ stripcommand(temp);
+ sprintf(response,"503 server %.100s refuses to talk: %.100s",server->hostname,temp);
+ closeserver(server); return 0x503;
+ }
+ if (server->send) {
+ if (fprintf(server->wfile,"%s\r\n",server->send) == EOF ||
+ fflush(server->wfile) == EOF) {
+ sprintf(response,"503 error sending %.50s to %.100s: %.100s",
+ server->send,server->hostname,strerror(errno));
+ closeserver(server); return 0x503;
+ }
+ if (!fgets(response,MAX_RESPONSE+3,server->rfile)) {
+ sprintf(response,"503 error reading from %.100s after %.50s: %.100s.",
+ server->hostname,server->send,strerror(errno));
+ closeserver(server); return 0x503;
+ }
+ }
+ if (!(flags & scf_nogroup) && currentgroup && currentgroup->readfrom == server) {
+ if (fprintf(server->wfile,"GROUP %s\r\n",currentgroupname) == EOF ||
+ fflush(server->wfile) == EOF) {
+ sprintf(response,"503 error sending GROUP to %.100s: %.100s",
+ server->hostname,strerror(errno));
+ closeserver(server); return 0x503;
+ }
+ if (!fgets(response,MAX_RESPONSE+3,server->rfile)) {
+ sprintf(response,"503 error reading from %.100s after GROUP: %.100s.",
+ server->hostname,strerror(errno));
+ closeserver(server); return 0x503;
+ }
+ }
+ if (fprintf(server->wfile,"%s\r\n",command) == EOF ||
+ fflush(server->wfile) == EOF) {
+ sprintf(response,"503 error sending to %.100s: %.100s",
+ server->hostname,strerror(errno));
+ closeserver(server); return 0x503;
+ }
+ if (!fgets(response,MAX_RESPONSE+3,server->rfile)) {
+ sprintf(response,"503 error reading from %.100s: %.100s.",server->hostname,
+ strerror(errno));
+ closeserver(server); return 0x503;
+ }
+ if (!stripcommand(response)) {
+ sprintf(response,"503 server %.100s is replying with null garbage.",
+ server->hostname);
+ closeserver(server); return 0x503;
+ }
+ message_sent_response_ok:
+ if (!decoderesponse(response,&rv,server)) return 0x503;
+ if (rv != 0x400) return rv;
+ server_is_unavailable:
+ strcpy(temp,response);
+ sprintf(response,"503 server %.100s unavailable: %.200s",
+ server->hostname,temp);
+ closeserver(server); return 0x503;
+}
+
+static int adjust(int original, int offset) {
+ if (offset < 0 && original < -offset) return 0;
+ return original+offset;
+}
+
+void serverdataerr(struct serverinfo *si) {
+ if (ferror(si->rfile)) {
+ printf("\r\n\r\n*** problem detected by nntp-merge:\r\n"
+ "error reading data from server %s: %s\r\n",
+ si->hostname, strerror(errno));
+ } else {
+ printf("\r\n\r\n*** problem detected by nntp-merge:\r\n"
+ "server %s closed connection while sending data\r\n",
+ si->hostname);
+ }
+ printf("closing connection on you, sorry.\r\n");
+ exit(0);
+}
+
+static int noargs(const char *arg) {
+ if (!*arg) return 1;
+ printf("501 no arguments please.\r\n");
+ return 0;
+}
+
+static void cmd_unimplemented(char *arg, const struct cmdinfo *cip) { \
+ printf("500 %s not implemented.\r\n",cip->command); \
+}
+
+static void cmd_quit(char *arg, const struct cmdinfo *cip) {
+ char responsebuf[MAX_RESPONSE+3];
+ struct serverinfo *si;
+
+ if (!noargs(arg)) return;
+ for (si= servers; si; si= si->next) {
+ if (!si->rfile) continue;
+ servercommand(si,"QUIT",responsebuf,scf_norecover);
+ fclose(si->wfile); fclose(si->rfile); /* we're not interested in the response */
+ }
+ printf("205 goodbye.\r\n");
+ exit(0);
+}
+
+static void cmd_date(char *arg, const struct cmdinfo *cip) {
+ char responsebuf[MAX_RESPONSE+3];
+ struct tm *brokendown;
+ char buf[100];
+ int n, rcode;
+ time_t now;
+ struct serverinfo *si;
+
+ if (!noargs(arg)) return;
+ time(&now);
+ brokendown= gmtime(&now); if (!brokendown) die("unable to break down date/time");
+ n= strftime(buf,sizeof(buf)-1,"%Y%m%d%H%M%S",brokendown);
+ if (n <= 0 || n >= sizeof(buf)-1 || strlen(buf) != 14)
+ die("unable to format date/time");
+ for (si= servers; si; si= si->next) {
+ if (!si->searchthis) continue;
+ rcode= servercommand(si,"DATE",responsebuf,0);
+ if (rcode == 0x111 && responsebuf[3]==' ' &&
+ strspn(responsebuf+4,"0123456789")==14 &&
+ responsebuf[18]==0 &&
+ strcmp(responsebuf+4,buf) < 0)
+ strcpy(buf,responsebuf+4);
+ }
+ printf("111 %s\r\n",buf);
+}
+
+static void cmd_noop(char *arg, const struct cmdinfo *cip) {
+ if (!noargs(arg)) return;
+ printf("200 doing that has no effect.\r\n");
+}
+
+static void cmd_slave(char *arg, const struct cmdinfo *cip) {
+ if (!noargs(arg)) return;
+ printf("202 bind me, whip me.\r\n");
+}
+
+static void cmd_ihave(char *arg, const struct cmdinfo *cip) {
+ printf("502 please don't feed me.\r\n");
+}
+
+static void cmd_newnews(char *arg, const struct cmdinfo *cip) {
+ printf("502 get a proper feed, slurping through this merger is silly.\r\n");
+}
+
+static void cmd_mode(char *arg, const struct cmdinfo *cip) {
+ if (!strcasecmp(arg,"READER")) {
+ printf("200 %s that was uncalled-for.\r\n",myfqdn);
+ } else {
+ printf("501 you want me to do *what* ?\r\n");
+ }
+}
+
+static void authrequired(void) {
+ if (lastdoneauth) {
+ printf("480 the sight of %s is but dim.\r\n",lastdoneauth);
+ } else {
+ printf("480 identify yourself ! friend or foe ?\r\n");
+ }
+}
+
+int stillrestricted(struct permission **pip) {
+ if (!*pip) return 0;
+ if (!(*pip)->authd) return 1;
+ *pip= 0; return 0;
+}
+
+static int groupreadable(struct groupinfo *gi) {
+ if (!stillrestricted(&gi->restrictto) ||
+ !stillrestricted(&gi->readonlyto)) return 1;
+ authrequired();
+ return 0;
+}
+
+static void cmd_group(char *arg, const struct cmdinfo *cip) {
+ struct groupinfo *gi;
+ int estcount, first, last, startfrom;
+ char commandbuf[MAX_COMMAND+3];
+ char responsebuf[MAX_RESPONSE+3];
+ int rcode;
+
+ if (strlen(arg) > MAX_COMMAND-12) {
+ printf("501 too long.\r\n");
+ return;
+ }
+ if (*arg) {
+ gi= findgroup(arg); if (!groupreadable(gi)) return;
+ } else {
+ printf("501 which group, then ?\r\n");
+ return;
+ }
+
+ sprintf(commandbuf,"%s %s",cip->command,arg);
+ rcode= servercommand(gi->readfrom,commandbuf,responsebuf,scf_nogroup);
+ if ((rcode & 0xf00) != 0x200) {
+ printf("%s\r\n",responsebuf);
+ return;
+ } else if (gi->offset) {
+ startfrom= -1;
+ if (sscanf(responsebuf,"211 %d %d %d %n", &estcount,&first,&last,&startfrom) != 3 ||
+ startfrom == -1) {
+ printf("503 %s said (after GROUP): %s\r\n",gi->readfrom->hostname,responsebuf);
+ closeserver(gi->readfrom); return;
+ }
+ printf("211 %d %d %d %s\r\n",
+ estcount, adjust(first,gi->offset), adjust(last,gi->offset),
+ responsebuf + startfrom);
+ } else {
+ printf("%s\r\n",responsebuf);
+ }
+ currentgroup= gi;
+ strcpy(currentgroupname,arg);
+}
+
+int copydatafile(FILE *from, FILE *to) {
+ /* `to' may be null */
+ int stopstate, c;
+
+ stopstate= 1; /* 0: middle of line; 1: start of line; 2: after `.' */
+ while ((c= getc(from)) != EOF) {
+ if (to) putc(c,to);
+ if (c == '\r') continue;
+ switch (stopstate) {
+ case 0:
+ if (c == '\n') stopstate= 1;
+ continue;
+ case 1:
+ stopstate= (c == '.') ? 2 : (c == '\n') ? 1 : 0;
+ continue;
+ case 2:
+ if (c == '\n') return 1;
+ stopstate= 0;
+ continue;
+ }
+ }
+ return 0;
+}
+
+static void copydata(struct serverinfo *from, FILE *to) {
+ if (!copydatafile(from->rfile,to)) serverdataerr(from);
+}
+
+static void cmd_listgroup(char *arg, const struct cmdinfo *cip) {
+ struct groupinfo *gi;
+ char commandbuf[MAX_COMMAND+3];
+ char responsebuf[MAX_RESPONSE+3];
+ long an;
+ char *p;
+ int rcode;
+
+ if (strlen(arg) > MAX_COMMAND-12) {
+ printf("501 too long.\r\n");
+ return;
+ }
+ if (*arg) {
+ gi= findgroup(arg); if (!groupreadable(gi)) return;
+ } else if (!currentgroup) {
+ printf("412 list of articles in which group ?\r\n");
+ return;
+ } else {
+ gi= currentgroup;
+ }
+
+ sprintf(commandbuf,"%s %s",cip->command,arg);
+ rcode= servercommand(gi->readfrom,commandbuf,responsebuf,scf_nogroup);
+ if ((rcode & 0xf00) != 0x200) {
+ printf("%s\r\n",responsebuf);
+ return;
+ }
+ if (rcode != 0x211) {
+ printf("503 %s said (after %s): %s\r\n",
+ gi->readfrom->hostname,cip->command,responsebuf);
+ return;
+ }
+ printf("211 article list:\r\n");
+ if (!gi->offset) {
+ copydata(gi->readfrom,stdout);
+ } else {
+ for (;;) {
+ if (!fgets(responsebuf,MAX_RESPONSE+3,gi->readfrom->rfile))
+ serverdataerr(gi->readfrom);
+ if (!strcmp(responsebuf,".\r\n") || !strcmp(responsebuf,".\n"))
+ break;
+ an= strtol(responsebuf,&p,10);
+ printf("%d%s",adjust(an,gi->offset),p);
+ }
+ printf(".\r\n");
+ }
+ currentgroup= gi;
+}
+
+static void pserver(struct serverinfo *si) {
+ printf(" %s= %s:%u %s (%s)\r\n",
+ si->nickname,
+ si->hostname, si->port,
+ si->rfile ? "open" : "closed",
+ si->send ? si->send : "-");
+}
+
+static void cmd_xmergeinfo(char *arg, const struct cmdinfo *cip) {
+ struct groupinfo *gi;
+ int i;
+
+ if (strlen(arg) > MAX_COMMAND-6) {
+ printf("501 too long.\r\n");
+ return;
+ }
+ gi= findgroup(arg);
+ printf("100 %s\r\n read:\r\n",arg);
+ pserver(gi->readfrom);
+ printf(" post:\r\n");
+ for (i= 0; gi->postto[i]; i++) pserver(gi->postto[i]);
+ printf(" offset %d\r\n"
+ " auth %s / read-auth %s\r\n"
+ ".\r\n",
+ gi->offset,
+ gi->restrictto && gi->restrictto->name ? gi->restrictto->name : "<none>",
+ gi->readonlyto && gi->readonlyto->name ? gi->readonlyto->name : "<none>");
+}
+
+static void cmd_help(char *arg, const struct cmdinfo *cip);
+
+static int articleselectresponse(char responsebuf[MAX_RESPONSE+3], char *typecharp,
+ struct serverinfo *si, const struct cmdinfo *cip,
+ char modifiedresponse[MAX_RESPONSE+3]) {
+ int startfrom, ran;
+
+ startfrom= -1;
+ if (sscanf(responsebuf,"22%c %d %n",typecharp,&ran,&startfrom) != 2 ||
+ startfrom == -1 || !strchr("0123",*typecharp)) {
+ printf("503 %s said (after %s): %s\r\n",
+ si->hostname, cip->command, responsebuf);
+ return 0;
+ }
+ ran= (currentgroup && si == currentgroup->readfrom)
+ ? adjust(ran,currentgroup->offset) : 0;
+ sprintf(modifiedresponse,"22%c %d %s",*typecharp,ran,responsebuf+startfrom);
+ return 1;
+}
+
+static void cmd_last_next(char *arg, const struct cmdinfo *cip) {
+ int rcode;
+ char responsebuf[MAX_RESPONSE+3];
+ char modresponse[MAX_RESPONSE+3];
+ char dummy;
+
+ if (!noargs(arg)) return;
+ if (!currentgroup) {
+ printf("412 use GROUP and ARTICLE first.\r\n");
+ return;
+ }
+ rcode= servercommand(currentgroup->readfrom,cip->command,responsebuf,0);
+ if ((rcode & 0xf00) != 0x200) {
+ printf("%s\r\n",responsebuf); return;
+ }
+ articleselectresponse(responsebuf,&dummy,currentgroup->readfrom,cip,modresponse);
+ printf("%s\r\n",modresponse);
+}
+
+static void processxref(char *linebufplus6, int n, FILE *writeto,
+ struct serverinfo *gotfrom) {
+ char *space, *cpos, *colon;
+ struct groupinfo *gi;
+ unsigned long an;
+
+ linebufplus6[n++]= ' ';
+ linebufplus6[n]= 0;
+ cpos= linebufplus6;
+ space= strchr(cpos,' ');
+ if (!space) return;
+ *space++= 0;
+ fprintf(writeto,"%s",myxref);
+ cpos= space;
+ while ((space= strchr(cpos,' '))) {
+ *space++= 0;
+ colon= strrchr(cpos,':');
+ if (colon) {
+ *colon++= 0;
+ gi= findgroup(cpos);
+ if (gi->readfrom != gotfrom) {
+ cpos= space; continue;
+ }
+ fprintf(writeto," %s:",cpos);
+ if (gi->offset && isdigit(*colon)) {
+ an= strtol(colon,&colon,10);
+ fprintf(writeto,"%d", adjust(an,gi->offset));
+ }
+ fputs(colon,writeto);
+ } else {
+ if (*cpos) putc(' ',writeto);
+ fputs(cpos,writeto);
+ }
+ cpos= space;
+ }
+}
+
+static void cmd_article(char *arg, const struct cmdinfo *cip) {
+ struct serverinfo *si;
+ struct groupinfo *gi;
+ char commandbuf[MAX_COMMAND+40+3];
+ char responsebuf[MAX_RESPONSE+3];
+ char modresponse[MAX_RESPONSE+3];
+ char linebuf[MAX_XREFLINE+3];
+ char typechar;
+ unsigned long an;
+ int rcode, n, c, realeinfo;
+ char *p;
+ const char *realcommand;
+ int checkpermission= 0;
+ FILE *file;
+
+ realcommand= cip->command;
+ if (*arg == '<') {
+ checkpermission= 1;
+ switch (cip->einfo) {
+ case 0: realcommand= "HEAD"; break;
+ case 2: realcommand= "ARTICLE"; break;
+ }
+ sprintf(commandbuf,"%s %s",realcommand,arg);
+ rcode= 0x423;
+ for (si= servers; si; si= si->next) {
+ if (!si->searchthis) continue;
+ rcode= servercommand(si,commandbuf,responsebuf,0);
+ if ((rcode & 0xf00) == 0x200) break;
+ }
+ } else if (isdigit(*arg) || !*arg) {
+ if (!currentgroup) {
+ printf("412 use GROUP first.\r\n");
+ return;
+ }
+ if (*arg) {
+ an= strtol(arg,&p,10);
+ if (*p) { printf("501 bad article number.\r\n"); return; }
+ if (an < currentgroup->offset)
+ printf("423 offset makes number negative, so no such article.\r\n");
+ sprintf(commandbuf,"%s %lu",cip->command,an - currentgroup->offset);
+ } else {
+ sprintf(commandbuf,"%s",cip->command);
+ }
+ si= currentgroup->readfrom;
+ rcode= servercommand(si,commandbuf,responsebuf,0);
+ } else {
+ printf("501 optional arg must be message-id or article number.\r\n");
+ return;
+ }
+ if ((rcode & 0xff0) != 0x220) {
+ printf("%s\r\n",responsebuf);
+ return;
+ }
+ switch (rcode & 0x00f) {
+ case 0: realeinfo= 03; break;
+ case 1: realeinfo= 01; break;
+ case 2: realeinfo= 02; break;
+ case 3: realeinfo= 00; break;
+ default: realeinfo= -1;
+ }
+ if (realeinfo == (cip->einfo | 01)) {
+ switch (cip->einfo) {
+ case 0: responsebuf[2]= '3'; break;
+ case 2: responsebuf[2]= '2'; break;
+ }
+ } else if (realeinfo != cip->einfo) {
+ printf("503 %s gave bad code (in response to %s): %s\r\n",
+ si->hostname,realcommand,responsebuf);
+ closeserver(si);
+ return;
+ }
+
+ if (!articleselectresponse(responsebuf,&typechar,si,cip,modresponse)) return;
+ if (checkpermission) {
+ file= tmpfile(); if (!file) die("failed to create temp file");
+ } else {
+ printf("%s\r\n",modresponse);
+ if (!realeinfo) return;
+ file= stdout;
+ }
+ if (realeinfo & 01) {
+ for (;;) {
+ if (!fgets(linebuf,MAX_XREFLINE+3,si->rfile)) serverdataerr(si);
+ n= strlen(linebuf);
+ if (n==0) continue;
+ while (n>0 && ((c= linebuf[n-1]) == '\n' || c == '\r')) n--;
+ linebuf[n]= 0;
+ if (!strcmp(linebuf,".")) { fputs(".\r\n",file); break; }
+ if ((cip->einfo & 01) && !strncasecmp(linebuf,"Xref: ",6)) {
+ fprintf(file,"Xref: ");
+ processxref(linebuf+6,n-6,file,si);
+ fputs("\r\n",file);
+ continue;
+ }
+ if (cip->einfo & 01) fprintf(file,"%s\r\n",linebuf);
+ if (checkpermission && !strncasecmp(linebuf,"Newsgroups: ",12)) {
+ p= strtok(linebuf," "); assert(p);
+ while ((p= strtok(0,","))) {
+ gi= findgroup(p);
+ if (!stillrestricted(&gi->restrictto) ||
+ !stillrestricted(&gi->readonlyto)) break;
+ }
+ if (!p) checkpermission= -1; /* Don't return, we must clean up &c */
+ }
+ if (n == 0 && realeinfo == 03) break; /* end of header, go on to body */
+ continue;
+ }
+ }
+ if (realeinfo & 02) {
+ /* process body */
+ copydata(si,file);
+ }
+
+ switch (checkpermission) {
+ case 0:
+ return;
+ case 1:
+ if (ferror(file) && fflush(file)) die("error writing article temp file");
+ printf("%s\r\n",modresponse);
+ if (!cip->einfo) { fclose(file); return; }
+ if (fseek(file,0,SEEK_SET)) die("unable to rewind article temp file");
+ while ((c= getc(file)) != EOF) putchar(c);
+ if (ferror(file)) die("unable to read article temp file");
+ fclose(file);
+ return;
+ case -1:
+ fclose(file);
+ authrequired();
+ return;
+ }
+}
+
+struct listwhatinfo {
+ const char *name;
+ void (*call)(const struct listwhatinfo *lwi, const char *cachename);
+ int mustall;
+ const char *what;
+ int einfo;
+};
+
+static void lwc_bynewsgroup(const struct listwhatinfo *lwi, const char *cachename) {
+ struct serverinfo *si;
+ struct groupinfo *gi;
+ char fnbuf[250];
+ char linebuf[MAX_RESPONSE+3];
+ FILE *file;
+ long an1, an2;
+ char *space1, *space2, *space3;
+
+ for (si= servers; si; si= si->next) {
+ if (!si->searchthis) continue;
+ sprintf(fnbuf,"%.100s:%.100s",si->nickname,cachename);
+ file= fopen(fnbuf,"r");
+ if (!file) {
+ if (lwi->mustall) die("unable to open list file when spouting");
+ continue;
+ }
+ while (fgets(linebuf,MAX_RESPONSE+3,file)) {
+ gi= findgroup(linebuf);
+ if (gi->readfrom != si) continue;
+ if (lwi->einfo && gi->offset &&
+ (space1= strchr(linebuf,' ')) &&
+ ((an1= strtol(space1+1,&space2,10)), *space2 == ' ') &&
+ ((an2= strtol(space2+1,&space3,10)), *space3 == ' ')) {
+ *space1= 0;
+ printf("%s %d %d %s",linebuf,
+ adjust(an1,gi->offset),adjust(an2,gi->offset),space3+1);
+ } else {
+ fputs(linebuf,stdout);
+ }
+ }
+ if (ferror(file)) die("read error on list file");
+ fclose(file);
+ }
+ return;
+}
+
+static void lwc_fixed(const struct listwhatinfo *lwi, const char *cachename) {
+ char *pathname;
+ pathname=
+ FILE *f= fopen(
+ char *p, tc;
+ struct disdone { struct disdone *next; char *name; } *done, *search, *tmp;
+ struct serverinfo *si;
+ char fnbuf[250];
+ char linebuf[MAX_RESPONSE+3];
+ FILE *file;
+
+ done= 0;
+ for (si= servers; si; si= si->next) {
+ if (!si->searchthis) continue;
+ sprintf(fnbuf,"%.100s:%.100s",si->nickname,cachename);
+ file= fopen(fnbuf,"r");
+ if (!file) continue;
+ while (fgets(linebuf,MAX_RESPONSE+3,file)) {
+ p= linebuf; while (*p && !isspace(*p)) p++;
+ tc= *p; *p= 0;
+ for (search= done;
+ search && strcasecmp(linebuf,search->name);
+ search= search->next);
+ if (search) continue;
+ tmp= xmalloc(sizeof(struct disdone));
+ tmp->name= xstrdup(linebuf);
+ tmp->next= done;
+ done= tmp;
+ *p= tc;
+ fputs(linebuf,stdout);
+ }
+ if (ferror(file)) die("read error on list file");
+ fclose(file);
+ }
+ for (search= done; search; search= tmp) {
+ tmp= search->next; free(search->name); free(search);
+ }
+ return;
+}
+
+static void lwc_overview(const struct listwhatinfo *lwi, const char *cachename) {
+ fputs("Subject:\r\n"
+ "From:\r\n"
+ "Date:\r\n"
+ "Message-ID:\r\n"
+ "References:\r\n"
+ "Bytes:\r\n"
+ "Lines:\r\n"
+ "Xref:full\r\n", stdout);
+}
+
+static const struct listwhatinfo listwhatinfos[]= {
+ { "", lwc_bynewsgroup, 1, "active file", 1 },
+ { "active", lwc_bynewsgroup, 1, "active file", 1 },
+ { "active.times", lwc_bynewsgroup, 1, "newsgroups' creation info", 0 },
+ { "newsgroups", lwc_bynewsgroup, 0, "newsgroup titles", 0 },
+ { "subscriptions", lwc_bynewsgroup, 0, "default subscription list", 0 },
+ { "distributions", lwc_distributions, 0, "distributions available" },
+ { "overview.fmt", lwc_overview, -1, "field list" },
+ { 0 }
+};
+
+static int copydatanodot(struct serverinfo *si, FILE *file) {
+ int c;
+
+ while ((c= getc(si->rfile)) != EOF) {
+ if (c == '.') {
+ c= getc(si->rfile);
+ if (c == '\r') {
+ c= getc(si->rfile);
+ if (c == '\n')
+ return 1;
+ fputs(".\r",file);
+ } else if (c == '\n') {
+ return 1;
+ } else {
+ putc('.',file);
+ }
+ }
+ while (c != EOF && c != '\r' && c != '\n') {
+ putc(c,file);
+ c= getc(si->rfile);
+ }
+ if (c == EOF) break;
+ putc(c,file);
+ }
+ if (ferror(si->rfile)) {
+ printf("503 error getting data from %s: %s\r\n",si->hostname,strerror(errno));
+ } else {
+ printf("503 connection closed during data transfer from %s\r\n",si->hostname);
+ }
+ return 0;
+}
+
+static void cmd_list(char *arg, const struct cmdinfo *cip) {
+ const struct listwhatinfo *lwi;
+ int rcode, c;
+ char commandbuf[MAX_COMMAND+40+3];
+ char responsebuf[MAX_RESPONSE+3];
+ char ufnbuf[250], nfnbuf[250], fnbuf[250];
+ struct stat stab;
+ FILE *file;
+ struct serverinfo *si;
+ const char *cachename;
+
+ for (lwi= listwhatinfos; lwi->name && strcasecmp(arg,lwi->name); lwi++);
+ if (!lwi->name) {
+ printf("501 LIST %s not available, sorry.\r\n",arg);
+ return;
+ }
+ if (lwi->mustall >= 0) {
+ cachename= lwi->name; if (!*cachename) cachename= (lwi+1)->name;
+ } else {
+ cachename= 0;
+ }
+ if (lwi->mustall > 0) {
+ sprintf(ufnbuf,"unavailable:%.100s",arg);
+ file= fopen(ufnbuf,"r");
+ if (file) {
+ printf("501 %s not available because not supported by ",arg);
+ while ((c= getc(file)) != EOF) { if (!isspace(c)) putchar(c); }
+ printf(".\r\n");
+ fclose(file);
+ return;
+ }
+ }
+ for (si= servers; lwi->mustall >= 0 && si; si= si->next) {
+ if (!si->searchthis) continue;
+ sprintf(commandbuf,"LIST %.100s",lwi->name);
+ rcode= servercommand(si,commandbuf,responsebuf,0);
+ sprintf(fnbuf,"%.100s:%.100s",si->nickname,cachename);
+ if (rcode == 0x500 || rcode == 0x501) {
+ if (lwi->mustall) {
+ printf("501 just discovered: %s doesn't support it - it says: %s\r\n",
+ si->hostname,responsebuf);
+ sprintf(nfnbuf,"~%s~%ld",ufnbuf,(long)getpid());
+ file= fopen(nfnbuf,"w"); if (!file) die("unable to create tmp unsup list file");
+ if (fprintf(file,"%s\n",si->hostname) == EOF)
+ die("unable to write unsup list file");
+ if (fclose(file)) die("unable to close unsup list file");
+ if (rename(nfnbuf,ufnbuf)) die("unable to install unsup list file");
+ return;
+ } else {
+ if (unlink(fnbuf) && errno != ENOENT) die("unable to remove now unsup list");
+ continue;
+ }
+ }
+ if (rcode == 0x215) {
+ sprintf(nfnbuf,"~%s~%ld",fnbuf,(long)getpid());
+ file= fopen(nfnbuf,"w"); if (!file) die("unable to create tmp list file");
+ if (!copydatanodot(si,file)) {
+ fclose(file); unlink(nfnbuf); return;
+ }
+ if (ferror(file) || fclose(file)) die("unable to write tmp list file");
+ if (rename(nfnbuf,fnbuf)) die("unable to install new list file");
+ } else if (rcode == 0x503) {
+ if (lwi->mustall && stat(fnbuf,&stab)) {
+ printf("501 no can do: need all, but file for %s not accessible: %s\r\n",
+ si->hostname,strerror(errno));
+ }
+ } else {
+ printf("503 %s said (after LIST): %s\r\n",si->hostname,responsebuf);
+ return;
+ }
+ }
+ printf("215 %s follows:\r\n",lwi->what);
+ lwi->call(lwi,cachename);
+ printf(".\r\n");
+}
+
+static int unadjustnumber(char *appendto, char *from, int offset) {
+ char *p;
+ int n;
+ long an;
+
+ an= strtol(from,&p,10);
+ if (!*from || *p) {
+ printf("503 bad article range.\r\n");
+ return 0;
+ }
+ if (an > offset)
+ an-= offset;
+ else
+ an= 0;
+ n= strlen(appendto);
+ sprintf(appendto+n,"%lu",an);
+ return 1;
+}
+
+static int unadjustrange(char *appendto, char *from, int offset) {
+ char *p;
+
+ p= strchr(from,'-');
+ if (p) {
+ *p++= 0;
+ if (!unadjustnumber(appendto,from,offset)) return 0;
+ from= p;
+ strcat(appendto,"-");
+ }
+ return unadjustnumber(appendto,from,offset);
+}
+
+static void cmd_xover(char *arg, const struct cmdinfo *cipa) {
+ char commandbuf[MAX_COMMAND+40+3];
+ char responsebuf[MAX_RESPONSE+3];
+ char xrefbuf[MAX_XREFLINE+3];
+ char *p;
+ int rcode;
+ struct serverinfo *si;
+ int an, n, fieldn, c;
+
+ if (!currentgroup) {
+ printf("412 overview of which group ?\r\n");
+ return;
+ }
+ strcpy(commandbuf,"XOVER ");
+ if (*arg && !unadjustrange(commandbuf,arg,currentgroup->offset)) return;
+ si= currentgroup->readfrom;
+ rcode= servercommand(si,commandbuf,responsebuf,0);
+ if ((rcode & 0xf00) != 0x200) {
+ printf("%s\r\n",responsebuf);
+ return;
+ } else if (rcode != 0x224) {
+ printf("503 %s said (after XOVER): %s\r\n",si->hostname,responsebuf);
+ return;
+ }
+ printf("%s\r\n",responsebuf);
+ for (;;) {
+ c= getc(si->rfile);
+ if (c == EOF) serverdataerr(si);
+ if (c == '.') {
+ c= getc(si->rfile);
+ if (c == '\r') c= getc(si->rfile);
+ if (c == EOF) serverdataerr(si);
+ if (c == '\n') break;
+ /* Oh, well, it's clearly bozoid, so what if we dropped a `.' ... */
+ putchar('.');
+ putchar(c);
+ }
+ ungetc(c,si->rfile);
+ if (fscanf(si->rfile,"%d",&an) != 1) {
+ while ((c= getc(si->rfile)) != EOF && c != '\n');
+ if (c == EOF) serverdataerr(si);
+ continue;
+ }
+ printf("%d",adjust(an,currentgroup->offset));
+ p= xrefbuf; n= 0; fieldn= 0;
+ for (;;) {
+ c= getc(si->rfile);
+ if (c == EOF) serverdataerr(si);
+ if (c == '\r') continue;
+ if (c != '\t' && c != '\n' && n < MAX_XREFLINE) {
+ *p++= c; n++;
+ continue;
+ }
+ if (c == '\t' || c == '\n') fieldn++;
+ *p++= 0;
+ if (fieldn >= 8 && !strncasecmp("Xref: ",xrefbuf,6)) {
+ printf("Xref: ");
+ processxref(xrefbuf+6,strlen(xrefbuf)-6,stdout,si);
+ } else {
+ fputs(xrefbuf,stdout);
+ }
+ if (c == '\n') break;
+ putchar('\t');
+ p= xrefbuf; n= 0;
+ }
+ printf("\r\n");
+ }
+ printf(".\r\n");
+ return;
+}
+
+static void cmd_xhdr(char *arg, const struct cmdinfo *cipa) {
+ char commandbuf[MAX_COMMAND+40+3];
+ char responsebuf[MAX_RESPONSE+3];
+ char linebuf[MAX_XREFLINE+40+3];
+ char *p, *q;
+ int rcode, isxref;
+ struct serverinfo *si;
+ long an;
+
+ if (!currentgroup) {
+ printf("412 headers in which group ?\r\n");
+ return;
+ }
+ p= strchr(arg,' ');
+ if (!p) {
+ printf("501 need header and range.\r\n");
+ return;
+ }
+ *p++= 0;
+ sprintf(commandbuf,"XHDR %s ",arg);
+ if (!unadjustrange(commandbuf,p,currentgroup->offset)) return;
+ si= currentgroup->readfrom;
+ rcode= servercommand(si,commandbuf,responsebuf,0);
+ if ((rcode & 0xf00) != 0x200) {
+ printf("%s\r\n",responsebuf);
+ return;
+ } else if (rcode != 0x221) {
+ printf("503 %s said (after XHDR): %s\r\n",si->hostname,responsebuf);
+ return;
+ }
+ printf("%s\r\n",responsebuf);
+ isxref= !strcasecmp(arg,"Xref");
+ if (!isxref && !currentgroup->offset) {
+ copydata(si,stdout);
+ } else {
+ for (;;) {
+ if (!fgets(linebuf,MAX_XREFLINE+40,si->rfile)) serverdataerr(si);
+ if (!stripcommand(linebuf)) continue;
+ if (!strcmp(linebuf,".")) break;
+ q= linebuf;
+ if (currentgroup->offset) {
+ an= strtol(linebuf,&q,10);
+ printf("%d",adjust(an,currentgroup->offset));
+ }
+ if (isxref && (p= strchr(q,' ')) && strcmp(p+1,"(none)")) {
+ *p++= 0;
+ printf("%s ",q);
+ processxref(p,strlen(p),stdout,si);
+ } else {
+ fputs(q,stdout);
+ }
+ fputs("\r\n",stdout);
+ }
+ printf(".\r\n");
+ }
+}
+
+static void cmd_newgroups_xgtitle(char *arg, const struct cmdinfo *cip) {
+ char commandbuf[MAX_COMMAND+40+3];
+ char responsebuf[MAX_RESPONSE+3];
+ char linebuf[MAX_XREFLINE+40+3];
+ int rcode, okrcode;
+ struct serverinfo *si, *gsi;
+
+ sprintf(commandbuf,"%s %s",cip->command,arg);
+
+ okrcode= cip->einfo ? 0x282 : 0x231;
+ if (cip->einfo && !strchr(arg,'*') && !strchr(arg,'?')) {
+ if (!*arg) {
+ if (!currentgroup) {
+ printf("412 title of which group ?\r\n");
+ return;
+ }
+ si= currentgroup->readfrom;
+ } else {
+ si= findgroup(arg)->readfrom;
+ }
+ rcode= servercommand(si,commandbuf,responsebuf,0);
+ printf("%s\r\n",responsebuf);
+ if ((rcode & 0xf00) == 0x200) copydata(si,stdout);
+ return;
+ }
+
+ for (si= servers; si; si= si->next) {
+ if (!si->searchthis) continue;
+ si->tempfile= 0;
+ }
+ for (si= servers; si; si= si->next) {
+ if (!si->searchthis) continue;
+ rcode= servercommand(si,commandbuf,responsebuf,0);
+ if (rcode != okrcode) {
+ if ((rcode & 0xf00) == 0x200) closeserver(si);
+ if (cip->einfo) continue;
+ printf("503 NEWGROUPS not available - %s: %s\r\n",si->hostname,responsebuf);
+ goto close_files_and_abort;
+ }
+ si->tempfile= tmpfile();
+ if (!si->tempfile) die("unable to create tempfile");
+ if (!copydatanodot(si,si->tempfile)) goto close_files_and_abort;
+ if (ferror(si->tempfile) || fflush(si->tempfile) || fseek(si->tempfile,0,SEEK_SET))
+ die("unable to write temp file");
+ }
+ printf("%x here you are:\r\n",okrcode);
+ for (si= servers; si; si= si->next) {
+ if (!si->searchthis) continue;
+ if (!si->tempfile) continue;
+ while (fgets(linebuf,MAX_XREFLINE+3,si->tempfile)) {
+ gsi= findgroup(linebuf)->readfrom;
+ if (gsi != si) continue;
+ fputs(linebuf,stdout);
+ }
+ if (ferror(si->tempfile)) die("read error on temp file");
+ fclose(si->tempfile); si->tempfile= 0;
+ }
+ printf(".\r\n");
+ return;
+
+ close_files_and_abort:
+ for (si= servers; si; si= si->next) {
+ if (!si->searchthis) continue;
+ if (si->tempfile) fclose(si->tempfile);
+ }
+ return;
+}
+
+static void cmd_authinfo(char *arg, const struct cmdinfo *cip) {
+ struct authdas { struct authdas *next; char *name; };
+
+ static struct authdas *alreadydone= 0;
+
+ struct authdas *asearch;
+ struct MD5Context md5ctx;
+ char *p, *claim, *here, *what;
+ FILE *file;
+ char buf[500], buf2[MAX_RESPONSE+3];
+ unsigned char message[16+MAX_SECRET], expect[16], reply[16];
+ int n, nused, matching, ifmatch;
+ struct timeval timevab;
+ unsigned long ul;
+ unsigned short us;
+ struct permission *pi;
+
+ if (strcasecmp(strtok(arg," "),"generic") ||
+ strcmp(strtok(0," "),"md5cookie1way")) {
+ printf("501 please use AUTHINFO GENERIC md5cookie1way <claim>.\r\n");
+ return;
+ }
+ claim= strtok(0," ");
+ if (strtok(0," ")) {
+ printf("501 something after claim.\r\n");
+ return;
+ }
+ for (asearch= alreadydone;
+ asearch && strcmp(asearch->name,claim);
+ asearch= asearch->next);
+ if (asearch) {
+ printf("502 you are already user/group %s; need other group for more access.\r\n",
+ claim);
+ return;
+ }
+ file= fopen("md5cookies","r");
+ if (!file) {
+ printf("503 couldn't open md5cookies file: %s\r\n",strerror(errno));
+ return;
+ }
+ matching= 0;
+ for (;;) {
+ if (!fgets(buf,sizeof(buf),file)) {
+ if (ferror(file)) {
+ printf("503 error reading md5cookies file: %s\r\n",strerror(errno));
+ } else {
+ printf("502 who did you say you were ?\r\n");
+ }
+ fclose(file);
+ return;
+ }
+ if (!stripcommand(buf) || *buf == '#' || !*buf) continue;
+ here= strtok(buf," \t");
+ if (!strcmp(buf,"@")) {
+ what= strtok(0," \t");
+ if (!strcmp(what,"allow")) {
+ ifmatch= 1;
+ } else if (!strcmp(what,"refuse")) {
+ ifmatch= 0;
+ } else {
+ continue;
+ }
+ what= strtok(0,"");
+ if (!what) continue;
+ if (fnmatch(what,theirfqdn ? theirfqdn : "_unknown_",0)) continue;
+ matching= ifmatch;
+ } else if (!strcmp(here,claim)) {
+ if (matching) break;
+ }
+ }
+ fclose(file);
+
+ if (gettimeofday(&timevab,(void*)0)) die("unable to gettimeofday for nonce");
+ memcpy(message,&timevab.tv_sec,4);
+ ul= timevab.tv_usec; memcpy(message+4,&ul,4);
+ memcpy(message+8,&peername.sin_addr,4);
+ memcpy(message+12,&peername.sin_port,2);
+ us= getpid(); memcpy(message+14,&us,2);
+
+ p= strtok(0," \t");
+ if (!p) {
+ printf("502 md5cookies file is missing secret for you.\r\n");
+ return;
+ }
+ nused= parsehex(p,message+16,MAX_SECRET);
+
+ MD5Init(&md5ctx);
+ MD5Update(&md5ctx,message,16+nused);
+ MD5Final(expect,&md5ctx);
+
+ printf("100 ");
+ for (n=0; n<16; n++) {
+ printf("%s%02x", n?":":"", message[n]);
+ }
+ printf("\r\n");
+ if (ferror(stdout) || fflush(stdout)) die("unable to write auth challenge");
+
+ if (!fgets(buf2,MAX_RESPONSE,stdin)) {
+ if (ferror(stdin)) die("client connection failed during crypto");
+ exit(1);
+ }
+ if (strncasecmp(buf2,"MD5 ",4)) {
+ printf("502 expecting MD5.\r\n");
+ return;
+ }
+ nused= parsehex(buf2+4,reply,17);
+ if (nused != 16) {
+ printf("502 expecting 16 pairs (got %d).\r\n",nused);
+ return;
+ }
+ if (memcmp(reply,expect,16)) {
+ printf("502 pull the other one, it's got bells on.\r\n");
+ return;
+ }
+ asearch= xmalloc(sizeof(struct authdas));
+ asearch->name= xstrdup(claim);
+ asearch->next= alreadydone;
+ alreadydone= asearch;
+ printf("281 ok");
+ while ((p= strtok(0," \t"))) {
+ printf(" %s",p);
+ for (pi= permissions; pi; pi= pi->next) {
+ if (strcmp(pi->name,p)) continue;
+ if (!pi->authd) {
+ putchar('+');
+ pi->authd= 1;
+ }
+ }
+ }
+ lastdoneauth= asearch->name;
+ printf("\r\n");
+}
+
+const struct cmdinfo cmdinfos[]= {
+ { "ARTICLE", cmd_article, 3 },
+ { "AUTHINFO", cmd_authinfo },
+ { "XAUTHINFO", cmd_authinfo },
+ { "HEAD", cmd_article, 1 },
+ { "BODY", cmd_article, 2 },
+ { "STAT", cmd_article, 0 },
+ { "GROUP", cmd_group },
+ { "HELP", cmd_help },
+ { "IHAVE", cmd_ihave },
+ { "LAST", cmd_last_next },
+ { "LIST", cmd_list },
+ { "NEWGROUPS", cmd_newgroups_xgtitle, 0 },
+ { "NEWNEWS", cmd_newnews },
+ { "NEXT", cmd_last_next },
+ { "NOOP", cmd_noop },
+ { "POST", cmd_post },
+ { "QUIT", cmd_quit },
+ { "SLAVE", cmd_slave },
+ { "DATE", cmd_date },
+ { "LISTGROUP", cmd_listgroup },
+ { "XLISTGROUP", cmd_listgroup },
+ { "MODE", cmd_mode },
+ { "XMODE", cmd_mode },
+ { "XMERGEINFO", cmd_xmergeinfo },
+ { "XGTITLE", cmd_newgroups_xgtitle, 1 },
+ { "XHDR", cmd_xhdr },
+ { "XOVER", cmd_xover },
+
+ { "XPAT", cmd_unimplemented },
+ { "XPATH", cmd_unimplemented },
+ { 0 }
+};
+
+static void cmd_help(char *arg, const struct cmdinfo *cipa) {
+ /* close outstanding channels */
+ const struct cmdinfo *cip;
+ if (!noargs(arg)) return;
+ printf("100 commands are (* = implemented)\r\n");
+ for (cip= cmdinfos; cip->command; cip++) {
+ printf(" %s %s\r\n",
+ cip->call == cmd_unimplemented ? " " : "*",
+ cip->command);
+ }
+ printf(".\r\n");
+}
+
+int main(int argc, char **argv) {
+ char cmdbuf[1000];
+ int n;
+ unsigned int nu;
+ const struct cmdinfo *cip;
+ struct hostent *he;
+
+ cmdbuf[sizeof(cmdbuf)-1]= 0;
+ if (gethostname(cmdbuf,sizeof(cmdbuf)-1)) die("gethostname");
+ if (cmdbuf[sizeof(cmdbuf)-1]) die("gethostname overflow");
+ if (!(he= gethostbyname(cmdbuf))) die("gethostbyname");
+ myfqdn= xstrdup(he->h_name);
+ myxref= xstrdup(he->h_name);
+
+ if (chdir("/var/lib/news/merge")) die("chdir");
+ readconfig();
+
+ setvbuf(stdin,0,_IOLBF,0);
+ setvbuf(stdout,0,_IOFBF,10240);
+ signal(SIGPIPE,SIG_IGN);
+ nu= sizeof(peername);
+ memset(&peername,0,nu);
+ if (getpeername(0,(struct sockaddr*)&peername,&nu)) {
+ theirfqdn= "_direct_";
+ } else {
+ he= gethostbyaddr((void*)&peername.sin_addr,sizeof(peername.sin_addr),AF_INET);
+ if (he && he->h_name) theirfqdn= xstrdup(he->h_name);
+ }
+
+ printf("200 %s nntp-merge ready (posting might be ok).\r\n",myfqdn);
+
+ for (;;) {
+ if (fflush(stdout)) die("flush stdout");
+ if (ferror(stdout) || ferror(stdin)) die("client connection died");
+ waitpid(-1,0,WNOHANG); /* ignore all children */
+ errno=0; if (!fgets(cmdbuf,sizeof(cmdbuf),stdin)) {
+ if (ferror(stdin)) die("client closed"); else exit(0);
+ }
+ if (!stripcommand(cmdbuf)) continue;
+ for (cip= cmdinfos; cip->command; cip++) {
+ n= strlen(cip->command);
+ if (!strncasecmp(cip->command,cmdbuf,n) &&
+ (!cmdbuf[n] || isspace(cmdbuf[n])))
+ break;
+ }
+ if (cip->command) {
+ while (isspace(cmdbuf[n])) n++;
+ cip->call(cmdbuf+n,cip);
+ } else {
+ printf("500 huh?\r\n");
+ }
+ }
+}