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