18 #include <sys/types.h>
20 #include <arpa/inet.h>
21 #include <netinet/in.h>
23 #include "nntp-merge.h"
26 char *myfqdn, *myxref, *lastdoneauth= 0;
27 const char *theirfqdn= 0;
28 char currentgroupname[MAX_COMMAND+3];
29 struct groupinfo *currentgroup;
30 struct sockaddr_in peername;
32 int stripcommand(char *buf) {
37 if (buf[n-1] != '\n') return 0;
38 while (n>0 && isspace(buf[n-1])) n--;
43 void die(const char *msg) {
46 printf("400 server failure: %s - %s\r\n",msg,strerror(e));
50 static int parsehex(char *p, unsigned char *up, int maxlen) {
54 while (nused < MAX_SECRET && (n=-1, sscanf(p,"%x%n",&v,&n) >0) && n>0) {
63 enum { scf_norecover=001, scf_nogroup };
65 void closeserver(struct serverinfo *server) {
66 fclose(server->rfile); server->rfile= 0;
67 fclose(server->wfile); server->wfile= 0;
70 int decoderesponse(char response[MAX_RESPONSE+3], unsigned long *rvp,
71 struct serverinfo *server) {
72 char temp[MAX_RESPONSE+3];
73 if (!isdigit(response[0]) || !isdigit(response[1]) || !isdigit(response[2]) ||
74 !(isspace(response[3]) || !response[3])) {
75 strcpy(temp,response);
76 sprintf(response,"503 server %.100s produced garbage: %.100s",
77 server->hostname,temp);
78 closeserver(server); return 0;
80 *rvp= strtol(response,0,16);
85 static int realservercommand(struct serverinfo *server,
86 const char command[], char response[MAX_RESPONSE+3],
89 int servercommand(struct serverinfo *server,
90 const char command[], char response[MAX_RESPONSE+3],
94 fprintf(stderr,"[%03o]>>>%s: %s\n",flags,server->nickname,command);
95 rcode= realservercommand(server,command,response,flags);
96 fprintf(stderr,"[%03x]<<<%s: %s\n",rcode,server->nickname,response);
99 static int realservercommand(struct serverinfo *server,
100 const char command[], char response[MAX_RESPONSE+3],
105 struct sockaddr_in sin;
107 int rpipefds[2], wpipefds[2], rfd, wfd, l, n;
109 char temp[MAX_RESPONSE+3], *p;
110 unsigned char message[16+MAX_SECRET], cryptresponse[16];
111 struct MD5Context md5ctx;
114 /* response will be null-terminated and have trailing whitespace
115 * removed including \r\n
116 * (but indeed the extra two spaces used by this routine are for \r\n)
119 if (fprintf(server->wfile,"%s\r\n",command) == EOF ||
120 fflush(server->wfile) == EOF) goto conn_broken;
121 if (!fgets(response,MAX_RESPONSE+3,server->rfile)) goto conn_broken;
122 if (!stripcommand(response)) goto conn_broken;
123 if (!strncmp(response,"400",3)) goto conn_broken;
124 goto message_sent_response_ok;
127 if (flags & scf_norecover) {
128 strcpy(response,"205 it had already gone"); return 0x205;
131 if (server->program) {
132 if (pipe(rpipefds) || pipe(wpipefds)) die("unable to create pipe");
133 if ((c1= fork()) == -1) die("unable to fork");
135 close(0); close(1); dup(wpipefds[0]); dup(rpipefds[1]);
136 close(wpipefds[0]); close(wpipefds[1]);
137 close(rpipefds[0]); close(rpipefds[1]);
138 signal(SIGPIPE,SIG_DFL);
139 if (lastdoneauth) setenv("NNTPMERGE_AUTHD_AS",lastdoneauth,1);
140 execl(server->hostname,server->hostname,(char*)0);
141 printf("400 exec %s: %s\r\n",server->hostname,strerror(errno));
144 rfd= rpipefds[0]; close(rpipefds[1]);
145 wfd= wpipefds[1]; close(wpipefds[0]);
147 he= gethostbyname(server->hostname);
149 sprintf(response,"503 unable to find address of %.100s.",server->hostname);
152 if (he->h_addrtype != AF_INET) {
153 sprintf(response,"503 address of %.100s is of unknown type 0x%x.",
154 server->hostname,he->h_addrtype);
157 for (alist= he->h_addr_list; *alist; alist++) {
158 rfd= socket(PF_INET,SOCK_STREAM,0);
159 if (rfd == -1) die("unable to create TCP socket");
160 memset(&sin,0,sizeof(sin));
161 sin.sin_family= AF_INET;
162 sin.sin_addr= *(struct in_addr*)*alist;
163 sin.sin_port= htons(server->port);
164 if (!connect(rfd,(struct sockaddr*)&sin,sizeof(sin))) break;
165 sprintf(response,"503 unable to connect to %.100s (%.50s:%u): %.100s.",
166 server->hostname, inet_ntoa(sin.sin_addr), server->port, strerror(errno));
169 if (!*alist) return 0x503;
171 if (wfd < 0) die("failed to dup socket fd");
173 server->rfile= fdopen(rfd,"r");
174 server->wfile= fdopen(wfd,"w");
175 if (!server->rfile || !server->wfile) die("failed to fdopen");
176 if (setvbuf(server->rfile,0,_IOLBF,0) || setvbuf(server->wfile,0,_IOLBF,0))
177 die("failed to setvbuf linebuf");
178 if (!fgets(response,MAX_RESPONSE+3,server->rfile)) {
179 sprintf(response,"503 failed to read from server %.100s: %.100s.",
180 server->hostname,strerror(errno));
182 if (!stripcommand(response)) {
183 sprintf(response,"503 server %.100s is spouting null garbage.",server->hostname);
184 closeserver(server); return 0x503;
186 if (!strncmp(response,"480",3) &&
187 response[3] == ' ' && (p= strchr(response+4,' ')) &&
188 ((*p=0), (parsehex(response+4,message,17) == 16)) &&
189 (file= fopen(SESAMEFILE,"r"))) {
191 if (!fgets(temp,sizeof(temp),file)) {
193 sprintf(response,"503 error reading sesame file: %.100s",strerror(errno));
195 sprintf(response,"503 no sesame for server nicknamed %.100s",server->nickname);
197 fclose(file); closeserver(server); return 0x503;
199 } while (!stripcommand(temp) || !*temp || *temp == '#' ||
200 !(p= strchr(temp,' ')) || ((*p++= 0), strcmp(temp,server->nickname)));
201 fclose(file); l= strlen(p);
203 sprintf(response,"503 sesame file secret for %.100s is too long",server->nickname);
204 closeserver(server); return 0x503;
206 memcpy(message+16,p,l);
209 MD5Update(&md5ctx,message,16+l);
210 MD5Final(cryptresponse,&md5ctx);
212 fprintf(server->wfile,"PASS ");
213 for (n=0; n<16; n++) {
214 fprintf(server->wfile,"%s%02x", n?":":"", cryptresponse[n]);
216 fprintf(server->wfile,"\r\n");
217 if (fflush(server->wfile) == EOF || ferror(server->wfile)) {
218 sprintf(response,"503 error sending sesame response to %.100s: %.100s",
219 server->hostname,strerror(errno));
220 closeserver(server); return 0x503;
222 if (!fgets(response,MAX_RESPONSE+3,server->rfile)) {
223 sprintf(response,"503 error reading from %.100s after sesame: %.100s.",
224 server->hostname,strerror(errno));
225 closeserver(server); return 0x503;
228 if (!strncmp(response,"400",3)) goto server_is_unavailable;
229 if (response[0] != '2') {
230 strcpy(temp,response);
232 sprintf(response,"503 server %.100s refuses to talk: %.100s",server->hostname,temp);
233 closeserver(server); return 0x503;
236 if (fprintf(server->wfile,"%s\r\n",server->send) == EOF ||
237 fflush(server->wfile) == EOF) {
238 sprintf(response,"503 error sending %.50s to %.100s: %.100s",
239 server->send,server->hostname,strerror(errno));
240 closeserver(server); return 0x503;
242 if (!fgets(response,MAX_RESPONSE+3,server->rfile)) {
243 sprintf(response,"503 error reading from %.100s after %.50s: %.100s.",
244 server->hostname,server->send,strerror(errno));
245 closeserver(server); return 0x503;
248 if (!(flags & scf_nogroup) && currentgroup && currentgroup->readfrom == server) {
249 if (fprintf(server->wfile,"GROUP %s\r\n",currentgroupname) == EOF ||
250 fflush(server->wfile) == EOF) {
251 sprintf(response,"503 error sending GROUP to %.100s: %.100s",
252 server->hostname,strerror(errno));
253 closeserver(server); return 0x503;
255 if (!fgets(response,MAX_RESPONSE+3,server->rfile)) {
256 sprintf(response,"503 error reading from %.100s after GROUP: %.100s.",
257 server->hostname,strerror(errno));
258 closeserver(server); return 0x503;
261 if (fprintf(server->wfile,"%s\r\n",command) == EOF ||
262 fflush(server->wfile) == EOF) {
263 sprintf(response,"503 error sending to %.100s: %.100s",
264 server->hostname,strerror(errno));
265 closeserver(server); return 0x503;
267 if (!fgets(response,MAX_RESPONSE+3,server->rfile)) {
268 sprintf(response,"503 error reading from %.100s: %.100s.",server->hostname,
270 closeserver(server); return 0x503;
272 if (!stripcommand(response)) {
273 sprintf(response,"503 server %.100s is replying with null garbage.",
275 closeserver(server); return 0x503;
277 message_sent_response_ok:
278 if (!decoderesponse(response,&rv,server)) return 0x503;
279 if (rv != 0x400) return rv;
280 server_is_unavailable:
281 strcpy(temp,response);
282 sprintf(response,"503 server %.100s unavailable: %.200s",
283 server->hostname,temp);
284 closeserver(server); return 0x503;
287 static int adjust(int original, int offset) {
288 if (offset < 0 && original < -offset) return 0;
289 return original+offset;
292 void serverdataerr(struct serverinfo *si) {
293 if (ferror(si->rfile)) {
294 printf("\r\n\r\n*** problem detected by nntp-merge:\r\n"
295 "error reading data from server %s: %s\r\n",
296 si->hostname, strerror(errno));
298 printf("\r\n\r\n*** problem detected by nntp-merge:\r\n"
299 "server %s closed connection while sending data\r\n",
302 printf("closing connection on you, sorry.\r\n");
306 static int noargs(const char *arg) {
308 printf("501 no arguments please.\r\n");
312 static void cmd_unimplemented(char *arg, const struct cmdinfo *cip) { \
313 printf("500 %s not implemented.\r\n",cip->command); \
316 static void cmd_quit(char *arg, const struct cmdinfo *cip) {
317 char responsebuf[MAX_RESPONSE+3];
318 struct serverinfo *si;
320 if (!noargs(arg)) return;
321 for (si= servers; si; si= si->next) {
322 if (!si->rfile) continue;
323 servercommand(si,"QUIT",responsebuf,scf_norecover);
324 fclose(si->wfile); fclose(si->rfile); /* we're not interested in the response */
326 printf("205 goodbye.\r\n");
330 static void cmd_date(char *arg, const struct cmdinfo *cip) {
331 char responsebuf[MAX_RESPONSE+3];
332 struct tm *brokendown;
336 struct serverinfo *si;
338 if (!noargs(arg)) return;
340 brokendown= gmtime(&now); if (!brokendown) die("unable to break down date/time");
341 n= strftime(buf,sizeof(buf)-1,"%Y%m%d%H%M%S",brokendown);
342 if (n <= 0 || n >= sizeof(buf)-1 || strlen(buf) != 14)
343 die("unable to format date/time");
344 for (si= servers; si; si= si->next) {
345 if (!si->searchthis) continue;
346 rcode= servercommand(si,"DATE",responsebuf,0);
347 if (rcode == 0x111 && responsebuf[3]==' ' &&
348 strspn(responsebuf+4,"0123456789")==14 &&
349 responsebuf[18]==0 &&
350 strcmp(responsebuf+4,buf) < 0)
351 strcpy(buf,responsebuf+4);
353 printf("111 %s\r\n",buf);
356 static void cmd_noop(char *arg, const struct cmdinfo *cip) {
357 if (!noargs(arg)) return;
358 printf("200 doing that has no effect.\r\n");
361 static void cmd_slave(char *arg, const struct cmdinfo *cip) {
362 if (!noargs(arg)) return;
363 printf("202 bind me, whip me.\r\n");
366 static void cmd_ihave(char *arg, const struct cmdinfo *cip) {
367 printf("502 please don't feed me.\r\n");
370 static void cmd_newnews(char *arg, const struct cmdinfo *cip) {
371 printf("502 get a proper feed, slurping through this merger is silly.\r\n");
374 static void cmd_mode(char *arg, const struct cmdinfo *cip) {
375 if (!strcasecmp(arg,"READER")) {
376 printf("200 %s that was uncalled-for.\r\n",myfqdn);
378 printf("501 you want me to do *what* ?\r\n");
382 static void authrequired(void) {
384 printf("480 the sight of %s is but dim.\r\n",lastdoneauth);
386 printf("480 identify yourself ! friend or foe ?\r\n");
390 int stillrestricted(struct permission **pip) {
392 if (!(*pip)->authd) return 1;
396 static int groupreadable(struct groupinfo *gi) {
397 if (!stillrestricted(&gi->restrictto) ||
398 !stillrestricted(&gi->readonlyto)) return 1;
403 static void cmd_group(char *arg, const struct cmdinfo *cip) {
404 struct groupinfo *gi;
405 int estcount, first, last, startfrom;
406 char commandbuf[MAX_COMMAND+3];
407 char responsebuf[MAX_RESPONSE+3];
410 if (strlen(arg) > MAX_COMMAND-12) {
411 printf("501 too long.\r\n");
415 gi= findgroup(arg); if (!groupreadable(gi)) return;
417 printf("501 which group, then ?\r\n");
421 sprintf(commandbuf,"%s %s",cip->command,arg);
422 rcode= servercommand(gi->readfrom,commandbuf,responsebuf,scf_nogroup);
423 if ((rcode & 0xf00) != 0x200) {
424 printf("%s\r\n",responsebuf);
426 } else if (gi->offset) {
428 if (sscanf(responsebuf,"211 %d %d %d %n", &estcount,&first,&last,&startfrom) != 3 ||
430 printf("503 %s said (after GROUP): %s\r\n",gi->readfrom->hostname,responsebuf);
431 closeserver(gi->readfrom); return;
433 printf("211 %d %d %d %s\r\n",
434 estcount, adjust(first,gi->offset), adjust(last,gi->offset),
435 responsebuf + startfrom);
437 printf("%s\r\n",responsebuf);
440 strcpy(currentgroupname,arg);
443 int copydatafile(FILE *from, FILE *to) {
444 /* `to' may be null */
447 stopstate= 1; /* 0: middle of line; 1: start of line; 2: after `.' */
448 while ((c= getc(from)) != EOF) {
450 if (c == '\r') continue;
453 if (c == '\n') stopstate= 1;
456 stopstate= (c == '.') ? 2 : (c == '\n') ? 1 : 0;
459 if (c == '\n') return 1;
467 static void copydata(struct serverinfo *from, FILE *to) {
468 if (!copydatafile(from->rfile,to)) serverdataerr(from);
471 static void cmd_listgroup(char *arg, const struct cmdinfo *cip) {
472 struct groupinfo *gi;
473 char commandbuf[MAX_COMMAND+3];
474 char responsebuf[MAX_RESPONSE+3];
479 if (strlen(arg) > MAX_COMMAND-12) {
480 printf("501 too long.\r\n");
484 gi= findgroup(arg); if (!groupreadable(gi)) return;
485 } else if (!currentgroup) {
486 printf("412 list of articles in which group ?\r\n");
492 sprintf(commandbuf,"%s %s",cip->command,arg);
493 rcode= servercommand(gi->readfrom,commandbuf,responsebuf,scf_nogroup);
494 if ((rcode & 0xf00) != 0x200) {
495 printf("%s\r\n",responsebuf);
498 if (rcode != 0x211) {
499 printf("503 %s said (after %s): %s\r\n",
500 gi->readfrom->hostname,cip->command,responsebuf);
503 printf("211 article list:\r\n");
505 copydata(gi->readfrom,stdout);
508 if (!fgets(responsebuf,MAX_RESPONSE+3,gi->readfrom->rfile))
509 serverdataerr(gi->readfrom);
510 if (!strcmp(responsebuf,".\r\n") || !strcmp(responsebuf,".\n"))
512 an= strtol(responsebuf,&p,10);
513 printf("%d%s",adjust(an,gi->offset),p);
520 static void pserver(struct serverinfo *si) {
521 printf(" %s= %s:%u %s (%s)\r\n",
523 si->hostname, si->port,
524 si->rfile ? "open" : "closed",
525 si->send ? si->send : "-");
528 static void cmd_xmergeinfo(char *arg, const struct cmdinfo *cip) {
529 struct groupinfo *gi;
532 if (strlen(arg) > MAX_COMMAND-6) {
533 printf("501 too long.\r\n");
537 printf("100 %s\r\n read:\r\n",arg);
538 pserver(gi->readfrom);
539 printf(" post:\r\n");
540 for (i= 0; gi->postto[i]; i++) pserver(gi->postto[i]);
541 printf(" offset %d\r\n"
542 " auth %s / read-auth %s\r\n"
545 gi->restrictto && gi->restrictto->name ? gi->restrictto->name : "<none>",
546 gi->readonlyto && gi->readonlyto->name ? gi->readonlyto->name : "<none>");
549 static void cmd_help(char *arg, const struct cmdinfo *cip);
551 static int articleselectresponse(char responsebuf[MAX_RESPONSE+3], char *typecharp,
552 struct serverinfo *si, const struct cmdinfo *cip,
553 char modifiedresponse[MAX_RESPONSE+3]) {
557 if (sscanf(responsebuf,"22%c %d %n",typecharp,&ran,&startfrom) != 2 ||
558 startfrom == -1 || !strchr("0123",*typecharp)) {
559 printf("503 %s said (after %s): %s\r\n",
560 si->hostname, cip->command, responsebuf);
563 ran= (currentgroup && si == currentgroup->readfrom)
564 ? adjust(ran,currentgroup->offset) : 0;
565 sprintf(modifiedresponse,"22%c %d %s",*typecharp,ran,responsebuf+startfrom);
569 static void cmd_last_next(char *arg, const struct cmdinfo *cip) {
571 char responsebuf[MAX_RESPONSE+3];
572 char modresponse[MAX_RESPONSE+3];
575 if (!noargs(arg)) return;
577 printf("412 use GROUP and ARTICLE first.\r\n");
580 rcode= servercommand(currentgroup->readfrom,cip->command,responsebuf,0);
581 if ((rcode & 0xf00) != 0x200) {
582 printf("%s\r\n",responsebuf); return;
584 articleselectresponse(responsebuf,&dummy,currentgroup->readfrom,cip,modresponse);
585 printf("%s\r\n",modresponse);
588 static void processxref(char *linebufplus6, int n, FILE *writeto,
589 struct serverinfo *gotfrom) {
590 char *space, *cpos, *colon;
591 struct groupinfo *gi;
594 linebufplus6[n++]= ' ';
597 space= strchr(cpos,' ');
600 fprintf(writeto,"%s",myxref);
602 while ((space= strchr(cpos,' '))) {
604 colon= strrchr(cpos,':');
608 if (gi->readfrom != gotfrom) {
609 cpos= space; continue;
611 fprintf(writeto," %s:",cpos);
612 if (gi->offset && isdigit(*colon)) {
613 an= strtol(colon,&colon,10);
614 fprintf(writeto,"%d", adjust(an,gi->offset));
616 fputs(colon,writeto);
618 if (*cpos) putc(' ',writeto);
625 static void cmd_article(char *arg, const struct cmdinfo *cip) {
626 struct serverinfo *si;
627 struct groupinfo *gi;
628 char commandbuf[MAX_COMMAND+40+3];
629 char responsebuf[MAX_RESPONSE+3];
630 char modresponse[MAX_RESPONSE+3];
631 char linebuf[MAX_XREFLINE+3];
634 int rcode, n, c, realeinfo;
636 const char *realcommand;
637 int checkpermission= 0;
640 realcommand= cip->command;
643 switch (cip->einfo) {
644 case 0: realcommand= "HEAD"; break;
645 case 2: realcommand= "ARTICLE"; break;
647 sprintf(commandbuf,"%s %s",realcommand,arg);
649 for (si= servers; si; si= si->next) {
650 if (!si->searchthis) continue;
651 rcode= servercommand(si,commandbuf,responsebuf,0);
652 if ((rcode & 0xf00) == 0x200) break;
654 } else if (isdigit(*arg) || !*arg) {
656 printf("412 use GROUP first.\r\n");
660 an= strtol(arg,&p,10);
661 if (*p) { printf("501 bad article number.\r\n"); return; }
662 if (an < currentgroup->offset)
663 printf("423 offset makes number negative, so no such article.\r\n");
664 sprintf(commandbuf,"%s %lu",cip->command,an - currentgroup->offset);
666 sprintf(commandbuf,"%s",cip->command);
668 si= currentgroup->readfrom;
669 rcode= servercommand(si,commandbuf,responsebuf,0);
671 printf("501 optional arg must be message-id or article number.\r\n");
674 if ((rcode & 0xff0) != 0x220) {
675 printf("%s\r\n",responsebuf);
678 switch (rcode & 0x00f) {
679 case 0: realeinfo= 03; break;
680 case 1: realeinfo= 01; break;
681 case 2: realeinfo= 02; break;
682 case 3: realeinfo= 00; break;
683 default: realeinfo= -1;
685 if (realeinfo == (cip->einfo | 01)) {
686 switch (cip->einfo) {
687 case 0: responsebuf[2]= '3'; break;
688 case 2: responsebuf[2]= '2'; break;
690 } else if (realeinfo != cip->einfo) {
691 printf("503 %s gave bad code (in response to %s): %s\r\n",
692 si->hostname,realcommand,responsebuf);
697 if (!articleselectresponse(responsebuf,&typechar,si,cip,modresponse)) return;
698 if (checkpermission) {
699 file= tmpfile(); if (!file) die("failed to create temp file");
701 printf("%s\r\n",modresponse);
702 if (!realeinfo) return;
705 if (realeinfo & 01) {
707 if (!fgets(linebuf,MAX_XREFLINE+3,si->rfile)) serverdataerr(si);
710 while (n>0 && ((c= linebuf[n-1]) == '\n' || c == '\r')) n--;
712 if (!strcmp(linebuf,".")) { fputs(".\r\n",file); break; }
713 if ((cip->einfo & 01) && !strncasecmp(linebuf,"Xref: ",6)) {
714 fprintf(file,"Xref: ");
715 processxref(linebuf+6,n-6,file,si);
719 if (cip->einfo & 01) fprintf(file,"%s\r\n",linebuf);
720 if (checkpermission && !strncasecmp(linebuf,"Newsgroups: ",12)) {
721 p= strtok(linebuf," "); assert(p);
722 while ((p= strtok(0,","))) {
724 if (!stillrestricted(&gi->restrictto) ||
725 !stillrestricted(&gi->readonlyto)) break;
727 if (!p) checkpermission= -1; /* Don't return, we must clean up &c */
729 if (n == 0 && realeinfo == 03) break; /* end of header, go on to body */
733 if (realeinfo & 02) {
738 switch (checkpermission) {
742 if (ferror(file) && fflush(file)) die("error writing article temp file");
743 printf("%s\r\n",modresponse);
744 if (!cip->einfo) { fclose(file); return; }
745 if (fseek(file,0,SEEK_SET)) die("unable to rewind article temp file");
746 while ((c= getc(file)) != EOF) putchar(c);
747 if (ferror(file)) die("unable to read article temp file");
757 struct listwhatinfo {
759 void (*call)(const struct listwhatinfo *lwi, const char *cachename);
760 int mustall; /* -2 means ->call() must also print response and final . */
765 static void lwc_bynewsgroup(const struct listwhatinfo *lwi, const char *cachename) {
766 struct serverinfo *si;
767 struct groupinfo *gi;
769 char linebuf[MAX_RESPONSE+3];
772 char *space1, *space2, *space3;
774 for (si= servers; si; si= si->next) {
775 if (!si->searchthis) continue;
776 sprintf(fnbuf,"%.100s:%.100s",si->nickname,cachename);
777 file= fopen(fnbuf,"r");
779 if (lwi->mustall) die("unable to open list file when spouting");
782 while (fgets(linebuf,MAX_RESPONSE+3,file)) {
783 gi= findgroup(linebuf);
784 if (gi->readfrom != si) continue;
785 if (lwi->einfo && gi->offset &&
786 (space1= strchr(linebuf,' ')) &&
787 ((an1= strtol(space1+1,&space2,10)), *space2 == ' ') &&
788 ((an2= strtol(space2+1,&space3,10)), *space3 == ' ')) {
790 printf("%s %d %d %s",linebuf,
791 adjust(an1,gi->offset),adjust(an2,gi->offset),space3+1);
793 fputs(linebuf,stdout);
796 if (ferror(file)) die("read error on list file");
802 static void lwc_fixed(const struct listwhatinfo *lwi, const char *cachename) {
804 int r= asprintf(&pathname, "/etc/news/nntp-merge/%s", lwi->name);
805 if (r<0) die("allocate for lwc fixed path");
806 FILE *file= fopen(pathname, "r");
808 if (errno == ENOENT) {
809 printf("503 %s not available\r\n",lwi->what);
812 die("open a list file");
815 printf("215 %s (as configured) follows:\r\n",lwi->what);
817 int linestart= 1; /* -1 means comment */
818 while ((c= getc(file)) != EOF) {
820 if (c=='\n') linestart= 1;
824 if (c=='#') { linestart= -1; continue; }
825 if (c=='.') putchar('.');
828 if (c=='\n') { putchar('\r'); linestart=1; }
832 die("error reading list");
834 fputs(".\r\n",stdout);
836 if (file) fclose(file);
840 static void lwc_overview(const struct listwhatinfo *lwi, const char *cachename) {
848 "Xref:full\r\n", stdout);
851 static const struct listwhatinfo listwhatinfos[]= {
852 { "", lwc_bynewsgroup, 1, "active file", 1 },
853 { "active", lwc_bynewsgroup, 1, "active file", 1 },
854 { "active.times", lwc_bynewsgroup, 1, "newsgroups' creation info", 0 },
855 { "newsgroups", lwc_bynewsgroup, 0, "newsgroup titles", 0 },
856 { "subscriptions", lwc_bynewsgroup, 0, "default subscription list", 0 },
857 { "distributions", lwc_fixed, -2, "distributions available" },
858 { "distrib.pats", lwc_fixed, -2, "default distribution patterns" },
859 { "overview.fmt", lwc_overview, -1, "field list" },
863 static int copydatanodot(struct serverinfo *si, FILE *file) {
866 while ((c= getc(si->rfile)) != EOF) {
874 } else if (c == '\n') {
880 while (c != EOF && c != '\r' && c != '\n') {
887 if (ferror(si->rfile)) {
888 printf("503 error getting data from %s: %s\r\n",si->hostname,strerror(errno));
890 printf("503 connection closed during data transfer from %s\r\n",si->hostname);
895 static void cmd_list(char *arg, const struct cmdinfo *cip) {
896 const struct listwhatinfo *lwi;
898 char commandbuf[MAX_COMMAND+40+3];
899 char responsebuf[MAX_RESPONSE+3];
900 char ufnbuf[250], nfnbuf[250], fnbuf[250];
903 struct serverinfo *si;
904 const char *cachename;
906 for (lwi= listwhatinfos; lwi->name && strcasecmp(arg,lwi->name); lwi++);
908 printf("501 LIST %s not available, sorry.\r\n",arg);
911 if (lwi->mustall >= 0) {
912 cachename= lwi->name; if (!*cachename) cachename= (lwi+1)->name;
916 if (lwi->mustall > 0) {
917 sprintf(ufnbuf,"unavailable:%.100s",arg);
918 file= fopen(ufnbuf,"r");
920 printf("501 %s not available because not supported by ",arg);
921 while ((c= getc(file)) != EOF) { if (!isspace(c)) putchar(c); }
927 for (si= servers; lwi->mustall >= 0 && si; si= si->next) {
928 if (!si->searchthis) continue;
929 sprintf(commandbuf,"LIST %.100s",lwi->name);
930 rcode= servercommand(si,commandbuf,responsebuf,0);
931 sprintf(fnbuf,"%.100s:%.100s",si->nickname,cachename);
932 if (rcode == 0x500 || rcode == 0x501) {
934 printf("501 just discovered: %s doesn't support it - it says: %s\r\n",
935 si->hostname,responsebuf);
936 sprintf(nfnbuf,"~%s~%ld",ufnbuf,(long)getpid());
937 file= fopen(nfnbuf,"w"); if (!file) die("unable to create tmp unsup list file");
938 if (fprintf(file,"%s\n",si->hostname) == EOF)
939 die("unable to write unsup list file");
940 if (fclose(file)) die("unable to close unsup list file");
941 if (rename(nfnbuf,ufnbuf)) die("unable to install unsup list file");
944 if (unlink(fnbuf) && errno != ENOENT) die("unable to remove now unsup list");
948 if (rcode == 0x215) {
949 sprintf(nfnbuf,"~%s~%ld",fnbuf,(long)getpid());
950 file= fopen(nfnbuf,"w"); if (!file) die("unable to create tmp list file");
951 if (!copydatanodot(si,file)) {
952 fclose(file); unlink(nfnbuf); return;
954 if (ferror(file) || fclose(file)) die("unable to write tmp list file");
955 if (rename(nfnbuf,fnbuf)) die("unable to install new list file");
956 } else if (rcode == 0x503) {
957 if (lwi->mustall && stat(fnbuf,&stab)) {
958 printf("501 no can do: need all, but file for %s not accessible: %s\r\n",
959 si->hostname,strerror(errno));
962 printf("503 %s said (after LIST): %s\r\n",si->hostname,responsebuf);
966 if (lwi->mustall>=-1)
967 printf("215 %s follows:\r\n",lwi->what);
968 lwi->call(lwi,cachename);
969 if (lwi->mustall>=-1)
973 static int unadjustnumber(char *appendto, char *from, int offset) {
978 an= strtol(from,&p,10);
980 printf("503 bad article range.\r\n");
988 sprintf(appendto+n,"%lu",an);
992 static int unadjustrange(char *appendto, char *from, int offset) {
998 if (!unadjustnumber(appendto,from,offset)) return 0;
1000 strcat(appendto,"-");
1002 return unadjustnumber(appendto,from,offset);
1005 static void cmd_xover(char *arg, const struct cmdinfo *cipa) {
1006 char commandbuf[MAX_COMMAND+40+3];
1007 char responsebuf[MAX_RESPONSE+3];
1008 char xrefbuf[MAX_XREFLINE+3];
1011 struct serverinfo *si;
1012 int an, n, fieldn, c;
1014 if (!currentgroup) {
1015 printf("412 overview of which group ?\r\n");
1018 strcpy(commandbuf,"XOVER ");
1019 if (*arg && !unadjustrange(commandbuf,arg,currentgroup->offset)) return;
1020 si= currentgroup->readfrom;
1021 rcode= servercommand(si,commandbuf,responsebuf,0);
1022 if ((rcode & 0xf00) != 0x200) {
1023 printf("%s\r\n",responsebuf);
1025 } else if (rcode != 0x224) {
1026 printf("503 %s said (after XOVER): %s\r\n",si->hostname,responsebuf);
1029 printf("%s\r\n",responsebuf);
1032 if (c == EOF) serverdataerr(si);
1035 if (c == '\r') c= getc(si->rfile);
1036 if (c == EOF) serverdataerr(si);
1037 if (c == '\n') break;
1038 /* Oh, well, it's clearly bozoid, so what if we dropped a `.' ... */
1042 ungetc(c,si->rfile);
1043 if (fscanf(si->rfile,"%d",&an) != 1) {
1044 while ((c= getc(si->rfile)) != EOF && c != '\n');
1045 if (c == EOF) serverdataerr(si);
1048 printf("%d",adjust(an,currentgroup->offset));
1049 p= xrefbuf; n= 0; fieldn= 0;
1052 if (c == EOF) serverdataerr(si);
1053 if (c == '\r') continue;
1054 if (c != '\t' && c != '\n' && n < MAX_XREFLINE) {
1058 if (c == '\t' || c == '\n') fieldn++;
1060 if (fieldn >= 8 && !strncasecmp("Xref: ",xrefbuf,6)) {
1062 processxref(xrefbuf+6,strlen(xrefbuf)-6,stdout,si);
1064 fputs(xrefbuf,stdout);
1066 if (c == '\n') break;
1076 static void cmd_xhdr(char *arg, const struct cmdinfo *cipa) {
1077 char commandbuf[MAX_COMMAND+40+3];
1078 char responsebuf[MAX_RESPONSE+3];
1079 char linebuf[MAX_XREFLINE+40+3];
1082 struct serverinfo *si;
1085 if (!currentgroup) {
1086 printf("412 headers in which group ?\r\n");
1091 printf("501 need header and range.\r\n");
1095 sprintf(commandbuf,"XHDR %s ",arg);
1096 if (!unadjustrange(commandbuf,p,currentgroup->offset)) return;
1097 si= currentgroup->readfrom;
1098 rcode= servercommand(si,commandbuf,responsebuf,0);
1099 if ((rcode & 0xf00) != 0x200) {
1100 printf("%s\r\n",responsebuf);
1102 } else if (rcode != 0x221) {
1103 printf("503 %s said (after XHDR): %s\r\n",si->hostname,responsebuf);
1106 printf("%s\r\n",responsebuf);
1107 isxref= !strcasecmp(arg,"Xref");
1108 if (!isxref && !currentgroup->offset) {
1109 copydata(si,stdout);
1112 if (!fgets(linebuf,MAX_XREFLINE+40,si->rfile)) serverdataerr(si);
1113 if (!stripcommand(linebuf)) continue;
1114 if (!strcmp(linebuf,".")) break;
1116 if (currentgroup->offset) {
1117 an= strtol(linebuf,&q,10);
1118 printf("%d",adjust(an,currentgroup->offset));
1120 if (isxref && (p= strchr(q,' ')) && strcmp(p+1,"(none)")) {
1123 processxref(p,strlen(p),stdout,si);
1127 fputs("\r\n",stdout);
1133 static void cmd_newgroups_xgtitle(char *arg, const struct cmdinfo *cip) {
1134 char commandbuf[MAX_COMMAND+40+3];
1135 char responsebuf[MAX_RESPONSE+3];
1136 char linebuf[MAX_XREFLINE+40+3];
1138 struct serverinfo *si, *gsi;
1140 sprintf(commandbuf,"%s %s",cip->command,arg);
1142 okrcode= cip->einfo ? 0x282 : 0x231;
1143 if (cip->einfo && !strchr(arg,'*') && !strchr(arg,'?')) {
1145 if (!currentgroup) {
1146 printf("412 title of which group ?\r\n");
1149 si= currentgroup->readfrom;
1151 si= findgroup(arg)->readfrom;
1153 rcode= servercommand(si,commandbuf,responsebuf,0);
1154 printf("%s\r\n",responsebuf);
1155 if ((rcode & 0xf00) == 0x200) copydata(si,stdout);
1159 for (si= servers; si; si= si->next) {
1160 if (!si->searchthis) continue;
1163 for (si= servers; si; si= si->next) {
1164 if (!si->searchthis) continue;
1165 rcode= servercommand(si,commandbuf,responsebuf,0);
1166 if (rcode != okrcode) {
1167 if ((rcode & 0xf00) == 0x200) closeserver(si);
1168 if (cip->einfo) continue;
1169 printf("503 NEWGROUPS not available - %s: %s\r\n",si->hostname,responsebuf);
1170 goto close_files_and_abort;
1172 si->tempfile= tmpfile();
1173 if (!si->tempfile) die("unable to create tempfile");
1174 if (!copydatanodot(si,si->tempfile)) goto close_files_and_abort;
1175 if (ferror(si->tempfile) || fflush(si->tempfile) || fseek(si->tempfile,0,SEEK_SET))
1176 die("unable to write temp file");
1178 printf("%x here you are:\r\n",okrcode);
1179 for (si= servers; si; si= si->next) {
1180 if (!si->searchthis) continue;
1181 if (!si->tempfile) continue;
1182 while (fgets(linebuf,MAX_XREFLINE+3,si->tempfile)) {
1183 gsi= findgroup(linebuf)->readfrom;
1184 if (gsi != si) continue;
1185 fputs(linebuf,stdout);
1187 if (ferror(si->tempfile)) die("read error on temp file");
1188 fclose(si->tempfile); si->tempfile= 0;
1193 close_files_and_abort:
1194 for (si= servers; si; si= si->next) {
1195 if (!si->searchthis) continue;
1196 if (si->tempfile) fclose(si->tempfile);
1201 static void cmd_authinfo(char *arg, const struct cmdinfo *cip) {
1202 struct authdas { struct authdas *next; char *name; };
1204 static struct authdas *alreadydone= 0;
1206 struct authdas *asearch;
1207 struct MD5Context md5ctx;
1208 char *p, *claim, *here, *what;
1210 char buf[500], buf2[MAX_RESPONSE+3];
1211 unsigned char message[16+MAX_SECRET], expect[16], reply[16];
1212 int n, nused, matching, ifmatch;
1213 struct timeval timevab;
1216 struct permission *pi;
1218 if (strcasecmp(strtok(arg," "),"generic") ||
1219 strcmp(strtok(0," "),"md5cookie1way")) {
1220 printf("501 please use AUTHINFO GENERIC md5cookie1way <claim>.\r\n");
1223 claim= strtok(0," ");
1224 if (strtok(0," ")) {
1225 printf("501 something after claim.\r\n");
1228 for (asearch= alreadydone;
1229 asearch && strcmp(asearch->name,claim);
1230 asearch= asearch->next);
1232 printf("502 you are already user/group %s; need other group for more access.\r\n",
1236 file= fopen("md5cookies","r");
1238 printf("503 couldn't open md5cookies file: %s\r\n",strerror(errno));
1243 if (!fgets(buf,sizeof(buf),file)) {
1245 printf("503 error reading md5cookies file: %s\r\n",strerror(errno));
1247 printf("502 who did you say you were ?\r\n");
1252 if (!stripcommand(buf) || *buf == '#' || !*buf) continue;
1253 here= strtok(buf," \t");
1254 if (!strcmp(buf,"@")) {
1255 what= strtok(0," \t");
1256 if (!strcmp(what,"allow")) {
1258 } else if (!strcmp(what,"refuse")) {
1264 if (!what) continue;
1265 if (fnmatch(what,theirfqdn ? theirfqdn : "_unknown_",0)) continue;
1267 } else if (!strcmp(here,claim)) {
1268 if (matching) break;
1273 if (gettimeofday(&timevab,(void*)0)) die("unable to gettimeofday for nonce");
1274 memcpy(message,&timevab.tv_sec,4);
1275 ul= timevab.tv_usec; memcpy(message+4,&ul,4);
1276 memcpy(message+8,&peername.sin_addr,4);
1277 memcpy(message+12,&peername.sin_port,2);
1278 us= getpid(); memcpy(message+14,&us,2);
1282 printf("502 md5cookies file is missing secret for you.\r\n");
1285 nused= parsehex(p,message+16,MAX_SECRET);
1288 MD5Update(&md5ctx,message,16+nused);
1289 MD5Final(expect,&md5ctx);
1292 for (n=0; n<16; n++) {
1293 printf("%s%02x", n?":":"", message[n]);
1296 if (ferror(stdout) || fflush(stdout)) die("unable to write auth challenge");
1298 if (!fgets(buf2,MAX_RESPONSE,stdin)) {
1299 if (ferror(stdin)) die("client connection failed during crypto");
1302 if (strncasecmp(buf2,"MD5 ",4)) {
1303 printf("502 expecting MD5.\r\n");
1306 nused= parsehex(buf2+4,reply,17);
1308 printf("502 expecting 16 pairs (got %d).\r\n",nused);
1311 if (memcmp(reply,expect,16)) {
1312 printf("502 pull the other one, it's got bells on.\r\n");
1315 asearch= xmalloc(sizeof(struct authdas));
1316 asearch->name= xstrdup(claim);
1317 asearch->next= alreadydone;
1318 alreadydone= asearch;
1320 while ((p= strtok(0," \t"))) {
1322 for (pi= permissions; pi; pi= pi->next) {
1323 if (strcmp(pi->name,p)) continue;
1330 lastdoneauth= asearch->name;
1334 const struct cmdinfo cmdinfos[]= {
1335 { "ARTICLE", cmd_article, 3 },
1336 { "AUTHINFO", cmd_authinfo },
1337 { "XAUTHINFO", cmd_authinfo },
1338 { "HEAD", cmd_article, 1 },
1339 { "BODY", cmd_article, 2 },
1340 { "STAT", cmd_article, 0 },
1341 { "GROUP", cmd_group },
1342 { "HELP", cmd_help },
1343 { "IHAVE", cmd_ihave },
1344 { "LAST", cmd_last_next },
1345 { "LIST", cmd_list },
1346 { "NEWGROUPS", cmd_newgroups_xgtitle, 0 },
1347 { "NEWNEWS", cmd_newnews },
1348 { "NEXT", cmd_last_next },
1349 { "NOOP", cmd_noop },
1350 { "POST", cmd_post },
1351 { "QUIT", cmd_quit },
1352 { "SLAVE", cmd_slave },
1353 { "DATE", cmd_date },
1354 { "LISTGROUP", cmd_listgroup },
1355 { "XLISTGROUP", cmd_listgroup },
1356 { "MODE", cmd_mode },
1357 { "XMODE", cmd_mode },
1358 { "XMERGEINFO", cmd_xmergeinfo },
1359 { "XGTITLE", cmd_newgroups_xgtitle, 1 },
1360 { "XHDR", cmd_xhdr },
1361 { "XOVER", cmd_xover },
1363 { "XPAT", cmd_unimplemented },
1364 { "XPATH", cmd_unimplemented },
1368 static void cmd_help(char *arg, const struct cmdinfo *cipa) {
1369 /* close outstanding channels */
1370 const struct cmdinfo *cip;
1371 if (!noargs(arg)) return;
1372 printf("100 commands are (* = implemented)\r\n");
1373 for (cip= cmdinfos; cip->command; cip++) {
1374 printf(" %s %s\r\n",
1375 cip->call == cmd_unimplemented ? " " : "*",
1381 int main(int argc, char **argv) {
1385 const struct cmdinfo *cip;
1388 cmdbuf[sizeof(cmdbuf)-1]= 0;
1389 if (gethostname(cmdbuf,sizeof(cmdbuf)-1)) die("gethostname");
1390 if (cmdbuf[sizeof(cmdbuf)-1]) die("gethostname overflow");
1391 if (!(he= gethostbyname(cmdbuf))) die("gethostbyname");
1392 myfqdn= xstrdup(he->h_name);
1393 myxref= xstrdup(he->h_name);
1395 if (chdir("/var/lib/news/merge")) die("chdir");
1398 setvbuf(stdin,0,_IOLBF,0);
1399 setvbuf(stdout,0,_IOFBF,10240);
1400 signal(SIGPIPE,SIG_IGN);
1401 nu= sizeof(peername);
1402 memset(&peername,0,nu);
1403 if (getpeername(0,(struct sockaddr*)&peername,&nu)) {
1404 theirfqdn= "_direct_";
1406 he= gethostbyaddr((void*)&peername.sin_addr,sizeof(peername.sin_addr),AF_INET);
1407 if (he && he->h_name) theirfqdn= xstrdup(he->h_name);
1410 printf("200 %s nntp-merge ready (posting might be ok).\r\n",myfqdn);
1413 if (fflush(stdout)) die("flush stdout");
1414 if (ferror(stdout) || ferror(stdin)) die("client connection died");
1415 waitpid(-1,0,WNOHANG); /* ignore all children */
1416 errno=0; if (!fgets(cmdbuf,sizeof(cmdbuf),stdin)) {
1417 if (ferror(stdin)) die("client closed"); else exit(0);
1419 if (!stripcommand(cmdbuf)) continue;
1420 for (cip= cmdinfos; cip->command; cip++) {
1421 n= strlen(cip->command);
1422 if (!strncasecmp(cip->command,cmdbuf,n) &&
1423 (!cmdbuf[n] || isspace(cmdbuf[n])))
1427 while (isspace(cmdbuf[n])) n++;
1428 cip->call(cmdbuf+n,cip);
1430 printf("500 huh?\r\n");