chiark / gitweb /
fix warnings
[nntp-merge-chiark.git] / post.c
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 commandbuf[MAX_COMMAND+3];
83   char buf[1000];
84   IDENT *id;
85   time_t now;
86   struct tm local, gm, *tmtmp;
87
88   file= tmpfile();  if (!file) die("unable to make tmpfile for POST");
89   if (lastdoneauth)
90     fputs("340 go ahead.\r\n",stdout);
91   else
92     fputs("340 make my day, punk.\r\n",stdout);
93   if (ferror(stdout) || fflush(stdout)) die("unable to say go ahead to POST");
94   hadmessageid= haddate= hadnewsgroups= 0; hadpermprob= 0;
95   postto[0]= 0;
96   for (si= servers; si; si= si->next) si->tempfile= 0;
97   for (;;) {
98     errno= 0; if (!fgets(linebuf,MAX_XREFLINE,stdin)) die("error reading data in POST");
99     m= n= strlen(linebuf); if (!n) die("null data line in POST");
100     while (n > 0 && (linebuf[n-1] == '\n' || linebuf[n-1] == '\r')) n--;
101     if (m == n) { badpost("line in POST too long"); fclose(file); return; }
102     if (n==0) break;
103     linebuf[n]= 0;
104     if (n == 1 && linebuf[0] == '.') {
105       printf("441 message must have a body.\r\n"); fclose(file); return;
106     }
107     colon= strchr(linebuf,':');
108     if (!colon) { badpost("header line with no colon"); fclose(file); return; }
109     *colon++= 0;
110     while (isspace(*colon)) colon++;
111     for (p=linebuf; *p; p++)
112       if (isspace(*p)) { badpost("space in header name"); fclose(file); return; }
113     if (!strcasecmp(linebuf,"originator")) continue;
114     if (!strcasecmp(linebuf,"nntp-posting-host")) continue;
115     if (!strcasecmp(linebuf,"path")) continue;
116     fprintf(file,"%s: %s\r\n",linebuf,colon);
117     if (!strcasecmp(linebuf,"message-id")) {
118       if (hadmessageid++) {
119         badpost("two (or more) Message-ID headers"); fclose(file); return;
120       }
121     } else if (!strcasecmp(linebuf,"date")) {
122       if (haddate++) {
123         badpost("two (or more) Date headers"); fclose(file); return;
124       }
125     } else if (!strcasecmp(linebuf,"newsgroups")) {
126       if (hadnewsgroups++) {
127         badpost("two (or more) Newsgroups headers"); fclose(file); return;
128       }
129       p= colon;
130       while (p) {
131         comma= strchr(p,',');
132         if (comma) *comma++= 0;
133         gi= findgroup(p);
134         if (stillrestricted(&gi->restrictto)) {
135           hadpermprob= 1; p= comma; continue;
136         } else if (!gi->postto[0]) {
137           p= comma; continue;
138         }
139         for (i=0;
140              i<(sizeof(gi->postto)/sizeof(gi->postto[0])) &&
141              gi->postto[i];
142              i++) {
143           for (j=0;
144                j<(sizeof(postto)/sizeof(postto[0])) && postto[j] &&
145                postto[j] != gi->postto[i];
146                j++);
147           if (j >= (sizeof(postto)/sizeof(postto[0]))) {
148             badpost("would have to post to too many servers"); fclose(file); return;
149           }
150           if (!postto[j]) {
151             postto[j]= gi->postto[i];
152             if (j+1 < (sizeof(postto)/sizeof(postto[0]))) postto[j+1]= 0;
153           }
154         }
155         /* We don't bother checking for moderation status if we're only
156          * posting to one group, and that group is only on one server.
157          */
158         if (p == colon && !comma && !gi->postto[1]) break;
159        /* Try undocumented nnrp feature, falling back on getting whole active file. */
160        snprintf(commandbuf, sizeof(commandbuf), "LIST ACTIVE %s", p);
161        rcode= servercommand(gi->postto[0],commandbuf,response,0);
162        if (rcode != 0x215) {
163          rcode= servercommand(gi->postto[0],"LIST",response,0);
164          if (rcode != 0x215) {
165            eatpost(); fclose(file);
166            printf("441 couldn't LIST to check moderation status of %s - %s: %s\r\n",
167                   p,gi->postto[0]->hostname,response);
168            return;
169          }
170        }
171         moderated= 0;
172         for (;;) {
173           if (!fgets(response,sizeof(response),gi->postto[0]->rfile))
174             serverdataerr(gi->postto[0]);
175           if (!strcmp(response,".\r\n") || !strcmp(response,".\n")) break;
176           space= strchr(response,' ');
177           if (!space) continue;
178           *space++= 0;
179           if (strcmp(response,p)) continue;
180           space= strchr(space,' ');
181           if (!space) continue;
182           space= strchr(++space,' ');
183           if (!space) continue;
184           ++space;
185           if (*space == 'm') moderated= 1;
186         }
187         if (moderated) {
188           postto[1]= 0;
189           break;
190         }
191         p= comma;
192       }
193     } else {
194       for (;;) {
195         c= getchar();
196         if (c != ' ' && c != '\t') break;
197         for (;;) {
198           putc(c,file);
199           if (c == '\n') break;
200           c= getchar();
201           if (c == EOF) break;
202         }
203       }
204       ungetc(c,stdin);
205     }
206   }
207   /* Right, we've copied the header into tmpfile, and we've read
208    * but not yet copied the blank line.  We must add the Originator
209    * field.
210    */
211   if (!hadnewsgroups) { badpost("missing Newsgroups header"); fclose(file); return; }
212   if (!postto[0]) {
213     if (hadpermprob) {
214       eatpost();
215       if (lastdoneauth) {
216         printf("480 the power of %s is but weak.\r\n",lastdoneauth);
217       } else {
218         printf("480 thou must prove thine strength.\r\n");
219       }
220     } else {
221       badpost("no server(s) for those groups, cannot post.\r\n");
222     }
223     return;
224   }
225   id= ident_lookup(0,30);
226   fprintf(file,"Originator: %s@", id && id->identifier ? id->identifier : "");
227   if (theirfqdn) fprintf(file,"%s ([%s])",
228                          strcmp(theirfqdn,"localhost") ? theirfqdn : myfqdn,
229                          inet_ntoa(peername.sin_addr));
230   else fprintf(file,"[%s]", inet_ntoa(peername.sin_addr));
231   fputs("\r\n",file);
232   /* Forms of Originator line are:
233    *  Originator: <identifier>@<hostname> ([<addr>])
234    *  Originator: <identifier>@[<addr>]
235    *  Originator: @<hostname> ([<addr>])
236    *  Originator: @[addr]
237    * <opsys>, <charset> are empty strings if not available.
238    */
239
240   if (!haddate || !hadmessageid) {
241     time(&now);
242   }
243   if (!haddate) {
244     tmtmp= gmtime(&now); if (!tmtmp) die("can't get Greenwich Mean Time");
245     gm= *tmtmp;
246     tmtmp= localtime(&now); if (!tmtmp) die("can't get local time");
247     local= *tmtmp;
248     zonediff= (local.tm_hour*60 + local.tm_min) - (gm.tm_hour*60 + gm.tm_min);
249     zonediff += 2880;
250     zonediff %= 1440;
251     zonediffc= '+';
252     if (zonediff > 720) { zonediff= 1440-zonediff; zonediffc= '-'; }
253     assert(sprintf(buf,"Date: %%d %%b %%Y %%H:%%M:%%S %c%02d%02d (%%Z)\r\n",
254                    zonediffc,zonediff/60,zonediff%60) < sizeof(buf));
255     if (strftime(response,sizeof(response),buf,&local) == sizeof(response))
256       die("date is too long for buffer");
257     fputs(response,file);
258   }
259   if (!hadmessageid) {
260     unsigned long pid= getpid();
261     fputs("Message-ID: <",file);
262     output64(file,(now&03)|(pid<<2),0);
263     putc('*',file);
264     output64(file,now>>2,5);
265     fprintf(file,"@%s>\r\n",myfqdn);
266     sleep(2);
267   }
268
269   fputs("\r\n",file);
270   if (!copydatafile(stdin,file)) die("failed read from client during POST");
271
272   if (ferror(file) || fflush(file) || fseek(file,0,SEEK_SET))
273     die("error writing tmp posting file");
274
275   rcode= attemptposting(postto[0],file,response);
276   if (rcode != 0x240) {
277     fclose(file);
278     printf("441 POST failed - %s: %s\r\n",postto[0]->hostname,response);
279     return;
280   }
281   printf("240 %s ok",postto[0]->hostname); fflush(stdout);
282   for (i=1; i<sizeof(postto)/sizeof(postto[0]) && postto[i]; i++) {
283     rcode= attemptposting(postto[i],file,response);
284     if (rcode != 0x240) {
285       printf("; %s err %.30s",postto[i]->hostname,response);
286     } else {
287       printf("; %s ok",postto[i]->hostname);
288     }
289     fflush(stdout);
290   }
291   printf(" - posted.\r\n");
292   fclose(file);
293   return;
294 }