1 /*
2 ** Newsgroups and the active file.
3 */
4
5 #include "portable/system.h"
6
7 #include "inn/innconf.h"
8 #include "inn/ov.h"
9 #include "nnrpd.h"
10
11 /*
12 ** Change to or list the specified newsgroup. If invalid, stay in the old
13 ** group.
14 ** Do not forget to free(group) before any return after "group" has been set.
15 */
16 void
CMDgroup(int ac,char * av[])17 CMDgroup(int ac, char *av[])
18 {
19 ARTNUM i;
20 int low, high;
21 char *grplist[2];
22 char *group;
23 void *handle;
24 TOKEN token;
25 int count;
26 bool boolval;
27 bool hookpresent = false;
28
29 #ifdef DO_PYTHON
30 hookpresent = PY_use_dynamic;
31 #endif /* DO_PYTHON */
32
33 /* Parse arguments. */
34 if (ac == 1) {
35 if (GRPcur == NULL) {
36 Reply("%d No group specified\r\n", NNTP_FAIL_NO_GROUP);
37 return;
38 } else {
39 group = xstrdup(GRPcur);
40 }
41 } else {
42 group = xstrdup(av[1]);
43 }
44
45 /* Check whether the second argument is valid (for LISTGROUP). */
46 if (ac == 3 && !IsValidRange(av[2])) {
47 Reply("%d Syntax error in range\r\n", NNTP_ERR_SYNTAX);
48 free(group);
49 return;
50 }
51
52 /* Check authorizations. */
53 if (!hookpresent && !PERMcanread) {
54 Reply("%d Read access denied\r\n",
55 PERMcanauthenticate ? NNTP_FAIL_AUTH_NEEDED : NNTP_ERR_ACCESS);
56 free(group);
57 return;
58 }
59
60 /* FIXME: Temporarily work around broken API. */
61 if (!OVgroupstats(group, &low, &high, &count, NULL)) {
62 Reply("%d No such group %s\r\n", NNTP_FAIL_BAD_GROUP, group);
63 free(group);
64 return;
65 }
66
67 #ifdef DO_PYTHON
68 if (PY_use_dynamic) {
69 char *reply;
70
71 /* Authorize user using Python module method dynamic. */
72 if (PY_dynamic(PERMuser, group, false, &reply) < 0) {
73 syslog(L_NOTICE, "PY_dynamic(): authorization skipped due to no "
74 "Python dynamic method defined");
75 } else {
76 if (reply != NULL) {
77 syslog(L_TRACE,
78 "PY_dynamic() returned a refuse string for user %s at "
79 "%s who wants to read %s: %s",
80 PERMuser, Client.host, group, reply);
81 Reply("%d %s\r\n",
82 PERMcanauthenticate ? NNTP_FAIL_AUTH_NEEDED
83 : NNTP_ERR_ACCESS,
84 reply);
85 free(group);
86 free(reply);
87 return;
88 }
89 }
90 }
91 #endif /* DO_PYTHON */
92
93 if (!hookpresent) {
94 if (PERMspecified) {
95 grplist[0] = group;
96 grplist[1] = NULL;
97 if (!PERMmatch(PERMreadlist, grplist)) {
98 Reply("%d Read access denied\r\n", PERMcanauthenticate
99 ? NNTP_FAIL_AUTH_NEEDED
100 : NNTP_ERR_ACCESS);
101 free(group);
102 return;
103 }
104 } else {
105 Reply("%d Read access denied\r\n", PERMcanauthenticate
106 ? NNTP_FAIL_AUTH_NEEDED
107 : NNTP_ERR_ACCESS);
108 free(group);
109 return;
110 }
111 }
112
113 /* Close out any existing article, report group stats. */
114 ARTclose();
115 GRPreport();
116
117 /* These values must be changed after the Python dynamic hook and
118 * everything that can lead to a failure of authorization. */
119 ARTlow = low;
120 ARThigh = high;
121
122 /* Doing a GROUP command? */
123 if (strcasecmp(av[0], "GROUP") == 0) {
124 if (count == 0) {
125 Reply("%d 0 %lu %lu %s\r\n", NNTP_OK_GROUP, ARThigh + 1, ARThigh,
126 group);
127 } else {
128 /* If we are an NFS reader, check the last nfsreaderdelay
129 * articles in the group to see if they arrived in the
130 * last nfsreaderdelay (default 60) seconds. If they did,
131 * don't report them as we don't want them to appear too
132 * soon. */
133 if (innconf->nfsreader != 0) {
134 ARTNUM nfslow, prev;
135 time_t now, arrived;
136
137 time(&now);
138 /* We assume that during the last nfsreaderdelay seconds,
139 * we did not receive more than 1 article per second. */
140 if (ARTlow + innconf->nfsreaderdelay > ARThigh)
141 nfslow = ARTlow;
142 else
143 nfslow = ARThigh - innconf->nfsreaderdelay;
144 handle = OVopensearch(group, nfslow, ARThigh);
145 if (!handle) {
146 Reply("%d group disappeared\r\n", NNTP_FAIL_ACTION);
147 free(group);
148 return;
149 }
150 prev = nfslow;
151 while (OVsearch(handle, &i, NULL, NULL, NULL, &arrived)) {
152 if ((time_t)(arrived + innconf->nfsreaderdelay) > now) {
153 ARThigh = prev;
154 /* No need to update the count since it is only
155 * an estimate but make sure it is not too high. */
156 if ((unsigned int) count > ARThigh - ARTlow)
157 count = ARThigh - ARTlow + 1;
158 break;
159 }
160 prev = i;
161 }
162 OVclosesearch(handle);
163 }
164 Reply("%d %d %lu %lu %s\r\n", NNTP_OK_GROUP, count, ARTlow,
165 ARThigh, group);
166 }
167 GRPcount++;
168 ARTnumber = (count == 0 ? 0 : ARTlow);
169 if (GRPcur) {
170 if (strcmp(GRPcur, group) != 0) {
171 OVctl(OVCACHEFREE, &boolval);
172 free(GRPcur);
173 GRPcur = xstrdup(group);
174 }
175 } else
176 GRPcur = xstrdup(group);
177 PERMgroupmadeinvalid = false;
178 } else {
179 /* Must be doing a LISTGROUP command. We used to just return
180 something bland here ("Article list follows"), but reference NNTP
181 returns the same data as GROUP does and since we have it all
182 available it shouldn't hurt to return the same thing. */
183 ARTRANGE range;
184 bool DidReply;
185
186 /* Parse the range. */
187 if (ac == 3) {
188 /* CMDgetrange() expects av[1] to contain the range.
189 * It is av[2] for LISTGROUP.
190 * We already know that GRPcur exists. */
191 if (!CMDgetrange(ac, av + 1, &range, &DidReply)) {
192 if (DidReply) {
193 free(group);
194 return;
195 }
196 }
197 } else {
198 range.Low = ARTlow;
199 range.High = ARThigh;
200 }
201
202 if (count == 0) {
203 Reply("%d 0 %lu %lu %s\r\n", NNTP_OK_GROUP, ARThigh + 1, ARThigh,
204 group);
205 Printf(".\r\n");
206 } else {
207 Reply("%d %d %lu %lu %s\r\n", NNTP_OK_GROUP, count, ARTlow,
208 ARThigh, group);
209 /* If OVopensearch() is restricted to the range, it returns NULL
210 * in case there isn't any article within the range. We already
211 * know that the group exists. */
212 if ((handle = OVopensearch(group, range.Low, range.High))
213 != NULL) {
214 while (OVsearch(handle, &i, NULL, NULL, &token, NULL)) {
215 if (PERMaccessconf->nnrpdcheckart
216 && !ARTinstorebytoken(token))
217 continue;
218 Printf("%lu\r\n", i);
219 }
220
221 OVclosesearch(handle);
222 }
223 Printf(".\r\n");
224 }
225 GRPcount++;
226 ARTnumber = (count == 0 ? 0 : ARTlow);
227 if (GRPcur) {
228 if (strcmp(GRPcur, group) != 0) {
229 OVctl(OVCACHEFREE, &boolval);
230 free(GRPcur);
231 GRPcur = xstrdup(group);
232 }
233 } else
234 GRPcur = xstrdup(group);
235 PERMgroupmadeinvalid = false;
236 }
237 free(group);
238 }
239
240
241 /*
242 ** Report on the number of articles read in the group, and clear the count.
243 */
244 void
GRPreport(void)245 GRPreport(void)
246 {
247 char buff[SPOOLNAMEBUFF];
248
249 if (GRPcur) {
250 strlcpy(buff, GRPcur, sizeof(buff));
251 syslog(L_NOTICE, "%s group %s %lu", Client.host, buff, GRParticles);
252 GRParticles = 0;
253 }
254 }
255
256
257 /*
258 ** Used by ANU-News clients.
259 */
260 void
CMDxgtitle(int ac,char * av[])261 CMDxgtitle(int ac, char *av[])
262 {
263 QIOSTATE *qp;
264 char *line;
265 char *p;
266 char *q;
267 char *grplist[2];
268 char save;
269
270 /* Parse the arguments. */
271 if (ac == 1) {
272 if (GRPcount == 0) {
273 /* Keep the legacy response code 481 instead of 412. */
274 Reply("%d No group specified\r\n", NNTP_FAIL_XGTITLE);
275 return;
276 }
277 p = GRPcur;
278 } else
279 p = av[1];
280
281 if (!PERMspecified) {
282 Reply("%d No descriptions follow\r\n", NNTP_OK_XGTITLE);
283 Printf(".\r\n");
284 return;
285 }
286
287 /* Open the file, get ready to scan. */
288 if ((qp = QIOopen(NEWSGROUPS)) == NULL) {
289 syslog(L_ERROR, "%s can't open %s %m", Client.host, NEWSGROUPS);
290 Reply("%d Can't open %s\r\n", NNTP_FAIL_XGTITLE, NEWSGROUPS);
291 return;
292 }
293 Reply("%d Descriptions in form \"group description\"\r\n",
294 NNTP_OK_XGTITLE);
295
296 /* Print all lines with matching newsgroup name. */
297 while ((line = QIOread(qp)) != NULL) {
298 for (q = line; *q && !ISWHITE(*q); q++)
299 continue;
300 save = *q;
301 *q = '\0';
302 if (uwildmat(line, p)) {
303 if (PERMspecified) {
304 grplist[0] = line;
305 grplist[1] = NULL;
306 if (!PERMmatch(PERMreadlist, grplist))
307 continue;
308 }
309 *q = save;
310 Printf("%s\r\n", line);
311 }
312 }
313
314 /* Done. */
315 QIOclose(qp);
316 Printf(".\r\n");
317 }
318