10 #include <arpa/inet.h>
13 #include <netinet/in.h>
17 #include "nntp-merge.h"
19 static int output64(FILE *file, unsigned long v, int minn) {
20 static const char *const b64=
21 "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789" "+-";
24 putc(b64[v&077],file);
31 static void eatpost() {
32 if (!copydatafile(stdin,0)) die("failed read from client during POST");
35 static void badpost(const char *why) {
37 printf("441 not acceptable: %s\r\n",why);
40 static int attemptposting(struct serverinfo *si, FILE *file,
41 char response[MAX_RESPONSE+3]) {
45 if (fseek(file,0,SEEK_SET)) {
46 sprintf(response,"503 rewind article failed: %.100s",strerror(errno));
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;
57 if (fflush(si->wfile) == EOF || ferror(file)) {
58 sprintf(response,"503 read article failed: %.100s",strerror(errno));
59 closeserver(si); return 0x503;
61 if (!fgets(response,MAX_RESPONSE+3,si->rfile)) {
62 sprintf(response,"503 server read failed: %.100s",strerror(errno));
63 closeserver(si); return 0x503;
65 if (!stripcommand(response)) {
66 sprintf(response,"503 null garbage in reply to article");
67 closeserver(si); return 0x503;
69 if (!decoderesponse(response,&rv,si)) return 0x503;
73 void cmd_post(char *arg, const struct cmdinfo *cip) {
74 char linebuf[MAX_XREFLINE+3];
76 int n, m, hadmessageid, haddate, hadnewsgroups, moderated, zonediff, i, j, rcode;
77 int zonediffc, hadpermprob, c;
79 char *p, *colon, *space, *comma;
80 struct serverinfo *postto[20], *si;
81 char response[MAX_RESPONSE+3];
82 char commandbuf[MAX_COMMAND+3];
86 struct tm local, gm, *tmtmp;
88 file= tmpfile(); if (!file) die("unable to make tmpfile for POST");
90 fputs("340 go ahead.\r\n",stdout);
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;
96 for (si= servers; si; si= si->next) si->tempfile= 0;
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; }
104 if (n == 1 && linebuf[0] == '.') {
105 printf("441 message must have a body.\r\n"); fclose(file); return;
107 colon= strchr(linebuf,':');
108 if (!colon) { badpost("header line with no colon"); fclose(file); return; }
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;
121 } else if (!strcasecmp(linebuf,"date")) {
123 badpost("two (or more) Date headers"); fclose(file); return;
125 } else if (!strcasecmp(linebuf,"newsgroups")) {
126 if (hadnewsgroups++) {
127 badpost("two (or more) Newsgroups headers"); fclose(file); return;
131 comma= strchr(p,',');
132 if (comma) *comma++= 0;
134 if (stillrestricted(&gi->restrictto)) {
135 hadpermprob= 1; p= comma; continue;
136 } else if (!gi->postto[0]) {
140 i<(sizeof(gi->postto)/sizeof(gi->postto[0])) &&
144 j<(sizeof(postto)/sizeof(postto[0])) && postto[j] &&
145 postto[j] != gi->postto[i];
147 if (j >= (sizeof(postto)/sizeof(postto[0]))) {
148 badpost("would have to post to too many servers"); fclose(file); return;
151 postto[j]= gi->postto[i];
152 if (j+1 < (sizeof(postto)/sizeof(postto[0]))) postto[j+1]= 0;
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.
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);
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;
179 if (strcmp(response,p)) continue;
180 space= strchr(space,' ');
181 if (!space) continue;
182 space= strchr(++space,' ');
183 if (!space) continue;
185 if (*space == 'm') moderated= 1;
196 if (c != ' ' && c != '\t') break;
199 if (c == '\n') break;
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
211 if (!hadnewsgroups) { badpost("missing Newsgroups header"); fclose(file); return; }
216 printf("480 the power of %s is but weak.\r\n",lastdoneauth);
218 printf("480 thou must prove thine strength.\r\n");
221 badpost("no server(s) for those groups, cannot post.\r\n");
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));
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.
240 if (!haddate || !hadmessageid) {
244 tmtmp= gmtime(&now); if (!tmtmp) die("can't get Greenwich Mean Time");
246 tmtmp= localtime(&now); if (!tmtmp) die("can't get local time");
248 zonediff= (local.tm_hour*60 + local.tm_min) - (gm.tm_hour*60 + gm.tm_min);
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);
260 unsigned long pid= getpid();
261 fputs("Message-ID: <",file);
262 output64(file,(now&03)|(pid<<2),0);
264 output64(file,now>>2,5);
265 fprintf(file,"@%s>\r\n",myfqdn);
270 if (!copydatafile(stdin,file)) die("failed read from client during POST");
272 if (ferror(file) || fflush(file) || fseek(file,0,SEEK_SET))
273 die("error writing tmp posting file");
275 rcode= attemptposting(postto[0],file,response);
276 if (rcode != 0x240) {
278 printf("441 POST failed - %s: %s\r\n",postto[0]->hostname,response);
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);
287 printf("; %s ok",postto[i]->hostname);
291 printf(" - posted.\r\n");