mailing list of musl libc
 help / color / mirror / code / Atom feed
From: "Luka Marčetić" <paxcoder@gmail.com>
To: musl@lists.openwall.com
Subject: Re: New daily reports - nothing
Date: Wed, 10 Aug 2011 13:47:17 +0200	[thread overview]
Message-ID: <4E426FC5.7020303@gmail.com> (raw)
In-Reply-To: <20110810013839.GU132@brightrain.aerifal.cx>

[-- Attachment #1: Type: text/plain, Size: 1170 bytes --]

On 08/10/2011 03:38 AM, Rich Felker wrote:

(Thanks for explaining mprotect first of all)

> Especially as you're nearing the deadline, I'd like to ask you to
> please listen when I make recommendations like this. Sure learning
> about mprotect is educational, but in terms of getting stuff done, if
> you'd taken my advice several days (a week now?) back about how to
> check for writes past the end of the buffer, you would have been able
> to spend your time today getting something done rather than wondering
> why mprotect wasn't doing what you wanted....
>
> Rich

The worst thing is, I already do check that: I write '\r' in the last 
byte of the buffer, and then call the function saying the buffer is 
size-1 long (so it shows if it gets overwritten). It doesn't even make 
sense to test for reading/writing beyond size+1, except to test for 
implementation lunacy. I have no idea why I did that anymore. I 
should've just removed sigset altogether. I didn't need mprotect, nor 
wrappers... :-(
P.S. Final buf.c thus won't look like the attached file. In it, 
alloc_bounded() is the broken one - a fixed version may appear somewhere 
else in the future though.

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: buf.c --]
[-- Type: text/x-csrc; name="buf.c", Size: 20092 bytes --]

#include <errno.h>
#include <signal.h>
#include <setjmp.h>
#include <sys/mman.h>
#include <fcntl.h>
#include "common/common.h"
#include <string.h>   //strerror_r
#include <limits.h>   //PATH_MAX
#include <stdlib.h>   //mbstowcs
#include <stdio.h>    //getdelim, snprintf
#include <unistd.h>   //confstr, getcwd, gethostname, readlink
#include <iconv.h>    //iconv
#include <time.h>     //time, gmtime
#include <monetary.h> //strfmon

/*
 * Copyright (c) 2011 Luka Marčetić<paxcoder@gmail.com>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted.
 *
 * There's ABSOLUTELY NO WARRANTY, express or implied.
 */

/**
 ** \file
 ** Tests functions for writing beyond string lenght and errno's they set
 ** tests: confstr, getcwd, getdelim, gethostname, iconv, mbstowcs, snprintf,
 **        readlink, strfmon, strftime, wcstombs, ttyname_r, strerror_r
 ** depends: malloc, sigaction, strlen, mkstemp, fdopen,fclose,fileno, strptime
 **          iconv_open,iconv_close, free, setjmp, longjmp, open,close,
 **          mmap,mprotect,munmap
 **/

jmp_buf env;
struct sigaction act, oldact[2]; //set by main, used in wrappers:

void bridge_sig_jmp(int sig);
static void* malloc_bounded(size_t size);
static int   free_bounded(void* vp);
static int wrap_confstr(int name, char *buf, size_t len);
static int wrap_getcwd(char *buf, size_t size);
static int wrap_getdelim(char **lineptr, size_t *n, int delimiter,FILE *stream);
static int wrap_gethostname(char *name, size_t namelen);
static int wrap_mbstowcs(wchar_t *pwcs, char *s, size_t n);
static int wrap_snprintf(char *s, size_t n, char *format/*, ...*/);
static int wrap_readlink(char *path, char *buf, size_t bufsize);
static int wrap_strfmon(char *s, size_t maxsize, char *format/*,...*/, float F);
static int wrap_strftime(char *s,size_t maxsize,char *format,struct tm *timeptr);
static int wrap_wcstombs(char *s, wchar_t *pwcs, size_t n);
static int wrap_strerror_r(int errnum, char *strerrbuf, size_t buflen);
static int wrap_ttyname_r(int fildes, char *name, size_t namesize);
static int wrap_iconv(iconv_t cd, char **inbuf,  size_t *inbytesleft,
                                  char **outbuf, size_t *outbytesleft);

//još malloc wrapper thingy----
int main()
{
    iconv_t cd;
    wchar_t *ws;
    size_t size;
    struct tm tm;
    FILE *stream = NULL;
    char *s, *fun, *tmp, filename[] = "/tmp/clutsXXXXXX";
    int fd, sig, function, wrong, failed, err, err_expected;
    int ffun[] = {2, 3,8}; //functions that depend on the temporary file(two do)
    
    //GLOBAL:
    act=(struct sigaction){.sa_handler=bridge_sig_jmp, .sa_flags=SA_NODEFER};

    failed = 0;
    function = 1;
    do {
        err = wrong = 0;
        s = fun = tmp = NULL;
        ws = NULL;
        stream = NULL;
        if (seq_has(function, ffun) && (fd=mkstemp(filename)) != -1) {
            if ((stream=fdopen(fd, "w+b")) == NULL)
                wrong = -1;
        }
        switch(function) {
            case 1:
                fun = sreturnf("confstr(_CS_PATH, s, sizeof(s)-1)");
                size = confstr(_CS_PATH, NULL, 0);
                if (size < 2) {
                    wrong = -2;
                    break;
                }
                s = malloc_bounded(size);
                s[size-1] = '\r';
                
                
                if ((err=wrap_confstr(_CS_PATH, s, size-1)) == -1)
                    wrong = 1;
                else
                    /*if ((err))
                        wrong = 5;
                    else */if (s[size-1] != '\r')
                        wrong = 2;
            break;
            case 2:
                fun = sreturnf("getcwd(s, sizeof(s)-1)");
                s = malloc(PATH_MAX);
                size = strlen(getcwd(s, PATH_MAX))+1;
                free(s); s = NULL;
                if (size < 2) {
                    wrong = -2;
                    break;
                }
                s = malloc_bounded(size);
                s[size-1] = '\r';
                
                if ((err=wrap_getcwd(s, size-1)) == -1)
                    wrong = 1;
                else if (s[size-1] != '\r')
                    wrong = 2;
                else if (err != (err_expected = ERANGE))
                    wrong = 3;
            break;
            case 3:
                //same for getline
                fun = sreturnf("getdelim(&s, &<strlen(s)>, '\\n', stream)");
                if (stream == NULL)
                    break;
                fwrite("123\n", (size = 4), 1, stream);
                s = malloc_bounded(size);
                s[size-1] = '\r';
                
                if((err=wrap_getdelim(&s,(size_t[]){size-1},'\n',stream)) == -1)
                    wrong = 1;
                else if (s[size-1] != '\r')
                    wrong = 2;
            break;
            case 4:
                fun = sreturnf("gethostname(s, sizeof(s)-1)");
                s = malloc(HOST_NAME_MAX);
                gethostname(s, HOST_NAME_MAX);
                size = strlen(s) + 1;
                free(s); s = NULL;
                if (size < 2) {
                    wrong = -2;
                    break;
                }
                s = malloc_bounded(size);
                s[size-1] = '\r';
                
                if ((err=wrap_gethostname(s, size-1)) == -1)
                    wrong = 1;
                else if (s[size-1] != '\r')
                    wrong = 2;
                else if (err != (err_expected = ERANGE))
                    wrong = 3;
            break;
            case 5:
                fun = sreturnf("iconv(&<\"abcd\">, &<4>, &s, &<3>)");
                s = malloc_bounded((size = 4));
                s[size-1] = '\r';
                
                cd = iconv_open("", "");
                err = wrap_iconv(
                        cd, 
                        (char **)(char *[]){"abcd"},
                        (size_t *)(size_t []){size},
                        (char **)(char *[]){s},
                        (size_t *)(size_t []){size-1}
                );
                iconv_close(cd);
                if (err == -1)
                    wrong = 1;
                else if (s[size-1] != '\r')
                    wrong = 2;
                else if (err != (err_expected = E2BIG))
                    wrong = 3;
            break;
            case 6:
                fun = sreturnf("mbstowcs(ws, \"abc\", 3)");
                size = 4;
                ws = malloc_bounded(size * sizeof(wchar_t));
                ws[size-1] = L'\r';
                
                if ((err=wrap_mbstowcs(ws, "abcd", size-1)) == -1)
                    wrong = 1;
                else if (ws[size-1] != L'\r')
                    wrong = 2;
                else if (err != (err_expected = E2BIG))
                    wrong = 3;
            break;
            case 7:
                fun = sreturnf("snprintf(s, 3, \"abc\")");
                s = malloc_bounded((size = 4));
                s[size-1] = '\r';
                
                if ((err=wrap_snprintf(s, size-1, "abc")) == -1)
                    wrong = 1;
                else if (s[size-1] != '\r')
                    wrong = 2;
            break;
            case 8:
                fun=sreturnf("readlink(\"%s.sym\", s, sizeof(s)-1)", filename);
                tmp = sreturnf("%s.sym", filename);
                if (stream == NULL)
                    break;
                else if (symlink(filename, tmp)) {
                    wrong = 4;
                    break;
                }
                s = malloc(PATH_MAX);
                size = readlink(tmp, s, PATH_MAX);
                free(s); s = NULL;
                if (size < 2) {
                    wrong = -2;
                    break;
                }
                s = malloc_bounded(size);
                s[size-1] = '\r';
                
                if ((err=wrap_readlink(tmp, s, size-1)) == -1)
                    wrong = 1;
                else if (s[size-1] != '\r')
                    wrong = 2;
            break;
            case 9:
                fun = sreturnf("strerror_r(1, s, sizeof(s)-1)");
                s = malloc(80);
                strerror_r(1, s, 80);
                size = strlen(s);
                free(s); s = NULL;
                if (size < 2) {
                    wrong = -2;
                    break;
                }
                s = malloc_bounded(size);
                s[size-1] = '\r';
                
                if ((err = wrap_strerror_r(1, s, size-1)) == -1)
                    wrong = 1;
                else if (s[size-1] != '\r')
                    wrong = 2;
                else if (err != (err_expected = ERANGE))
                    wrong = 3;
            break;
            case 10:
                fun = sreturnf("strfmon(s, sizeof(s)-1, \"%%!i\", 123.0)");
                s = malloc(80);
                size = strfmon(s, 80, "%!i", 123.0) + 1;
                free(s); s = NULL;
                if (size < 2) {
                    wrong = -2;
                    break;
                }
                s = malloc_bounded(size);
                s[size-1] = '\r';
                
                if ((err=wrap_strfmon(s, size-1, "%!i", 123.0)) == -1)
                    wrong = 1;
                else if (s[size-1] != '\r')
                    wrong = 2;
                else if (err != (err_expected = E2BIG))
                    wrong = 3;
            break;
            case 11:
                fun = sreturnf("strftime(s, sizeof(s)-1, \"%Y\", tm)");
                s = malloc(80);
                tm = *gmtime((time_t []){time(NULL)});
                strftime(s, 80, "%Y", &tm);
                size = strlen(s) + 1;
                free(s); s = NULL;
                if (size < 2) {
                    wrong = -2;
                    break;
                }
                s = malloc_bounded(size);
                s[size-1] = '\r';
                
                if ((err=wrap_strftime(s, size-1, "%Y", &tm)) == -1)
                    wrong = 1;
                else if (s[size-1] != '\r')
                    wrong = 2;
            break;
            case 12:
                fun = sreturnf("ttyname_r(STDERR_FILENO, s, sizeof(s)-1)");
                s = malloc(TTY_NAME_MAX);
                err = ttyname_r(STDERR_FILENO, s, TTY_NAME_MAX);
                size = strlen(s) + 1;
                free(s); s = NULL;
                if (size < 2) {
                    wrong = -2;
                    break;
                }
                s = malloc_bounded(size);
                s[size-1] = '\r';
                
                if ((err = wrap_ttyname_r(STDERR_FILENO, s, size-1)) == -1)
                    wrong = 1;
                else if (s[size-1] != '\r')
                    wrong = 2;
                else if (err != (err_expected = ERANGE))
                    wrong = 3;
            break;
            case 13:
                fun = sreturnf("wcstombs(s, L\"abc\" 3)");
                s = malloc_bounded((size = 4));
                s[size-1] = '\r';
                
                if ((err=wrap_wcstombs(s, L"abcd", size-1)) == -1)
                    wrong = 1;
                else if (s[size-1] != '\r')
                    wrong = 2;
            break;
            default:
                function = 0; //exit while
            break;
        }
        if(((sig=free_bounded(ws)) || (sig=free_bounded(s))) && !wrong)
            wrong = 6;

        if (wrong) {
            ++failed;
            if (wrong == -2)
                fprintf(
                    stderr,
                    "%s could not be tested, because a preceeding call to the"
                    " same function returned length (of s) lesser than 2\n",
                    fun
                );
            else if (wrong == -1)
                fprintf(stderr, "%s is unable to run, opening \"%s\" failed\n",
                        fun, filename);
            else if (wrong == 1)
                fprintf(stderr, "%s caused a SIGSEGV!\n", fun);
            else if (wrong == 2)
                fprintf(stderr, "%s wrote too many bytes to the buffer\n",fun);
            else if (wrong == 3) {
                free(tmp);
                fprintf(stderr, "%s failed to set errno=%s",
                        fun, (tmp = e_name(err_expected)));
                free(tmp);
                fprintf(stderr, " (it is %s)\n", (tmp = e_name(err)));
            }
            else if (wrong == 4)
                fprintf(stderr,
                        "%s was not called,"
                        " symlink(\"%s\", \"%s.sym\") failed\n",
                        fun, filename, filename);
            else if (wrong == 5) {
                free(tmp);
                fprintf(stderr, "%s set errno = %s\n", fun, (tmp=e_name(err)));
            }
            else if (wrong == 6)
                fprintf(
                    stderr,
                    "%s caused a signal %d to be received. A pointer passed to"
                    " the function might be errorneously redirected\n",
                    fun, sig
                );
        }
        free(fun);
        free(tmp);
        if (stream != NULL && seq_has(function, ffun))
            fclose(stream);
    } while(function++);
    
    return failed;
}

///A bridge function for sigaction(), jumps via longjmp() back to a setjmp()
void bridge_sig_jmp(int sig)
{
    longjmp(env, sig);
    return;
}

/**
 ** A quasi-implementation of malloc(), which allocates 2 additional words, one
 ** before the memory segment of the requested size, one after. It protects
 ** those words against reading/writing, so accessing them raises a SIGSEGV.
 ** \param size length of the memory segment to be allocated for reading/writing
 ** \returns a pointer to the allocated memory, NULL on failure
 ** \warning Needs to be freed by free_bounded(), not the regular free()
 **/
static void* malloc_bounded(size_t size) {
    int fd = open("/dev/zero", O_RDWR);
    size_t *stp = NULL;
    
    if (fd != -1) {
        size   +=  2 * sizeof(size_t);  //+boundry words
        stp    =   mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
        if (stp != MAP_FAILED) {
            *stp   =   size;                //write metadata to the first word
            //seal both ends:
            mprotect(stp,                                sizeof(size_t), PROT_NONE);
            mprotect((((char*)stp)+size)-sizeof(size_t), sizeof(size_t), PROT_NONE);
            ++stp;
        }
        else
            stp = NULL;
        close(fd);
    }
    return (void *)stp;
}
/**
 ** A quasi-implementation of free, which uses the metadata hidden in boundry
 ** words (allocated by malloc_bounded()) to unmap space.
 ** \param vp the pointer returned by malloc_bounded()
 ** \returns a caught signal on failure
 **/
static int free_bounded(void* vp) {
    size_t *stp = ((size_t *)vp)-1;
    int sig = 0;
    
    if(vp != NULL) {
        sigaction(SIGABRT, &act, &oldact[0]);
        sigaction(SIGSEGV, &act, &oldact[0]);
        if(!(sig = setjmp(env))) {
            mprotect(stp, sizeof(size_t), PROT_READ);
            munmap(stp, *stp);
        }   
        sigaction(SIGSEGV, &oldact[0], NULL);
        sigaction(SIGABRT, &oldact[0], NULL);
    }
    return sig;
}

///< Wrappers start here: They take the same~ arguments as the functions that
///< they call, but catch SIGSEGVs, and return error codes on failure
static int wrap_confstr(int name, char *buf, size_t len)
{
        int err = 0;
        sigaction(SIGSEGV, &act, &oldact[0]);
        if(!setjmp(env)) {
            if (confstr(name, buf, len)  ==  0)
                err = errno;
        }else
            err = -1;
        sigaction(SIGSEGV, &oldact[0], NULL);
        return err;
}
static int wrap_getcwd(char *buf, size_t size)
{
        int err = 0;
        sigaction(SIGSEGV, &act, &oldact[0]);
        if(!setjmp(env)) {
            if (getcwd(buf, size)  ==  NULL)
                err = errno;
        }else
            err = -1;
        sigaction(SIGSEGV, &oldact[0], NULL);
        return err;
}
static int wrap_getdelim(char **lineptr, size_t *n, int delimiter, FILE *stream)
{
        int err = 0;
        sigaction(SIGSEGV, &act, &oldact[0]);
        if(!setjmp(env)) {
            if (getdelim(lineptr, n, delimiter, stream)  ==  -1)
                err = errno;
        }else
            err = -1;
        sigaction(SIGSEGV, &oldact[0], NULL);
        return err;
}
static int wrap_gethostname(char *name, size_t namelen)
{
        int err = 0;
        sigaction(SIGSEGV, &act, &oldact[0]);
        if(!setjmp(env)) {
            if (gethostname(name, namelen)  ==  -1)
                err = errno;
        }else
            err = -1;
        sigaction(SIGSEGV, &oldact[0], NULL);
        return err;
}
static int wrap_iconv(iconv_t cd, char **inbuf,  size_t *inbytesleft,
                                  char **outbuf, size_t *outbytesleft)
{
        int err = 0;
        sigaction(SIGSEGV, &act, &oldact[0]);
        if(!setjmp(env)) {
            if (iconv(cd, inbuf, inbytesleft, outbuf, outbytesleft)==(size_t)-1)
                err = errno;
        }else
            err = -1;
        sigaction(SIGSEGV, &oldact[0], NULL);
        return err;
}
static int wrap_mbstowcs(wchar_t *pwcs, char *s, size_t n)
{
        int err = 0;
        sigaction(SIGSEGV, &act, &oldact[0]);
        if(!setjmp(env)) {
            if (mbstowcs(pwcs, s, n)  ==  (size_t)-1)
                err = errno;
        }else
            err = -1;
        sigaction(SIGSEGV, &oldact[0], NULL);
        return err;
}
static int wrap_snprintf(char *s, size_t n, char *format/*, ...*/)
{
        int err = 0;
        sigaction(SIGSEGV, &act, &oldact[0]);
        if(!setjmp(env)) {
            if (snprintf(s, n, format)  <  0)
                err = errno;
        }else
            err = -1;
        sigaction(SIGSEGV, &oldact[0], NULL);
        return err;
}
static int wrap_readlink(char *path, char *buf, size_t bufsize)
{
        int err = 0;
        sigaction(SIGSEGV, &act, &oldact[0]);
        if(!setjmp(env)) {
            if (readlink(path, buf, bufsize)  ==  -1)
                err = errno;
        }else
            err = -1;
        sigaction(SIGSEGV, &oldact[0], NULL);
        return err;
}
static int wrap_strfmon(char *s, size_t maxsize, char *format/*, ...*/, float F)
{
        int err = 0;
        sigaction(SIGSEGV, &act, &oldact[0]);
        if(!setjmp(env)) {
            if (strfmon(s, maxsize, format, F)  ==  -1)
                err = errno;
        }else
            err = -1;
        sigaction(SIGSEGV, &oldact[0], NULL);
        return err;
}
static int wrap_strftime(char *s,size_t maxsize,char *format,struct tm *timeptr)
{
        int err = 0;
        sigaction(SIGSEGV, &act, &oldact[0]);
        if(!setjmp(env)) {
            if (strftime(s, maxsize, format, timeptr)  ==  0)
                err = errno;
        }else
            err = -1;
        sigaction(SIGSEGV, &oldact[0], NULL);
        return err;
}
static int wrap_wcstombs(char *s, wchar_t *pwcs, size_t n)
{
        int err = 0;
        sigaction(SIGSEGV, &act, &oldact[0]);
        if(!setjmp(env)) {
            if (wcstombs(s, pwcs, n)  ==  (size_t)-1)
                err = errno;
        }else
            err = -1;
        sigaction(SIGSEGV, &oldact[0], NULL);
        return err;
}
static int wrap_strerror_r(int errnum, char *strerrbuf, size_t buflen)
{
        int err = 0;
        sigaction(SIGSEGV, &act, &oldact[0]);
        if(!setjmp(env))
            err = strerror_r(errnum, strerrbuf, buflen);
        else
            err = -1;
        sigaction(SIGSEGV, &oldact[0], NULL);
        return err;
}
static int wrap_ttyname_r(int fildes, char *name, size_t namesize)
{
        int err = 0;
        sigaction(SIGSEGV, &act, &oldact[0]);
        if(!setjmp(env))
            err = ttyname_r(fildes, name, namesize);
        else
            err = -1;
        sigaction(SIGSEGV, &oldact[0], NULL);
        return err;
}

  reply	other threads:[~2011-08-10 11:47 UTC|newest]

Thread overview: 39+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2011-08-03 22:14 New daily reports Luka Marčetić
2011-08-03 22:46 ` Solar Designer
2011-08-04 10:51   ` Luka Marčetić
2011-08-04 11:54     ` Solar Designer
2011-08-04 12:01       ` Luka Marčetić
2011-08-04 12:12         ` Solar Designer
2011-08-05  0:02     ` New daily reports - started pthread_eintr.c Luka Marčetić
2011-08-05  0:10       ` Solar Designer
2011-08-06  4:40       ` New daily reports - debugging alloc.c et al Luka Marčetić
2011-08-06 11:15         ` Szabolcs Nagy
2011-08-06 11:50           ` Szabolcs Nagy
2011-08-06 14:34             ` Szabolcs Nagy
2011-08-06 15:38               ` Szabolcs Nagy
2011-08-07  2:41         ` New daily reports - debugging alloc.c still Luka Marčetić
2011-08-07  2:50           ` Solar Designer
2011-08-07  7:32           ` Rich Felker
2011-08-07 22:25             ` Luka Marčetić
2011-08-09  3:02               ` New daily reports - buf.c Luka Marčetić
2011-08-10  1:34                 ` New daily reports - nothing Luka Marčetić
2011-08-10  1:38                   ` Rich Felker
2011-08-10 11:47                     ` Luka Marčetić [this message]
2011-08-10  2:02                   ` Solar Designer
2011-08-10 11:23                     ` Luka Marčetić
2011-08-10 11:56                       ` Solar Designer
2011-08-10 12:13                         ` Luka Marčetić
2011-08-10  2:07                   ` Solar Designer
2011-08-10  2:12                     ` Rich Felker
2011-08-10  4:59                   ` Rich Felker
2011-08-10 12:09                     ` Luka Marčetić
2011-08-10 12:44                     ` Luka Marčetić
2011-08-10 14:25                       ` Rich Felker
2011-08-10 17:21                         ` Luka Marčetić
2011-08-10 17:33                           ` Rich Felker
2011-08-10 18:23                             ` Luka Marčetić
2011-08-10 18:21                               ` Rich Felker
2011-08-10 18:34                                 ` Luka Marčetić
2011-08-10 18:33                                   ` Rich Felker
2011-08-14 20:00                     ` Rich Felker
2011-08-15 14:14                       ` Luka Marčetić

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=4E426FC5.7020303@gmail.com \
    --to=paxcoder@gmail.com \
    --cc=musl@lists.openwall.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.vuxu.org/mirror/musl/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).