chiark / gitweb /
new lwc_fixed for distrib.pats and distributions
[nntp-merge-chiark.git] / post.c.orig
1 /**/
2
3 #include <stdio.h>
4 #include <errno.h>
5 #include <string.h>
6 #include <ctype.h>
7 #include <assert.h>
8 #include <sys/types.h>
9 #include <netdb.h>
10 #include <arpa/inet.h>
11 #include <time.h>
12 #include <unistd.h>
13 #include <netinet/in.h>
14
15 #include <ident.h>
16
17 #include "nntp-merge.h"
18
19 static int output64(FILE *file, unsigned long v, int minn) {
20   static const char *const b64=
21     "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789" "+-";
22   int i= 0;
23   while (minn>0 || v) {
24     putc(b64[v&077],file);
25     v >>= 6;
26     i++; minn--;
27   }
28   return i;
29 }
30
31 static void eatpost() {
32   if (!copydatafile(stdin,0)) die("failed read from client during POST");
33 }
34
35 static void badpost(const char *why) {
36   eatpost();
37   printf("441 not acceptable: %s\r\n",why);
38 }
39
40 static int attemptposting(struct serverinfo *si, FILE *file,
41                           char response[MAX_RESPONSE+3]) {
42   int rcode, c;
43   unsigned long rv;
44
45   if (fseek(file,0,SEEK_SET)) {
46     sprintf(response,"503 rewind article failed: %.100s",strerror(errno));
47     return 0x503;
48   }
49   rcode= servercommand(si,"POST",response,0);
50   if (rcode != 0x340) return rcode;
51   while ((c= getc(file)) != EOF) {
52     if (putc(c,si->wfile) == EOF) {
53       sprintf(response,"503 server write failed: %.100s",strerror(errno));
54       closeserver(si); return 0x503;
55     }
56   }
57   if (fflush(si->wfile) == EOF || ferror(file)) {
58     sprintf(response,"503 read article failed: %.100s",strerror(errno));
59     closeserver(si); return 0x503;
60   }
61   if (!fgets(response,MAX_RESPONSE+3,si->rfile)) {
62     sprintf(response,"503 server read failed: %.100s",strerror(errno));
63     closeserver(si); return 0x503;
64   }
65   if (!stripcommand(response)) {
66     sprintf(response,"503 null garbage in reply to article");
67     closeserver(si); return 0x503;
68   }
69   if (!decoderesponse(response,&rv,si)) return 0x503;
70   return rv;
71 }
72
73 void cmd_post(char *arg, const struct cmdinfo *cip) {
74   char linebuf[MAX_XREFLINE+3];
75   FILE *file;
76   int n, m, hadmessageid, haddate, hadnewsgroups, moderated, zonediff, i, j, rcode;
77   int zonediffc, hadpermprob, c;
78   struct groupinfo *gi;
79   char *p, *colon, *space, *comma;
80   struct serverinfo *postto[20], *si;
81   char response[MAX_RESPONSE+3];
82   char buf[1000];
83   IDENT *id;
84   time_t now;
85   struct tm local, gm, *tmtmp;
86
87   file= tmpfile();  if (!file) die("unable to make tmpfile for POST");
88   if (lastdoneauth)
89     fputs("340 go ahead.\r\n",stdout);
90   else
91     fputs("340 make my day, punk.\r\n",stdout);
92   if (ferror(stdout) || fflush(stdout)) die("unable to say go ahead to POST");
93   hadmessageid= haddate= hadnewsgroups= 0; hadpermprob= 0;
94   postto[0]= 0;
95   for (si= servers; si; si= si->next) si->tempfile= 0;
96   for (;;) {
97     errno= 0; if (!fgets(linebuf,MAX_XREFLINE,stdin)) die("error reading data in POST");
98     m= n= strlen(linebuf); if (!n) die("null data line in POST");
99     while (n > 0 && (linebuf[n-1] == '\n' || linebuf[n-1] == '\r')) n--;
100     if (m == n) { badpost("line in POST too long"); fclose(file); return; }
101     if (n==0) break;
102     linebuf[n]= 0;
103     if (n == 1 && linebuf[0] == '.') {
104       printf("441 message must have a body.\r\n"); fclose(file); return;
105     }
106     colon= strchr(linebuf,':');
107     if (!colon) { badpost("header line with no colon"); fclose(file); return; }
108     *colon++= 0;
109     while (isspace(*colon)) colon++;
110     for (p=linebuf; *p; p++)
111       if (isspace(*p)) { badpost("space in header name"); fclose(file); return; }
112     if (!strcasecmp(linebuf,"originator")) continue;
113     if (!strcasecmp(linebuf,"nntp-posting-host")) continue;
114     if (!strcasecmp(linebuf,"path")) continue;
115     fprintf(file,"%s: %s\r\n",linebuf,colon);
116     if (!strcasecmp(linebuf,"message-id")) {
117       if (hadmessageid++) {
118         badpost("two (or more) Message-ID headers"); fclose(file); return;
119       }
120     } else if (!strcasecmp(linebuf,"date")) {
121       if (haddate++) {
122         badpost("two (or more) Date headers"); fclose(file); return;
123       }
124     } else if (!strcasecmp(linebuf,"newsgroups")) {
125       if (hadnewsgroups++) {
126         badpost("two (or more) Newsgroups headers"); fclose(file); return;
127       }
128       p= colon;
129       while (p) {
130         comma= strchr(p,',');
131         if (comma) *comma++= 0;
132         gi= findgroup(p);
133         if (stillrestricted(&gi->restrictto)) {
134           hadpermprob= 1; p= comma; continue;
135         } else if (!gi->postto[0]) {
136           p= comma; continue;
137         }
138         for (i=0;
139              i<(sizeof(gi->postto)/sizeof(gi->postto[0])) &&
140              gi->postto[i];
141              i++) {
142           for (j=0;
143                j<(sizeof(postto)/sizeof(postto[0])) && postto[j] &&
144                postto[j] != gi->postto[i];
145                j++);
146           if (j >= (sizeof(postto)/sizeof(postto[0]))) {
147             badpost("would have to post to too many servers"); fclose(file); return;
148           }
149           if (!postto[j]) {
150             postto[j]= gi->postto[i];
151             if (j+1 < (sizeof(postto)/sizeof(postto[0]))) postto[j+1]= 0;
152           }
153         }
154         /* We don't bother checking for moderation status if we're only
155          * posting to one group, and that group is only on one server.
156          */
157         if (p == colon && !comma && !gi->postto[1]) break;
158         rcode= servercommand(gi->postto[0],"LIST",response,0);
159         if (rcode != 0x215) {
160           eatpost(); fclose(file);
161           printf("441 couldn't LIST to check moderation status of %s - %s: %s\r\n",
162                  p,gi->postto[0]->hostname,response);
163           return;
164         }
165         moderated= 0;
166         for (;;) {
167           if (!fgets(response,sizeof(response),gi->postto[0]->rfile))
168             serverdataerr(gi->postto[0]);
169           if (!strcmp(response,".\r\n") || !strcmp(response,".\n")) break;
170           space= strchr(response,' ');
171           if (!space) continue;
172           *space++= 0;
173           if (strcmp(response,p)) continue;
174           space= strchr(space,' ');
175           if (!space) continue;
176           space= strchr(++space,' ');
177           if (!space) continue;
178           ++space;
179           if (*space == 'm') moderated= 1;
180         }
181         if (moderated) {
182           postto[1]= 0;
183           break;
184         }
185         p= comma;
186       }
187     } else {
188       for (;;) {
189         c= getchar();
190         if (c != ' ' && c != '\t') break;
191         for (;;) {
192           putc(c,file);
193           if (c == '\n') break;
194           c= getchar();
195           if (c == EOF) break;
196         }
197       }
198       ungetc(c,stdin);
199     }
200   }
201   /* Right, we've copied the header into tmpfile, and we've read
202    * but not yet copied the blank line.  We must add the Originator
203    * field.
204    */
205   if (!hadnewsgroups) { badpost("missing Newsgroups header"); fclose(file); return; }
206   if (!postto[0]) {
207     if (hadpermprob) {
208       eatpost();
209       if (lastdoneauth) {
210         printf("480 the power of %s is but weak.\r\n",lastdoneauth);
211       } else {
212         printf("480 thou must prove thine strength.\r\n");
213       }
214     } else {
215       badpost("no server(s) for those groups, cannot post.\r\n");
216     }
217     return;
218   }
219   id= ident_lookup(0,30);
220   fprintf(file,"Originator: %s@", id && id->identifier ? id->identifier : "");
221   if (theirfqdn) fprintf(file,"%s ([%s])",
222                          strcmp(theirfqdn,"localhost") ? theirfqdn : myfqdn,
223                          inet_ntoa(peername.sin_addr));
224   else fprintf(file,"[%s]", inet_ntoa(peername.sin_addr));
225   fputs("\r\n",file);
226   /* Forms of Originator line are:
227    *  Originator: <identifier>@<hostname> ([<addr>])
228    *  Originator: <identifier>@[<addr>]
229    *  Originator: @<hostname> ([<addr>])
230    *  Originator: @[addr]
231    * <opsys>, <charset> are empty strings if not available.
232    */
233
234   if (!haddate || !hadmessageid) {
235     time(&now);
236   }
237   if (!haddate) {
238     tmtmp= gmtime(&now); if (!tmtmp) die("can't get Greenwich Mean Time");
239     gm= *tmtmp;
240     tmtmp= localtime(&now); if (!tmtmp) die("can't get local time");
241     local= *tmtmp;
242     zonediff= (local.tm_hour*60 + local.tm_min) - (gm.tm_hour*60 + gm.tm_min);
243     zonediff += 2880;
244     zonediff %= 1440;
245     zonediffc= '+';
246     if (zonediff > 720) { zonediff= 1440-zonediff; zonediffc= '-'; }
247     assert(sprintf(buf,"Date: %%d %%b %%Y %%H:%%M:%%S %c%02d%02d (%%Z)\r\n",
248                    zonediffc,zonediff/60,zonediff%60) < sizeof(buf));
249     if (strftime(response,sizeof(response),buf,&local) == sizeof(response))
250       die("date is too long for buffer");
251     fputs(response,file);
252   }
253   if (!hadmessageid) {
254     unsigned long pid= getpid();
255     fputs("Message-ID: <",file);
256     output64(file,(now&03)|(pid<<2),0);
257     putc('*',file);
258     output64(file,now>>2,5);
259     fprintf(file,"@%s>\r\n",myfqdn);
260     sleep(2);
261   }
262
263   fputs("\r\n",file);
264   if (!copydatafile(stdin,file)) die("failed read from client during POST");
265
266   if (ferror(file) || fflush(file) || fseek(file,0,SEEK_SET))
267     die("error writing tmp posting file");
268
269   rcode= attemptposting(postto[0],file,response);
270   if (rcode != 0x240) {
271     fclose(file);
272     printf("441 POST failed - %s: %s\r\n",postto[0]->hostname,response);
273     return;
274   }
275   printf("240 %s ok",postto[0]->hostname); fflush(stdout);
276   for (i=1; i<sizeof(postto)/sizeof(postto[0]) && postto[i]; i++) {
277     rcode= attemptposting(postto[i],file,response);
278     if (rcode != 0x240) {
279       printf("; %s err %.30s",postto[i]->hostname,response);
280     } else {
281       printf("; %s ok",postto[i]->hostname);
282     }
283     fflush(stdout);
284   }
285   printf(" - posted.\r\n");
286   fclose(file);
287   return;
288 }