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