"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.3/src/auth.c" (23 Nov 2018, 16934 Bytes) of package /linux/misc/tin-2.4.3.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.2_vs_2.4.3.

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