1 /* $Id: commands.c 7542 2006-08-26 05:57:11Z eagle $
3 ** Miscellaneous commands.
7 #include "portable/wait.h"
11 #include "inn/innconf.h"
12 #include "inn/messages.h"
22 extern const char *NNRPinstance;
25 -1 for problem (such as no such authenticator etc.)
26 0 for authentication succeeded
27 1 for authentication failed
30 static char *PERMauthstring;
33 PERMgeneric(char *av[], char *accesslist)
35 char path[BIG_BUFFER], *fields[6], *p;
36 int i, pan[2], status;
44 PERMaccessconf->locpost = false;
45 PERMaccessconf->allowapproved = false;
48 Reply("%d no authenticator\r\n", NNTP_SYNTAX_VAL);
52 /* check for ../. I'd use strstr, but there doesn't appear to
53 be any other references for it, and I don't want to break
55 for (p = av[0]; *p; p++)
56 if (strncmp(p, "../", 3) == 0) {
57 Reply("%d ../ in authenticator %s\r\n", NNTP_SYNTAX_VAL, av[0]);
61 if (strchr(_PATH_AUTHDIR,'/') == NULL)
62 snprintf(path, sizeof(path), "%s/%s/%s/%s", innconf->pathbin,
63 _PATH_AUTHDIR, _PATH_AUTHDIR_GENERIC, av[0]);
65 snprintf(path, sizeof(path), "%s/%s/%s", _PATH_AUTHDIR,
66 _PATH_AUTHDIR_GENERIC, av[0]);
68 #if !defined(S_IXUSR) && defined(_S_IXUSR)
69 #define S_IXUSR _S_IXUSR
70 #endif /* !defined(S_IXUSR) && defined(_S_IXUSR) */
72 #if !defined(S_IXUSR) && defined(S_IEXEC)
73 #define S_IXUSR S_IEXEC
74 #endif /* !defined(S_IXUSR) && defined(S_IEXEC) */
76 if (stat(path, &stb) || !(stb.st_mode&S_IXUSR)) {
77 Reply("%d No such authenticator %s\r\n", NNTP_TEMPERR_VAL, av[0]);
84 syslog(L_FATAL, "cant pipe for %s %m", av[0]);
88 for (i = 0; (pid = fork()) < 0; i++) {
89 if (i == innconf->maxforks) {
90 Reply("%d Can't fork %s\r\n", NNTP_TEMPERR_VAL,
92 syslog(L_FATAL, "cant fork %s %m", av[0]);
95 syslog(L_NOTICE, "cant fork %s -- waiting", av[0]);
99 /* Run the child, with redirection. */
101 close(STDERR_FILENO); /* Close existing stderr */
102 close(pan[PIPE_READ]);
104 /* stderr goes down the pipe. */
105 if (pan[PIPE_WRITE] != STDERR_FILENO) {
106 if ((i = dup2(pan[PIPE_WRITE], STDERR_FILENO)) != STDERR_FILENO) {
107 syslog(L_FATAL, "cant dup2 %d to %d got %d %m",
108 pan[PIPE_WRITE], STDERR_FILENO, i);
111 close(pan[PIPE_WRITE]);
114 close_on_exec(STDIN_FILENO, false);
115 close_on_exec(STDOUT_FILENO, false);
116 close_on_exec(STDERR_FILENO, false);
119 Reply("%s\r\n", NNTP_BAD_COMMAND);
121 syslog(L_FATAL, "cant execv %s %m", path);
125 close(pan[PIPE_WRITE]);
126 i = read(pan[PIPE_READ], path, sizeof(path));
128 waitpid(pid, &status, 0);
129 if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
132 if ((p = strchr(path, '\n')) != NULL)
136 free(PERMauthstring);
138 PERMauthstring = xstrdup(path);
140 /*syslog(L_NOTICE, "%s (%ld) returned: %d %s %d\n", av[0], (long) pid, i, path, status);*/
141 /* Split "host:permissions:user:pass:groups" into fields. */
142 for (fields[0] = path, i = 0, p = path; *p; p++)
148 PERMcanread = strchr(fields[1], 'R') != NULL;
149 PERMcanpost = strchr(fields[1], 'P') != NULL;
150 PERMaccessconf->allowapproved = strchr(fields[1], 'A') != NULL;
151 PERMaccessconf->locpost = strchr(fields[1], 'L') != NULL;
152 PERMaccessconf->allowihave = strchr(fields[1], 'I') != NULL;
153 if (strchr(fields[1], 'N') != NULL) PERMaccessconf->allownewnews = true;
154 snprintf(PERMuser, sizeof(PERMuser), "%s@%s", fields[2], fields[0]);
155 strlcpy(PERMpass, fields[3], sizeof(PERMpass));
156 strcpy(accesslist, fields[4]);
157 /*strcpy(writeaccess, fields[5]); future work? */
159 /*for (i = 0; fields[i] && i < 6; i++)
160 printf("fields[%d] = %s\n", i, fields[i]);*/
171 static char User[SMBUF];
172 static char Password[SMBUF];
173 char accesslist[BIG_BUFFER];
174 char errorstr[BIG_BUFFER];
176 if (strcasecmp(av[1], "generic") == 0) {
177 char *logrec = Glom(av);
179 strlcpy(PERMuser, "<none>", sizeof(PERMuser));
181 switch (PERMgeneric(av, accesslist)) {
183 PERMspecified = NGgetlist(&PERMreadlist, accesslist);
184 PERMpostlist = PERMreadlist;
185 syslog(L_NOTICE, "%s auth %s (%s -> %s)", ClientHost, PERMuser,
186 logrec, PERMauthstring? PERMauthstring: "" );
187 Reply("%d Authentication succeeded\r\n", NNTP_AUTH_OK_VAL);
188 PERMneedauth = false;
189 PERMauthorized = true;
193 syslog(L_NOTICE, "%s bad_auth %s (%s)", ClientHost, PERMuser,
195 Reply("%d Authentication failed\r\n", NNTP_ACCESS_VAL);
197 ExitWithStats(1, false);
199 /* lower level has issued Reply */
205 if (strcasecmp(av[1], "simple") == 0) {
207 Reply("%d AUTHINFO SIMPLE <USER> <PASS>\r\n", NNTP_BAD_COMMAND_VAL);
210 strlcpy(User, av[2], sizeof(User));
211 strlcpy(Password, av[3], sizeof(Password));
213 if (strcasecmp(av[1], "user") == 0) {
214 strlcpy(User, av[2], sizeof(User));
215 Reply("%d PASS required\r\n", NNTP_AUTH_NEXT_VAL);
219 if (strcasecmp(av[1], "pass") != 0) {
220 Reply("%d bad authinfo param\r\n", NNTP_BAD_COMMAND_VAL);
223 if (User[0] == '\0') {
224 Reply("%d USER required\r\n", NNTP_AUTH_REJECT_VAL);
228 strlcpy(Password, av[2], sizeof(Password));
231 if (strcmp(User, PERMuser) == 0 && strcmp(Password, PERMpass) == 0) {
232 syslog(L_NOTICE, "%s user %s", ClientHost, PERMuser);
234 fprintf(locallog, "%s user (%s):%s\n", ClientHost, Username, PERMuser);
237 Reply("%d Ok\r\n", NNTP_AUTH_OK_VAL);
238 PERMneedauth = false;
239 PERMauthorized = true;
245 PERMlogin(User, Password, errorstr);
246 PERMgetpermissions();
248 syslog(L_NOTICE, "%s user %s", ClientHost, PERMuser);
250 fprintf(locallog, "%s user (%s):%s\n", ClientHost, Username, PERMuser);
253 Reply("%d Ok\r\n", NNTP_AUTH_OK_VAL);
254 PERMneedauth = false;
255 PERMauthorized = true;
259 syslog(L_NOTICE, "%s bad_auth", ClientHost);
260 if (errorstr[0] != '\0') {
261 syslog(L_NOTICE, "%s script error str: %s", ClientHost, errorstr);
262 Reply("%d %s\r\n", NNTP_ACCESS_VAL, errorstr);
264 Reply("%d Authentication error\r\n", NNTP_ACCESS_VAL);
266 ExitWithStats(1, false);
273 ** The "DATE" command. Part of NNTPv2.
284 if (GetTimeInfo(&t) < 0 || (gmt = gmtime(&t.time)) == NULL) {
285 Reply("%d Can't get time, %s\r\n", NNTP_TEMPERR_VAL, strerror(errno));
288 Reply("%d %04.4d%02.2d%02.2d%02.2d%02.2d%02.2d\r\n",
289 NNTP_DATE_FOLLOWS_VAL,
290 gmt->tm_year + 1900, gmt->tm_mon + 1, gmt->tm_mday,
291 gmt->tm_hour, gmt->tm_min, gmt->tm_sec);
296 ** Handle the "mode" command.
304 if (strcasecmp(av[1], "reader") == 0)
305 Reply("%d %s InterNetNews NNRP server %s ready (%s).\r\n",
306 PERMcanpost ? NNTP_POSTOK_VAL : NNTP_NOPOSTOK_VAL,
307 PERMaccessconf->pathhost, inn_version_string,
308 PERMcanpost ? "posting ok" : "no posting");
310 Reply("%d What?\r\n", NNTP_SYNTAX_VAL);
313 static int GroupCompare(const void *a1, const void* b1) {
314 const GROUPDATA *a = a1;
315 const GROUPDATA *b = b1;
317 return strcmp(a->name, b->name);
321 ** Display new newsgroups since a given date and time for specified
324 void CMDnewgroups(int ac, char *av[])
331 int hi, lo, count, flag;
332 GROUPDATA *grouplist = NULL;
341 /* Parse the date. */
342 local = !(ac > 3 && strcasecmp(av[3], "GMT") == 0);
343 date = parsedate_nntp(av[1], av[2], local);
344 if (date == (time_t) -1) {
345 Reply("%d Bad date\r\n", NNTP_SYNTAX_VAL);
349 /* Log an error if active.times doesn't exist, but don't return an error
350 to the client. The most likely cause of this is a new server
351 installation that's yet to have any new groups created, and returning
352 an error was causing needless confusion. Just return the empty list
354 if ((qp = QIOopen(ACTIVETIMES)) == NULL) {
355 syslog(L_ERROR, "%s cant fopen %s %m", ClientHost, ACTIVETIMES);
356 Reply("%d New newsgroups follow.\r\n", NNTP_NEWGROUPS_FOLLOWS_VAL);
361 /* Read the file, ignoring long lines. */
362 while ((p = QIOread(qp)) != NULL) {
363 if ((q = strchr(p, ' ')) == NULL)
366 if ((time_t) atol(q) < date)
368 if (!OVgroupstats(p, &lo, &hi, &count, &flag))
374 if (!PERMmatch(PERMreadlist, grplist))
380 if (grouplist == NULL) {
381 grouplist = xmalloc(1000 * sizeof(GROUPDATA));
384 if (listsize <= numgroups) {
386 grouplist = xrealloc(grouplist, listsize * sizeof(GROUPDATA));
389 grouplist[numgroups].high = hi;
390 grouplist[numgroups].low = lo;
391 grouplist[numgroups].count = count;
392 grouplist[numgroups].name = xstrdup(p);
397 if ((qp = QIOopen(ACTIVE)) == NULL) {
398 syslog(L_ERROR, "%s cant fopen %s %m", ClientHost, ACTIVE);
399 Reply("%d Cannot open active file.\r\n", NNTP_TEMPERR_VAL);
402 qsort(grouplist, numgroups, sizeof(GROUPDATA), GroupCompare);
403 Reply("%d New newsgroups follow.\r\n", NNTP_NEWGROUPS_FOLLOWS_VAL);
404 for (numfound = numgroups; (p = QIOread(qp)) && numfound;) {
405 if ((q = strchr(p, ' ')) == NULL)
408 if ((q = strchr(q, ' ')) == NULL)
411 if ((q = strchr(q, ' ')) == NULL)
415 if ((gd = bsearch(&key, grouplist, numgroups, sizeof(GROUPDATA), GroupCompare)) == NULL)
417 Printf("%s %u %u %s\r\n", p, gd->high, gd->low, q);
420 for (i = 0; i < numgroups; i++) {
421 free(grouplist[i].name);
434 CMDpost(int ac UNUSED, char *av[] UNUSED)
436 static char *article;
446 const char *response;
448 static int backoff_inited = false;
449 bool ihave, permanent;
451 ihave = (strcasecmp(av[0], "ihave") == 0);
452 if (ihave && (!PERMaccessconf->allowihave || !PERMcanpost)) {
453 syslog(L_NOTICE, "%s noperm ihave without permission", ClientHost);
454 Reply("%s\r\n", NNTP_ACCESS);
457 if (!ihave && !PERMcanpost) {
458 syslog(L_NOTICE, "%s noperm post without permission", ClientHost);
459 Reply("%s\r\n", NNTP_CANTPOST);
463 if (!backoff_inited) {
464 /* Exponential posting backoff */
465 InitBackoffConstants();
466 backoff_inited = true;
469 /* Dave's posting limiter - Limit postings to a certain rate
470 * And now we support multiprocess rate limits. Questions?
471 * Email dave@jetcafe.org.
473 if (BACKOFFenabled) {
475 /* Acquire lock (this could be in RateLimit but that would
476 * invoke the spaghetti factor).
478 if ((path = (char *) PostRecFilename(ClientIpString,PERMuser)) == NULL) {
479 Reply("%s\r\n", NNTP_CANTPOST);
483 if (LockPostRec(path) == 0) {
484 syslog(L_ERROR, "%s Error write locking '%s'",
486 Reply("%s\r\n", NNTP_CANTPOST);
490 if (!RateLimit(&sleeptime,path)) {
491 syslog(L_ERROR, "%s can't check rate limit info", ClientHost);
492 Reply("%s\r\n", NNTP_CANTPOST);
495 } else if (sleeptime != 0L) {
496 syslog(L_NOTICE,"%s post sleep time is now %ld", ClientHost, sleeptime);
500 /* Remove the lock here so that only one nnrpd process does the
501 * backoff sleep at once. Other procs are sleeping for the lock.
505 } /* end backoff code */
507 /* Start at beginning of buffer. */
508 if (article == NULL) {
510 article = xmalloc(size);
514 Reply(NNTP_SENDIT "\r\n");
516 if ((p = GenerateMessageID(PERMaccessconf->domain)) != NULL) {
517 if (VirtualPathlen > 0) {
519 if ((p = strchr(p, '@')) != NULL) {
521 snprintf(idbuff, sizeof(idbuff), "%s%s@%s>", q,
522 NNRPinstance, PERMaccessconf->domain);
525 strlcpy(idbuff, p, sizeof(idbuff));
528 Reply("%d Ok, recommended ID %s\r\n", NNTP_START_POST_VAL, idbuff);
533 end = &article[size];
540 r = line_read(&NNTPline, PERMaccessconf->clienttimeout, &line, &len);
543 warn("%s internal %d in post", ClientHost, r);
546 warn("%s timeout in post", ClientHost);
547 ExitWithStats(1, false);
550 warn("%s eof in post", ClientHost);
551 ExitWithStats(1, false);
561 /* if its the terminator, break out */
562 if (strcmp(line, ".") == 0) {
566 /* if they broke our line length limit, there's little point
567 * in processing any more of their input */
572 /* +2 because of the \n\0 we append; note we don't add the 2
573 * when increasing the size of the buffer as ART_LINE_MALLOC
574 * will always be larger than 2 bytes */
575 if ((len + 2) > (size_t)(end - p)) {
577 size += len + ART_LINE_MALLOC;
578 article = xrealloc(article, size);
579 end = &article[size];
583 /* reverse any byte-stuffing */
588 memcpy(p, line, len);
595 warn("%s toolong in post", ClientHost);
596 Printf("%d Line %d too long\r\n",
597 ihave ? NNTP_REJECTIT_VAL : NNTP_POSTFAIL_VAL, longline);
602 /* Send the article to the server. */
603 response = ARTpost(article, idbuff, ihave, &permanent);
604 if (response == NULL) {
605 notice("%s post ok %s", ClientHost, idbuff);
606 Reply("%s %s\r\n", ihave ? NNTP_TOOKIT : NNTP_POSTEDOK, idbuff);
610 if ((p = strchr(response, '\r')) != NULL)
612 if ((p = strchr(response, '\n')) != NULL)
614 notice("%s post failed %s", ClientHost, response);
615 if (!ihave || permanent) {
616 /* for permanent errors reject the message */
617 Reply("%d %s\r\n", ihave ? NNTP_REJECTIT_VAL : NNTP_POSTFAIL_VAL,
620 /* non-permanent errors only have relevance to ihave, for
621 * these we have the error status from the upstream
622 * server to report */
623 Reply("%s\r\n", response);
630 ** The "xpath" command. An uncommon extension.
638 Reply("%d Syntax error or bad command\r\n", NNTP_BAD_COMMAND_VAL);