"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.6.2/src/auth.c" (9 Dec 2022, 18213 Bytes) of package /linux/misc/tin-2.6.2.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "auth.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.6.1_vs_2.6.2.

    1 /*
    2  *  Project   : tin - a Usenet reader
    3  *  Module    : auth.c
    4  *  Author    : Dirk Nimmich <nimmich@muenster.de>
    5  *  Created   : 1997-04-05
    6  *  Updated   : 2022-06-16
    7  *  Notes     : Routines to authenticate to a news server via NNTP.
    8  *              DON'T USE get_respcode() THROUGHOUT THIS CODE.
    9  *
   10  * Copyright (c) 1997-2023 Dirk Nimmich <nimmich@muenster.de>
   11  * All rights reserved.
   12  *
   13  * Redistribution and use in source and binary forms, with or without
   14  * modification, are permitted provided that the following conditions
   15  * are met:
   16  *
   17  * 1. Redistributions of source code must retain the above copyright notice,
   18  *    this list of conditions and the following disclaimer.
   19  *
   20  * 2. Redistributions in binary form must reproduce the above copyright
   21  *    notice, this list of conditions and the following disclaimer in the
   22  *    documentation and/or other materials provided with the distribution.
   23  *
   24  * 3. Neither the name of the copyright holder nor the names of its
   25  *    contributors may be used to endorse or promote products derived from
   26  *    this software without specific prior written permission.
   27  *
   28  * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   29  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   30  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   31  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
   32  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   33  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   34  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   35  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
   36  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   37  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   38  * POSSIBILITY OF SUCH DAMAGE.
   39  */
   40 
   41 
   42 #ifndef TIN_H
   43 #   include "tin.h"
   44 #endif /* !TIN_H */
   45 #ifndef TCURSES_H
   46 #   include "tcurses.h"
   47 #endif /* !TCURSES_H */
   48 
   49 
   50 /*
   51  * we don't need authentication stuff at all if we don't have NNTP support
   52  */
   53 #ifdef NNTP_ABLE
   54 /*
   55  * local prototypes
   56  */
   57 static int do_authinfo_user(char *server, char *authuser, char *authpass);
   58 static t_bool read_newsauth_file(char *server, char *authuser, char *authpass);
   59 static t_bool authinfo_plain(char *server, char *authuser, t_bool startup);
   60 #   ifdef USE_SASL
   61     static char *sasl_auth_plain(char *user, char *pass);
   62     static int do_authinfo_sasl_plain(char *authuser, char *authpass);
   63 #   endif /* USE_SASL */
   64 
   65 
   66 /*
   67  * Read the ${TIN_HOMEDIR:-"$HOME"}/.newsauth file and put authentication
   68  * username and password for the specified server in the given strings.
   69  * Returns TRUE if at least a password was found, FALSE if there was
   70  * no .newsauth file or no matching server.
   71  */
   72 static t_bool
   73 read_newsauth_file(
   74     char *server,
   75     char *authuser,
   76     char *authpass)
   77 {
   78     FILE *fp;
   79     char *_authpass;
   80     char *ptr;
   81     char filename[PATH_LEN];
   82     char line[PATH_LEN];
   83     int found = 0;
   84     int fd;
   85     struct stat statbuf;
   86 
   87     joinpath(filename, sizeof(filename), homedir, ".newsauth");
   88 
   89     if ((fp = fopen(filename, "r"))) {
   90         if ((fd = fileno(fp)) == -1) {
   91             fclose(fp);
   92             return FALSE;
   93         }
   94         if (fstat(fd, &statbuf) == -1) {
   95             fclose(fp);
   96             return FALSE;
   97         }
   98 
   99 #   ifndef FILE_MODE_BROKEN
  100         if (S_ISREG(statbuf.st_mode) && (statbuf.st_mode|S_IRUSR|S_IWUSR) != (S_IRUSR|S_IWUSR|S_IFREG)) {
  101             error_message(4, _(txt_error_insecure_permissions), filename, statbuf.st_mode);
  102             /*
  103              * TODO: fix permissions?
  104              * #ifdef HAVE_FCHMOD
  105              *  fchmod(fd, S_IRUSR|S_IWUSR);
  106              * #else
  107              * #    ifdef HAVE_CHMOD
  108              *  chmod(filename, S_IRUSR|S_IWUSR);
  109              * #    endif
  110              * #endif
  111              */
  112         }
  113 #   endif /* !FILE_MODE_BROKEN */
  114 
  115         /*
  116          * Search through authorization file for correct NNTP server
  117          * File has format: 'nntp-server' 'password' ['username']
  118          */
  119         while (fgets(line, sizeof(line), fp) != NULL) {
  120             /* strip trailing newline character */
  121             ptr = strchr(line, '\n');
  122             if (ptr != NULL)
  123                 *ptr = '\0';
  124 
  125             /* Get server from 1st part of the line */
  126             ptr = strpbrk(line, " \t");
  127 
  128             if (ptr == NULL)        /* no passwd, no auth, skip */
  129                 continue;
  130 
  131             *ptr++ = '\0';      /* cut off server part */
  132 
  133             if ((strcasecmp(line, server)))
  134                 continue;       /* wrong server, keep on */
  135 
  136             /* Get password from 2nd part of the line */
  137             while (*ptr == ' ' || *ptr == '\t')
  138                 ptr++;  /* skip any blanks */
  139 
  140             _authpass = ptr;
  141 
  142             if (*_authpass == '"') {    /* skip "embedded" password string */
  143                 ptr = strrchr(_authpass, '"');
  144                 if ((ptr != NULL) && (ptr > _authpass)) {
  145                     _authpass++;
  146                     *ptr++ = '\0';  /* cut off trailing " */
  147                 } else  /* no matching ", proceed as normal */
  148                     ptr = _authpass;
  149             }
  150 
  151             /* Get user from 3rd part of the line */
  152             ptr = strpbrk(ptr, " \t");  /* find next separating blank */
  153 
  154             if (ptr != NULL) {  /* a 3rd argument follows */
  155                 while (*ptr == ' ' || *ptr == '\t') /* skip any blanks */
  156                     *ptr++ = '\0';
  157                 if (*ptr != '\0')   /* if it is not just empty */
  158                     strcpy(authuser, ptr);  /* so will replace default user */
  159             }
  160             strcpy(authpass, _authpass);
  161             found++;
  162             break;  /* if we end up here, everything seems OK */
  163         }
  164         fclose(fp);
  165         return (found > 0);
  166     }
  167     return FALSE;
  168 }
  169 
  170 
  171 /*
  172  * Perform authentication with AUTHINFO USER method. Return response
  173  * code from server.
  174  *
  175  * we don't handle ERR_ENCRYPT right now
  176  *
  177  * we don't convert authuser and authpass to UTF-8 as required by 3977
  178  * and we do in do_authinfo_sasl_plain(); if we want todo so it should
  179  * lkely be done in authinfo_plain() instead.
  180  */
  181 static int
  182 do_authinfo_user(
  183     char *server,
  184     char *authuser,
  185     char *authpass)
  186 {
  187     char line[PATH_LEN];
  188     int ret;
  189 
  190     /* may violate RFC 3977 3.1; use MIN(NNTP_STRLEN, sizeof(line)) ? */
  191     snprintf(line, sizeof(line), "AUTHINFO USER %s", authuser);
  192 #   ifdef DEBUG
  193     if ((debug & DEBUG_NNTP) && verbose > 1)
  194         debug_print_file("NNTP", "authorization %s", line);
  195 #   endif /* DEBUG */
  196     put_server(line);
  197     if ((ret = get_only_respcode(NULL, 0)) != NEED_AUTHDATA)
  198         return ret;
  199 
  200     if ((authpass == NULL) || (*authpass == '\0')) {
  201 #   ifdef DEBUG
  202         if ((debug & DEBUG_NNTP) && verbose > 1)
  203             debug_print_file("NNTP", "authorization failed: no password");
  204 #   endif /* DEBUG */
  205         error_message(2, _(txt_auth_failed_nopass), server);
  206         return ERR_AUTHBAD;
  207     }
  208 
  209     /* may violate RFC 3977 3.1; use MIN(NNTP_STRLEN, sizeof(line)) ? */
  210     snprintf(line, sizeof(line), "AUTHINFO PASS %s", authpass);
  211 #   ifdef DEBUG
  212     if ((debug & DEBUG_NNTP) && verbose > 1)
  213         debug_print_file("NNTP", "authorization %s", line);
  214 #   endif /* DEBUG */
  215     put_server(line);
  216     ret = get_only_respcode(line, sizeof(line));
  217     if (!batch_mode || verbose || ret != OK_AUTH)
  218         wait_message(2, (ret == OK_AUTH ? _(txt_authorization_ok) : _(txt_authorization_fail)), authuser);
  219     return ret;
  220 }
  221 
  222 
  223 /*
  224  * NNTP user authorization. Returns TRUE if authorization succeeded,
  225  * FALSE if not.
  226  *
  227  * tries AUTHINFO SASL PLAIN (if available) fist and if not successful
  228  * AUTHINFO USER/PASS
  229  *
  230  * If username/passwd already given, and server wasn't changed, retry those.
  231  * Otherwise, read password from ~/.newsauth or, if not present or no matching
  232  * server found, from console.
  233  *
  234  * The ~/.newsauth authorization file has the format:
  235  *   nntpserver1 password [user]
  236  *   nntpserver2 password [user]
  237  *   etc.
  238  *
  239  * TODO: convert authuser and authpass to UTF-8 as required by 3977?
  240  */
  241 static t_bool
  242 authinfo_plain(
  243     char *server,
  244     char *authuser,
  245     t_bool startup)
  246 {
  247     char *authpass;
  248     int ret = ERR_AUTHBAD, changed;
  249     static char authusername[PATH_LEN] = "";
  250     static char authpassword[PATH_LEN] = "";
  251     static char last_server[PATH_LEN] = "";
  252     static t_bool already_failed = FALSE;
  253     static t_bool initialized = FALSE;
  254 
  255     if ((changed = strcmp(server, last_server)))    /* do we need new auth values? */
  256         STRCPY(last_server, server);
  257 
  258     /*
  259      * Let's try the previous auth pair first, if applicable.
  260      * Else, proceed to the other mechanisms.
  261      */
  262     if (initialized && !changed && !already_failed) {
  263 #   ifdef USE_SASL
  264         if (nntp_caps.sasl & SASL_PLAIN)
  265             ret = do_authinfo_sasl_plain(authusername, authpassword);
  266         if (ret != OK_AUTH)
  267 #   endif /* USE_SASL */
  268         {
  269             if (nntp_caps.type != CAPABILITIES || nntp_caps.authinfo_user)
  270                 ret = do_authinfo_user(server, authusername, authpassword);
  271         }
  272         return (ret == OK_AUTH);
  273     }
  274 
  275     authpassword[0] = '\0';
  276     STRCPY(authusername, authuser);
  277     authuser = authusername;
  278     authpass = authpassword;
  279 
  280     /*
  281      * No username/password given yet.
  282      * Read .newsauth only if we had not failed authentication yet for the
  283      * current server (we don't want to try wrong username/password pairs
  284      * more than once because this may lead to an infinite loop at connection
  285      * startup: nntp_open tries to authenticate, it fails, server closes
  286      * connection; next time tin tries to access the server it will do
  287      * nntp_open again ...). This means, however, that if configuration
  288      * changed on the server between two authentication attempts tin will
  289      * prompt you the second time instead of reading .newsauth (except when
  290      * at startup time; in this case, it will just leave); you have to leave
  291      * and restart tin or change to another server and back in order to get
  292      * it read again.
  293      */
  294     if ((changed || !initialized) && !already_failed) {
  295         if (read_newsauth_file(server, authuser, authpass)) {
  296 #   ifdef USE_SASL
  297             if (nntp_caps.sasl & SASL_PLAIN)
  298                 ret = do_authinfo_sasl_plain(authuser, authpass);
  299 
  300             if (ret != OK_AUTH)
  301 #   endif /* USE_SASL */
  302             {
  303                 if (force_auth_on_conn_open || nntp_caps.type != CAPABILITIES || (nntp_caps.type == CAPABILITIES && nntp_caps.authinfo_user))
  304                     ret = do_authinfo_user(server, authuser, authpass);
  305             }
  306             already_failed = (ret != OK_AUTH);
  307 
  308             if (ret == OK_AUTH) {
  309 #   ifdef DEBUG
  310                 if ((debug & DEBUG_NNTP) && verbose > 1)
  311                     debug_print_file("NNTP", "authorization succeeded");
  312 #   endif /* DEBUG */
  313                 initialized = TRUE;
  314                 return TRUE;
  315             }
  316         }
  317 #   ifdef DEBUG
  318         else {
  319             if ((debug & DEBUG_NNTP) && verbose > 1)
  320                 debug_print_file("NNTP", "read_newsauth_file(\"%s\", \"%s\", \"%s\") failed", server, authuser, authpass);
  321         }
  322 #   endif /* DEBUG */
  323     }
  324 
  325     /*
  326      * At this point, either authentication with username/password pair from
  327      * .newsauth has failed or there's no .newsauth file respectively no
  328      * matching username/password for the current server. If we are not at
  329      * startup we ask the user to enter such a pair by hand. Don't ask him
  330      * at startup except if requested by -A option because if he doesn't need
  331      * to authenticate (we don't know), the "Server expects authentication"
  332      * messages are annoying (and even wrong).
  333      * UNSURE: Maybe we want to make this decision configurable in the
  334      * options menu, too, so that the user doesn't need -A.
  335      * TODO: Put questions into do_authinfo_user() because it is possible
  336      * that the server doesn't want a password; so only ask for it if needed.
  337      */
  338     if (force_auth_on_conn_open || !startup) {
  339         if (batch_mode) { /* no interactive username/password prompting */
  340             error_message(0, _(txt_auth_needed));
  341             return (ret == OK_AUTH);
  342         }
  343         if (nntp_caps.type != CAPABILITIES || (nntp_caps.type == CAPABILITIES && !nntp_caps.authinfo_state && ((nntp_caps.sasl & SASL_PLAIN) || nntp_caps.authinfo_user || (!nntp_caps.authinfo_user && !(nntp_caps.sasl & SASL_PLAIN))))) {
  344 #   ifdef USE_CURSES
  345             int state = RawState();
  346 #   endif /* USE_CURSES */
  347 
  348             wait_message(0, _(txt_auth_needed));
  349 #   ifdef USE_CURSES
  350             Raw(TRUE);
  351 #   endif /* USE_CURSES */
  352             if (!prompt_default_string(_(txt_auth_user), authuser, sizeof(authusername) - 1, authusername, HIST_NONE)) {
  353 #   ifdef DEBUG
  354                 if ((debug & DEBUG_NNTP) && verbose > 1)
  355                     debug_print_file("NNTP", "authorization failed: no username");
  356 #   endif /* DEBUG */
  357                 return FALSE;
  358             }
  359 
  360 #   ifdef USE_CURSES
  361             Raw(state);
  362             my_printf("%s", _(txt_auth_pass));
  363             wgetnstr(stdscr, authpassword, sizeof(authpassword) - 1);
  364             authpassword[sizeof(authpassword) - 1] = '\0';
  365             Raw(TRUE);
  366 #   else
  367             /*
  368              * on some systems (i.e. Solaris) getpass(3) is limited
  369              * to 8 chars -> we use tin_getline()
  370              */
  371             STRCPY(authpassword, tin_getline(_(txt_auth_pass), 0, NULL, sizeof(authpassword) - 1, TRUE, HIST_NONE));
  372 #   endif /* USE_CURSES */
  373 
  374 #   ifdef USE_SASL
  375             if (nntp_caps.sasl & SASL_PLAIN)
  376                 ret = do_authinfo_sasl_plain(authuser, authpass);
  377             if (ret != OK_AUTH)
  378 #   endif /* USE_SASL */
  379             {
  380                 if (nntp_caps.type != CAPABILITIES || (nntp_caps.authinfo_user || !nntp_caps.authinfo_sasl)) {
  381 #   ifdef DEBUG
  382                     if (debug & DEBUG_NNTP) {
  383                         if (nntp_caps.type == CAPABILITIES && !nntp_caps.authinfo_sasl && !nntp_caps.authinfo_user)
  384                             debug_print_file("NNTP", "!!! No supported authmethod available, trying AUTHINFO USER/PASS");
  385                     }
  386 #   endif /* DEBUG */
  387                     ret = do_authinfo_user(server, authuser, authpass);
  388                     if (ret != OK_AUTH)
  389                         already_failed = TRUE;
  390                     /*
  391                      * giganews once responded to CAPABILITIES with just
  392                      * "VERSION 2", no mode-switching indication, no reader
  393                      * indication, no post indication, no authentication
  394                      * indication, ... so in case AUTHINFO USER/PASS succeeds
  395                      * if not advertised we simply go on but fully ignore
  396                      * CAPABILITIES
  397                      */
  398                     if (nntp_caps.type == CAPABILITIES && !nntp_caps.authinfo_user && !nntp_caps.authinfo_sasl && ret == OK_AUTH)
  399                         nntp_caps.type = BROKEN;
  400                 }
  401             }
  402             initialized = TRUE;
  403             my_retouch();           /* Get rid of the chaff */
  404         } else {
  405             /*
  406              * TODO:
  407              * nntp_caps.type == CAPABILITIES && nntp_caps.authinfo_state
  408              * can we change the state here? and if so how? SARTTLS? MODE
  409              * READER?
  410              */
  411 #   ifdef DEBUG
  412             if ((debug & DEBUG_NNTP) && verbose > 1) {
  413                 debug_print_file("NNTP", "authorization not allowed in current state:");
  414                 debug_print_file("NNTP", "\tCAPABILITIES: %s", nntp_caps.type ? (nntp_caps.type < 2 ? "CAPABILITIES" : "BROKEN" ) : "NONE");
  415                 debug_print_file("NNTP", "\t%cREADER, %cMODE READER", nntp_caps.reader ? '+' : '-', nntp_caps.mode_reader ? '+' : '-');
  416                 debug_print_file("NNTP", "\t%cSTARTTLS", nntp_caps.starttls ? '+' : '-');
  417                 debug_print_file("NNTP", "\t%cAUTHINFO %s%s", nntp_caps.authinfo_state ? '+' : '-', nntp_caps.authinfo_user ? "USER " : "" , nntp_caps.authinfo_sasl ? "SASL" : "");
  418             }
  419 #   endif /* DEBUG */
  420             /*
  421              * we return OK_AUTH here once so tin doesn't exit just because a
  422              * single command requested auth ...
  423              */
  424             if (!already_failed)
  425                 ret = OK_AUTH;
  426         }
  427     }
  428 
  429 #   ifdef DEBUG
  430     if ((debug & DEBUG_NNTP) && verbose > 1)
  431         debug_print_file("NNTP", "authorization %s", (ret == OK_AUTH ? "succeeded" : "failed"));
  432 #   endif /* DEBUG */
  433 
  434     return (ret == OK_AUTH);
  435 }
  436 
  437 
  438 /*
  439  * Do authentication stuff. Return TRUE if authentication was successful,
  440  * FALSE otherwise.
  441  *
  442  * try ORIGINAL AUTHINFO method.
  443  * Other authentication methods can easily be added.
  444  */
  445 t_bool
  446 authenticate(
  447     char *server,
  448     char *user,
  449     t_bool startup)
  450 {
  451     char line[NNTP_STRLEN];
  452     t_bool ret;
  453 
  454     ret = authinfo_plain(server, user, startup);
  455 
  456     if (ret && nntp_caps.type == CAPABILITIES) {
  457         /* resend CAPABILITIES, but "manually" to avoid AUTH loop */
  458         snprintf(line, sizeof(line), "%s", "CAPABILITIES");
  459 #   ifdef DEBUG
  460         if ((debug & DEBUG_NNTP) && verbose > 1)
  461             debug_print_file("NNTP", "authenticate(%s)", line);
  462 #   endif /* DEBUG */
  463         put_server(line);
  464 
  465         check_extensions(get_only_respcode(line, sizeof(line)));
  466     }
  467 
  468     return ret;
  469 }
  470 
  471 
  472 #   ifdef USE_SASL
  473 static int
  474 do_authinfo_sasl_plain(
  475     char *authuser,
  476     char *authpass)
  477 {
  478     char line[PATH_LEN];
  479     char *foo;
  480     char *utf8user;
  481     char *utf8pass;
  482     int ret;
  483 #       ifdef CHARSET_CONVERSION
  484     char *cp;
  485     int i, c = 0;
  486     t_bool contains_8bit = FALSE;
  487 #       endif /* CHARSET_CONVERSION */
  488 
  489     utf8user = my_strdup(authuser);
  490     utf8pass = my_strdup(authpass);
  491 #       ifdef CHARSET_CONVERSION
  492     /* RFC 4616 */
  493     if (!IS_LOCAL_CHARSET("UTF-8")) {
  494         for (cp = utf8user; *cp && !contains_8bit; cp++) {
  495             if (!isascii(*cp)) {
  496                 contains_8bit = TRUE;
  497                 break;
  498             }
  499         }
  500         for (cp = utf8pass; *cp && !contains_8bit; cp++) {
  501             if (!isascii(*cp)) {
  502                 contains_8bit = TRUE;
  503                 break;
  504             }
  505         }
  506         if (contains_8bit) {
  507             for (i = 0; txt_mime_charsets[i] != NULL; i++) {
  508                 if (!strcasecmp("UTF-8", txt_mime_charsets[i])) {
  509                     c = i;
  510                     break;
  511                 }
  512             }
  513             if (c == i) { /* should never fail */
  514                 if (!buffer_to_network(utf8user, c)) {
  515                     free(utf8user);
  516                     utf8user = my_strdup(authuser);
  517                 }
  518                 if (!buffer_to_network(utf8pass, c)) {
  519                     free(utf8pass);
  520                     utf8pass = my_strdup(authpass);
  521                 }
  522             }
  523         }
  524     }
  525 #       endif /* CHARSET_CONVERSION */
  526 
  527 #       ifdef DEBUG
  528     if ((debug & DEBUG_NNTP) && verbose > 1)
  529         debug_print_file("NNTP", "do_authinfo_sasl_plain(\"%s\", \"%s\")", BlankIfNull(authuser), BlankIfNull(authpass));
  530 #       endif /* DEBUG */
  531 
  532     if ((foo = sasl_auth_plain(utf8user, utf8pass)) == NULL) {
  533         free(utf8user);
  534         free(utf8pass);
  535         return ERR_AUTHBAD;
  536     }
  537 
  538     free(utf8user);
  539     free(utf8pass);
  540 
  541     snprintf(line, sizeof(line), "AUTHINFO SASL PLAIN %s", foo);
  542     FreeIfNeeded(foo);
  543 #       ifdef DEBUG
  544     if ((debug & DEBUG_NNTP) && verbose > 1)
  545         debug_print_file("NNTP", "authorization %s", line);
  546 #       endif /* DEBUG */
  547     put_server(line);
  548     ret = get_only_respcode(line, sizeof(line));
  549     if (!batch_mode || verbose || ret != OK_AUTH)
  550         wait_message(2, (ret == OK_AUTH ? _(txt_authorization_ok) : _(txt_authorization_fail)), authuser);
  551     return ret;
  552 }
  553 
  554 
  555 static char *
  556 sasl_auth_plain(
  557     char *user,
  558     char *pass)
  559 {
  560     Gsasl *ctx = NULL;
  561     Gsasl_session *session;
  562     char *p = NULL;
  563     const char *mech = "PLAIN";
  564 
  565     if (gsasl_init(&ctx) != GSASL_OK) /* TODO: do this only once at startup */
  566         return p;
  567     if (gsasl_client_start(ctx, mech, &session) != GSASL_OK) {
  568         gsasl_done(ctx);
  569         return p;
  570     }
  571     /* GSASL_AUTHZID (authorization identity) is (usually) unused with NNTP */
  572     gsasl_property_set(session, GSASL_AUTHID, user);    /* authentication identity */
  573     gsasl_property_set(session, GSASL_PASSWORD, pass);  /* password of the authentication identity */
  574     if (gsasl_step64(session, NULL, &p) != GSASL_OK)
  575         FreeAndNull(p);
  576     gsasl_finish(session);
  577     gsasl_done(ctx);
  578     return p;
  579 }
  580 #   endif /* USE_SASL */
  581 
  582 #else
  583 static void no_authenticate(void);          /* proto-type */
  584 static void
  585 no_authenticate(                    /* ANSI C requires non-empty source file */
  586     void)
  587 {
  588 }
  589 #endif /* NNTP_ABLE */