1 /* $Id: misc.c 6535 2003-12-10 09:02:22Z rra $
3 ** Miscellaneous support routines.
9 /* Needed on AIX 4.1 to get fd_set and friends. */
10 #ifdef HAVE_SYS_SELECT_H
11 # include <sys/select.h>
14 #include "inn/innconf.h"
17 #include "sasl_config.h"
21 extern int nnrpd_starttls_done;
26 ** Parse a string into a NULL-terminated array of words; return number
27 ** of words. If argvp isn't NULL, it and what it points to will be freed.
42 /* Copy the line, which we will split up. */
43 while (ISWHITE(*line))
47 /* Allocate worst-case amount of space. */
48 for (*argvp = argv = xmalloc((strlen(p) + 2) * sizeof(char *)); *p; ) {
49 /* Mark start of this word, find its end. */
50 for (*argv++ = p; *p && !ISWHITE(*p); )
55 /* Nip off word, skip whitespace. */
56 for (*p++ = '\0'; ISWHITE(*p); )
65 ** Take a vector which Argify made and glue it back together with
66 ** spaces between each element. Returns a pointer to dynamic space.
77 for (i = 0, v = av; *v; v++)
83 for (v = av; *v; v++) {
85 strlcat(save, " ", i);
94 ** Match a list of newsgroup specifiers against a list of newsgroups.
95 ** func is called to see if there is a match.
97 bool PERMmatch(char **Pats, char **list)
103 if (Pats == NULL || Pats[0] == NULL)
106 for ( ; *list; list++) {
107 for (i = 0; (p = Pats[i]) != NULL; i++) {
109 if (uwildmat(*list, ++p))
112 else if (uwildmat(*list, p))
116 /* If we can read it in one group, we can read it, period. */
125 ** Check to see if user is allowed to see this article by matching
131 static char **grplist;
137 if ((p = GetHeader("Xref")) == NULL) {
138 /* in case article does not include Xref */
139 if ((p = GetHeader("Newsgroups")) != NULL) {
140 if (!NGgetlist(&grplist, p))
141 /* No newgroups or null entry. */
147 /* skip path element */
148 if ((p = strchr(p, ' ')) == NULL)
150 for (p++ ; *p == ' ' ; p++);
153 if (!NGgetlist(&grplist, p))
154 /* No newgroups or null entry. */
156 /* chop ':' and article number */
157 for (grp = grplist ; *grp != NULL ; grp++) {
158 if ((p = strchr(*grp, ':')) == NULL)
165 if (PY_use_dynamic) {
168 /* Authorize user at a Python authorization module */
169 if (PY_dynamic(PERMuser, p, false, &reply) < 0) {
170 syslog(L_NOTICE, "PY_dynamic(): authorization skipped due to no Python dynamic method defined.");
173 syslog(L_TRACE, "PY_dynamic() returned a refuse string for user %s at %s who wants to read %s: %s", PERMuser, ClientHost, p, reply);
180 #endif /* DO_PYTHON */
182 return PERMmatch(PERMreadlist, grplist);
187 ** Parse a newsgroups line, return true if there were any.
190 NGgetlist(argvp, list)
196 for (p = list; *p; p++)
200 return Argify(list, argvp) != 0;
204 /*********************************************************************
205 * POSTING RATE LIMITS - The following code implements posting rate
206 * limits. News clients are indexed by IP number (or PERMuser, see
207 * config file). After a relatively configurable number of posts, the nnrpd
208 * process will sleep for a period of time before posting anything.
210 * Each time that IP number posts a message, the time of
211 * posting and the previous sleep time is stored. The new sleep time
212 * is computed based on these values.
214 * To compute the new sleep time, the previous sleep time is, for most
215 * cases multiplied by a factor (backoff_k).
217 * See inn.conf(5) for how this code works
219 *********************************************************************/
221 /* Defaults are pass through, i.e. not enabled
222 * NEW for INN 1.8 - Use the inn.conf file to specify the following:
224 * backoff_k: <integer>
225 * backoff_postfast: <integer>
226 * backoff_postslow: <integer>
227 * backoff_trigger: <integer>
229 * backoff_auth: <on|off>
231 * You may also specify posting backoffs on a per user basis. To do this
232 * turn on "backoff_auth"
234 * Now these are runtime constants. <grin>
236 static char postrec_dir[SMBUF]; /* Where is the post record directory? */
239 InitBackoffConstants()
243 /* Default is not to enable this code */
244 BACKOFFenabled = false;
246 /* Read the runtime config file to get parameters */
248 if ((PERMaccessconf->backoff_db == NULL) ||
249 !(PERMaccessconf->backoff_k >= 0L && PERMaccessconf->backoff_postfast >= 0L && PERMaccessconf->backoff_postslow >= 1L))
252 /* Need this database for backing off */
253 strlcpy(postrec_dir, PERMaccessconf->backoff_db, sizeof(postrec_dir));
254 if (stat(postrec_dir, &st) < 0) {
255 if (ENOENT == errno) {
256 if (!MakeDirectory(postrec_dir, true)) {
257 syslog(L_ERROR, "%s cannot create backoff_db '%s': %s",ClientHost,postrec_dir,strerror(errno));
261 syslog(L_ERROR, "%s cannot stat backoff_db '%s': %s",ClientHost,postrec_dir,strerror(errno));
265 if (!S_ISDIR(st.st_mode)) {
266 syslog(L_ERROR, "%s backoff_db '%s' is not a directory",ClientHost,postrec_dir);
270 BACKOFFenabled = true;
276 * PostRecs are stored in individual files. I didn't have a better
277 * way offhand, don't want to touch DBZ, and the number of posters is
278 * small compared to the number of readers. This is the filename corresponding
282 *PostRecFilename(ip,user)
286 static char buff[SPOOLNAMEBUFF];
287 char dirbuff[SPOOLNAMEBUFF];
288 struct in_addr inaddr;
289 unsigned long int addr;
290 unsigned char quads[4];
293 if (PERMaccessconf->backoff_auth) {
294 snprintf(buff, sizeof(buff), "%s/%s", postrec_dir, user);
298 if (inet_aton(ip, &inaddr) < 1) {
299 /* If inet_aton() fails, we'll assume it's an IPv6 address. We'll
300 * also assume for now that we're dealing with a limited number of
301 * IPv6 clients so we'll place their files all in the same
302 * directory for simplicity. Someday we'll need to change this to
303 * something more scalable such as DBZ when IPv6 clients become
305 snprintf(buff, sizeof(buff), "%s/%s", postrec_dir, ip);
308 /* If it's an IPv4 address just fall through. */
310 addr = ntohl(inaddr.s_addr);
312 quads[i] = (unsigned char) (0xff & (addr>>(i*8)));
314 snprintf(dirbuff, sizeof(dirbuff), "%s/%03d%03d/%03d",
315 postrec_dir, quads[3], quads[2], quads[1]);
316 if (!MakeDirectory(dirbuff,true)) {
317 syslog(L_ERROR, "%s Unable to create postrec directories '%s': %s",
318 ClientHost, dirbuff, strerror(errno));
321 snprintf(buff, sizeof(buff), "%s/%03d", dirbuff, quads[0]);
326 * Lock the post rec file. Return 1 on lock, 0 on error
332 char lockname[SPOOLNAMEBUFF];
333 char temp[SPOOLNAMEBUFF];
336 snprintf(lockname, sizeof(lockname), "%s.lock", path);
343 fd = open(lockname, O_WRONLY|O_EXCL|O_CREAT, 0600);
345 /* We got the lock! */
346 snprintf(temp, sizeof(temp), "pid:%ld\n", (unsigned long) getpid());
347 write(fd, temp, strlen(temp));
352 /* No lock. See if the file is there. */
353 if (stat(lockname, &st) < 0) {
354 syslog(L_ERROR, "%s cannot stat lock file %s", ClientHost, strerror(errno));
355 if (statfailed++ > 5) return(0);
359 /* If lockfile is older than the value of
360 PERMaccessconf->backoff_postslow, remove it */
363 if (now < st.st_ctime + PERMaccessconf->backoff_postslow) continue;
364 syslog(L_ERROR, "%s removing stale lock file %s", ClientHost, lockname);
373 char lockname[SPOOLNAMEBUFF];
375 snprintf(lockname, sizeof(lockname), "%s.lock", path);
376 if (unlink(lockname) < 0) {
377 syslog(L_ERROR, "%s can't unlink lock file: %s", ClientHost,strerror(errno)) ;
383 * Get the stored postrecord for that IP
386 GetPostRecord(char *path, long *lastpost, long *lastsleep, long *lastn)
388 static char buff[SMBUF];
392 fp = fopen(path,"r");
394 if (errno == ENOENT) {
397 syslog(L_ERROR, "%s Error opening '%s': %s",
398 ClientHost, path, strerror(errno));
402 if (fgets(buff,SMBUF,fp) == NULL) {
403 syslog(L_ERROR, "%s Error reading '%s': %s",
404 ClientHost, path, strerror(errno));
407 *lastpost = atol(buff);
409 if ((s = strchr(buff,',')) == NULL) {
410 syslog(L_ERROR, "%s bad data in postrec file: '%s'",
414 s++; *lastsleep = atol(s);
416 if ((s = strchr(s,',')) == NULL) {
417 syslog(L_ERROR, "%s bad data in postrec file: '%s'",
421 s++; *lastn = atol(s);
428 * Store the postrecord for that IP
431 StorePostRecord(char *path, time_t lastpost, long lastsleep, long lastn)
435 fp = fopen(path,"w");
437 syslog(L_ERROR, "%s Error opening '%s': %s",
438 ClientHost, path, strerror(errno));
442 fprintf(fp,"%ld,%ld,%ld\n",(long) lastpost,lastsleep,lastn);
448 * Return the proper sleeptime. Return false on error.
451 RateLimit(sleeptime,path)
456 long prevpost,prevsleep,prevn,n;
458 if (GetTimeInfo(&Now) < 0)
461 prevpost = 0L; prevsleep = 0L; prevn = 0L; n = 0L;
462 if (!GetPostRecord(path,&prevpost,&prevsleep,&prevn)) {
463 syslog(L_ERROR, "%s can't get post record: %s",
464 ClientHost, strerror(errno));
468 * Just because yer paranoid doesn't mean they ain't out ta get ya
469 * This is called paranoid clipping
471 if (prevn < 0L) prevn = 0L;
472 if (prevsleep < 0L) prevsleep = 0L;
473 if (prevsleep > PERMaccessconf->backoff_postfast) prevsleep = PERMaccessconf->backoff_postfast;
476 * Compute the new sleep time
479 if (prevpost <= 0L) {
483 n = Now.time - prevpost;
485 syslog(L_NOTICE,"%s previous post was in the future (%ld sec)",
489 if (n < PERMaccessconf->backoff_postfast) {
490 if (prevn >= PERMaccessconf->backoff_trigger) {
491 *sleeptime = 1 + (prevsleep * PERMaccessconf->backoff_k);
493 } else if (n < PERMaccessconf->backoff_postslow) {
494 if (prevn >= PERMaccessconf->backoff_trigger) {
495 *sleeptime = prevsleep;
503 *sleeptime = ((*sleeptime) > PERMaccessconf->backoff_postfast) ? PERMaccessconf->backoff_postfast : (*sleeptime);
504 /* This ought to trap this bogon */
505 if ((*sleeptime) < 0L) {
506 syslog(L_ERROR,"%s Negative sleeptime detected: %ld, prevsleep: %ld, N: %ld",ClientHost,*sleeptime,prevsleep,n);
510 /* Store the postrecord */
511 if (!StorePostRecord(path,Now.time,*sleeptime,prevn)) {
512 syslog(L_ERROR, "%s can't store post record: %s", ClientHost, strerror(errno));
521 ** The "STARTTLS" command. RFC2595.
533 if (nnrpd_starttls_done == 1) {
534 Reply("%d Already successfully executed STARTTLS\r\n",
535 NNTP_STARTTLS_DONE_VAL);
539 Reply("%d Begin TLS negotiation now\r\n", NNTP_STARTTLS_NEXT_VAL);
542 /* must flush our buffers before starting tls */
544 result=tls_start_servertls(0, /* read */
547 Reply("%d Starttls failed\r\n", NNTP_STARTTLS_BAD_VAL);
550 nnrpd_starttls_done = 1;
552 #endif /* HAVE_SSL */