1 /*
2 **  The NEWNEWS command.
3 */
4 
5 #include "portable/system.h"
6 
7 #include <errno.h>
8 
9 #include "cache.h"
10 #include "inn/innconf.h"
11 #include "inn/messages.h"
12 #include "inn/ov.h"
13 #include "inn/overview.h"
14 #include "inn/wire.h"
15 #include "nnrpd.h"
16 
17 #define GROUP_LIST_DELTA 10
18 
19 static bool
FindHeader(ARTHANDLE * art,const char ** pp,const char ** qp,const char * hdr,size_t hdrlen UNUSED)20 FindHeader(ARTHANDLE *art, const char **pp, const char **qp, const char *hdr,
21            size_t hdrlen UNUSED)
22 {
23     const char *p, *p1, *q;
24     bool Nocr = true;
25 
26     p = wire_findheader(art->data, art->len, hdr, true);
27     if (p == NULL)
28         return false;
29     q = p;
30     for (p1 = NULL; p < art->data + art->len; p++) {
31         if (p1 != NULL && *p1 == '\r' && *p == '\n') {
32             Nocr = false;
33             break;
34         }
35         if (*p == '\n') {
36             Nocr = true;
37             break;
38         }
39         p1 = p;
40     }
41     if (p >= art->data + art->len)
42         return false;
43     if (!Nocr)
44         p = p1;
45 
46     *pp = p;
47     *qp = q;
48     return true;
49 }
50 
51 /*
52 **  Get Xref header field.
53 */
54 static char *
GetXref(ARTHANDLE * art)55 GetXref(ARTHANDLE *art)
56 {
57     const char *p, *q;
58 
59     if (!FindHeader(art, &p, &q, "xref", sizeof("xref")))
60         return NULL;
61     return xstrndup(q, p - q);
62 }
63 
64 /*
65 **  Split newsgroup list into array of newsgroups.  Return static pointer,
66 **  or NULL if there are no newsgroup.
67 */
68 static char **
GetGroups(char * p)69 GetGroups(char *p)
70 {
71     static int size;
72     static char **list;
73     int i;
74     char *q;
75     static char *Xrefbuf = NULL;
76     char *Xref = p;
77 
78     if (size == 0) {
79         size = GROUP_LIST_DELTA;
80         list = xmalloc((size + 1) * sizeof(char *));
81     }
82     Xref = p;
83     for (Xref++; *Xref == ' '; Xref++)
84         ;
85     if ((Xref = strchr(Xref, ' ')) == NULL)
86         return NULL;
87     for (Xref++; *Xref == ' '; Xref++)
88         ;
89     if (!Xrefbuf)
90         Xrefbuf = xmalloc(BIG_BUFFER);
91     strlcpy(Xrefbuf, Xref, BIG_BUFFER);
92     if ((q = strchr(Xrefbuf, '\t')) != NULL)
93         *q = '\0';
94     p = Xrefbuf;
95 
96     for (i = 0;; i++) {
97         while (ISWHITE(*p))
98             p++;
99         if (*p == '\0' || *p == '\n')
100             break;
101 
102         if (i >= size - 1) {
103             size += GROUP_LIST_DELTA;
104             list = xrealloc(list, (size + 1) * sizeof(char *));
105         }
106         for (list[i] = p; *p && *p != '\n' && !ISWHITE(*p); p++) {
107             if (*p == ':')
108                 *p = '\0';
109         }
110         if (*p)
111             *p++ = '\0';
112     }
113     list[i] = NULL;
114     return i ? list : NULL;
115 }
116 
117 static bool
HaveSeen(bool AllGroups,char * group,char ** groups,char ** xrefs)118 HaveSeen(bool AllGroups, char *group, char **groups, char **xrefs)
119 {
120     char *list[2];
121 
122     list[1] = NULL;
123     for (; *xrefs; xrefs++) {
124         list[0] = *xrefs;
125         if ((!AllGroups && PERMmatch(groups, list))
126             && (!PERMspecified || PERMmatch(PERMreadlist, list))) {
127             if (!strcmp(*xrefs, group))
128                 return false;
129             else
130                 return true;
131         }
132     }
133     return false;
134 }
135 
136 static char **groups;
137 static char xref_header[] = "Xref";
138 
139 static void
process_newnews(char * group,bool AllGroups,time_t date)140 process_newnews(char *group, bool AllGroups, time_t date)
141 {
142     int low, high;
143     char **xrefs;
144     int count;
145     void *handle;
146     char *p;
147     time_t arrived;
148     ARTHANDLE *art = NULL;
149     TOKEN token;
150     char *data;
151     int len;
152     char *grplist[2];
153     time_t now = 0;
154 
155     grplist[0] = group;
156     grplist[1] = NULL;
157     if (PERMspecified && !PERMmatch(PERMreadlist, grplist))
158         return;
159     if (!AllGroups && !PERMmatch(groups, grplist))
160         return;
161     if (!OVgroupstats(group, &low, &high, &count, NULL))
162         return;
163     ARTlow = low;
164     ARThigh = high;
165     if ((handle = OVopensearch(group, ARTlow, ARThigh)) != NULL) {
166         ARTNUM artnum;
167         unsigned long artcount = 0;
168         struct cvector *vector = NULL;
169 
170         if (innconf->nfsreader) {
171             time(&now);
172             /* Move the start time back nfsreaderdelay seconds
173              * as we are an NFS reader. */
174             if (date >= (time_t) innconf->nfsreaderdelay)
175                 date -= innconf->nfsreaderdelay;
176         }
177         while (OVsearch(handle, &artnum, &data, &len, &token, &arrived)) {
178             if (innconf->nfsreader != 0
179                 && (time_t)(arrived + innconf->nfsreaderdelay) > now)
180                 continue;
181             if (len == 0 || date > arrived)
182                 continue;
183 
184             vector = overview_split(data, len, NULL, vector);
185             if (overhdr_xref == -1) {
186                 if ((art = SMretrieve(token, RETR_HEAD)) == NULL)
187                     continue;
188                 p = GetXref(art);
189                 SMfreearticle(art);
190             } else {
191                 if (PERMaccessconf->nnrpdcheckart && !ARTinstorebytoken(token))
192                     continue;
193                 /* We only care about the newsgroup list here, virtual
194                  * hosting isn't relevant. */
195                 p = overview_get_extra_header(vector, xref_header);
196             }
197             if (p == NULL)
198                 continue;
199             xrefs = GetGroups(p);
200             free(p);
201             if (xrefs == NULL)
202                 continue;
203             if (HaveSeen(AllGroups, group, groups, xrefs))
204                 continue;
205             p = overview_get_standard_header(vector, OVERVIEW_MESSAGE_ID);
206             if (p == NULL)
207                 continue;
208 
209             ++artcount;
210             cache_add(HashMessageID(p), token);
211             Printf("%s\r\n", p);
212             free(p);
213         }
214         OVclosesearch(handle);
215         notice("%s newnews %s %lu", Client.host, group, artcount);
216         if (vector)
217             cvector_free(vector);
218     }
219 }
220 
221 /*
222 **  NEWNEWS wildmat date time [GMT]
223 **  Return the Message-ID of any articles after the specified date.
224 */
225 void
CMDnewnews(int ac,char * av[])226 CMDnewnews(int ac, char *av[])
227 {
228     char *p, *q;
229     char *path;
230     bool AllGroups;
231     char line[BIG_BUFFER];
232     time_t date;
233     QIOSTATE *qp;
234     int i;
235     bool local = true;
236 
237     TMRstart(TMR_NEWNEWS);
238 
239     /* Check the arguments and parse the date. */
240     if (ac > 4) {
241         if (strcasecmp(av[4], "GMT") == 0)
242             local = false;
243         else {
244             Reply("%d Syntax error for \"GMT\"\r\n", NNTP_ERR_SYNTAX);
245             TMRstop(TMR_NEWNEWS);
246             return;
247         }
248     }
249 
250     /* Parse the newsgroups. */
251     AllGroups = (strcmp(av[1], "*") == 0);
252     if (!AllGroups && !NGgetlist(&groups, av[1])) {
253         Reply("%d Bad newsgroup specifier %s\r\n", NNTP_ERR_SYNTAX, av[1]);
254         TMRstop(TMR_NEWNEWS);
255         return;
256     }
257 
258     /* Parse the date. */
259     date = parsedate_nntp(av[2], av[3], local);
260     if (date == (time_t) -1) {
261         Reply("%d Bad date\r\n", NNTP_ERR_SYNTAX);
262         TMRstop(TMR_NEWNEWS);
263         return;
264     }
265 
266     /* Check authorizations. */
267     if (!PERMaccessconf->allownewnews) {
268         Reply("%d NEWNEWS command disabled by administrator\r\n",
269               PERMcanauthenticate ? NNTP_FAIL_AUTH_NEEDED : NNTP_ERR_ACCESS);
270         TMRstop(TMR_NEWNEWS);
271         return;
272     }
273 
274     if (!PERMcanread) {
275         Reply("%d Read access denied\r\n",
276               PERMcanauthenticate ? NNTP_FAIL_AUTH_NEEDED : NNTP_ERR_ACCESS);
277         TMRstop(TMR_NEWNEWS);
278         return;
279     }
280 
281     snprintf(line, sizeof(line), "%s %s %s %s", av[1], av[2], av[3],
282              local ? "local" : "GMT");
283     notice("%s newnews %s", Client.host, line);
284 
285     /* Optimization in case client asks for !* (no groups). */
286     if (strcmp(av[1], "!*") == 0) {
287         Reply("%d No new news\r\n", NNTP_OK_NEWNEWS);
288         Printf(".\r\n");
289         TMRstop(TMR_NEWNEWS);
290         return;
291     }
292 
293     /* Make other processes happier if someone uses NEWNEWS. */
294     if (innconf->nicenewnews != 0) {
295         errno = 0;
296         if (nice(innconf->nicenewnews) < 0 && errno != 0)
297             syswarn("%s can't nice to %ld for NEWNEWS", Client.host,
298                     innconf->nicenewnews);
299         innconf->nicenewnews = 0;
300     }
301 
302     if (strcspn(av[1], "\\!*[?]") == strlen(av[1])) {
303         /* Optimise case -- don't need to scan the active file pattern
304          * matching. */
305         Reply("%d New news follows\r\n", NNTP_OK_NEWNEWS);
306         for (i = 0; groups[i]; ++i) {
307             process_newnews(groups[i], AllGroups, date);
308         }
309     } else {
310         path = concatpath(innconf->pathdb, INN_PATH_ACTIVE);
311         qp = QIOopen(path);
312         if (qp == NULL) {
313             if (errno == ENOENT) {
314                 Reply("%d Can't open active\r\n", NNTP_FAIL_ACTION);
315             } else {
316                 syswarn("%s can't fopen %s", Client.host, path);
317                 Reply("%d Can't open active\r\n", NNTP_FAIL_ACTION);
318             }
319             free(path);
320             TMRstop(TMR_NEWNEWS);
321             return;
322         }
323         free(path);
324 
325         Reply("%d New news follows\r\n", NNTP_OK_NEWNEWS);
326 
327         while ((p = QIOread(qp)) != NULL) {
328             for (q = p; *q != '\0'; q++) {
329                 if (*q == ' ' || *q == '\t') {
330                     *q = '\0';
331                     break;
332                 }
333             }
334             process_newnews(p, AllGroups, date);
335         }
336         QIOclose(qp);
337     }
338     Printf(".\r\n");
339     TMRstop(TMR_NEWNEWS);
340 }
341