"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.4/src/auth.c" (20 Nov 2019, 17036 Bytes) of package /linux/misc/tin-2.4.4.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.3_vs_2.4.4.

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