chiark / gitweb /
fix warnings
[nntp-merge-chiark.git] / main.c
1 /**/
2
3 #define _GNU_SOURCE
4
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <errno.h>
9 #include <ctype.h>
10 #include <unistd.h>
11 #include <netdb.h>
12 #include <assert.h>
13 #include <time.h>
14 #include <errno.h>
15 #include <fnmatch.h>
16 #include <sys/stat.h>
17 #include <sys/time.h>
18 #include <sys/types.h>
19 #include <sys/wait.h>
20 #include <arpa/inet.h>
21 #include <netinet/in.h>
22
23 #include "nntp-merge.h"
24 #include "md5.h"
25
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;
31
32 int stripcommand(char *buf) {
33   int n;
34   
35   n= strlen(buf);
36   if (n == 0) return 0;
37   if (buf[n-1] != '\n') return 0;
38   while (n>0 && isspace(buf[n-1])) n--;
39   buf[n]= 0;
40   return n;
41 }
42
43 void die(const char *msg) {
44   int e;
45   e= errno;
46   printf("400 server failure: %s - %s\r\n",msg,strerror(e));
47   exit(0);
48 }
49
50 static int parsehex(char *p, unsigned char *up, int maxlen) {
51   int nused, v, n;
52   
53   nused= 0;
54   while (nused < MAX_SECRET && (n=-1, sscanf(p,"%x%n",&v,&n) >0) && n>0) {
55     p+= n;
56     *up++= v;
57     nused++;
58     if (*p == ':') p++;
59   }
60   return nused;
61 }
62
63 enum { scf_norecover=001, scf_nogroup };
64
65 void closeserver(struct serverinfo *server) {
66   fclose(server->rfile); server->rfile= 0;
67   fclose(server->wfile); server->wfile= 0;
68 }
69
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;
79   }
80   *rvp= strtol(response,0,16);
81   return 1;
82 }
83
84 #ifdef DEBUG
85 static int realservercommand(struct serverinfo *server,
86                              const char command[], char response[MAX_RESPONSE+3],
87                              int flags);
88 #endif
89 int servercommand(struct serverinfo *server,
90                   const char command[], char response[MAX_RESPONSE+3],
91                   int flags) {
92 #ifdef DEBUG
93   int rcode;
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);
97   return rcode;
98 }
99 static int realservercommand(struct serverinfo *server,
100                              const char command[], char response[MAX_RESPONSE+3],
101                              int flags) {
102 #endif
103   struct hostent *he;
104   char **alist;
105   struct sockaddr_in sin;
106   pid_t c1;
107   int rpipefds[2], wpipefds[2], rfd, wfd, l, n;
108   unsigned long rv;
109   char temp[MAX_RESPONSE+3], *p;
110   unsigned char message[16+MAX_SECRET], cryptresponse[16];
111   struct MD5Context md5ctx;
112   FILE *file;
113   
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)
117    */
118   if (server->rfile) {
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;
125   conn_broken:
126     closeserver(server);
127     if (flags & scf_norecover) {
128       strcpy(response,"205 it had already gone"); return 0x205;
129     }
130   }
131   if (server->program) {
132     if (pipe(rpipefds) || pipe(wpipefds)) die("unable to create pipe");
133     if ((c1= fork()) == -1) die("unable to fork");
134     if (!c1) {
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));
142       exit(1);
143     }
144     rfd= rpipefds[0]; close(rpipefds[1]);
145     wfd= wpipefds[1]; close(wpipefds[0]);
146   } else {
147     he= gethostbyname(server->hostname);
148     if (!he) {
149       sprintf(response,"503 unable to find address of %.100s.",server->hostname);
150       return 0x503;
151     }
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);
155       return 0x503;
156     }
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));
167       close(rfd);
168     }
169     if (!*alist) return 0x503;
170     wfd= dup(rfd);
171     if (wfd < 0) die("failed to dup socket fd");
172   }
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));
181   }
182   if (!stripcommand(response)) {
183     sprintf(response,"503 server %.100s is spouting null garbage.",server->hostname);
184     closeserver(server); return 0x503;
185   }
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"))) {
190     do {
191       if (!fgets(temp,sizeof(temp),file)) {
192         if (ferror(file)) {
193           sprintf(response,"503 error reading sesame file: %.100s",strerror(errno));
194         } else {
195           sprintf(response,"503 no sesame for server nicknamed %.100s",server->nickname);
196         }
197         fclose(file); closeserver(server); return 0x503;
198       }
199     } while (!stripcommand(temp) || !*temp || *temp == '#' ||
200              !(p= strchr(temp,' ')) || ((*p++= 0), strcmp(temp,server->nickname)));
201     fclose(file); l= strlen(p);
202     if (l>MAX_SECRET) {
203       sprintf(response,"503 sesame file secret for %.100s is too long",server->nickname);
204       closeserver(server); return 0x503;
205     }
206     memcpy(message+16,p,l);
207
208     MD5Init(&md5ctx);
209     MD5Update(&md5ctx,message,16+l);
210     MD5Final(cryptresponse,&md5ctx);
211
212     fprintf(server->wfile,"PASS ");
213     for (n=0; n<16; n++) {
214       fprintf(server->wfile,"%s%02x", n?":":"", cryptresponse[n]);
215     }
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;
221     }
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;
226     }
227   }
228   if (!strncmp(response,"400",3)) goto server_is_unavailable;
229   if (response[0] != '2') {
230     strcpy(temp,response);
231     stripcommand(temp);
232     sprintf(response,"503 server %.100s refuses to talk: %.100s",server->hostname,temp);
233     closeserver(server); return 0x503;
234   }
235   if (server->send) {
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;
241     }
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;
246     }
247   }
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;
254     }
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;
259     }
260   }
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;
266   }
267   if (!fgets(response,MAX_RESPONSE+3,server->rfile)) {
268     sprintf(response,"503 error reading from %.100s: %.100s.",server->hostname,
269             strerror(errno));
270     closeserver(server); return 0x503;
271   }
272   if (!stripcommand(response)) {
273     sprintf(response,"503 server %.100s is replying with null garbage.",
274             server->hostname);
275     closeserver(server); return 0x503;
276   }
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;
285 }
286
287 static int adjust(int original, int offset) {
288   if (offset < 0 && original < -offset) return 0;
289   return original+offset;
290 }
291
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));
297   } else {
298     printf("\r\n\r\n*** problem detected by nntp-merge:\r\n"
299            "server %s closed connection while sending data\r\n",
300            si->hostname);
301   }
302   printf("closing connection on you, sorry.\r\n");
303   exit(0);
304 }
305
306 static int noargs(const char *arg) {
307   if (!*arg) return 1;
308   printf("501 no arguments please.\r\n");
309   return 0;
310 }
311
312 static void cmd_unimplemented(char *arg, const struct cmdinfo *cip) { \
313   printf("500 %s not implemented.\r\n",cip->command); \
314 }
315
316 static void cmd_quit(char *arg, const struct cmdinfo *cip) {
317   char responsebuf[MAX_RESPONSE+3];
318   struct serverinfo *si;
319
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 */
325   }
326   printf("205 goodbye.\r\n");
327   exit(0);
328 }
329
330 static void cmd_date(char *arg, const struct cmdinfo *cip) {
331   char responsebuf[MAX_RESPONSE+3];
332   struct tm *brokendown;
333   char buf[100];
334   int n, rcode;
335   time_t now;
336   struct serverinfo *si;
337
338   if (!noargs(arg)) return;
339   time(&now);
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);
352   }
353   printf("111 %s\r\n",buf);
354 }
355
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");
359 }
360
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");
364 }
365
366 static void cmd_ihave(char *arg, const struct cmdinfo *cip) {
367    printf("502 please don't feed me.\r\n");
368 }
369
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");
372 }
373
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);
377   } else {
378     printf("501 you want me to do *what* ?\r\n");
379   }
380 }
381
382 static void authrequired(void) {
383   if (lastdoneauth) {
384     printf("480 the sight of %s is but dim.\r\n",lastdoneauth);
385   } else {
386     printf("480 identify yourself !  friend or foe ?\r\n");
387   }
388 }
389
390 int stillrestricted(struct permission **pip) {
391   if (!*pip) return 0;
392   if (!(*pip)->authd) return 1;
393   *pip= 0; return 0;
394 }
395
396 static int groupreadable(struct groupinfo *gi) {
397   if (!stillrestricted(&gi->restrictto) ||
398       !stillrestricted(&gi->readonlyto)) return 1;
399   authrequired();
400   return 0;
401 }
402
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];
408   int rcode;
409   
410   if (strlen(arg) > MAX_COMMAND-12) {
411     printf("501 too long.\r\n");
412     return;
413   }
414   if (*arg) {
415     gi= findgroup(arg); if (!groupreadable(gi)) return;
416   } else {
417     printf("501 which group, then ?\r\n");
418     return;
419   }
420   
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);
425     return;
426   } else if (gi->offset) {
427     startfrom= -1;
428     if (sscanf(responsebuf,"211 %d %d %d %n", &estcount,&first,&last,&startfrom) != 3 ||
429         startfrom == -1) {
430       printf("503 %s said (after GROUP): %s\r\n",gi->readfrom->hostname,responsebuf);
431       closeserver(gi->readfrom); return;
432     }
433     printf("211 %d %d %d %s\r\n",
434            estcount, adjust(first,gi->offset), adjust(last,gi->offset),
435            responsebuf + startfrom);
436   } else {
437     printf("%s\r\n",responsebuf);
438   }
439   currentgroup= gi;
440   strcpy(currentgroupname,arg);
441 }
442
443 int copydatafile(FILE *from, FILE *to) {
444   /* `to' may be null */
445   int stopstate, c;
446   
447   stopstate= 1; /* 0: middle of line; 1: start of line; 2: after `.' */
448   while ((c= getc(from)) != EOF) {
449     if (to) putc(c,to);
450     if (c == '\r') continue;
451     switch (stopstate) {
452     case 0:
453       if (c == '\n') stopstate= 1;
454       continue;
455     case 1:
456       stopstate= (c == '.') ? 2 : (c == '\n') ? 1 : 0;
457       continue;
458     case 2:
459       if (c == '\n') return 1;
460       stopstate= 0;
461       continue;
462     }
463   }
464   return 0;
465 }  
466
467 static void copydata(struct serverinfo *from, FILE *to) {
468   if (!copydatafile(from->rfile,to)) serverdataerr(from);
469 }
470
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];
475   long an;
476   char *p;
477   int rcode;
478   
479   if (strlen(arg) > MAX_COMMAND-12) {
480     printf("501 too long.\r\n");
481     return;
482   }
483   if (*arg) {
484     gi= findgroup(arg); if (!groupreadable(gi)) return;
485   } else if (!currentgroup) {
486     printf("412 list of articles in which group ?\r\n");
487     return;
488   } else {
489     gi= currentgroup;
490   }
491   
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);
496     return;
497   }
498   if (rcode != 0x211) {
499     printf("503 %s said (after %s): %s\r\n",
500            gi->readfrom->hostname,cip->command,responsebuf);
501     return;
502   }
503   printf("211 article list:\r\n");
504   if (!gi->offset) {
505     copydata(gi->readfrom,stdout);
506   } else {
507     for (;;) {
508       if (!fgets(responsebuf,MAX_RESPONSE+3,gi->readfrom->rfile))
509         serverdataerr(gi->readfrom);
510       if (!strcmp(responsebuf,".\r\n") || !strcmp(responsebuf,".\n"))
511         break;
512       an= strtol(responsebuf,&p,10);
513       printf("%d%s",adjust(an,gi->offset),p);
514     }
515     printf(".\r\n");
516   }
517   currentgroup= gi;
518 }
519
520 static void pserver(struct serverinfo *si) {
521   printf("  %s= %s:%u %s (%s)\r\n",
522          si->nickname,
523          si->hostname, si->port,
524          si->rfile ? "open" : "closed",
525          si->send ? si->send : "-");
526 }
527
528 static void cmd_xmergeinfo(char *arg, const struct cmdinfo *cip) {
529   struct groupinfo *gi;
530   int i;
531   
532   if (strlen(arg) > MAX_COMMAND-6) {
533     printf("501 too long.\r\n");
534     return;
535   }
536   gi= findgroup(arg);
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"
543          ".\r\n",
544          gi->offset,
545          gi->restrictto && gi->restrictto->name ? gi->restrictto->name : "<none>",
546          gi->readonlyto && gi->readonlyto->name ? gi->readonlyto->name : "<none>");
547 }
548
549 static void cmd_help(char *arg, const struct cmdinfo *cip);
550
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]) {
554   int startfrom, ran;
555   
556   startfrom= -1;
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);
561     return 0;
562   }
563   ran= (currentgroup && si == currentgroup->readfrom)
564     ? adjust(ran,currentgroup->offset) : 0;
565   sprintf(modifiedresponse,"22%c %d %s",*typecharp,ran,responsebuf+startfrom);
566   return 1;
567 }
568
569 static void cmd_last_next(char *arg, const struct cmdinfo *cip) {
570   int rcode;
571   char responsebuf[MAX_RESPONSE+3];
572   char modresponse[MAX_RESPONSE+3];
573   char dummy;
574   
575   if (!noargs(arg)) return;
576   if (!currentgroup) {
577     printf("412 use GROUP and ARTICLE first.\r\n");
578     return;
579   }
580   rcode= servercommand(currentgroup->readfrom,cip->command,responsebuf,0);
581   if ((rcode & 0xf00) != 0x200) {
582     printf("%s\r\n",responsebuf); return;
583   }
584   articleselectresponse(responsebuf,&dummy,currentgroup->readfrom,cip,modresponse);
585   printf("%s\r\n",modresponse);
586 }
587
588 static void processxref(char *linebufplus6, int n, FILE *writeto,
589                         struct serverinfo *gotfrom) {
590   char *space, *cpos, *colon;
591   struct groupinfo *gi;
592   unsigned long an;
593   
594   linebufplus6[n++]= ' ';
595   linebufplus6[n]= 0;
596   cpos= linebufplus6;
597   space= strchr(cpos,' ');
598   if (!space) return;
599   *space++= 0;
600   fprintf(writeto,"%s",myxref);
601   cpos= space;
602   while ((space= strchr(cpos,' '))) {
603     *space++= 0;
604     colon= strrchr(cpos,':');
605     if (colon) {
606       *colon++= 0;
607       gi= findgroup(cpos);
608       if (gi->readfrom != gotfrom) {
609         cpos= space; continue;
610       }
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));
615       }
616       fputs(colon,writeto);
617     } else {
618       if (*cpos) putc(' ',writeto);
619       fputs(cpos,writeto);
620     }
621     cpos= space;
622   }
623 }
624
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];
632   char typechar;
633   unsigned long an;
634   int rcode, n, c, realeinfo;
635   char *p;
636   const char *realcommand;
637   int checkpermission= 0;
638   FILE *file;
639
640   realcommand= cip->command;
641   if (*arg == '<') {
642     checkpermission= 1;
643     switch (cip->einfo) {
644     case 0:  realcommand= "HEAD";       break;
645     case 2:  realcommand= "ARTICLE";    break;
646     }
647     sprintf(commandbuf,"%s %s",realcommand,arg);
648     rcode= 0x423;
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;
653     }
654   } else if (isdigit(*arg) || !*arg) {
655     if (!currentgroup) {
656       printf("412 use GROUP first.\r\n");
657       return;
658     }
659     if (*arg) {
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);
665     } else {
666       sprintf(commandbuf,"%s",cip->command);
667     }
668     si= currentgroup->readfrom;
669     rcode= servercommand(si,commandbuf,responsebuf,0);
670   } else {
671     printf("501 optional arg must be message-id or article number.\r\n");
672     return;
673   }
674   if ((rcode & 0xff0) != 0x220) {
675     printf("%s\r\n",responsebuf);
676     return;
677   }
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;
684   }
685   if (realeinfo == (cip->einfo | 01)) {
686     switch (cip->einfo) {
687     case 0:  responsebuf[2]= '3';    break;
688     case 2:  responsebuf[2]= '2';    break;
689     }
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);
693     closeserver(si);
694     return;
695   }
696     
697   if (!articleselectresponse(responsebuf,&typechar,si,cip,modresponse)) return;
698   if (checkpermission) {
699     file= tmpfile(); if (!file) die("failed to create temp file");
700   } else {
701     printf("%s\r\n",modresponse);
702     if (!realeinfo) return;
703     file= stdout;
704   }
705   if (realeinfo & 01) {
706     for (;;) {
707       if (!fgets(linebuf,MAX_XREFLINE+3,si->rfile)) serverdataerr(si);
708       n= strlen(linebuf);
709       if (n==0) continue;
710       while (n>0 && ((c= linebuf[n-1]) == '\n' || c == '\r')) n--;
711       linebuf[n]= 0;
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);
716         fputs("\r\n",file);
717         continue;
718       }
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,","))) {
723           gi= findgroup(p);
724           if (!stillrestricted(&gi->restrictto) ||
725               !stillrestricted(&gi->readonlyto)) break;
726         }
727         if (!p) checkpermission= -1; /* Don't return, we must clean up &c */
728       }
729       if (n == 0 && realeinfo == 03) break; /* end of header, go on to body */
730       continue;
731     }
732   }
733   if (realeinfo & 02) {
734     /* process body */
735     copydata(si,file);
736   }
737
738   switch (checkpermission) {
739   case 0:
740     return;
741   case 1:
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");
748     fclose(file);
749     return;
750   case -1:
751     fclose(file);
752     authrequired();
753     return;
754   }
755 }
756
757 struct listwhatinfo {
758   const char *name;
759   void (*call)(const struct listwhatinfo *lwi, const char *cachename);
760   int mustall; /* -2 means ->call() must also print response and final . */
761   const char *what;
762   int einfo;
763 };
764
765 static void lwc_bynewsgroup(const struct listwhatinfo *lwi, const char *cachename) {
766   struct serverinfo *si;
767   struct groupinfo *gi;
768   char fnbuf[250];
769   char linebuf[MAX_RESPONSE+3];
770   FILE *file;
771   long an1, an2;
772   char *space1, *space2, *space3;
773
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");
778     if (!file) {
779       if (lwi->mustall) die("unable to open list file when spouting");
780       continue;
781     }
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 == ' ')) {
789         *space1= 0;
790         printf("%s %d %d %s",linebuf,
791                adjust(an1,gi->offset),adjust(an2,gi->offset),space3+1);
792       } else {
793         fputs(linebuf,stdout);
794       }
795     }
796     if (ferror(file)) die("read error on list file");
797     fclose(file);
798   }
799   return;
800 }
801
802 static void lwc_fixed(const struct listwhatinfo *lwi, const char *cachename) {
803   char *pathname=0;
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");
807   if (!file) {
808     if (errno == ENOENT) {
809       printf("503 %s not available\r\n",lwi->what);
810       goto x;
811     }
812     die("open a list file");
813   }
814
815   printf("215 %s (as configured) follows:\r\n",lwi->what);
816   int c;
817   int linestart= 1; /* -1 means comment */
818   while ((c= getc(file)) != EOF) {
819     if (linestart < 0) {
820       if (c=='\n') linestart= 1;
821       continue;
822     }
823     if (linestart) {
824       if (c=='#') { linestart= -1; continue; }
825       if (c=='.') putchar('.');
826       linestart= 0;
827     }
828     if (c=='\n') { putchar('\r'); linestart=1; }
829     putchar(c);
830   }
831   if (ferror(file))
832     die("error reading list");
833   assert(feof(file));
834   fputs(".\r\n",stdout);
835  x:
836   if (file) fclose(file);
837   free(pathname);
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_fixed,         -2, "distributions available"        },
858   { "distrib.pats",  lwc_fixed,         -2, "default distribution patterns"  },
859   { "overview.fmt",  lwc_overview,      -1, "field list"                     },
860   {  0                                                                       }
861 };
862
863 static int copydatanodot(struct serverinfo *si, FILE *file) {
864   int c;
865   
866   while ((c= getc(si->rfile)) != EOF) {
867     if (c == '.') {
868       c= getc(si->rfile);
869       if (c == '\r')  {
870         c= getc(si->rfile);
871         if (c == '\n')
872           return 1;
873         fputs(".\r",file);
874       } else if (c == '\n') {
875         return 1;
876       } else {
877         putc('.',file);
878       }
879     }
880     while (c != EOF && c != '\r' && c != '\n') {
881       putc(c,file);
882       c= getc(si->rfile);
883     }
884     if (c == EOF) break;
885     putc(c,file);
886   }
887   if (ferror(si->rfile)) {
888     printf("503 error getting data from %s: %s\r\n",si->hostname,strerror(errno));
889   } else {
890     printf("503 connection closed during data transfer from %s\r\n",si->hostname);
891   }
892   return 0;
893 }
894
895 static void cmd_list(char *arg, const struct cmdinfo *cip) {
896   const struct listwhatinfo *lwi;
897   int rcode, c;
898   char commandbuf[MAX_COMMAND+40+3];
899   char responsebuf[MAX_RESPONSE+3];
900   char ufnbuf[250], nfnbuf[250], fnbuf[250];
901   struct stat stab;
902   FILE *file;
903   struct serverinfo *si;
904   const char *cachename;
905   
906   for (lwi= listwhatinfos; lwi->name && strcasecmp(arg,lwi->name); lwi++);
907   if (!lwi->name) {
908     printf("501 LIST %s not available, sorry.\r\n",arg);
909     return;
910   }
911   if (lwi->mustall >= 0) {
912     cachename= lwi->name; if (!*cachename) cachename= (lwi+1)->name;
913   } else {
914     cachename= 0;
915   }
916   if (lwi->mustall > 0) {
917     sprintf(ufnbuf,"unavailable:%.100s",arg);
918     file= fopen(ufnbuf,"r");
919     if (file) {
920       printf("501 %s not available because not supported by ",arg);
921       while ((c= getc(file)) != EOF) { if (!isspace(c)) putchar(c); }
922       printf(".\r\n");
923       fclose(file);
924       return;
925     }
926   }
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) {
933       if (lwi->mustall) {
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");
942         return;
943       } else {
944         if (unlink(fnbuf) && errno != ENOENT) die("unable to remove now unsup list");
945         continue;
946       }
947     }
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;
953       }
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));
960       }
961     } else {
962       printf("503 %s said (after LIST): %s\r\n",si->hostname,responsebuf);
963       return;
964     }
965   }
966   if (lwi->mustall>=-1)
967     printf("215 %s follows:\r\n",lwi->what);
968   lwi->call(lwi,cachename);
969   if (lwi->mustall>=-1)
970     printf(".\r\n");
971 }
972
973 static int unadjustnumber(char *appendto, char *from, int offset) {
974   char *p;
975   int n;
976   long an;
977
978   an= strtol(from,&p,10);
979   if (!*from || *p) {
980     printf("503 bad article range.\r\n");
981     return 0;
982   }
983   if (an > offset)
984     an-= offset;
985   else
986     an= 0;
987   n= strlen(appendto);
988   sprintf(appendto+n,"%lu",an);
989   return 1;
990 }
991
992 static int unadjustrange(char *appendto, char *from, int offset) {
993   char *p;
994
995   p= strchr(from,'-');
996   if (p) {
997     *p++= 0;
998     if (!unadjustnumber(appendto,from,offset)) return 0;
999     from= p;
1000     strcat(appendto,"-");
1001   }
1002   return unadjustnumber(appendto,from,offset);
1003 }
1004  
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];
1009   char *p;
1010   int rcode;
1011   struct serverinfo *si;
1012   int an, n, fieldn, c;
1013   
1014   if (!currentgroup) {
1015     printf("412 overview of which group ?\r\n");
1016     return;
1017   }
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);
1024     return;
1025   } else if (rcode != 0x224) {
1026     printf("503 %s said (after XOVER): %s\r\n",si->hostname,responsebuf);
1027     return;
1028   }
1029   printf("%s\r\n",responsebuf);
1030   for (;;) {
1031     c= getc(si->rfile);
1032     if (c == EOF) serverdataerr(si);
1033     if (c == '.') {
1034       c= getc(si->rfile); 
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 `.' ... */
1039       putchar('.');
1040       putchar(c);
1041     }
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);
1046       continue;
1047     }
1048     printf("%d",adjust(an,currentgroup->offset));
1049     p= xrefbuf; n= 0; fieldn= 0;
1050     for (;;) {
1051       c= getc(si->rfile);
1052       if (c == EOF) serverdataerr(si);
1053       if (c == '\r') continue;
1054       if (c != '\t' && c != '\n' && n < MAX_XREFLINE) {
1055         *p++= c; n++;
1056         continue;
1057       }
1058       if (c == '\t' || c == '\n') fieldn++;
1059       *p++= 0;
1060       if (fieldn >= 8 && !strncasecmp("Xref: ",xrefbuf,6)) {
1061         printf("Xref: ");
1062         processxref(xrefbuf+6,strlen(xrefbuf)-6,stdout,si);
1063       } else {
1064         fputs(xrefbuf,stdout);
1065       }
1066       if (c == '\n') break;
1067       putchar('\t');
1068       p= xrefbuf; n= 0;
1069     }
1070     printf("\r\n");
1071   }
1072   printf(".\r\n");
1073   return;
1074 }
1075
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];
1080   char *p, *q;
1081   int rcode, isxref;
1082   struct serverinfo *si;
1083   long an;
1084   
1085   if (!currentgroup) {
1086     printf("412 headers in which group ?\r\n");
1087     return;
1088   }
1089   p= strchr(arg,' ');
1090   if (!p) {
1091     printf("501 need header and range.\r\n");
1092     return;
1093   }
1094   *p++= 0;
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);
1101     return;
1102   } else if (rcode != 0x221) {
1103     printf("503 %s said (after XHDR): %s\r\n",si->hostname,responsebuf);
1104     return;
1105   }
1106   printf("%s\r\n",responsebuf);
1107   isxref= !strcasecmp(arg,"Xref");
1108   if (!isxref && !currentgroup->offset) {
1109     copydata(si,stdout);
1110   } else {
1111     for (;;) {
1112       if (!fgets(linebuf,MAX_XREFLINE+40,si->rfile)) serverdataerr(si);
1113       if (!stripcommand(linebuf)) continue;
1114       if (!strcmp(linebuf,".")) break;
1115       q= linebuf;
1116       if (currentgroup->offset) {
1117         an= strtol(linebuf,&q,10);
1118         printf("%d",adjust(an,currentgroup->offset));
1119       }
1120       if (isxref && (p= strchr(q,' ')) && strcmp(p+1,"(none)")) {
1121         *p++= 0;
1122         printf("%s ",q);
1123         processxref(p,strlen(p),stdout,si);
1124       } else {
1125         fputs(q,stdout);
1126       }
1127       fputs("\r\n",stdout);
1128     }
1129     printf(".\r\n");
1130   }
1131 }
1132
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];
1137   int rcode, okrcode;
1138   struct serverinfo *si, *gsi;
1139
1140   sprintf(commandbuf,"%s %s",cip->command,arg);
1141
1142   okrcode= cip->einfo ? 0x282 : 0x231;
1143   if (cip->einfo && !strchr(arg,'*') && !strchr(arg,'?')) {
1144     if (!*arg) {
1145       if (!currentgroup) {
1146         printf("412 title of which group ?\r\n");
1147         return;
1148       }
1149       si= currentgroup->readfrom;
1150     } else {
1151       si= findgroup(arg)->readfrom;
1152     }
1153     rcode= servercommand(si,commandbuf,responsebuf,0);
1154     printf("%s\r\n",responsebuf);
1155     if ((rcode & 0xf00) == 0x200) copydata(si,stdout);
1156     return;
1157   }
1158
1159   for (si= servers; si; si= si->next) {
1160     if (!si->searchthis) continue;
1161     si->tempfile= 0;
1162   }
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;
1171     }
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");
1177   }
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);
1186     }
1187     if (ferror(si->tempfile)) die("read error on temp file");
1188     fclose(si->tempfile); si->tempfile= 0;
1189   }
1190   printf(".\r\n");
1191   return;
1192
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);
1197   }
1198   return;
1199 }
1200
1201 static void cmd_authinfo(char *arg, const struct cmdinfo *cip) {
1202   struct authdas { struct authdas *next; char *name; };
1203
1204   static struct authdas *alreadydone= 0;
1205
1206   struct authdas *asearch;
1207   struct MD5Context md5ctx;
1208   char *p, *claim, *here, *what;
1209   FILE *file;
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;
1214   unsigned long ul;
1215   unsigned short us;
1216   struct permission *pi;
1217
1218   if (strcasecmp(strtok(arg," "),"generic") ||
1219       strcmp(strtok(0," "),"md5cookie1way")) {
1220     printf("501 please use AUTHINFO GENERIC md5cookie1way <claim>.\r\n");
1221     return;
1222   }
1223   claim= strtok(0," ");
1224   if (strtok(0," ")) {
1225     printf("501 something after claim.\r\n");
1226     return;
1227   }
1228   for (asearch= alreadydone;
1229        asearch && strcmp(asearch->name,claim);
1230        asearch= asearch->next);
1231   if (asearch) {
1232     printf("502 you are already user/group %s; need other group for more access.\r\n",
1233            claim);
1234     return;
1235   }
1236   file= fopen("md5cookies","r");
1237   if (!file) {
1238     printf("503 couldn't open md5cookies file: %s\r\n",strerror(errno));
1239     return;
1240   }
1241   matching= 0;
1242   for (;;) {
1243     if (!fgets(buf,sizeof(buf),file)) {
1244       if (ferror(file)) {
1245         printf("503 error reading md5cookies file: %s\r\n",strerror(errno));
1246       } else {
1247         printf("502 who did you say you were ?\r\n");
1248       }
1249       fclose(file);
1250       return;
1251     }
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")) {
1257         ifmatch= 1;
1258       } else if (!strcmp(what,"refuse")) {
1259         ifmatch= 0;
1260       } else {
1261         continue;
1262       }
1263       what= strtok(0,"");
1264       if (!what) continue;
1265       if (fnmatch(what,theirfqdn ? theirfqdn : "_unknown_",0)) continue;
1266       matching= ifmatch;
1267     } else if (!strcmp(here,claim)) {
1268       if (matching) break;
1269     }
1270   }
1271   fclose(file);
1272   
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);
1279
1280   p= strtok(0," \t");
1281   if (!p) {
1282     printf("502 md5cookies file is missing secret for you.\r\n");
1283     return;
1284   }
1285   nused= parsehex(p,message+16,MAX_SECRET);
1286
1287   MD5Init(&md5ctx);
1288   MD5Update(&md5ctx,message,16+nused);
1289   MD5Final(expect,&md5ctx);
1290   
1291   printf("100 ");
1292   for (n=0; n<16; n++) {
1293     printf("%s%02x", n?":":"", message[n]);
1294   }
1295   printf("\r\n");
1296   if (ferror(stdout) || fflush(stdout)) die("unable to write auth challenge");
1297
1298   if (!fgets(buf2,MAX_RESPONSE,stdin)) {
1299     if (ferror(stdin)) die("client connection failed during crypto");
1300     exit(1);
1301   }
1302   if (strncasecmp(buf2,"MD5 ",4)) {
1303     printf("502 expecting MD5.\r\n");
1304     return;
1305   }
1306   nused= parsehex(buf2+4,reply,17);
1307   if (nused != 16) {
1308     printf("502 expecting 16 pairs (got %d).\r\n",nused);
1309     return;
1310   }
1311   if (memcmp(reply,expect,16)) {
1312     printf("502 pull the other one, it's got bells on.\r\n");
1313     return;
1314   }
1315   asearch= xmalloc(sizeof(struct authdas));
1316   asearch->name= xstrdup(claim);
1317   asearch->next= alreadydone;
1318   alreadydone= asearch;
1319   printf("281 ok");
1320   while ((p= strtok(0," \t"))) {
1321     printf(" %s",p);
1322     for (pi= permissions; pi; pi= pi->next) {
1323       if (strcmp(pi->name,p)) continue;
1324       if (!pi->authd) {
1325         putchar('+');
1326         pi->authd= 1;
1327       }
1328     }
1329   }
1330   lastdoneauth= asearch->name;
1331   printf("\r\n");
1332 }
1333
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                  },
1362
1363   { "XPAT",         cmd_unimplemented          },
1364   { "XPATH",        cmd_unimplemented          },
1365   {  0                                         }
1366 };
1367
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 ? " " : "*",
1376            cip->command);
1377   }
1378   printf(".\r\n");
1379 }
1380
1381 int main(int argc, char **argv) {
1382   char cmdbuf[1000];
1383   int n;
1384   unsigned int nu;
1385   const struct cmdinfo *cip;
1386   struct hostent *he;
1387
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);
1394
1395   if (chdir("/var/lib/news/merge")) die("chdir");
1396   readconfig();
1397
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_";
1405   } else {
1406     he= gethostbyaddr((void*)&peername.sin_addr,sizeof(peername.sin_addr),AF_INET);
1407     if (he && he->h_name) theirfqdn= xstrdup(he->h_name);
1408   }
1409   
1410   printf("200 %s nntp-merge ready (posting might be ok).\r\n",myfqdn);
1411
1412   for (;;) {
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);
1418     }
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])))
1424         break;
1425     }
1426     if (cip->command) {
1427       while (isspace(cmdbuf[n])) n++;
1428       cip->call(cmdbuf+n,cip);
1429     } else {
1430       printf("500 huh?\r\n");
1431     }
1432   }
1433 }