INN commit: trunk/nnrpd (article.c list.c nnrpd.c)

INN Commit Russ_Allbery at isc.org
Sat Sep 6 11:16:28 UTC 2008


    Date: Saturday, September 6, 2008 @ 04:16:27
  Author: iulius
Revision: 8006

* Add support for HDR.
* Correctly parse the arguments before anything else.
* Reject unimplemented metadata requests with 503.
* LIST HEADERS is implemented with a different output between RANGE and MSGID.
* Homogenize the HELP result with "wildmat" everywhere (instead of "group_pattern"
  or "pat").
* More user-friendly answers.
* XHDR and XPAT were not checking the permissions the user has to read
  articles when using a message-ID.  Now fixed.
* Fix calls to ARTclose().

Modified:
  trunk/nnrpd/article.c
  trunk/nnrpd/list.c
  trunk/nnrpd/nnrpd.c

-----------+
 article.c |  199 ++++++++++++++++++++++++++++++++++++++++++++++--------------
 list.c    |   52 ++++++++++++---
 nnrpd.c   |   15 ++--
 3 files changed, 206 insertions(+), 60 deletions(-)

Modified: article.c
===================================================================
--- article.c	2008-09-06 08:58:33 UTC (rev 8005)
+++ article.c	2008-09-06 11:16:27 UTC (rev 8006)
@@ -292,7 +292,7 @@
 
 
 /*
-**  Open the article for a given Message-ID.
+**  Open the article for a given message-ID.
 */
 static bool
 ARTopenbyid(char *msg_id, ARTNUM *ap, bool final)
@@ -625,10 +625,10 @@
 	return;
     }
 
-    /* Requesting by Message-ID? */
+    /* Requesting by message-ID? */
     if (ac == 2 && av[1][0] == '<') {
         if (!IsValidMessageID(av[1])) {
-            Reply("%d Syntax error in Message-ID\r\n", NNTP_ERR_SYNTAX);
+            Reply("%d Syntax error in message-ID\r\n", NNTP_ERR_SYNTAX);
             return;
         }
 	if (!ARTopenbyid(av[1], &art, final)) {
@@ -684,6 +684,7 @@
     if (ac > 1)
 	ARTnumber = tart;
     if ((msgid = GetHeader("Message-ID")) == NULL) {
+        ARTclose();
         Reply("%s\r\n", ARTnoartingroup);
 	return;
     }
@@ -743,9 +744,9 @@
         if (!ARTopen(ARTnumber))
             continue;
         msgid = GetHeader("Message-ID");
+        ARTclose();
     } while (msgid == NULL);
 
-    ARTclose();
     Reply("%d %d %s Article retrieved; request text separately\r\n",
 	   NNTP_OK_STAT, ARTnumber, msgid);
 }
@@ -781,7 +782,7 @@
     *DidReply = false;
 
     if (ac == 1) {
-	/* No argument, do only current article. */
+	/* No arguments, do only current article. */
 	if (ARTnumber < ARTlow || ARTnumber > ARThigh) {
 	    Reply("%s\r\n", ARTnocurrart);
 	    *DidReply = true;
@@ -878,7 +879,7 @@
 
 /*
 **  Dump parts of the overview database with the OVER command.
-**  The legacy XOVER is also kept.
+**  The legacy XOVER is also kept, with its specific behaviour.
 */
 void
 CMDover(int ac, char *av[])
@@ -927,7 +928,7 @@
     }
 
     /* Parse range.  CMDgetrange() correctly sets the range when
-     * there is no argument. */
+     * there is no arguments. */
     if (!CMDgetrange(ac, av, &range, &DidReply))
         if (DidReply)
             return;
@@ -1060,19 +1061,18 @@
 }
 
 /*
-**  XHDR and XPAT extensions.  Note that HDR as specified in the new NNTP
-**  draft works differently than XHDR has historically, so don't just use this
-**  function to implement it without reviewing the differences.
+**  Access specific fields from an article with HDR.
+**  The legacy XHDR and XPAT are also kept, with their specific behaviours.
 */
