16 #include <sys/types.h>
18 #include <arpa/inet.h>
19 #include <netinet/in.h>
21 #include "nntp-merge.h"
24 char *myfqdn, *myxref, *lastdoneauth= 0;
25 const char *theirfqdn= 0;
26 char currentgroupname[MAX_COMMAND+3];
27 struct groupinfo *currentgroup;
28 struct sockaddr_in peername;
30 int stripcommand(char *buf) {
35 if (buf[n-1] != '\n') return 0;
36 while (n>0 && isspace(buf[n-1])) n--;
41 void die(const char *msg) {
44 printf("400 server failure: %s - %s\r\n",msg,strerror(errno));
48 static int parsehex(char *p, unsigned char *up, int maxlen) {
52 while (nused < MAX_SECRET && (n=-1, sscanf(p,"%x%n",&v,&n) >0) && n>0) {
61 enum { scf_norecover=001, scf_nogroup };
63 void closeserver(struct serverinfo *server) {
64 fclose(server->rfile); server->rfile= 0;
65 fclose(server->wfile); server->wfile= 0;
68 int decoderesponse(char response[MAX_RESPONSE+3], unsigned long *rvp,
69 struct serverinfo *server) {
70 char temp[MAX_RESPONSE+3];
71 if (!isdigit(response[0]) || !isdigit(response[1]) || !isdigit(response[2]) ||
72 !(isspace(response[3]) || !response[3])) {
73 strcpy(temp,response);
74 sprintf(response,"503 server %.100s produced garbage: %.100s",
75 server->hostname,temp);
76 closeserver(server); return 0;
78 *rvp= strtol(response,0,16);
83 static int realservercommand(struct serverinfo *server,
84 const char command[], char response[MAX_RESPONSE+3],
87 int servercommand(struct serverinfo *server,
88 const char command[], char response[MAX_RESPONSE+3],
92 fprintf(stderr,"[%03o]>>>%s: %s\n",flags,server->nickname,command);
93 rcode= realservercommand(server,command,response,flags);
94 fprintf(stderr,"[%03x]<<<%s: %s\n",rcode,server->nickname,response);
97 static int realservercommand(struct serverinfo *server,
98 const char command[], char response[MAX_RESPONSE+3],
103 struct sockaddr_in sin;
105 int rpipefds[2], wpipefds[2], rfd, wfd, l, n;
107 char temp[MAX_RESPONSE+3], *p;
108 unsigned char message[16+MAX_SECRET], cryptresponse[16];
109 struct MD5Context md5ctx;
112 /* response will be null-terminated and have trailing whitespace
113 * removed including \r\n
114 * (but indeed the extra two spaces used by this routine are for \r\n)
117 if (fprintf(server->wfile,"%s\r\n",command) == EOF ||
118 fflush(server->wfile) == EOF) goto conn_broken;
119 if (!fgets(response,MAX_RESPONSE+3,server->rfile)) goto conn_broken;
120 if (!stripcommand(response)) goto conn_broken;
121 if (!strncmp(response,"400",3)) goto conn_broken;
122 goto message_sent_response_ok;
125 if (flags & scf_norecover) {
126 strcpy(response,"205 it had already gone"); return 0x205;
129 if (server->program) {
130 if (pipe(rpipefds) || pipe(wpipefds)) die("unable to create pipe");
131 if ((c1= fork()) == -1) die("unable to fork");
133 close(0); close(1); dup(wpipefds[0]); dup(rpipefds[1]);
134 close(wpipefds[0]); close(wpipefds[1]);
135 close(rpipefds[0]); close(rpipefds[1]);
136 signal(SIGPIPE,SIG_DFL);
137 if (lastdoneauth) setenv("NNTPMERGE_AUTHD_AS",lastdoneauth,1);
138 execl(server->hostname,server->hostname,(char*)0);
139 printf("400 exec %s: %s\r\n",server->hostname,strerror(errno));
142 rfd= rpipefds[0]; close(rpipefds[1]);
143 wfd= wpipefds[1]; close(wpipefds[0]);
145 he= gethostbyname(server->hostname);
147 sprintf(response,"503 unable to find address of %.100s.",server->hostname);
150 if (he->h_addrtype != AF_INET) {
151 sprintf(response,"503 address of %.100s is of unknown type 0x%x.",
152 server->hostname,he->h_addrtype);
155 for (alist= he->h_addr_list; *alist; alist++) {
156 rfd= socket(PF_INET,SOCK_STREAM,0);
157 if (rfd == -1) die("unable to create TCP socket");
158 memset(&sin,0,sizeof(sin));
159 sin.sin_family= AF_INET;
160 sin.sin_addr= *(struct in_addr*)*alist;
161 sin.sin_port= htons(server->port);
162 if (!connect(rfd,(struct sockaddr*)&sin,sizeof(sin))) break;
163 sprintf(response,"503 unable to connect to %.100s (%.50s:%u): %.100s.",
164 server->hostname, inet_ntoa(sin.sin_addr), server->port, strerror(errno));
167 if (!*alist) return 0x503;
169 if (wfd < 0) die("failed to dup socket fd");
171 server->rfile= fdopen(rfd,"r");
172 server->wfile= fdopen(wfd,"w");
173 if (!server->rfile || !server->wfile) die("failed to fdopen");
174 if (setvbuf(server->rfile,0,_IOLBF,0) || setvbuf(server->wfile,0,_IOLBF,0))
175 die("failed to setvbuf linebuf");
176 if (!fgets(response,MAX_RESPONSE+3,server->rfile)) {
177 sprintf(response,"503 failed to read from server %.100s: %.100s.",
178 server->hostname,strerror(errno));
180 if (!stripcommand(response)) {
181 sprintf(response,"503 server %.100s is spouting null garbage.",server->hostname);
182 closeserver(server); return 0x503;
184 if (!strncmp(response,"480",3) &&
185 response[3] == ' ' && (p= strchr(response+4,' ')) &&
186 ((*p=0), (parsehex(response+4,message,17) == 16)) &&
187 (file= fopen(SESAMEFILE,"r"))) {
189 if (!fgets(temp,sizeof(temp),file)) {
191 sprintf(response,"503 error reading sesame file: %.100s",strerror(errno));
193 sprintf(response,"503 no sesame for server nicknamed %.100s",server->nickname);
195 fclose(file); closeserver(server); return 0x503;
197 } while (!stripcommand(temp) || !*temp || *temp == '#' ||
198 !(p= strchr(temp,' ')) || ((*p++= 0), strcmp(temp,server->nickname)));
199 fclose(file); l= strlen(p);
201 sprintf(response,"503 sesame file secret for %.100s is too long",server->nickname);
202 closeserver(server); return 0x503;
204 memcpy(message+16,p,l);
207 MD5Update(&md5ctx,message,16+l);
208 MD5Final(cryptresponse,&md5ctx);
210 fprintf(server->wfile,"PASS ");
211 for (n=0; n<16; n++) {
212 fprintf(server->wfile,"%s%02x", n?":":"", cryptresponse[n]);
214 fprintf(server->wfile,"\r\n");
215 if (fflush(server->wfile) == EOF || ferror(server->wfile)) {
216 sprintf(response,"503 error sending sesame response to %.100s: %.100s",
217 server->hostname,strerror(errno));
218 closeserver(server); return 0x503;
220 if (!fgets(response,MAX_RESPONSE+3,server->rfile)) {
221 sprintf(response,"503 error reading from %.100s after sesame: %.100s.",
222 server->hostname,strerror(errno));
223 closeserver(server); return 0x503;
226 if (!strncmp(response,"400",3)) goto server_is_unavailable;
227 if (response[0] != '2') {
228 strcpy(temp,response);
230 sprintf(response,"503 server %.100s refuses to talk: %.100s",server->hostname,temp);
231 closeserver(server); return 0x503;
234 if (fprintf(server->wfile,"%s\r\n",server->send) == EOF ||
235 fflush(server->wfile) == EOF) {
236 sprintf(response,"503 error sending %.50s to %.100s: %.100s",
237 server->send,server->hostname,strerror(errno));
238 closeserver(server); return 0x503;
240 if (!fgets(response,MAX_RESPONSE+3,server->rfile)) {
241 sprintf(response,"503 error reading from %.100s after %.50s: %.100s.",
242 server->hostname,server->send,strerror(errno));
243 closeserver(server); return 0x503;
246 if (!(flags & scf_nogroup) && currentgroup && currentgroup->readfrom == server) {
247 if (fprintf(server->wfile,"GROUP %s\r\n",currentgroupname) == EOF ||
248 fflush(server->wfile) == EOF) {
249 sprintf(response,"503 error sending GROUP to %.100s: %.100s",
250 server->hostname,strerror(errno));
251 closeserver(server); return 0x503;
253 if (!fgets(response,MAX_RESPONSE+3,server->rfile)) {
254 sprintf(response,"503 error reading from %.100s after GROUP: %.100s.",
255 server->hostname,strerror(errno));
256 closeserver(server); return 0x503;
259 if (fprintf(server->wfile,"%s\r\n",command) == EOF ||
260 fflush(server->wfile) == EOF) {
261 sprintf(response,"503 error sending to %.100s: %.100s",
262 server->hostname,strerror(errno));
263 closeserver(server); return 0x503;
265 if (!fgets(response,MAX_RESPONSE+3,server->rfile)) {
266 sprintf(response,"503 error reading from %.100s: %.100s.",server->hostname,
268 closeserver(server); return 0x503;
270 if (!stripcommand(response)) {
271 sprintf(response,"503 server %.100s is replying with null garbage.",
273 closeserver(server); return 0x503;
275 message_sent_response_ok:
276 if (!decoderesponse(response,&rv,server)) return 0x503;
277 if (rv != 0x400) return rv;
278 server_is_unavailable:
279 strcpy(temp,response);
280 sprintf(response,"503 server %.100s unavailable: %.200s",
281 server->hostname,temp);
282 closeserver(server); return 0x503;
285 static int adjust(int original, int offset) {
286 if (offset < 0 && original < -offset) return 0;
287 return original+offset;
290 void serverdataerr(struct serverinfo *si) {
291 if (ferror(si->rfile)) {
292 printf("\r\n\r\n*** problem detected by nntp-merge:\r\n"
293 "error reading data from server %s: %s\r\n",
294 si->hostname, strerror(errno));
296 printf("\r\n\r\n*** problem detected by nntp-merge:\r\n"
297 "server %s closed connection while sending data\r\n",
300 printf("closing connection on you, sorry.\r\n");
304 static int noargs(const char *arg) {
306 printf("501 no arguments please.\r\n");
310 static void cmd_unimplemented(char *arg, const struct cmdinfo *cip) { \
311 printf("500 %s not implemented.\r\n",cip->command); \
314 static void cmd_quit(char *arg, const struct cmdinfo *cip) {
315 char responsebuf[MAX_RESPONSE+3];
316 struct serverinfo *si;
318 if (!noargs(arg)) return;
319 for (si= servers; si; si= si->next) {
320 if (!si->rfile) continue;
321 servercommand(si,"QUIT",responsebuf,scf_norecover);
322 fclose(si->wfile); fclose(si->rfile); /* we're not interested in the response */
324 printf("205 goodbye.\r\n");
328 static void cmd_date(char *arg, const struct cmdinfo *cip) {
329 char responsebuf[MAX_RESPONSE+3];
330 struct tm *brokendown;
334 struct serverinfo *si;
336 if (!noargs(arg)) return;
338 brokendown= gmtime(&now); if (!brokendown) die("unable to break down date/time");
339 n= strftime(buf,sizeof(buf)-1,"%Y%m%d%H%M%S",brokendown);
340 if (n <= 0 || n >= sizeof(buf)-1 || strlen(buf) != 14)
341 die("unable to format date/time");
342 for (si= servers; si; si= si->next) {
343 if (!si->searchthis) continue;
344 rcode= servercommand(si,"DATE",responsebuf,0);
345 if (rcode == 0x111 && responsebuf[3]==' ' &&
346 strspn(responsebuf+4,"0123456789")==14 &&
347 responsebuf[18]==0 &&
348 strcmp(responsebuf+4,buf) < 0)
349 strcpy(buf,responsebuf+4);
351 printf("111 %s\r\n",buf);
354 static void cmd_noop(char *arg, const struct cmdinfo *cip) {
355 if (!noargs(arg)) return;
356 printf("200 doing that has no effect.\r\n");
359 static void cmd_slave(char *arg, const struct cmdinfo *cip) {
360 if (!noargs(arg)) return;
361 printf("202 bind me, whip me.\r\n");
364 static void cmd_ihave(char *arg, const struct cmdinfo *cip) {
365 printf("502 please don't feed me.\r\n");
368 static void cmd_newnews(char *arg, const struct cmdinfo *cip) {
369 printf("502 get a proper feed, slurping through this merger is silly.\r\n");
372 static void cmd_mode(char *arg, const struct cmdinfo *cip) {
373 if (!strcasecmp(arg,"READER")) {
374 printf("200 %s that was uncalled-for.\r\n",myfqdn);
376 printf("501 you want me to do *what* ?\r\n");
380 static void authrequired(void) {
382 printf("480 the sight of %s is but dim.\r\n",lastdoneauth);
384 printf("480 identify yourself ! friend or foe ?\r\n");
388 int stillrestricted(struct permission **pip) {
390 if (!(*pip)->authd) return 1;
394 static int groupreadable(struct groupinfo *gi) {
395 if (!stillrestricted(&gi->restrictto) ||
396 !stillrestricted(&gi->readonlyto)) return 1;
401 static void cmd_group(char *arg, const struct cmdinfo *cip) {
402 struct groupinfo *gi;
403 int estcount, first, last, startfrom;
404 char commandbuf[MAX_COMMAND+3];
405 char responsebuf[MAX_RESPONSE+3];
408 if (strlen(arg) > MAX_COMMAND-12) {
409 printf("501 too long.\r\n");
413 gi= findgroup(arg); if (!groupreadable(gi)) return;
415 printf("501 which group, then ?\r\n");
419 sprintf(commandbuf,"%s %s",cip->command,arg);
420 rcode= servercommand(gi->readfrom,commandbuf,responsebuf,scf_nogroup);
421 if ((rcode & 0xf00) != 0x200) {
422 printf("%s\r\n",responsebuf);
424 } else if (gi->offset) {
426 if (sscanf(responsebuf,"211 %d %d %d %n", &estcount,&first,&last,&startfrom) != 3 ||
428 printf("503 %s said (after GROUP): %s\r\n",gi->readfrom->hostname,responsebuf);
429 closeserver(gi->readfrom); return;
431 printf("211 %d %d %d %s\r\n",
432 estcount, adjust(first,gi->offset), adjust(last,gi->offset),
433 responsebuf + startfrom);
435 printf("%s\r\n",responsebuf);
438 strcpy(currentgroupname,arg);
441 int copydatafile(FILE *from, FILE *to) {
442 /* `to' may be null */
445 stopstate= 1; /* 0: middle of line; 1: start of line; 2: after `.' */
446 while ((c= getc(from)) != EOF) {
448 if (c == '\r') continue;
451 if (c == '\n') stopstate= 1;
454 stopstate= (c == '.') ? 2 : (c == '\n') ? 1 : 0;
457 if (c == '\n') return 1;
465 static void copydata(struct serverinfo *from, FILE *to) {
466 if (!copydatafile(from->rfile,to)) serverdataerr(from);
469 static void cmd_listgroup(char *arg, const struct cmdinfo *cip) {
470 struct groupinfo *gi;
471 char commandbuf[MAX_COMMAND+3];
472 char responsebuf[MAX_RESPONSE+3];
477 if (strlen(arg) > MAX_COMMAND-12) {
478 printf("501 too long.\r\n");
482 gi= findgroup(arg); if (!groupreadable(gi)) return;
483 } else if (!currentgroup) {
484 printf("412 list of articles in which group ?\r\n");
490 sprintf(commandbuf,"%s %s",cip->command,arg);
491 rcode= servercommand(gi->readfrom,commandbuf,responsebuf,scf_nogroup);
492 if ((rcode & 0xf00) != 0x200) {
493 printf("%s\r\n",responsebuf);
496 if (rcode != 0x211) {
497 printf("503 %s said (after %s): %s\r\n",
498 gi->readfrom->hostname,cip->command,responsebuf);
501 printf("211 article list:\r\n");
503 copydata(gi->readfrom,stdout);
506 if (!fgets(responsebuf,MAX_RESPONSE+3,gi->readfrom->rfile))
507 serverdataerr(gi->readfrom);
508 if (!strcmp(responsebuf,".\r\n") || !strcmp(responsebuf,".\n"))
510 an= strtol(responsebuf,&p,10);
511 printf("%d%s",adjust(an,gi->offset),p);
518 static void pserver(struct serverinfo *si) {
519 printf(" %s= %s:%u %s (%s)\r\n",
521 si->hostname, si->port,
522 si->rfile ? "open" : "closed",
523 si->send ? si->send : "-");
526 static void cmd_xmergeinfo(char *arg, const struct cmdinfo *cip) {
527 struct groupinfo *gi;
530 if (strlen(arg) > MAX_COMMAND-6) {
531 printf("501 too long.\r\n");
535 printf("100 %s\r\n read:\r\n",arg);
536 pserver(gi->readfrom);
537 printf(" post:\r\n");
538 for (i= 0; gi->postto[i]; i++) pserver(gi->postto[i]);
539 printf(" offset %d\r\n"
540 " auth %s / read-auth %s\r\n"
543 gi->restrictto && gi->restrictto->name ? gi->restrictto->name : "<none>",
544 gi->readonlyto && gi->readonlyto->name ? gi->readonlyto->name : "<none>");
547 static void cmd_help(char *arg, const struct cmdinfo *cip);
549 static int articleselectresponse(char responsebuf[MAX_RESPONSE+3], char *typecharp,
550 struct serverinfo *si, const struct cmdinfo *cip,
551 char modifiedresponse[MAX_RESPONSE+3]) {
555 if (sscanf(responsebuf,"22%c %d %n",typecharp,&ran,&startfrom) != 2 ||
556 startfrom == -1 || !strchr("0123",*typecharp)) {
557 printf("503 %s said (after %s): %s\r\n",
558 si->hostname, cip->command, responsebuf);
561 ran= (currentgroup && si == currentgroup->readfrom)
562 ? adjust(ran,currentgroup->offset) : 0;
563 sprintf(modifiedresponse,"22%c %d %s",*typecharp,ran,responsebuf+startfrom);
567 static void cmd_last_next(char *arg, const struct cmdinfo *cip) {
569 char responsebuf[MAX_RESPONSE+3];
570 char modresponse[MAX_RESPONSE+3];
573 if (!noargs(arg)) return;
575 printf("412 use GROUP and ARTICLE first.\r\n");
578 rcode= servercommand(currentgroup->readfrom,cip->command,responsebuf,0);
579 if ((rcode & 0xf00) != 0x200) {
580 printf("%s\r\n",responsebuf); return;
582 articleselectresponse(responsebuf,&dummy,currentgroup->readfrom,cip,modresponse);
583 printf("%s\r\n",modresponse);
586 static void processxref(char *linebufplus6, int n, FILE *writeto,
587 struct serverinfo *gotfrom) {
588 char *space, *cpos, *colon;
589 struct groupinfo *gi;
592 linebufplus6[n++]= ' ';
595 space= strchr(cpos,' ');
598 fprintf(writeto,"%s",myxref);
600 while ((space= strchr(cpos,' '))) {
602 colon= strrchr(cpos,':');
606 if (gi->readfrom != gotfrom) {
607 cpos= space; continue;
609 fprintf(writeto," %s:",cpos);
610 if (gi->offset && isdigit(*colon)) {
611 an= strtol(colon,&colon,10);
612 fprintf(writeto,"%d", adjust(an,gi->offset));
614 fputs(colon,writeto);
616 if (*cpos) putc(' ',writeto);
623 static void cmd_article(char *arg, const struct cmdinfo *cip) {
624 struct serverinfo *si;
625 struct groupinfo *gi;
626 char commandbuf[MAX_COMMAND+40+3];
627 char responsebuf[MAX_RESPONSE+3];
628 char modresponse[MAX_RESPONSE+3];
629 char linebuf[MAX_XREFLINE+3];
632 int rcode, n, c, realeinfo;
634 const char *realcommand;
635 int checkpermission= 0;
638 realcommand= cip->command;
641 switch (cip->einfo) {
642 case 0: realcommand= "HEAD"; break;
643 case 2: realcommand= "ARTICLE"; break;
645 sprintf(commandbuf,"%s %s",realcommand,arg);
647 for (si= servers; si; si= si->next) {
648 if (!si->searchthis) continue;
649 rcode= servercommand(si,commandbuf,responsebuf,0);
650 if ((rcode & 0xf00) == 0x200) break;
652 } else if (isdigit(*arg) || !*arg) {
654 printf("412 use GROUP first.\r\n");
658 an= strtol(arg,&p,10);
659 if (*p) { printf("501 bad article number.\r\n"); return; }
660 if (an < currentgroup->offset)
661 printf("423 offset makes number negative, so no such article.\r\n");
662 sprintf(commandbuf,"%s %lu",cip->command,an - currentgroup->offset);
664 sprintf(commandbuf,"%s",cip->command);
666 si= currentgroup->readfrom;
667 rcode= servercommand(si,commandbuf,responsebuf,0);
669 printf("501 optional arg must be message-id or article number.\r\n");
672 if ((rcode & 0xff0) != 0x220) {
673 printf("%s\r\n",responsebuf);
676 switch (rcode & 0x00f) {
677 case 0: realeinfo= 03; break;
678 case 1: realeinfo= 01; break;
679 case 2: realeinfo= 02; break;
680 case 3: realeinfo= 00; break;
681 default: realeinfo= -1;
683 if (realeinfo == (cip->einfo | 01)) {
684 switch (cip->einfo) {
685 case 0: responsebuf[2]= '3'; break;
686 case 2: responsebuf[2]= '2'; break;
688 } else if (realeinfo != cip->einfo) {
689 printf("503 %s gave bad code (in response to %s): %s\r\n",
690 si->hostname,realcommand,responsebuf);
695 if (!articleselectresponse(responsebuf,&typechar,si,cip,modresponse)) return;
696 if (checkpermission) {
697 file= tmpfile(); if (!file) die("failed to create temp file");
699 printf("%s\r\n",modresponse);
700 if (!realeinfo) return;
703 if (realeinfo & 01) {
705 if (!fgets(linebuf,MAX_XREFLINE+3,si->rfile)) serverdataerr(si);
708 while (n>0 && ((c= linebuf[n-1]) == '\n' || c == '\r')) n--;
710 if (!strcmp(linebuf,".")) { fputs(".\r\n",file); break; }
711 if ((cip->einfo & 01) && !strncasecmp(linebuf,"Xref: ",6)) {
712 fprintf(file,"Xref: ");
713 processxref(linebuf+6,n-6,file,si);
717 if (cip->einfo & 01) fprintf(file,"%s\r\n",linebuf);
718 if (checkpermission && !strncasecmp(linebuf,"Newsgroups: ",12)) {
719 p= strtok(linebuf," "); assert(p);
720 while ((p= strtok(0,","))) {
722 if (!stillrestricted(&gi->restrictto) ||
723 !stillrestricted(&gi->readonlyto)) break;
725 if (!p) checkpermission= -1; /* Don't return, we must clean up &c */
727 if (n == 0 && realeinfo == 03) break; /* end of header, go on to body */
731 if (realeinfo & 02) {
736 switch (checkpermission) {
740 if (ferror(file) && fflush(file)) die("error writing article temp file");
741 printf("%s\r\n",modresponse);
742 if (!cip->einfo) { fclose(file); return; }
743 if (fseek(file,0,SEEK_SET)) die("unable to rewind article temp file");
744 while ((c= getc(file)) != EOF) putchar(c);
745 if (ferror(file)) die("unable to read article temp file");
755 struct listwhatinfo {
757 void (*call)(const struct listwhatinfo *lwi, const char *cachename);
763 static void lwc_bynewsgroup(const struct listwhatinfo *lwi, const char *cachename) {
764 struct serverinfo *si;
765 struct groupinfo *gi;
767 char linebuf[MAX_RESPONSE+3];
770 char *space1, *space2, *space3;
772 for (si= servers; si; si= si->next) {
773 if (!si->searchthis) continue;
774 sprintf(fnbuf,"%.100s:%.100s",si->nickname,cachename);
775 file= fopen(fnbuf,"r");
777 if (lwi->mustall) die("unable to open list file when spouting");
780 while (fgets(linebuf,MAX_RESPONSE+3,file)) {
781 gi= findgroup(linebuf);
782 if (gi->readfrom != si) continue;
783 if (lwi->einfo && gi->offset &&
784 (space1= strchr(linebuf,' ')) &&
785 ((an1= strtol(space1+1,&space2,10)), *space2 == ' ') &&
786 ((an2= strtol(space2+1,&space3,10)), *space3 == ' ')) {
788 printf("%s %d %d %s",linebuf,
789 adjust(an1,gi->offset),adjust(an2,gi->offset),space3+1);
791 fputs(linebuf,stdout);
794 if (ferror(file)) die("read error on list file");
800 static void lwc_fixed(const struct listwhatinfo *lwi, const char *cachename) {
805 struct disdone { struct disdone *next; char *name; } *done, *search, *tmp;
806 struct serverinfo *si;
808 char linebuf[MAX_RESPONSE+3];
812 for (si= servers; si; si= si->next) {
813 if (!si->searchthis) continue;
814 sprintf(fnbuf,"%.100s:%.100s",si->nickname,cachename);
815 file= fopen(fnbuf,"r");
817 while (fgets(linebuf,MAX_RESPONSE+3,file)) {
818 p= linebuf; while (*p && !isspace(*p)) p++;
821 search && strcasecmp(linebuf,search->name);
822 search= search->next);
823 if (search) continue;
824 tmp= xmalloc(sizeof(struct disdone));
825 tmp->name= xstrdup(linebuf);
829 fputs(linebuf,stdout);
831 if (ferror(file)) die("read error on list file");
834 for (search= done; search; search= tmp) {
835 tmp= search->next; free(search->name); free(search);
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_distributions, 0, "distributions available" },
858 { "overview.fmt", lwc_overview, -1, "field list" },
862 static int copydatanodot(struct serverinfo *si, FILE *file) {
865 while ((c= getc(si->rfile)) != EOF) {
873 } else if (c == '\n') {
879 while (c != EOF && c != '\r' && c != '\n') {
886 if (ferror(si->rfile)) {
887 printf("503 error getting data from %s: %s\r\n",si->hostname,strerror(errno));
889 printf("503 connection closed during data transfer from %s\r\n",si->hostname);
894 static void cmd_list(char *arg, const struct cmdinfo *cip) {
895 const struct listwhatinfo *lwi;
897 char commandbuf[MAX_COMMAND+40+3];
898 char responsebuf[MAX_RESPONSE+3];
899 char ufnbuf[250], nfnbuf[250], fnbuf[250];
902 struct serverinfo *si;
903 const char *cachename;
905 for (lwi= listwhatinfos; lwi->name && strcasecmp(arg,lwi->name); lwi++);
907 printf("501 LIST %s not available, sorry.\r\n",arg);
910 if (lwi->mustall >= 0) {
911 cachename= lwi->name; if (!*cachename) cachename= (lwi+1)->name;
915 if (lwi->mustall > 0) {
916 sprintf(ufnbuf,"unavailable:%.100s",arg);
917 file= fopen(ufnbuf,"r");
919 printf("501 %s not available because not supported by ",arg);
920 while ((c= getc(file)) != EOF) { if (!isspace(c)) putchar(c); }
926 for (si= servers; lwi->mustall >= 0 && si; si= si->next) {
927 if (!si->searchthis) continue;
928 sprintf(commandbuf,"LIST %.100s",lwi->name);
929 rcode= servercommand(si,commandbuf,responsebuf,0);
930 sprintf(fnbuf,"%.100s:%.100s",si->nickname,cachename);
931 if (rcode == 0x500 || rcode == 0x501) {
933 printf("501 just discovered: %s doesn't support it - it says: %s\r\n",
934 si->hostname,responsebuf);
935 sprintf(nfnbuf,"~%s~%ld",ufnbuf,(long)getpid());
936 file= fopen(nfnbuf,"w"); if (!file) die("unable to create tmp unsup list file");
937 if (fprintf(file,"%s\n",si->hostname) == EOF)
938 die("unable to write unsup list file");
939 if (fclose(file)) die("unable to close unsup list file");
940 if (rename(nfnbuf,ufnbuf)) die("unable to install unsup list file");
943 if (unlink(fnbuf) && errno != ENOENT) die("unable to remove now unsup list");
947 if (rcode == 0x215) {
948 sprintf(nfnbuf,"~%s~%ld",fnbuf,(long)getpid());
949 file= fopen(nfnbuf,"w"); if (!file) die("unable to create tmp list file");
950 if (!copydatanodot(si,file)) {
951 fclose(file); unlink(nfnbuf); return;
953 if (ferror(file) || fclose(file)) die("unable to write tmp list file");
954 if (rename(nfnbuf,fnbuf)) die("unable to install new list file");
955 } else if (rcode == 0x503) {
956 if (lwi->mustall && stat(fnbuf,&stab)) {
957 printf("501 no can do: need all, but file for %s not accessible: %s\r\n",
958 si->hostname,strerror(errno));
961 printf("503 %s said (after LIST): %s\r\n",si->hostname,responsebuf);
965 printf("215 %s follows:\r\n",lwi->what);
966 lwi->call(lwi,cachename);
970 static int unadjustnumber(char *appendto, char *from, int offset) {
975 an= strtol(from,&p,10);
977 printf("503 bad article range.\r\n");
985 sprintf(appendto+n,"%lu",an);
989 static int unadjustrange(char *appendto, char *from, int offset) {
995 if (!unadjustnumber(appendto,from,offset)) return 0;
997 strcat(appendto,"-");
999 return unadjustnumber(appendto,from,offset);
1002 static void cmd_xover(char *arg, const struct cmdinfo *cipa) {
1003 char commandbuf[MAX_COMMAND+40+3];
1004 char responsebuf[MAX_RESPONSE+3];
1005 char xrefbuf[MAX_XREFLINE+3];
1008 struct serverinfo *si;
1009 int an, n, fieldn, c;
1011 if (!currentgroup) {
1012 printf("412 overview of which group ?\r\n");
1015 strcpy(commandbuf,"XOVER ");
1016 if (*arg && !unadjustrange(commandbuf,arg,currentgroup->offset)) return;
1017 si= currentgroup->readfrom;
1018 rcode= servercommand(si,commandbuf,responsebuf,0);
1019 if ((rcode & 0xf00) != 0x200) {
1020 printf("%s\r\n",responsebuf);
1022 } else if (rcode != 0x224) {
1023 printf("503 %s said (after XOVER): %s\r\n",si->hostname,responsebuf);
1026 printf("%s\r\n",responsebuf);
1029 if (c == EOF) serverdataerr(si);
1032 if (c == '\r') c= getc(si->rfile);
1033 if (c == EOF) serverdataerr(si);
1034 if (c == '\n') break;
1035 /* Oh, well, it's clearly bozoid, so what if we dropped a `.' ... */
1039 ungetc(c,si->rfile);
1040 if (fscanf(si->rfile,"%d",&an) != 1) {
1041 while ((c= getc(si->rfile)) != EOF && c != '\n');
1042 if (c == EOF) serverdataerr(si);
1045 printf("%d",adjust(an,currentgroup->offset));
1046 p= xrefbuf; n= 0; fieldn= 0;
1049 if (c == EOF) serverdataerr(si);
1050 if (c == '\r') continue;
1051 if (c != '\t' && c != '\n' && n < MAX_XREFLINE) {
1055 if (c == '\t' || c == '\n') fieldn++;
1057 if (fieldn >= 8 && !strncasecmp("Xref: ",xrefbuf,6)) {
1059 processxref(xrefbuf+6,strlen(xrefbuf)-6,stdout,si);
1061 fputs(xrefbuf,stdout);
1063 if (c == '\n') break;
1073 static void cmd_xhdr(char *arg, const struct cmdinfo *cipa) {
1074 char commandbuf[MAX_COMMAND+40+3];
1075 char responsebuf[MAX_RESPONSE+3];
1076 char linebuf[MAX_XREFLINE+40+3];
1079 struct serverinfo *si;
1082 if (!currentgroup) {
1083 printf("412 headers in which group ?\r\n");
1088 printf("501 need header and range.\r\n");
1092 sprintf(commandbuf,"XHDR %s ",arg);
1093 if (!unadjustrange(commandbuf,p,currentgroup->offset)) return;
1094 si= currentgroup->readfrom;
1095 rcode= servercommand(si,commandbuf,responsebuf,0);
1096 if ((rcode & 0xf00) != 0x200) {
1097 printf("%s\r\n",responsebuf);
1099 } else if (rcode != 0x221) {
1100 printf("503 %s said (after XHDR): %s\r\n",si->hostname,responsebuf);
1103 printf("%s\r\n",responsebuf);
1104 isxref= !strcasecmp(arg,"Xref");
1105 if (!isxref && !currentgroup->offset) {
1106 copydata(si,stdout);
1109 if (!fgets(linebuf,MAX_XREFLINE+40,si->rfile)) serverdataerr(si);
1110 if (!stripcommand(linebuf)) continue;
1111 if (!strcmp(linebuf,".")) break;
1113 if (currentgroup->offset) {
1114 an= strtol(linebuf,&q,10);
1115 printf("%d",adjust(an,currentgroup->offset));
1117 if (isxref && (p= strchr(q,' ')) && strcmp(p+1,"(none)")) {
1120 processxref(p,strlen(p),stdout,si);
1124 fputs("\r\n",stdout);
1130 static void cmd_newgroups_xgtitle(char *arg, const struct cmdinfo *cip) {
1131 char commandbuf[MAX_COMMAND+40+3];
1132 char responsebuf[MAX_RESPONSE+3];
1133 char linebuf[MAX_XREFLINE+40+3];
1135 struct serverinfo *si, *gsi;
1137 sprintf(commandbuf,"%s %s",cip->command,arg);
1139 okrcode= cip->einfo ? 0x282 : 0x231;
1140 if (cip->einfo && !strchr(arg,'*') && !strchr(arg,'?')) {
1142 if (!currentgroup) {
1143 printf("412 title of which group ?\r\n");
1146 si= currentgroup->readfrom;
1148 si= findgroup(arg)->readfrom;
1150 rcode= servercommand(si,commandbuf,responsebuf,0);
1151 printf("%s\r\n",responsebuf);
1152 if ((rcode & 0xf00) == 0x200) copydata(si,stdout);
1156 for (si= servers; si; si= si->next) {
1157 if (!si->searchthis) continue;
1160 for (si= servers; si; si= si->next) {
1161 if (!si->searchthis) continue;
1162 rcode= servercommand(si,commandbuf,responsebuf,0);
1163 if (rcode != okrcode) {
1164 if ((rcode & 0xf00) == 0x200) closeserver(si);
1165 if (cip->einfo) continue;
1166 printf("503 NEWGROUPS not available - %s: %s\r\n",si->hostname,responsebuf);
1167 goto close_files_and_abort;
1169 si->tempfile= tmpfile();
1170 if (!si->tempfile) die("unable to create tempfile");
1171 if (!copydatanodot(si,si->tempfile)) goto close_files_and_abort;
1172 if (ferror(si->tempfile) || fflush(si->tempfile) || fseek(si->tempfile,0,SEEK_SET))
1173 die("unable to write temp file");
1175 printf("%x here you are:\r\n",okrcode);
1176 for (si= servers; si; si= si->next) {
1177 if (!si->searchthis) continue;
1178 if (!si->tempfile) continue;
1179 while (fgets(linebuf,MAX_XREFLINE+3,si->tempfile)) {
1180 gsi= findgroup(linebuf)->readfrom;
1181 if (gsi != si) continue;
1182 fputs(linebuf,stdout);
1184 if (ferror(si->tempfile)) die("read error on temp file");
1185 fclose(si->tempfile); si->tempfile= 0;
1190 close_files_and_abort:
1191 for (si= servers; si; si= si->next) {
1192 if (!si->searchthis) continue;
1193 if (si->tempfile) fclose(si->tempfile);
1198 static void cmd_authinfo(char *arg, const struct cmdinfo *cip) {
1199 struct authdas { struct authdas *next; char *name; };
1201 static struct authdas *alreadydone= 0;
1203 struct authdas *asearch;
1204 struct MD5Context md5ctx;
1205 char *p, *claim, *here, *what;
1207 char buf[500], buf2[MAX_RESPONSE+3];
1208 unsigned char message[16+MAX_SECRET], expect[16], reply[16];
1209 int n, nused, matching, ifmatch;
1210 struct timeval timevab;
1213 struct permission *pi;
1215 if (strcasecmp(strtok(arg," "),"generic") ||
1216 strcmp(strtok(0," "),"md5cookie1way")) {
1217 printf("501 please use AUTHINFO GENERIC md5cookie1way <claim>.\r\n");
1220 claim= strtok(0," ");
1221 if (strtok(0," ")) {
1222 printf("501 something after claim.\r\n");
1225 for (asearch= alreadydone;
1226 asearch && strcmp(asearch->name,claim);
1227 asearch= asearch->next);
1229 printf("502 you are already user/group %s; need other group for more access.\r\n",
1233 file= fopen("md5cookies","r");
1235 printf("503 couldn't open md5cookies file: %s\r\n",strerror(errno));
1240 if (!fgets(buf,sizeof(buf),file)) {
1242 printf("503 error reading md5cookies file: %s\r\n",strerror(errno));
1244 printf("502 who did you say you were ?\r\n");
1249 if (!stripcommand(buf) || *buf == '#' || !*buf) continue;
1250 here= strtok(buf," \t");
1251 if (!strcmp(buf,"@")) {
1252 what= strtok(0," \t");
1253 if (!strcmp(what,"allow")) {
1255 } else if (!strcmp(what,"refuse")) {
1261 if (!what) continue;
1262 if (fnmatch(what,theirfqdn ? theirfqdn : "_unknown_",0)) continue;
1264 } else if (!strcmp(here,claim)) {
1265 if (matching) break;
1270 if (gettimeofday(&timevab,(void*)0)) die("unable to gettimeofday for nonce");
1271 memcpy(message,&timevab.tv_sec,4);
1272 ul= timevab.tv_usec; memcpy(message+4,&ul,4);
1273 memcpy(message+8,&peername.sin_addr,4);
1274 memcpy(message+12,&peername.sin_port,2);
1275 us= getpid(); memcpy(message+14,&us,2);
1279 printf("502 md5cookies file is missing secret for you.\r\n");
1282 nused= parsehex(p,message+16,MAX_SECRET);
1285 MD5Update(&md5ctx,message,16+nused);
1286 MD5Final(expect,&md5ctx);
1289 for (n=0; n<16; n++) {
1290 printf("%s%02x", n?":":"", message[n]);
1293 if (ferror(stdout) || fflush(stdout)) die("unable to write auth challenge");
1295 if (!fgets(buf2,MAX_RESPONSE,stdin)) {
1296 if (ferror(stdin)) die("client connection failed during crypto");
1299 if (strncasecmp(buf2,"MD5 ",4)) {
1300 printf("502 expecting MD5.\r\n");
1303 nused= parsehex(buf2+4,reply,17);
1305 printf("502 expecting 16 pairs (got %d).\r\n",nused);
1308 if (memcmp(reply,expect,16)) {
1309 printf("502 pull the other one, it's got bells on.\r\n");
1312 asearch= xmalloc(sizeof(struct authdas));
1313 asearch->name= xstrdup(claim);
1314 asearch->next= alreadydone;
1315 alreadydone= asearch;
1317 while ((p= strtok(0," \t"))) {
1319 for (pi= permissions; pi; pi= pi->next) {
1320 if (strcmp(pi->name,p)) continue;
1327 lastdoneauth= asearch->name;
1331 const struct cmdinfo cmdinfos[]= {
1332 { "ARTICLE", cmd_article, 3 },
1333 { "AUTHINFO", cmd_authinfo },
1334 { "XAUTHINFO", cmd_authinfo },
1335 { "HEAD", cmd_article, 1 },
1336 { "BODY", cmd_article, 2 },
1337 { "STAT", cmd_article, 0 },
1338 { "GROUP", cmd_group },
1339 { "HELP", cmd_help },
1340 { "IHAVE", cmd_ihave },
1341 { "LAST", cmd_last_next },
1342 { "LIST", cmd_list },
1343 { "NEWGROUPS", cmd_newgroups_xgtitle, 0 },
1344 { "NEWNEWS", cmd_newnews },
1345 { "NEXT", cmd_last_next },
1346 { "NOOP", cmd_noop },
1347 { "POST", cmd_post },
1348 { "QUIT", cmd_quit },
1349 { "SLAVE", cmd_slave },
1350 { "DATE", cmd_date },
1351 { "LISTGROUP", cmd_listgroup },
1352 { "XLISTGROUP", cmd_listgroup },
1353 { "MODE", cmd_mode },
1354 { "XMODE", cmd_mode },
1355 { "XMERGEINFO", cmd_xmergeinfo },
1356 { "XGTITLE", cmd_newgroups_xgtitle, 1 },
1357 { "XHDR", cmd_xhdr },
1358 { "XOVER", cmd_xover },
1360 { "XPAT", cmd_unimplemented },
1361 { "XPATH", cmd_unimplemented },
1365 static void cmd_help(char *arg, const struct cmdinfo *cipa) {
1366 /* close outstanding channels */
1367 const struct cmdinfo *cip;
1368 if (!noargs(arg)) return;
1369 printf("100 commands are (* = implemented)\r\n");
1370 for (cip= cmdinfos; cip->command; cip++) {
1371 printf(" %s %s\r\n",
1372 cip->call == cmd_unimplemented ? " " : "*",
1378 int main(int argc, char **argv) {
1382 const struct cmdinfo *cip;
1385 cmdbuf[sizeof(cmdbuf)-1]= 0;
1386 if (gethostname(cmdbuf,sizeof(cmdbuf)-1)) die("gethostname");
1387 if (cmdbuf[sizeof(cmdbuf)-1]) die("gethostname overflow");
1388 if (!(he= gethostbyname(cmdbuf))) die("gethostbyname");
1389 myfqdn= xstrdup(he->h_name);
1390 myxref= xstrdup(he->h_name);
1392 if (chdir("/var/lib/news/merge")) die("chdir");
1395 setvbuf(stdin,0,_IOLBF,0);
1396 setvbuf(stdout,0,_IOFBF,10240);
1397 signal(SIGPIPE,SIG_IGN);
1398 nu= sizeof(peername);
1399 memset(&peername,0,nu);
1400 if (getpeername(0,(struct sockaddr*)&peername,&nu)) {
1401 theirfqdn= "_direct_";
1403 he= gethostbyaddr((void*)&peername.sin_addr,sizeof(peername.sin_addr),AF_INET);
1404 if (he && he->h_name) theirfqdn= xstrdup(he->h_name);
1407 printf("200 %s nntp-merge ready (posting might be ok).\r\n",myfqdn);
1410 if (fflush(stdout)) die("flush stdout");
1411 if (ferror(stdout) || ferror(stdin)) die("client connection died");
1412 waitpid(-1,0,WNOHANG); /* ignore all children */
1413 errno=0; if (!fgets(cmdbuf,sizeof(cmdbuf),stdin)) {
1414 if (ferror(stdin)) die("client closed"); else exit(0);
1416 if (!stripcommand(cmdbuf)) continue;
1417 for (cip= cmdinfos; cip->command; cip++) {
1418 n= strlen(cip->command);
1419 if (!strncasecmp(cip->command,cmdbuf,n) &&
1420 (!cmdbuf[n] || isspace(cmdbuf[n])))
1424 while (isspace(cmdbuf[n])) n++;
1425 cip->call(cmdbuf+n,cip);
1427 printf("500 huh?\r\n");