chiark / gitweb /
cd1314ee4d6ae648e05da4296294543df21a7165
[nntp-merge-chiark.git] / main.c
1 /**/
2
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <errno.h>
7 #include <ctype.h>
8 #include <unistd.h>
9 #include <netdb.h>
10 #include <assert.h>
11 #include <time.h>
12 #include <errno.h>
13 #include <fnmatch.h>
14 #include <sys/stat.h>
15 #include <sys/time.h>
16 #include <sys/types.h>
17 #include <sys/wait.h>
18 #include <arpa/inet.h>
19 #include <netinet/in.h>
20
21 #include "nntp-merge.h"
22 #include "md5.h"
23
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;
29
30 int stripcommand(char *buf) {
31   int n;
32   
33   n= strlen(buf);
34   if (n == 0) return 0;
35   if (buf[n-1] != '\n') return 0;
36   while (n>0 && isspace(buf[n-1])) n--;
37   buf[n]= 0;
38   return n;
39 }
40
41 void die(const char *msg) {
42   int e;
43   e= errno;
44   printf("400 server failure: %s - %s\r\n",msg,strerror(errno));
45   exit(0);
46 }
47
48 static int parsehex(char *p, unsigned char *up, int maxlen) {
49   int nused, v, n;
50   
51   nused= 0;
52   while (nused < MAX_SECRET && (n=-1, sscanf(p,"%x%n",&v,&n) >0) && n>0) {
53     p+= n;
54     *up++= v;
55     nused++;
56     if (*p == ':') p++;
57   }
58   return nused;
59 }
60
61 enum { scf_norecover=001, scf_nogroup };
62
63 void closeserver(struct serverinfo *server) {
64   fclose(server->rfile); server->rfile= 0;
65   fclose(server->wfile); server->wfile= 0;
66 }
67
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;
77   }
78   *rvp= strtol(response,0,16);
79   return 1;
80 }
81
82 #ifdef DEBUG
83 static int realservercommand(struct serverinfo *server,
84                              const char command[], char response[MAX_RESPONSE+3],
85                              int flags);
86 #endif
87 int servercommand(struct serverinfo *server,
88                   const char command[], char response[MAX_RESPONSE+3],
89                   int flags) {
90 #ifdef DEBUG
91   int rcode;
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);
95   return rcode;
96 }
97 static int realservercommand(struct serverinfo *server,
98                              const char command[], char response[MAX_RESPONSE+3],
99                              int flags) {
100 #endif
101   struct hostent *he;
102   char **alist;
103   struct sockaddr_in sin;
104   pid_t c1;
105   int rpipefds[2], wpipefds[2], rfd, wfd, l, n;
106   unsigned long rv;
107   char temp[MAX_RESPONSE+3], *p;
108   unsigned char message[16+MAX_SECRET], cryptresponse[16];
109   struct MD5Context md5ctx;
110   FILE *file;
111   
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)
115    */
116   if (server->rfile) {
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;
123   conn_broken:
124     closeserver(server);
125     if (flags & scf_norecover) {
126       strcpy(response,"205 it had already gone"); return 0x205;
127     }
128   }
129   if (server->program) {
130     if (pipe(rpipefds) || pipe(wpipefds)) die("unable to create pipe");
131     if ((c1= fork()) == -1) die("unable to fork");
132     if (!c1) {
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));
140       exit(1);
141     }
142     rfd= rpipefds[0]; close(rpipefds[1]);
143     wfd= wpipefds[1]; close(wpipefds[0]);
144   } else {
145     he= gethostbyname(server->hostname);
146     if (!he) {
147       sprintf(response,"503 unable to find address of %.100s.",server->hostname);
148       return 0x503;
149     }
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);
153       return 0x503;
154     }
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));
165       close(rfd);
166     }
167     if (!*alist) return 0x503;
168     wfd= dup(rfd);
169     if (wfd < 0) die("failed to dup socket fd");
170   }
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));
179   }
180   if (!stripcommand(response)) {
181     sprintf(response,"503 server %.100s is spouting null garbage.",server->hostname);
182     closeserver(server); return 0x503;
183   }
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"))) {
188     do {
189       if (!fgets(temp,sizeof(temp),file)) {
190         if (ferror(file)) {
191           sprintf(response,"503 error reading sesame file: %.100s",strerror(errno));
192         } else {
193           sprintf(response,"503 no sesame for server nicknamed %.100s",server->nickname);
194         }
195         fclose(file); closeserver(server); return 0x503;
196       }
197     } while (!stripcommand(temp) || !*temp || *temp == '#' ||
198              !(p= strchr(temp,' ')) || ((*p++= 0), strcmp(temp,server->nickname)));
199     fclose(file); l= strlen(p);
200     if (l>MAX_SECRET) {
201       sprintf(response,"503 sesame file secret for %.100s is too long",server->nickname);
202       closeserver(server); return 0x503;
203     }
204     memcpy(message+16,p,l);
205
206     MD5Init(&md5ctx);
207     MD5Update(&md5ctx,message,16+l);
208     MD5Final(cryptresponse,&md5ctx);
209
210     fprintf(server->wfile,"PASS ");
211     for (n=0; n<16; n++) {
212       fprintf(server->wfile,"%s%02x", n?":":"", cryptresponse[n]);
213     }
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;
219     }
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;
224     }
225   }
226   if (!strncmp(response,"400",3)) goto server_is_unavailable;
227   if (response[0] != '2') {
228     strcpy(temp,response);
229     stripcommand(temp);
230     sprintf(response,"503 server %.100s refuses to talk: %.100s",server->hostname,temp);
231     closeserver(server); return 0x503;
232   }
233   if (server->send) {
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;
239     }
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;
244     }
245   }
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;
252     }
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;
257     }
258   }
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;
264   }
265   if (!fgets(response,MAX_RESPONSE+3,server->rfile)) {
266     sprintf(response,"503 error reading from %.100s: %.100s.",server->hostname,
267             strerror(errno));
268     closeserver(server); return 0x503;
269   }
270   if (!stripcommand(response)) {
271     sprintf(response,"503 server %.100s is replying with null garbage.",
272             server->hostname);
273     closeserver(server); return 0x503;
274   }
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;
283 }
284
285 static int adjust(int original, int offset) {
286   if (offset < 0 && original < -offset) return 0;
287   return original+offset;
288 }
289
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));
295   } else {
296     printf("\r\n\r\n*** problem detected by nntp-merge:\r\n"
297            "server %s closed connection while sending data\r\n",
298            si->hostname);
299   }
300   printf("closing connection on you, sorry.\r\n");
301   exit(0);
302 }
303
304 static int noargs(const char *arg) {
305   if (!*arg) return 1;
306   printf("501 no arguments please.\r\n");
307   return 0;
308 }
309
310 static void cmd_unimplemented(char *arg, const struct cmdinfo *cip) { \
311   printf("500 %s not implemented.\r\n",cip->command); \
312 }
313
314 static void cmd_quit(char *arg, const struct cmdinfo *cip) {
315   char responsebuf[MAX_RESPONSE+3];
316   struct serverinfo *si;
317
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 */
323   }
324   printf("205 goodbye.\r\n");
325   exit(0);
326 }
327
328 static void cmd_date(char *arg, const struct cmdinfo *cip) {
329   char responsebuf[MAX_RESPONSE+3];
330   struct tm *brokendown;
331   char buf[100];
332   int n, rcode;
333   time_t now;
334   struct serverinfo *si;
335
336   if (!noargs(arg)) return;
337   time(&now);
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);
350   }
351   printf("111 %s\r\n",buf);
352 }
353
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");
357 }
358
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");
362 }
363
364 static void cmd_ihave(char *arg, const struct cmdinfo *cip) {
365    printf("502 please don't feed me.\r\n");
366 }
367
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");
370 }
371
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);
375   } else {
376     printf("501 you want me to do *what* ?\r\n");
377   }
378 }
379
380 static void authrequired(void) {
381   if (lastdoneauth) {
382     printf("480 the sight of %s is but dim.\r\n",lastdoneauth);
383   } else {
384     printf("480 identify yourself !  friend or foe ?\r\n");
385   }
386 }
387
388 int stillrestricted(struct permission **pip) {
389   if (!*pip) return 0;
390   if (!(*pip)->authd) return 1;
391   *pip= 0; return 0;
392 }
393
394 static int groupreadable(struct groupinfo *gi) {
395   if (!stillrestricted(&gi->restrictto) ||
396       !stillrestricted(&gi->readonlyto)) return 1;
397   authrequired();
398   return 0;
399 }
400
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];
406   int rcode;
407   
408   if (strlen(arg) > MAX_COMMAND-12) {
409     printf("501 too long.\r\n");
410     return;
411   }
412   if (*arg) {
413     gi= findgroup(arg); if (!groupreadable(gi)) return;
414   } else {
415     printf("501 which group, then ?\r\n");
416     return;
417   }
418   
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);
423     return;
424   } else if (gi->offset) {
425     startfrom= -1;
426     if (sscanf(responsebuf,"211 %d %d %d %n", &estcount,&first,&last,&startfrom) != 3 ||
427         startfrom == -1) {
428       printf("503 %s said (after GROUP): %s\r\n",gi->readfrom->hostname,responsebuf);
429       closeserver(gi->readfrom); return;
430     }
431     printf("211 %d %d %d %s\r\n",
432            estcount, adjust(first,gi->offset), adjust(last,gi->offset),
433            responsebuf + startfrom);
434   } else {
435     printf("%s\r\n",responsebuf);
436   }
437   currentgroup= gi;
438   strcpy(currentgroupname,arg);
439 }
440
441 int copydatafile(FILE *from, FILE *to) {
442   /* `to' may be null */
443   int stopstate, c;
444   
445   stopstate= 1; /* 0: middle of line; 1: start of line; 2: after `.' */
446   while ((c= getc(from)) != EOF) {
447     if (to) putc(c,to);
448     if (c == '\r') continue;
449     switch (stopstate) {
450     case 0:
451       if (c == '\n') stopstate= 1;
452       continue;
453     case 1:
454       stopstate= (c == '.') ? 2 : (c == '\n') ? 1 : 0;
455       continue;
456     case 2:
457       if (c == '\n') return 1;
458       stopstate= 0;
459       continue;
460     }
461   }
462   return 0;
463 }  
464
465 static void copydata(struct serverinfo *from, FILE *to) {
466   if (!copydatafile(from->rfile,to)) serverdataerr(from);
467 }
468
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];
473   long an;
474   char *p;
475   int rcode;
476   
477   if (strlen(arg) > MAX_COMMAND-12) {
478     printf("501 too long.\r\n");
479     return;
480   }
481   if (*arg) {
482     gi= findgroup(arg); if (!groupreadable(gi)) return;
483   } else if (!currentgroup) {
484     printf("412 list of articles in which group ?\r\n");
485     return;
486   } else {
487     gi= currentgroup;
488   }
489   
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);
494     return;
495   }
496   if (rcode != 0x211) {
497     printf("503 %s said (after %s): %s\r\n",
498            gi->readfrom->hostname,cip->command,responsebuf);
499     return;
500   }
501   printf("211 article list:\r\n");
502   if (!gi->offset) {
503     copydata(gi->readfrom,stdout);
504   } else {
505     for (;;) {
506       if (!fgets(responsebuf,MAX_RESPONSE+3,gi->readfrom->rfile))
507         serverdataerr(gi->readfrom);
508       if (!strcmp(responsebuf,".\r\n") || !strcmp(responsebuf,".\n"))
509         break;
510       an= strtol(responsebuf,&p,10);
511       printf("%d%s",adjust(an,gi->offset),p);
512     }
513     printf(".\r\n");
514   }
515   currentgroup= gi;
516 }
517
518 static void pserver(struct serverinfo *si) {
519   printf("  %s= %s:%u %s (%s)\r\n",
520          si->nickname,
521          si->hostname, si->port,
522          si->rfile ? "open" : "closed",
523          si->send ? si->send : "-");
524 }
525
526 static void cmd_xmergeinfo(char *arg, const struct cmdinfo *cip) {
527   struct groupinfo *gi;
528   int i;
529   
530   if (strlen(arg) > MAX_COMMAND-6) {
531     printf("501 too long.\r\n");
532     return;
533   }
534   gi= findgroup(arg);
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"
541          ".\r\n",
542          gi->offset,
543          gi->restrictto && gi->restrictto->name ? gi->restrictto->name : "<none>",
544          gi->readonlyto && gi->readonlyto->name ? gi->readonlyto->name : "<none>");
545 }
546
547 static void cmd_help(char *arg, const struct cmdinfo *cip);
548
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]) {
552   int startfrom, ran;
553   
554   startfrom= -1;
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);
559     return 0;
560   }
561   ran= (currentgroup && si == currentgroup->readfrom)
562     ? adjust(ran,currentgroup->offset) : 0;
563   sprintf(modifiedresponse,"22%c %d %s",*typecharp,ran,responsebuf+startfrom);
564   return 1;
565 }
566
567 static void cmd_last_next(char *arg, const struct cmdinfo *cip) {
568   int rcode;
569   char responsebuf[MAX_RESPONSE+3];
570   char modresponse[MAX_RESPONSE+3];
571   char dummy;
572   
573   if (!noargs(arg)) return;
574   if (!currentgroup) {
575     printf("412 use GROUP and ARTICLE first.\r\n");
576     return;
577   }
578   rcode= servercommand(currentgroup->readfrom,cip->command,responsebuf,0);
579   if ((rcode & 0xf00) != 0x200) {
580     printf("%s\r\n",responsebuf); return;
581   }
582   articleselectresponse(responsebuf,&dummy,currentgroup->readfrom,cip,modresponse);
583   printf("%s\r\n",modresponse);
584 }
585
586 static void processxref(char *linebufplus6, int n, FILE *writeto,
587                         struct serverinfo *gotfrom) {
588   char *space, *cpos, *colon;
589   struct groupinfo *gi;
590   unsigned long an;
591   
592   linebufplus6[n++]= ' ';
593   linebufplus6[n]= 0;
594   cpos= linebufplus6;
595   space= strchr(cpos,' ');
596   if (!space) return;
597   *space++= 0;
598   fprintf(writeto,"%s",myxref);
599   cpos= space;
600   while ((space= strchr(cpos,' '))) {
601     *space++= 0;
602     colon= strrchr(cpos,':');
603     if (colon) {
604       *colon++= 0;
605       gi= findgroup(cpos);
606       if (gi->readfrom != gotfrom) {
607         cpos= space; continue;
608       }
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));
613       }
614       fputs(colon,writeto);
615     } else {
616       if (*cpos) putc(' ',writeto);
617       fputs(cpos,writeto);
618     }
619     cpos= space;
620   }
621 }
622
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];
630   char typechar;
631   unsigned long an;
632   int rcode, n, c, realeinfo;
633   char *p;
634   const char *realcommand;
635   int checkpermission= 0;
636   FILE *file;
637
638   realcommand= cip->command;
639   if (*arg == '<') {
640     checkpermission= 1;
641     switch (cip->einfo) {
642     case 0:  realcommand= "HEAD";       break;
643     case 2:  realcommand= "ARTICLE";    break;
644     }
645     sprintf(commandbuf,"%s %s",realcommand,arg);
646     rcode= 0x423;
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;
651     }
652   } else if (isdigit(*arg) || !*arg) {
653     if (!currentgroup) {
654       printf("412 use GROUP first.\r\n");
655       return;
656     }
657     if (*arg) {
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);
663     } else {
664       sprintf(commandbuf,"%s",cip->command);
665     }
666     si= currentgroup->readfrom;
667     rcode= servercommand(si,commandbuf,responsebuf,0);
668   } else {
669     printf("501 optional arg must be message-id or article number.\r\n");
670     return;
671   }
672   if ((rcode & 0xff0) != 0x220) {
673     printf("%s\r\n",responsebuf);
674     return;
675   }
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;
682   }
683   if (realeinfo == (cip->einfo | 01)) {
684     switch (cip->einfo) {
685     case 0:  responsebuf[2]= '3';    break;
686     case 2:  responsebuf[2]= '2';    break;
687     }
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);
691     closeserver(si);
692     return;
693   }
694     
695   if (!articleselectresponse(responsebuf,&typechar,si,cip,modresponse)) return;
696   if (checkpermission) {
697     file= tmpfile(); if (!file) die("failed to create temp file");
698   } else {
699     printf("%s\r\n",modresponse);
700     if (!realeinfo) return;
701     file= stdout;
702   }
703   if (realeinfo & 01) {
704     for (;;) {
705       if (!fgets(linebuf,MAX_XREFLINE+3,si->rfile)) serverdataerr(si);
706       n= strlen(linebuf);
707       if (n==0) continue;
708       while (n>0 && ((c= linebuf[n-1]) == '\n' || c == '\r')) n--;
709       linebuf[n]= 0;
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);
714         fputs("\r\n",file);
715         continue;
716       }
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,","))) {
721           gi= findgroup(p);
722           if (!stillrestricted(&gi->restrictto) ||
723               !stillrestricted(&gi->readonlyto)) break;
724         }
725         if (!p) checkpermission= -1; /* Don't return, we must clean up &c */
726       }
727       if (n == 0 && realeinfo == 03) break; /* end of header, go on to body */
728       continue;
729     }
730   }
731   if (realeinfo & 02) {
732     /* process body */
733     copydata(si,file);
734   }
735
736   switch (checkpermission) {
737   case 0:
738     return;
739   case 1:
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");
746     fclose(file);
747     return;
748   case -1:
749     fclose(file);
750     authrequired();
751     return;
752   }
753 }
754
755 struct listwhatinfo {
756   const char *name;
757   void (*call)(const struct listwhatinfo *lwi, const char *cachename);
758   int mustall;
759   const char *what;
760   int einfo;
761 };
762
763 static void lwc_bynewsgroup(const struct listwhatinfo *lwi, const char *cachename) {
764   struct serverinfo *si;
765   struct groupinfo *gi;
766   char fnbuf[250];
767   char linebuf[MAX_RESPONSE+3];
768   FILE *file;
769   long an1, an2;
770   char *space1, *space2, *space3;
771
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");
776     if (!file) {
777       if (lwi->mustall) die("unable to open list file when spouting");
778       continue;
779     }
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 == ' ')) {
787         *space1= 0;
788         printf("%s %d %d %s",linebuf,
789                adjust(an1,gi->offset),adjust(an2,gi->offset),space3+1);
790       } else {
791         fputs(linebuf,stdout);
792       }
793     }
794     if (ferror(file)) die("read error on list file");
795     fclose(file);
796   }
797   return;
798 }
799
800 static void lwc_fixed(const struct listwhatinfo *lwi, const char *cachename) {
801   char *pathname;
802                                                   pathname= 
803   FILE *f= fopen(
804   char *p, tc;
805   struct disdone { struct disdone *next; char *name; } *done, *search, *tmp;
806   struct serverinfo *si;
807   char fnbuf[250];
808   char linebuf[MAX_RESPONSE+3];
809   FILE *file;
810
811   done= 0;
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");
816     if (!file) continue;
817     while (fgets(linebuf,MAX_RESPONSE+3,file)) {
818       p= linebuf; while (*p && !isspace(*p)) p++;
819       tc= *p; *p= 0;
820       for (search= done;
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);
826       tmp->next= done;
827       done= tmp;
828       *p= tc;
829       fputs(linebuf,stdout);
830     }
831     if (ferror(file)) die("read error on list file");
832     fclose(file);
833   }
834   for (search= done; search; search= tmp) {
835     tmp= search->next; free(search->name); free(search);
836   }
837   return;
838
839
840 static void lwc_overview(const struct listwhatinfo *lwi, const char *cachename) {
841   fputs("Subject:\r\n"
842         "From:\r\n"
843         "Date:\r\n"
844         "Message-ID:\r\n"
845         "References:\r\n"
846         "Bytes:\r\n"
847         "Lines:\r\n"
848         "Xref:full\r\n", stdout);
849 }
850
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"                      },
859   {  0                                                                          }
860 };
861
862 static int copydatanodot(struct serverinfo *si, FILE *file) {
863   int c;
864   
865   while ((c= getc(si->rfile)) != EOF) {
866     if (c == '.') {
867       c= getc(si->rfile);
868       if (c == '\r')  {
869         c= getc(si->rfile);
870         if (c == '\n')
871           return 1;
872         fputs(".\r",file);
873       } else if (c == '\n') {
874         return 1;
875       } else {
876         putc('.',file);
877       }
878     }
879     while (c != EOF && c != '\r' && c != '\n') {
880       putc(c,file);
881       c= getc(si->rfile);
882     }
883     if (c == EOF) break;
884     putc(c,file);
885   }
886   if (ferror(si->rfile)) {
887     printf("503 error getting data from %s: %s\r\n",si->hostname,strerror(errno));
888   } else {
889     printf("503 connection closed during data transfer from %s\r\n",si->hostname);
890   }
891   return 0;
892 }
893
894 static void cmd_list(char *arg, const struct cmdinfo *cip) {
895   const struct listwhatinfo *lwi;
896   int rcode, c;
897   char commandbuf[MAX_COMMAND+40+3];
898   char responsebuf[MAX_RESPONSE+3];
899   char ufnbuf[250], nfnbuf[250], fnbuf[250];
900   struct stat stab;
901   FILE *file;
902   struct serverinfo *si;
903   const char *cachename;
904   
905   for (lwi= listwhatinfos; lwi->name && strcasecmp(arg,lwi->name); lwi++);
906   if (!lwi->name) {
907     printf("501 LIST %s not available, sorry.\r\n",arg);
908     return;
909   }
910   if (lwi->mustall >= 0) {
911     cachename= lwi->name; if (!*cachename) cachename= (lwi+1)->name;
912   } else {
913     cachename= 0;
914   }
915   if (lwi->mustall > 0) {
916     sprintf(ufnbuf,"unavailable:%.100s",arg);
917     file= fopen(ufnbuf,"r");
918     if (file) {
919       printf("501 %s not available because not supported by ",arg);
920       while ((c= getc(file)) != EOF) { if (!isspace(c)) putchar(c); }
921       printf(".\r\n");
922       fclose(file);
923       return;
924     }
925   }
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) {
932       if (lwi->mustall) {
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");
941         return;
942       } else {
943         if (unlink(fnbuf) && errno != ENOENT) die("unable to remove now unsup list");
944         continue;
945       }
946     }
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;
952       }
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));
959       }
960     } else {
961       printf("503 %s said (after LIST): %s\r\n",si->hostname,responsebuf);
962       return;
963     }
964   }
965   printf("215 %s follows:\r\n",lwi->what);
966   lwi->call(lwi,cachename);
967   printf(".\r\n");
968 }
969
970 static int unadjustnumber(char *appendto, char *from, int offset) {
971   char *p;
972   int n;
973   long an;
974
975   an= strtol(from,&p,10);
976   if (!*from || *p) {
977     printf("503 bad article range.\r\n");
978     return 0;
979   }
980   if (an > offset)
981     an-= offset;
982   else
983     an= 0;
984   n= strlen(appendto);
985   sprintf(appendto+n,"%lu",an);
986   return 1;
987 }
988
989 static int unadjustrange(char *appendto, char *from, int offset) {
990   char *p;
991
992   p= strchr(from,'-');
993   if (p) {
994     *p++= 0;
995     if (!unadjustnumber(appendto,from,offset)) return 0;
996     from= p;
997     strcat(appendto,"-");
998   }
999   return unadjustnumber(appendto,from,offset);
1000 }
1001  
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];
1006   char *p;
1007   int rcode;
1008   struct serverinfo *si;
1009   int an, n, fieldn, c;
1010   
1011   if (!currentgroup) {
1012     printf("412 overview of which group ?\r\n");
1013     return;
1014   }
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);
1021     return;
1022   } else if (rcode != 0x224) {
1023     printf("503 %s said (after XOVER): %s\r\n",si->hostname,responsebuf);
1024     return;
1025   }
1026   printf("%s\r\n",responsebuf);
1027   for (;;) {
1028     c= getc(si->rfile);
1029     if (c == EOF) serverdataerr(si);
1030     if (c == '.') {
1031       c= getc(si->rfile); 
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 `.' ... */
1036       putchar('.');
1037       putchar(c);
1038     }
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);
1043       continue;
1044     }
1045     printf("%d",adjust(an,currentgroup->offset));
1046     p= xrefbuf; n= 0; fieldn= 0;
1047     for (;;) {
1048       c= getc(si->rfile);
1049       if (c == EOF) serverdataerr(si);
1050       if (c == '\r') continue;
1051       if (c != '\t' && c != '\n' && n < MAX_XREFLINE) {
1052         *p++= c; n++;
1053         continue;
1054       }
1055       if (c == '\t' || c == '\n') fieldn++;
1056       *p++= 0;
1057       if (fieldn >= 8 && !strncasecmp("Xref: ",xrefbuf,6)) {
1058         printf("Xref: ");
1059         processxref(xrefbuf+6,strlen(xrefbuf)-6,stdout,si);
1060       } else {
1061         fputs(xrefbuf,stdout);
1062       }
1063       if (c == '\n') break;
1064       putchar('\t');
1065       p= xrefbuf; n= 0;
1066     }
1067     printf("\r\n");
1068   }
1069   printf(".\r\n");
1070   return;
1071 }
1072
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];
1077   char *p, *q;
1078   int rcode, isxref;
1079   struct serverinfo *si;
1080   long an;
1081   
1082   if (!currentgroup) {
1083     printf("412 headers in which group ?\r\n");
1084     return;
1085   }
1086   p= strchr(arg,' ');
1087   if (!p) {
1088     printf("501 need header and range.\r\n");
1089     return;
1090   }
1091   *p++= 0;
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);
1098     return;
1099   } else if (rcode != 0x221) {
1100     printf("503 %s said (after XHDR): %s\r\n",si->hostname,responsebuf);
1101     return;
1102   }
1103   printf("%s\r\n",responsebuf);
1104   isxref= !strcasecmp(arg,"Xref");
1105   if (!isxref && !currentgroup->offset) {
1106     copydata(si,stdout);
1107   } else {
1108     for (;;) {
1109       if (!fgets(linebuf,MAX_XREFLINE+40,si->rfile)) serverdataerr(si);
1110       if (!stripcommand(linebuf)) continue;
1111       if (!strcmp(linebuf,".")) break;
1112       q= linebuf;
1113       if (currentgroup->offset) {
1114         an= strtol(linebuf,&q,10);
1115         printf("%d",adjust(an,currentgroup->offset));
1116       }
1117       if (isxref && (p= strchr(q,' ')) && strcmp(p+1,"(none)")) {
1118         *p++= 0;
1119         printf("%s ",q);
1120         processxref(p,strlen(p),stdout,si);
1121       } else {
1122         fputs(q,stdout);
1123       }
1124       fputs("\r\n",stdout);
1125     }
1126     printf(".\r\n");
1127   }
1128 }
1129
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];
1134   int rcode, okrcode;
1135   struct serverinfo *si, *gsi;
1136
1137   sprintf(commandbuf,"%s %s",cip->command,arg);
1138
1139   okrcode= cip->einfo ? 0x282 : 0x231;
1140   if (cip->einfo && !strchr(arg,'*') && !strchr(arg,'?')) {
1141     if (!*arg) {
1142       if (!currentgroup) {
1143         printf("412 title of which group ?\r\n");
1144         return;
1145       }
1146       si= currentgroup->readfrom;
1147     } else {
1148       si= findgroup(arg)->readfrom;
1149     }
1150     rcode= servercommand(si,commandbuf,responsebuf,0);
1151     printf("%s\r\n",responsebuf);
1152     if ((rcode & 0xf00) == 0x200) copydata(si,stdout);
1153     return;
1154   }
1155
1156   for (si= servers; si; si= si->next) {
1157     if (!si->searchthis) continue;
1158     si->tempfile= 0;
1159   }
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;
1168     }
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");
1174   }
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);
1183     }
1184     if (ferror(si->tempfile)) die("read error on temp file");
1185     fclose(si->tempfile); si->tempfile= 0;
1186   }
1187   printf(".\r\n");
1188   return;
1189
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);
1194   }
1195   return;
1196 }
1197
1198 static void cmd_authinfo(char *arg, const struct cmdinfo *cip) {
1199   struct authdas { struct authdas *next; char *name; };
1200
1201   static struct authdas *alreadydone= 0;
1202
1203   struct authdas *asearch;
1204   struct MD5Context md5ctx;
1205   char *p, *claim, *here, *what;
1206   FILE *file;
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;
1211   unsigned long ul;
1212   unsigned short us;
1213   struct permission *pi;
1214
1215   if (strcasecmp(strtok(arg," "),"generic") ||
1216       strcmp(strtok(0," "),"md5cookie1way")) {
1217     printf("501 please use AUTHINFO GENERIC md5cookie1way <claim>.\r\n");
1218     return;
1219   }
1220   claim= strtok(0," ");
1221   if (strtok(0," ")) {
1222     printf("501 something after claim.\r\n");
1223     return;
1224   }
1225   for (asearch= alreadydone;
1226        asearch && strcmp(asearch->name,claim);
1227        asearch= asearch->next);
1228   if (asearch) {
1229     printf("502 you are already user/group %s; need other group for more access.\r\n",
1230            claim);
1231     return;
1232   }
1233   file= fopen("md5cookies","r");
1234   if (!file) {
1235     printf("503 couldn't open md5cookies file: %s\r\n",strerror(errno));
1236     return;
1237   }
1238   matching= 0;
1239   for (;;) {
1240     if (!fgets(buf,sizeof(buf),file)) {
1241       if (ferror(file)) {
1242         printf("503 error reading md5cookies file: %s\r\n",strerror(errno));
1243       } else {
1244         printf("502 who did you say you were ?\r\n");
1245       }
1246       fclose(file);
1247       return;
1248     }
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")) {
1254         ifmatch= 1;
1255       } else if (!strcmp(what,"refuse")) {
1256         ifmatch= 0;
1257       } else {
1258         continue;
1259       }
1260       what= strtok(0,"");
1261       if (!what) continue;
1262       if (fnmatch(what,theirfqdn ? theirfqdn : "_unknown_",0)) continue;
1263       matching= ifmatch;
1264     } else if (!strcmp(here,claim)) {
1265       if (matching) break;
1266     }
1267   }
1268   fclose(file);
1269   
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);
1276
1277   p= strtok(0," \t");
1278   if (!p) {
1279     printf("502 md5cookies file is missing secret for you.\r\n");
1280     return;
1281   }
1282   nused= parsehex(p,message+16,MAX_SECRET);
1283
1284   MD5Init(&md5ctx);
1285   MD5Update(&md5ctx,message,16+nused);
1286   MD5Final(expect,&md5ctx);
1287   
1288   printf("100 ");
1289   for (n=0; n<16; n++) {
1290     printf("%s%02x", n?":":"", message[n]);
1291   }
1292   printf("\r\n");
1293   if (ferror(stdout) || fflush(stdout)) die("unable to write auth challenge");
1294
1295   if (!fgets(buf2,MAX_RESPONSE,stdin)) {
1296     if (ferror(stdin)) die("client connection failed during crypto");
1297     exit(1);
1298   }
1299   if (strncasecmp(buf2,"MD5 ",4)) {
1300     printf("502 expecting MD5.\r\n");
1301     return;
1302   }
1303   nused= parsehex(buf2+4,reply,17);
1304   if (nused != 16) {
1305     printf("502 expecting 16 pairs (got %d).\r\n",nused);
1306     return;
1307   }
1308   if (memcmp(reply,expect,16)) {
1309     printf("502 pull the other one, it's got bells on.\r\n");
1310     return;
1311   }
1312   asearch= xmalloc(sizeof(struct authdas));
1313   asearch->name= xstrdup(claim);
1314   asearch->next= alreadydone;
1315   alreadydone= asearch;
1316   printf("281 ok");
1317   while ((p= strtok(0," \t"))) {
1318     printf(" %s",p);
1319     for (pi= permissions; pi; pi= pi->next) {
1320       if (strcmp(pi->name,p)) continue;
1321       if (!pi->authd) {
1322         putchar('+');
1323         pi->authd= 1;
1324       }
1325     }
1326   }
1327   lastdoneauth= asearch->name;
1328   printf("\r\n");
1329 }
1330
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                  },
1359
1360   { "XPAT",         cmd_unimplemented          },
1361   { "XPATH",        cmd_unimplemented          },
1362   {  0                                         }
1363 };
1364
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 ? " " : "*",
1373            cip->command);
1374   }
1375   printf(".\r\n");
1376 }
1377
1378 int main(int argc, char **argv) {
1379   char cmdbuf[1000];
1380   int n;
1381   unsigned int nu;
1382   const struct cmdinfo *cip;
1383   struct hostent *he;
1384
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);
1391
1392   if (chdir("/var/lib/news/merge")) die("chdir");
1393   readconfig();
1394
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_";
1402   } else {
1403     he= gethostbyaddr((void*)&peername.sin_addr,sizeof(peername.sin_addr),AF_INET);
1404     if (he && he->h_name) theirfqdn= xstrdup(he->h_name);
1405   }
1406   
1407   printf("200 %s nntp-merge ready (posting might be ok).\r\n",myfqdn);
1408
1409   for (;;) {
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);
1415     }
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])))
1421         break;
1422     }
1423     if (cip->command) {
1424       while (isspace(cmdbuf[n])) n++;
1425       cip->call(cmdbuf+n,cip);
1426     } else {
1427       printf("500 huh?\r\n");
1428     }
1429   }
1430 }