-/* ARGSUSED */
 void
 CMDpat(int ac, char *av[])
 {
     char	        *p;
     unsigned long      	i;
     ARTRANGE		range;
-    bool		IsLines;
-    bool		DidReply;
+    bool		IsBytes, IsLines;
+    bool                IsMetaBytes, IsMetaLines;
+    bool		DidReply, HasNotReplied;
     char		*header;
     char		*pattern;
     char		*text;
@@ -1084,102 +1084,190 @@
     int                 len;
     TOKEN               token;
     struct cvector *vector = NULL;
+    bool                hdr, mid;
 
+    hdr = (strcasecmp(av[0], "HDR") == 0);
+    mid = (ac > 2 && IsValidMessageID(av[2]));
+
+    /* Check the syntax of the arguments first. */
+    if (ac > 2 && !mid && !CMDisrange(av[2])) {
+        Reply("%d Syntax error in arguments\r\n", NNTP_ERR_SYNTAX);
+        return;
+    }
+
+    /* Check authorizations. */
     if (!PERMcanread) {
 	Reply("%d Read access denied\r\n", NNTP_ERR_ACCESS);
 	return;
     }
 
     header = av[1];
-    IsLines = (strcasecmp(header, "lines") == 0);
 
-    if (ac > 3) /* XPAT */
+    /* If metadata is asked for, convert it to headers that
+     * the overview database knows. */
+    IsBytes = (strcasecmp(header, "Bytes") == 0);
+    IsLines = (strcasecmp(header, "Lines") == 0);
+    IsMetaBytes = (strcasecmp(header, ":bytes") == 0);
+    IsMetaLines = (strcasecmp(header, ":lines") == 0);
+    /* Make these changes because our overview database does
+     * not currently know metadata names. */
+    if (IsMetaBytes)
+        header = xstrdup("Bytes");
+    if (IsMetaLines)
+        header = xstrdup("Lines");
+
+    /* We only allow :bytes and :lines for metadata. */
+    if ((strncasecmp(header, ":", 1) == 0) && !IsMetaLines && !IsMetaBytes) {
+        Reply("%d %s metadata request unsupported\r\n",
+              NNTP_ERR_UNAVAILABLE, header);
+        return;
+    }
+
+    if (ac > 3) /* Necessarily XPAT. */
 	pattern = Glom(&av[3]);
     else
 	pattern = NULL;
 
+    /* We will only do the loop once.  It is just in order to easily break. */
     do {
 	/* Message-ID specified? */
-	if (ac > 2 && av[2][0] == '<') {
-            if (!IsValidMessageID(av[2])) {
-                Reply("%d Syntax error in Message-ID\r\n", NNTP_ERR_SYNTAX);
-                break;
-            }
+	if (mid) {
 	    p = av[2];
 	    if (!ARTopenbyid(p, &artnum, false)) {
 		Printf("%d No such article\r\n", NNTP_FAIL_NOTFOUND);
 		break;
 	    }
-	    Printf("%d %s matches follow (ID)\r\n", NNTP_OK_HEAD,
-		   header);
-	    if ((text = GetHeader(header)) != NULL
+
+            /* FIXME:  We do not handle metadata requests by message-ID. */
+            if (hdr && (IsMetaBytes || IsMetaLines)) {
+                Reply("%d Metadata requests by message-ID unsupported\r\n",
+                      NNTP_ERR_UNAVAILABLE);
+                break;
+            }
+
+            if (!PERMartok()) {
+                ARTclose();
+                Reply("%d Read access denied for this article\r\n", NNTP_ERR_ACCESS);
+                break;
+            }
+
+	    Printf("%d Header information for %s follows (message-ID)\r\n",
+                   hdr ? NNTP_OK_HDR : NNTP_OK_HEAD, av[1]);
+
+	    if ((text = GetHeader(av[1])) != NULL
 		&& (!pattern || uwildmat_simple(text, pattern)))
-		Printf("%s %s\r\n", p, text);
+		Printf("%s %s\r\n", hdr ? "0" : p, text);
+            else if (hdr) {
+                /* We always have to answer something with HDR. */
+                Printf("0 \r\n");
+            }
 
 	    ARTclose();
 	    Printf(".\r\n");
 	    break;
 	}
 
+        /* Trying to read. */
 	if (GRPcount == 0) {
 	    Reply("%s\r\n", ARTnotingroup);
 	    break;
 	}
 
-	/* Range specified. */
-	if (!CMDgetrange(ac - 1, av + 1, &range, &DidReply)) {
-	    if (DidReply) {
-		break;
-	    }
-	}
+        /* Parse range.  CMDgetrange() correctly sets the range when
+         * there is no arguments. */
+        if (!CMDgetrange(ac - 1, av + 1, &range, &DidReply))
+            if (DidReply)
+                break;
 
 	/* In overview? */
         Overview = overview_index(header, OVextra);
 
+        HasNotReplied = true;
+
 	/* Not in overview, we have to fish headers out from the articles. */
-	if (Overview < 0) {
-	    Reply("%d %s matches follow (art)\r\n", NNTP_OK_HEAD,
-		  header);
+	if (Overview < 0 || IsBytes || IsLines) {
 	    for (i = range.Low; i <= range.High && range.High > 0; i++) {
 		if (!ARTopen(i))
 		    continue;
+                if (HasNotReplied) {
+                    Reply("%d Header information for %s follows (art)\r\n",
+                          hdr ? NNTP_OK_HDR : NNTP_OK_HEAD, av[1]);
+                    HasNotReplied = false;
+                }
 		p = GetHeader(header);
 		if (p && (!pattern || uwildmat_simple(p, pattern))) {
 		    snprintf(buff, sizeof(buff), "%lu ", i);
 		    SendIOb(buff, strlen(buff));
 		    SendIOb(p, strlen(p));
 		    SendIOb("\r\n", 2);
-		    ARTclose();
-		}
+		} else if (hdr) {
+                    /* We always have to answer something with HDR. */
+                    snprintf(buff, sizeof(buff), "%lu \r\n", i);
+                    SendIOb(buff, strlen(buff));
+                }
+                ARTclose();
 	    }
-	    SendIOb(".\r\n", 3);
+            if (HasNotReplied) {
+                if (hdr) {
+                    if (ac > 2)
+                        Reply("%d No articles in %s\r\n",
+                              NNTP_FAIL_BAD_ARTICLE, av[2]);
+                    else
+                        Reply("%d Current article number %d is invalid\r\n",
+                              NNTP_FAIL_NO_ARTICLE, ARTnumber);
+                } else {
+                    Reply("%d No header information for %s follows (art)\r\n",
+                          NNTP_OK_HEAD, av[1]);
+                    Reply(".\r\n");
+                }
+                break;
+            } else {
+                SendIOb(".\r\n", 3);
+            }
 	    PushIOb();
 	    break;
 	}
 
-	/* Okay then, we can grab values from overview. */
+	/* Okay then, we can grab values from the overview database. */
 	handle = (void *)OVopensearch(GRPcur, range.Low, range.High);
 	if (handle == NULL) {
-	    Reply("%d %s no matches follow (NOV)\r\n",
-		  NNTP_OK_HEAD, header);
-            Printf(".\r\n");
+            if (hdr) {
+                if (ac > 2)
+                    Reply("%d No articles in %s\r\n",
+                          NNTP_FAIL_BAD_ARTICLE, av[2]);
+                else
+                    Reply("%d Current article number %d is invalid\r\n",
+                          NNTP_FAIL_NO_ARTICLE, ARTnumber);
+            } else {
+                Reply("%d No header information for %s follows (NOV)\r\n",
+                      NNTP_OK_HEAD, av[1]);
+                Printf(".\r\n");
+            }
 	    break;
 	}	
 	
-	Printf("%d %s matches follow (NOV)\r\n", NNTP_OK_HEAD,
-	       header);
 	while (OVsearch(handle, &artnum, &data, &len, &token, NULL)) {
 	    if (len == 0 || (PERMaccessconf->nnrpdcheckart
 		&& !ARTinstorebytoken(token)))
 		continue;
+            if (HasNotReplied) {
+                Reply("%d Header or metadata information for %s follows (NOV)\r\n",
+                      hdr ? NNTP_OK_HDR : NNTP_OK_HEAD, av[1]);
+                HasNotReplied = false;
+            }
 	    vector = overview_split(data, len, NULL, vector);
 	    p = overview_getheader(vector, Overview, OVextra);
 	    if (p != NULL) {
 		if (PERMaccessconf->virtualhost &&
 			   Overview == overhdr_xref) {
 		    p = vhost_xref(p);
-		    if (p == NULL)
+		    if (p == NULL) {
+                        if (hdr) {
+                            snprintf(buff, sizeof(buff), "%lu \r\n", artnum);
+                            SendIOb(buff, strlen(buff));
+                        }
 			continue;
+                    }
 		}
 		if (!pattern || uwildmat_simple(p, pattern)) {
 		    snprintf(buff, sizeof(buff), "%lu ", artnum);
@@ -1187,10 +1275,31 @@
 		    SendIOb(p, strlen(p));
 		    SendIOb("\r\n", 2);
 		}
+                /* No need to have another condition for HDR because
+                 * pattern is NULL for it, and p is not NULL here. */
 		free(p);
-	    }
+	    } else if (hdr) {
+                snprintf(buff, sizeof(buff), "%lu \r\n", artnum);
+                SendIOb(buff, strlen(buff));
+            }
 	}
-	SendIOb(".\r\n", 3);
+        if (HasNotReplied) {
+            if (hdr) {
+                if (ac > 2)
+                    Reply("%d No articles in %s\r\n",
+                          NNTP_FAIL_BAD_ARTICLE, av[2]);
+                else
+                    Reply("%d Current article number %d is invalid\r\n",
+                          NNTP_FAIL_NO_ARTICLE, ARTnumber);
+            } else {
+                Reply("%d No header or metadata information for %s follows (NOV)\r\n",
+                      NNTP_OK_HEAD, av[1]);
+                Reply(".\r\n");
+            }
+            break;
+        } else {
+            SendIOb(".\r\n", 3);
+        }
 	PushIOb();
 	OVclosesearch(handle);
     } while (0);

Modified: list.c
===================================================================
--- list.c	2008-09-06 08:58:33 UTC (rev 8005)
+++ list.c	2008-09-06 11:16:27 UTC (rev 8006)
@@ -18,14 +18,15 @@
 typedef struct _LISTINFO {
     const char *method;
     const char * File;
-    void (*impl)(struct _LISTINFO *);
+    void (*impl)(struct _LISTINFO *, int ac, char *av[]);
     bool         Required;
     const char * Items;
     const char * Format;
 } LISTINFO;
 
-static void cmd_list_schema(LISTINFO *lp);
-static void cmd_list_extensions(LISTINFO *lp);
+static void cmd_list_schema(LISTINFO *lp, int ac, char *av[]);
+static void cmd_list_extensions(LISTINFO *lp, int ac, char *av[]);
+static void cmd_list_headers(LISTINFO *lp, int ac, char *av[]);
 
 static LISTINFO		INFOactive = {
     "ACTIVE", INN_PATH_ACTIVE, NULL, true, "active newsgroups",
@@ -39,6 +40,10 @@
     "DISTRIBUTIONS", INN_PATH_NNRPDIST, NULL, false, "newsgroup distributions",
     "Distributions in form \"area description\""
 };
+static LISTINFO         INFOheaders = {
+    "HEADERS", NULL, cmd_list_headers, false, "supported headers and metadata",
+    "Headers and metadata items supported"
+};
 static LISTINFO               INFOsubs = {
     "SUBSCRIPTIONS", INN_PATH_NNRPSUBS, NULL, false,
     "automatic group subscriptions", "Subscriptions in form \"group\""
@@ -72,6 +77,7 @@
     &INFOactive,
     &INFOactivetimes,
     &INFOdistribs,
+    &INFOheaders,
     &INFOsubs,
     &INFOdistribpats,
     &INFOextensions,
@@ -86,7 +92,7 @@
 **  List the overview schema (standard and extra fields).
 */
 static void
-cmd_list_schema(LISTINFO *lp)
+cmd_list_schema(LISTINFO *lp, int ac UNUSED, char *av[] UNUSED)
 {
     const struct cvector *standard;
     unsigned int i;
@@ -107,7 +113,7 @@
 **  List supported extensions.
 */
 static void
-cmd_list_extensions(LISTINFO *lp)
+cmd_list_extensions(LISTINFO *lp, int ac UNUSED, char *av[] UNUSED)
 {
     const char *mechlist = NULL;
 
@@ -134,6 +140,33 @@
 
 
 /*
+**  List supported headers and metadata information.
+*/
+static void
+cmd_list_headers(LISTINFO *lp, int ac, char *av[])
+{
+    bool range;
+
+    range = (ac > 2 && strcasecmp(av[2], "RANGE") == 0);
+    
+    if (ac > 2 && (strcasecmp(av[2], "MSGID") != 0)
+        && !range) {
+        Reply("%d Syntax error in arguments\r\n", NNTP_ERR_SYNTAX);
+        return;
+    }
+    Reply("%d %s\r\n", NNTP_OK_LIST, lp->Format);
+    Reply(":\r\n");
+    if (range) {
+        /* These information are only known by the overview system,
+         * and are only accessible with a range. */
+        Reply(":bytes\r\n");
+        Reply(":lines\r\n");
+    }
+    Reply(".\r\n");
+}
+
+
+/*
 **  List a single newsgroup.  Called by LIST ACTIVE with a single argument.
 **  This is quicker than parsing the whole active file, but only works with
 **  single groups.  It also doesn't work for aliased groups, since overview
@@ -204,12 +237,13 @@
             if (CMD_list_single(wildarg))
 		return;
 	}
-    } else if (lp == &INFOgroups || lp == &INFOactivetimes) {
+    } else if (lp == &INFOgroups || lp == &INFOactivetimes
+               || lp == &INFOheaders) {
 	if (ac == 3)
 	    wildarg = av[2];
     }
-    /* Three arguments can be passed only when ACTIVE, ACTIVE.TIMES
-     * or NEWSGROUPS keywords are used. */
+    /* Three arguments can be passed only when ACTIVE, ACTIVE.TIMES,
+     * HEADERS or NEWSGROUPS keywords are used. */
     if (ac > 2 && !wildarg) {
 	Reply("%s\r\n", NNTP_SYNTAX_USE);
 	return;
@@ -217,7 +251,7 @@
 
     /* If a function is provided for the given keyword, we call it. */
     if (lp->impl != NULL) {
-	lp->impl(lp);
+	lp->impl(lp, ac, av);
 	return;
     }
 

Modified: nnrpd.c
===================================================================
--- nnrpd.c	2008-09-06 08:58:33 UTC (rev 8005)
+++ nnrpd.c	2008-09-06 11:16:27 UTC (rev 8006)
@@ -98,7 +98,7 @@
 bool PY_use_dynamic = false;
 #endif
 
-static char	CMDfetchhelp[] = "[messageID|number]";
+static char	CMDfetchhelp[] = "[message-ID|number]";
 
 /* { command base name, function to call, need authentication,
      min args, max args, help string } */
@@ -117,17 +117,20 @@
 	NULL },
     {	"GROUP",	CMDgroup,	true,	2,	2,
 	"newsgroup" },
+    {   "HDR",          CMDpat  ,       true,   2,      3,
+        "header [range|message-ID]" },
     {	"HEAD",		CMDfetch,	true,	1,	2,
 	CMDfetchhelp },
     {	"HELP",		CMDhelp,	false,	1,	1,
 	NULL },
     {	"IHAVE",	CMDpost,	true,	2,	2,
-	"messageID" },
+	"message-ID" },
     {	"LAST",		CMDnextlast,	true,	1,	1,
 	NULL },
     {	"LIST",		CMDlist,	true,	1,	3,
 	"[ACTIVE [wildmat]|ACTIVE.TIMES [wildmat]|DISTRIB.PATS|DISTRIBUTIONS"
-        "|EXTENSIONS|MODERATORS|MOTD|NEWSGROUPS [wildmat]|OVERVIEW.FMT|SUBSCRIPTIONS]" },
+        "|EXTENSIONS|HEADERS [MSGID|RANGE]|MODERATORS|MOTD|NEWSGROUPS [wildmat]"
+        "|OVERVIEW.FMT|SUBSCRIPTIONS]" },
     {	"LISTGROUP",	CMDgroup,	true,	1,	3,
 	"[newsgroup [range]]" },
     {	"MODE",		CMDmode,	false,	2,	2,
@@ -155,13 +158,13 @@
     {	"STAT",		CMDfetch,	true,	1,	2,
 	CMDfetchhelp },
     {	"XGTITLE",	CMDxgtitle,	true,	1,	2,
-	"[group_pattern]" },
+	"[wildmat]" },
     {	"XHDR",		CMDpat,		true,	2,	3,
-	"header [range|messageID]" },
+	"header [range|message-ID]" },
     {	"XOVER",	CMDover,	true,	1,	2,
 	"[range]" },
     {	"XPAT",		CMDpat,		true,	4,	CMDany,
-	"header range|messageID pat [morepat...]" },
+	"header range|message-ID wildmat [wildmat ...]" },
     {	NULL,           CMD_unimp,      false,  0,      0,
         NULL }
 };



More information about the inn-committers mailing list