"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.1/src/auth.c" (13 Dec 2016, 16340 Bytes) of archive /linux/misc/tin-2.4.1.tar.gz:


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.0_vs_2.4.1.

    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   : 2016-12-13
    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-2017 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 permssions?
  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 ", proceede 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 its 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)
  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)
  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)
  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 succcessfull
  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)
  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)
  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)
  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), FALSE, 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 sate here? and if so how? SARTTLS? MODE
  392              * READER?
  393              */
  394 #   ifdef DEBUG
  395             if (debug & DEBUG_NNTP)
  396                 debug_print_file("NNTP", "authorization not allowed in current sate");
  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)
  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     return authinfo_plain(server, user, startup);
  430 }
  431 
  432 
  433 #   ifdef USE_SASL
  434 static int
  435 do_authinfo_sasl_plain(
  436     char *authuser,
  437     char *authpass)
  438 {
  439     char line[PATH_LEN];
  440     char *foo;
  441     char *utf8user;
  442     char *utf8pass;
  443     int ret;
  444 #       ifdef CHARSET_CONVERSION
  445     char *cp;
  446     int i, c = 0;
  447     t_bool contains_8bit = FALSE;
  448 #       endif /* CHARSET_CONVERSION */
  449 
  450     utf8user = my_strdup(authuser);
  451     utf8pass = my_strdup(authpass);
  452 #       ifdef CHARSET_CONVERSION
  453     /* RFC 4616 */
  454     if (!IS_LOCAL_CHARSET("UTF-8")) {
  455         for (cp = utf8user; *cp && !contains_8bit; cp++) {
  456             if (!isascii(*cp)) {
  457                 contains_8bit = TRUE;
  458                 break;
  459             }
  460         }
  461         for (cp = utf8pass; *cp && !contains_8bit; cp++) {
  462             if (!isascii(*cp)) {
  463                 contains_8bit = TRUE;
  464                 break;
  465             }
  466         }
  467         if (contains_8bit) {
  468             for (i = 0; txt_mime_charsets[i] != NULL; i++) {
  469                 if (!strcasecmp("UTF-8", txt_mime_charsets[i])) {
  470                     c = i;
  471                     break;
  472                 }
  473             }
  474             if (c == i) { /* should never fail */
  475                 if (!buffer_to_network(utf8user, c)) {
  476                     free(utf8user);
  477                     utf8user = my_strdup(authuser);
  478                 } else {
  479                     if (!buffer_to_network(utf8pass, c)) {
  480                         free(utf8pass);
  481                         utf8pass = my_strdup(authpass);
  482                     }
  483                 }
  484             }
  485         }
  486     }
  487 #       endif /* CHARSET_CONVERSION */
  488 
  489 #       ifdef DEBUG
  490     if (debug & DEBUG_NNTP)
  491         debug_print_file("NNTP", "do_authinfo_sasl_plain(\"%s\", \"%s\")", BlankIfNull(authuser), BlankIfNull(authpass));
  492 #       endif /* DEBUG */
  493 
  494     if ((foo = sasl_auth_plain(utf8user, utf8pass)) == NULL) {
  495         free(utf8user);
  496         free(utf8pass);
  497         return ERR_AUTHBAD;
  498     }
  499 
  500     free(utf8user);
  501     free(utf8pass);
  502 
  503     snprintf(line, sizeof(line), "AUTHINFO SASL PLAIN %s", foo);
  504     FreeIfNeeded(foo);
  505 #       ifdef DEBUG
  506     if (debug & DEBUG_NNTP)
  507         debug_print_file("NNTP", "authorization %s", line);
  508 #       endif /* DEBUG */
  509     put_server(line);
  510     ret = get_only_respcode(line, sizeof(line));
  511     if (!batch_mode || verbose || ret != OK_AUTH)
  512         wait_message(2, (ret == OK_AUTH ? _(txt_authorization_ok) : _(txt_authorization_fail)), authuser);
  513     return ret;
  514 }
  515 
  516 
  517 static char *
  518 sasl_auth_plain(
  519     char *user,
  520     char *pass)
  521 {
  522     Gsasl *ctx = NULL;
  523     Gsasl_session *session;
  524     char *p = NULL;
  525     const char *mech = "PLAIN";
  526 
  527     if (gsasl_init(&ctx) != GSASL_OK) /* TODO: do this only once at startup */
  528         return p;
  529     if (gsasl_client_start(ctx, mech, &session) != GSASL_OK) {
  530         gsasl_done(ctx);
  531         return p;
  532     }
  533     gsasl_property_set(session, GSASL_AUTHID, user);
  534     gsasl_property_set(session, GSASL_PASSWORD, pass);
  535     if (gsasl_step64(session, NULL, &p) != GSASL_OK)
  536         FreeAndNull(p);
  537     gsasl_finish(session);
  538     gsasl_done(ctx);
  539     return p;
  540 }
  541 #   endif /* USE_SASL */
  542 
  543 #else
  544 static void no_authenticate(void);          /* proto-type */
  545 static void
  546 no_authenticate(                    /* ANSI C requires non-empty source file */
  547     void)
  548 {
  549 }
  550 #endif /* NNTP_ABLE */