"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.5/src/auth.c" (15 Dec 2020, 17319 Bytes) of package /linux/misc/tin-2.4.5.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.4.4_vs_2.4.5.

    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   : 2018-06-04
    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-2021 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 static int
  178 do_authinfo_user(
  179     char *server,
  180     char *authuser,
  181     char *authpass)
  182 {
  183     char line[PATH_LEN];
  184     int ret;
  185 
  186     snprintf(line, sizeof(line), "AUTHINFO USER %s", authuser);
  187 #   ifdef DEBUG
  188     if ((debug & DEBUG_NNTP) && verbose > 1)
  189         debug_print_file("NNTP", "authorization %s", line);
  190 #   endif /* DEBUG */
  191     put_server(line);
  192     if ((ret = get_only_respcode(NULL, 0)) != NEED_AUTHDATA)
  193         return ret;
  194 
  195     if ((authpass == NULL) || (*authpass == '\0')) {
  196 #   ifdef DEBUG
  197         if ((debug & DEBUG_NNTP) && verbose > 1)
  198             debug_print_file("NNTP", "authorization failed: no password");
  199 #   endif /* DEBUG */
  200         error_message(2, _(txt_auth_failed_nopass), server);
  201         return ERR_AUTHBAD;
  202     }
  203 
  204     snprintf(line, sizeof(line), "AUTHINFO PASS %s", authpass);
  205 #   ifdef DEBUG
  206     if ((debug & DEBUG_NNTP) && verbose > 1)
  207         debug_print_file("NNTP", "authorization %s", line);
  208 #   endif /* DEBUG */
  209     put_server(line);
  210     ret = get_only_respcode(line, sizeof(line));
  211     if (!batch_mode || verbose || ret != OK_AUTH)
  212         wait_message(2, (ret == OK_AUTH ? _(txt_authorization_ok) : _(txt_authorization_fail)), authuser);
  213     return ret;
  214 }
  215 
  216 
  217 /*
  218  * NNTP user authorization. Returns TRUE if authorization succeeded,
  219  * FALSE if not.
  220  *
  221  * tries AUTHINFO SASL PLAIN (if available) fist and if not successful
  222  * AUTHINFO USER/PASS
  223  *
  224  * If username/passwd already given, and server wasn't changed, retry those.
  225  * Otherwise, read password from ~/.newsauth or, if not present or no matching
  226  * server found, from console.
  227  *
  228  * The ~/.newsauth authorization file has the format:
  229  *   nntpserver1 password [user]
  230  *   nntpserver2 password [user]
  231  *   etc.
  232  */
  233 static t_bool
  234 authinfo_plain(
  235     char *server,
  236     char *authuser,
  237     t_bool startup)
  238 {
  239     char *authpass;
  240     int ret = ERR_AUTHBAD, changed;
  241     static char authusername[PATH_LEN] = "";
  242     static char authpassword[PATH_LEN] = "";
  243     static char last_server[PATH_LEN] = "";
  244     static t_bool already_failed = FALSE;
  245     static t_bool initialized = FALSE;
  246 
  247     if ((changed = strcmp(server, last_server)))    /* do we need new auth values? */
  248         STRCPY(last_server, server);
  249 
  250     /*
  251      * Let's try the previous auth pair first, if applicable.
  252      * Else, proceed to the other mechanisms.
  253      */
  254     if (initialized && !changed && !already_failed) {
  255 #   ifdef USE_SASL
  256         if (nntp_caps.sasl & SASL_PLAIN)
  257             ret = do_authinfo_sasl_plain(authusername, authpassword);
  258         if (ret != OK_AUTH)
  259 #   endif /* USE_SASL */
  260         {
  261             if (nntp_caps.type != CAPABILITIES || nntp_caps.authinfo_user)
  262                 ret = do_authinfo_user(server, authusername, authpassword);
  263         }
  264         return (ret == OK_AUTH);
  265     }
  266 
  267     authpassword[0] = '\0';
  268     STRCPY(authusername, authuser);
  269     authuser = authusername;
  270     authpass = authpassword;
  271 
  272     /*
  273      * No username/password given yet.
  274      * Read .newsauth only if we had not failed authentication yet for the
  275      * current server (we don't want to try wrong username/password pairs
  276      * more than once because this may lead to an infinite loop at connection
  277      * startup: nntp_open tries to authenticate, it fails, server closes
  278      * connection; next time tin tries to access the server it will do
  279      * nntp_open again ...). This means, however, that if configuration
  280      * changed on the server between two authentication attempts tin will
  281      * prompt you the second time instead of reading .newsauth (except when
  282      * at startup time; in this case, it will just leave); you have to leave
  283      * and restart tin or change to another server and back in order to get
  284      * it read again.
  285      */
  286     if ((changed || !initialized) && !already_failed) {
  287         if (read_newsauth_file(server, authuser, authpass)) {
  288 #   ifdef USE_SASL
  289             if (nntp_caps.sasl & SASL_PLAIN)
  290                 ret = do_authinfo_sasl_plain(authuser, authpass);
  291 
  292             if (ret != OK_AUTH)
  293 #   endif /* USE_SASL */
  294             {
  295                 if (force_auth_on_conn_open || nntp_caps.type != CAPABILITIES || (nntp_caps.type == CAPABILITIES && nntp_caps.authinfo_user))
  296                     ret = do_authinfo_user(server, authuser, authpass);
  297             }
  298             already_failed = (ret != OK_AUTH);
  299 
  300             if (ret == OK_AUTH) {
  301 #   ifdef DEBUG
  302                 if ((debug & DEBUG_NNTP) && verbose > 1)
  303                     debug_print_file("NNTP", "authorization succeeded");
  304 #   endif /* DEBUG */
  305                 initialized = TRUE;
  306                 return TRUE;
  307             }
  308         }
  309 #   ifdef DEBUG
  310         else {
  311             if ((debug & DEBUG_NNTP) && verbose > 1)
  312                 debug_print_file("NNTP", "read_newsauth_file(\"%s\", \"%s\", \"%s\") failed", server, authuser, authpass);
  313         }
  314 #   endif /* DEBUG */
  315     }
  316 
  317     /*
  318      * At this point, either authentication with username/password pair from
  319      * .newsauth has failed or there's no .newsauth file respectively no
  320      * matching username/password for the current server. If we are not at
  321      * startup we ask the user to enter such a pair by hand. Don't ask him
  322      * at startup except if requested by -A option because if he doesn't need
  323      * to authenticate (we don't know), the "Server expects authentication"
  324      * messages are annoying (and even wrong).
  325      * UNSURE: Maybe we want to make this decision configurable in the
  326      * options menu, too, so that the user doesn't need -A.
  327      * TODO: Put questions into do_authinfo_user() because it is possible
  328      * that the server doesn't want a password; so only ask for it if needed.
  329      */
  330     if (force_auth_on_conn_open || !startup) {
  331         if (batch_mode) { /* no interactive username/password prompting */
  332             error_message(0, _(txt_auth_needed));
  333             return (ret == OK_AUTH);
  334         }
  335         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))))) {
  336 #   ifdef USE_CURSES
  337             int state = RawState();
  338 #   endif /* USE_CURSES */
  339 
  340             wait_message(0, _(txt_auth_needed));
  341 #   ifdef USE_CURSES
  342             Raw(TRUE);
  343 #   endif /* USE_CURSES */
  344             if (!prompt_default_string(_(txt_auth_user), authuser, sizeof(authusername) - 1, authusername, HIST_NONE)) {
  345 #   ifdef DEBUG
  346                 if ((debug & DEBUG_NNTP) && verbose > 1)
  347                     debug_print_file("NNTP", "authorization failed: no username");
  348 #   endif /* DEBUG */
  349                 return FALSE;
  350             }
  351 
  352 #   ifdef USE_CURSES
  353             Raw(state);
  354             my_printf("%s", _(txt_auth_pass));
  355             wgetnstr(stdscr, authpassword, sizeof(authpassword) - 1);
  356             authpassword[sizeof(authpassword) - 1] = '\0';
  357             Raw(TRUE);
  358 #   else
  359             /*
  360              * on some systems (i.e. Solaris) getpass(3) is limited
  361              * to 8 chars -> we use tin_getline()
  362              */
  363             STRCPY(authpassword, tin_getline(_(txt_auth_pass), 0, NULL, sizeof(authpassword) - 1, TRUE, HIST_NONE));
  364 #   endif /* USE_CURSES */
  365 
  366 #   ifdef USE_SASL
  367             if (nntp_caps.sasl & SASL_PLAIN)
  368                 ret = do_authinfo_sasl_plain(authuser, authpass);
  369             if (ret != OK_AUTH)
  370 #   endif /* USE_SASL */
  371             {
  372                 if (nntp_caps.type != CAPABILITIES || (nntp_caps.authinfo_user || !nntp_caps.authinfo_sasl)) {
  373 #   ifdef DEBUG
  374                     if (debug & DEBUG_NNTP) {
  375                         if (nntp_caps.type == CAPABILITIES && !nntp_caps.authinfo_sasl && !nntp_caps.authinfo_user)
  376                             debug_print_file("NNTP", "!!! No supported authmethod available, trying AUTHINFO USER/PASS");
  377                     }
  378 #   endif /* DEBUG */
  379                     ret = do_authinfo_user(server, authuser, authpass);
  380                     if (ret != OK_AUTH)
  381                         already_failed = TRUE;
  382                     /*
  383                      * giganews once responded to CAPABILITIES with just
  384                      * "VERSION 2", no mode-switching indication, no reader
  385                      * indication, no post indication, no authentication
  386                      * indication, ... so in case AUTHINFO USER/PASS succeeds
  387                      * if not advertized we simply go on but fully ignore
  388                      * CAPABILITIES
  389                      */
  390                     if (nntp_caps.type == CAPABILITIES && !nntp_caps.authinfo_user && !nntp_caps.authinfo_sasl && ret == OK_AUTH)
  391                         nntp_caps.type = BROKEN;
  392                 }
  393             }
  394             initialized = TRUE;
  395             my_retouch();           /* Get rid of the chaff */
  396         } else {
  397             /*
  398              * TODO:
  399              * nntp_caps.type == CAPABILITIES && nntp_caps.authinfo_state
  400              * can we change the state here? and if so how? SARTTLS? MODE
  401              * READER?
  402              */
  403 #   ifdef DEBUG
  404             if ((debug & DEBUG_NNTP) && verbose > 1)
  405                 debug_print_file("NNTP", "authorization not allowed in current state");
  406 #   endif /* DEBUG */
  407             /*
  408              * we return OK_AUTH here once so tin doesn't exit just because a
  409              * single command requested auth ...
  410              */
  411             if (!already_failed)
  412                 ret = OK_AUTH;
  413         }
  414     }
  415 
  416 #   ifdef DEBUG
  417     if ((debug & DEBUG_NNTP) && verbose > 1)
  418         debug_print_file("NNTP", "authorization %s", (ret == OK_AUTH ? "succeeded" : "failed"));
  419 #   endif /* DEBUG */
  420 
  421     return (ret == OK_AUTH);
  422 }
  423 
  424 
  425 /*
  426  * Do authentication stuff. Return TRUE if authentication was successful,
  427  * FALSE otherwise.
  428  *
  429  * try ORIGINAL AUTHINFO method.
  430  * Other authentication methods can easily be added.
  431  */
  432 t_bool
  433 authenticate(
  434     char *server,
  435     char *user,
  436     t_bool startup)
  437 {
  438     char line[NNTP_STRLEN];
  439     t_bool ret;
  440 
  441     ret = authinfo_plain(server, user, startup);
  442 
  443     if (ret && nntp_caps.type == CAPABILITIES) {
  444         /* resend CAPABILITIES, but "manually" to avoid AUTH loop */
  445         snprintf(line, sizeof(line), "%s", "CAPABILITIES");
  446 #   ifdef DEBUG
  447         if ((debug & DEBUG_NNTP) && verbose > 1)
  448             debug_print_file("NNTP", "authenticate(%s)", line);
  449 #   endif /* DEBUG */
  450         put_server(line);
  451 
  452         check_extensions(get_only_respcode(line, sizeof(line)));
  453     }
  454 
  455     return ret;
  456 }
  457 
  458 
  459 #   ifdef USE_SASL
  460 static int
  461 do_authinfo_sasl_plain(
  462     char *authuser,
  463     char *authpass)
  464 {
  465     char line[PATH_LEN];
  466     char *foo;
  467     char *utf8user;
  468     char *utf8pass;
  469     int ret;
  470 #       ifdef CHARSET_CONVERSION
  471     char *cp;
  472     int i, c = 0;
  473     t_bool contains_8bit = FALSE;
  474 #       endif /* CHARSET_CONVERSION */
  475 
  476     utf8user = my_strdup(authuser);
  477     utf8pass = my_strdup(authpass);
  478 #       ifdef CHARSET_CONVERSION
  479     /* RFC 4616 */
  480     if (!IS_LOCAL_CHARSET("UTF-8")) {
  481         for (cp = utf8user; *cp && !contains_8bit; cp++) {
  482             if (!isascii(*cp)) {
  483                 contains_8bit = TRUE;
  484                 break;
  485             }
  486         }
  487         for (cp = utf8pass; *cp && !contains_8bit; cp++) {
  488             if (!isascii(*cp)) {
  489                 contains_8bit = TRUE;
  490                 break;
  491             }
  492         }
  493         if (contains_8bit) {
  494             for (i = 0; txt_mime_charsets[i] != NULL; i++) {
  495                 if (!strcasecmp("UTF-8", txt_mime_charsets[i])) {
  496                     c = i;
  497                     break;
  498                 }
  499             }
  500             if (c == i) { /* should never fail */
  501                 if (!buffer_to_network(utf8user, c)) {
  502                     free(utf8user);
  503                     utf8user = my_strdup(authuser);
  504                 } else {
  505                     if (!buffer_to_network(utf8pass, c)) {
  506                         free(utf8pass);
  507                         utf8pass = my_strdup(authpass);
  508                     }
  509                 }
  510             }
  511         }
  512     }
  513 #       endif /* CHARSET_CONVERSION */
  514 
  515 #       ifdef DEBUG
  516     if ((debug & DEBUG_NNTP) && verbose > 1)
  517         debug_print_file("NNTP", "do_authinfo_sasl_plain(\"%s\", \"%s\")", BlankIfNull(authuser), BlankIfNull(authpass));
  518 #       endif /* DEBUG */
  519 
  520     if ((foo = sasl_auth_plain(utf8user, utf8pass)) == NULL) {
  521         free(utf8user);
  522         free(utf8pass);
  523         return ERR_AUTHBAD;
  524     }
  525 
  526     free(utf8user);
  527     free(utf8pass);
  528 
  529     snprintf(line, sizeof(line), "AUTHINFO SASL PLAIN %s", foo);
  530     FreeIfNeeded(foo);
  531 #       ifdef DEBUG
  532     if ((debug & DEBUG_NNTP) && verbose > 1)
  533         debug_print_file("NNTP", "authorization %s", line);
  534 #       endif /* DEBUG */
  535     put_server(line);
  536     ret = get_only_respcode(line, sizeof(line));
  537     if (!batch_mode || verbose || ret != OK_AUTH)
  538         wait_message(2, (ret == OK_AUTH ? _(txt_authorization_ok) : _(txt_authorization_fail)), authuser);
  539     return ret;
  540 }
  541 
  542 
  543 static char *
  544 sasl_auth_plain(
  545     char *user,
  546     char *pass)
  547 {
  548     Gsasl *ctx = NULL;
  549     Gsasl_session *session;
  550     char *p = NULL;
  551     const char *mech = "PLAIN";
  552 
  553     if (gsasl_init(&ctx) != GSASL_OK) /* TODO: do this only once at startup */
  554         return p;
  555     if (gsasl_client_start(ctx, mech, &session) != GSASL_OK) {
  556         gsasl_done(ctx);
  557         return p;
  558     }
  559     /* GSASL_AUTHZID (authorization identity) is (usually) unused with NNTP */
  560     gsasl_property_set(session, GSASL_AUTHID, user);    /* authentication identity */
  561     gsasl_property_set(session, GSASL_PASSWORD, pass);  /* password of the authentication identity */
  562     if (gsasl_step64(session, NULL, &p) != GSASL_OK)
  563         FreeAndNull(p);
  564     gsasl_finish(session);
  565     gsasl_done(ctx);
  566     return p;
  567 }
  568 #   endif /* USE_SASL */
  569 
  570 #else
  571 static void no_authenticate(void);          /* proto-type */
  572 static void
  573 no_authenticate(                    /* ANSI C requires non-empty source file */
  574     void)
  575 {
  576 }
  577 #endif /* NNTP_ABLE */