"Fossies" - the Fresh Open Source Software Archive

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


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

    1 /*
    2  *  Project   : tin - a Usenet reader
    3  *  Module    : inews.c
    4  *  Author    : I. Lea
    5  *  Created   : 1992-03-17
    6  *  Updated   : 2022-10-25
    7  *  Notes     : NNTP built in version of inews
    8  *
    9  * Copyright (c) 1991-2023 Iain Lea <iain@bricbrac.de>
   10  * All rights reserved.
   11  *
   12  * Redistribution and use in source and binary forms, with or without
   13  * modification, are permitted provided that the following conditions
   14  * are met:
   15  *
   16  * 1. Redistributions of source code must retain the above copyright notice,
   17  *    this list of conditions and the following disclaimer.
   18  *
   19  * 2. Redistributions in binary form must reproduce the above copyright
   20  *    notice, this list of conditions and the following disclaimer in the
   21  *    documentation and/or other materials provided with the distribution.
   22  *
   23  * 3. Neither the name of the copyright holder nor the names of its
   24  *    contributors may be used to endorse or promote products derived from
   25  *    this software without specific prior written permission.
   26  *
   27  * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   28  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   29  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   30  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
   31  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   32  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   33  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   34  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
   35  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   36  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   37  * POSSIBILITY OF SUCH DAMAGE.
   38  */
   39 
   40 
   41 #ifndef TIN_H
   42 #   include "tin.h"
   43 #endif /* !TIN_H */
   44 #ifndef TNNTP_H
   45 #   include "tnntp.h"
   46 #endif /* !TNNTP_H */
   47 
   48 
   49 /*
   50  * local prototypes
   51  */
   52 #ifdef NNTP_INEWS
   53     static t_bool submit_inews(char *name, struct t_group *group, char *a_message_id);
   54 #endif /* NNTP_INEWS */
   55 #if defined(NNTP_INEWS) && !defined(FORGERY)
   56     static int sender_needed(char *from, char *sender, const char *charset);
   57 #endif /* NNTP_INEWS && !FORGERY */
   58 
   59 
   60 /*
   61  * Submit an article using the NNTP POST command
   62  *
   63  * TODO: remove mailheaders (To, Cc, Bcc, ...)?
   64  */
   65 #ifdef NNTP_INEWS
   66 static t_bool
   67 submit_inews(
   68     char *name,
   69     struct t_group *group,
   70     char *a_message_id)
   71 {
   72     FILE *fp;
   73     char *line;
   74     char *ptr, *ptr2;
   75     char buf[HEADER_LEN];
   76     char from_name[HEADER_LEN];
   77     char message_id[HEADER_LEN];
   78     char response[NNTP_STRLEN];
   79     int auth_error = 0;
   80     int respcode;
   81     t_bool leave_loop;
   82     t_bool id_in_article = FALSE;
   83     t_bool ret_code = FALSE;
   84 #   ifndef FORGERY
   85     char sender_hdr[HEADER_LEN];
   86     int sender = 0;
   87     t_bool ismail = FALSE;
   88 #   endif /* !FORGERY */
   89 #   ifdef USE_CANLOCK
   90     t_bool can_lock_in_article = FALSE;
   91 #   endif /* USE_CANLOCK */
   92 
   93     if ((fp = fopen(name, "r")) == NULL) {
   94         perror_message(_(txt_cannot_open), name);
   95         return ret_code;
   96     }
   97 
   98     from_name[0] = '\0';
   99     message_id[0] = '\0';
  100 
  101     while ((line = tin_fgets(fp, TRUE)) != NULL) {
  102         if (line[0] == '\0') /* end of headers */
  103             break;
  104 
  105         if ((ptr = strchr(line, ':'))) {
  106             if (ptr - line == 4 && !strncasecmp(line, "From", 4)) {
  107                 STRCPY(from_name, line);
  108             }
  109 
  110             if (ptr - line == 10 && !strncasecmp(line, "Message-ID", 10)) {
  111                 STRCPY(message_id, ptr + 2);
  112                 id_in_article = TRUE;
  113             }
  114 #   ifdef USE_CANLOCK
  115             if (ptr - line == 11 && !strncasecmp(line, "Cancel-Lock", 11))
  116                 can_lock_in_article = TRUE;
  117 #   endif /* USE_CANLOCK */
  118         }
  119     }
  120 
  121     if ((from_name[0] == '\0') || (from_name[6] == '\0')) {
  122         /* we could silently add a From: line here if we want to... */
  123         error_message(2, _(txt_error_no_from));
  124         fclose(fp);
  125         return ret_code;
  126     }
  127 
  128 #   ifndef FORGERY
  129     /*
  130      * we should only skip the gnksa_check_from() test if we are going to
  131      * post a forged cancel, but inews.c doesn't know anything about the
  132      * message type, so we skip the test if FORGERY is set.
  133      *
  134      * TODO: check at least the local- and domainpart if post_8bit_header
  135      *       is set
  136      *
  137      * check for valid From: line
  138      */
  139     respcode = gnksa_check_from(from_name + 6);
  140     if (!(group ? group->attribute->post_8bit_header : tinrc.post_8bit_header) && respcode > GNKSA_OK && respcode < GNKSA_MISSING_REALNAME) { /* error in address */
  141         error_message(2, "inews158%s", _(txt_invalid_from), from_name + 6);
  142         fclose(fp);
  143         return ret_code;
  144     }
  145 #   endif /* !FORGERY */
  146 
  147     do {
  148         rewind(fp);
  149 
  150 #   ifndef FORGERY
  151         if (!disable_sender && (ptr = build_sender())) {
  152 #       ifdef CHARSET_CONVERSION
  153             const char *charset = group ? txt_mime_charsets[group->attribute->mm_network_charset] : txt_mime_charsets[tinrc.mm_network_charset];
  154 #       else
  155             const char *charset = tinrc.mm_charset;
  156 #       endif /* CHARSET_CONVERSION */
  157 
  158             sender = sender_needed(from_name + 6, ptr, charset);
  159             switch (sender) {
  160                 case -2: /* can't build Sender: */
  161                     error_message(2, _(txt_invalid_sender), ptr);
  162                     fclose(fp);
  163                     return ret_code;
  164                     /* NOTREACHED */
  165                     break;
  166 
  167                 case -1: /* illegal From: (can't happen as check is done above already) */
  168                     error_message(2, _(txt_invalid_from), from_name + 6);
  169                     fclose(fp);
  170                     return ret_code;
  171                     /* NOTREACHED */
  172                     break;
  173 
  174                 case 1: /* insert Sender */
  175                     snprintf(sender_hdr, sizeof(sender_hdr), "Sender: %s", ptr);
  176 #       ifdef CHARSET_CONVERSION
  177                     buffer_to_network(sender_hdr, group ? group->attribute->mm_network_charset : tinrc.mm_network_charset);
  178 #       endif /* CHARSET_CONVERSION */
  179                     if (!(group ? group->attribute->post_8bit_header : tinrc.post_8bit_header)) {
  180                         char *p = rfc1522_encode(sender_hdr, charset, ismail);
  181 
  182                         STRCPY(sender_hdr, p);
  183                         free(p);
  184                     }
  185                     break;
  186 
  187                 case 0: /* no sender needed */
  188                 default:
  189                     break;
  190             }
  191         }
  192 #   endif /* !FORGERY */
  193 
  194         /*
  195          * Send POST command to NNTP server
  196          * Receive CONT_POST or ERROR response code from NNTP server
  197          */
  198         if (nntp_command("POST", CONT_POST, response, sizeof(response)) == NULL) {
  199             error_message(2, "%s", response);
  200             fclose(fp);
  201             return ret_code;
  202         }
  203 
  204         /*
  205          * check article if it contains a Message-ID header
  206          * if not scan response line if it contains a Message-ID
  207          * if it's present: use it.
  208          */
  209         if (message_id[0] == '\0') {
  210             /* simple syntax check - locate last '<' */
  211             if ((ptr = strrchr(response, '<')) != NULL) {
  212                 /* search next '>' */
  213                 if ((ptr2 = strchr(ptr, '>')) != NULL) {
  214                     /* terminate string */
  215                     *++ptr2 = '\0';
  216                     /* check for @ and no whitespaces */
  217                     if ((strchr(ptr, '@') != NULL) && (strpbrk(ptr, " \t") == NULL))
  218                         my_strncpy(message_id, ptr, sizeof(message_id) - 1);    /* copy Message-ID */
  219                 }
  220             }
  221         }
  222 
  223 #   ifndef FORGERY
  224         /*
  225          * Send Path: (and Sender: if needed) headers
  226          */
  227         snprintf(buf, sizeof(buf), "Path: %s", PATHMASTER);
  228         u_put_server(buf);
  229         u_put_server("\r\n");
  230 
  231         if (sender == 1) {
  232             u_put_server(sender_hdr);
  233             u_put_server("\r\n");
  234         }
  235 #   endif /* !FORGERY */
  236 
  237         /*
  238          * check if Message-ID comes from the server
  239          */
  240         if (*message_id) {
  241             if (!id_in_article) {
  242                 snprintf(buf, sizeof(buf), "Message-ID: %s", message_id);
  243                 u_put_server(buf);
  244                 u_put_server("\r\n");
  245             }
  246 #   ifdef USE_CANLOCK
  247             if (tinrc.cancel_lock_algo && !can_lock_in_article) {
  248                 char lock[1024];
  249                 char *lptr;
  250 
  251                 lock[0] = '\0';
  252                 if ((lptr = build_canlock(message_id, get_secret())) != NULL) {
  253                     STRCPY(lock, lptr);
  254                     free(lptr);
  255                     snprintf(buf, sizeof(buf), "Cancel-Lock: %s", lock);
  256                     u_put_server(buf);
  257                     u_put_server("\r\n");
  258                 }
  259             }
  260 #   endif /* USE_CANLOCK */
  261         }
  262 
  263         /*
  264          * Send article 1 line at a time ending with "."
  265          */
  266         while ((line = tin_fgets(fp, FALSE)) != NULL) {
  267             /*
  268              * If line starts with a '.' add another '.' to stop truncation
  269              */
  270             if (line[0] == '.')
  271                 u_put_server(".");
  272 
  273 #   ifdef USE_CANLOCK
  274             /* skip any bogus Cancel-Locks */
  275             if (!strlen(line))
  276                 can_lock_in_article = FALSE;    /* don't touch the body */
  277 
  278             if (can_lock_in_article && !id_in_article) {
  279                 ptr = strchr(line, ':');
  280                 if (ptr - line != 11 || strncasecmp(line, "Cancel-Lock", 11)) {
  281                     u_put_server(line);
  282                     u_put_server("\r\n");
  283                 }
  284                 /* TODO: silently add a new Cancel-Lock if message_id is now known? */
  285             } else
  286 #   endif /* USE_CANLOCK */
  287             {
  288                 u_put_server(line);
  289                 u_put_server("\r\n");
  290             }
  291         }
  292 
  293         u_put_server(".\r\n");
  294         put_server(""); /* flush */
  295 
  296         /*
  297          * Receive OK_POSTED or ERROR response code from NNTP server
  298          * Don't use get_respcode at this point, because then we would not
  299          * recognize if posting has failed due to missing authentication in
  300          * which case the complete posting has to be resent.
  301          */
  302         respcode = get_only_respcode(response, sizeof(response));
  303         leave_loop = TRUE;
  304 
  305         /*
  306          * Don't leave this loop if we only tried once to post and an
  307          * authentication request was received. Leave loop on any other
  308          * response or any further authentication requests.
  309          *
  310          * TODO: add 483 (RFC 3977) support
  311          */
  312         if (((respcode == ERR_NOAUTH) || (respcode == NEED_AUTHINFO)) && (auth_error++ < 1) && (authenticate(nntp_server, userid, FALSE)))
  313             leave_loop = FALSE;
  314     } while (!leave_loop);
  315 
  316     fclose(fp);
  317 
  318     /*
  319      * FIXME: The displayed message may be wrong if authentication has
  320      * failed. (The message will be sth. like "Authentication required"
  321      * which is not really wrong but misleading. The problem is that
  322      * authenticate() does only return a bool value and not the server
  323      * response.)
  324      */
  325     if (respcode != OK_POSTED) {
  326         /* TODO: -> lang.c */
  327         error_message(2, "Posting failed (%s)", str_trim(response));
  328         return ret_code;
  329     }
  330 
  331     /*
  332      * scan line if it contains a Message-ID
  333      */
  334     /* simple syntax check - locate last '<' */
  335     if ((ptr = strrchr(response, '<')) != NULL) {
  336         /* search next '>' */
  337         if ((ptr2 = strchr(response, '>')) != NULL) {
  338             /* terminate string */
  339             *++ptr2 = '\0';
  340             /* check for @ and no whitespaces */
  341             if ((strchr(ptr, '@') != NULL) && (strpbrk(ptr, " \t") == NULL))
  342                 strcpy(a_message_id, ptr); /* copy Message-ID */
  343         }
  344     }
  345 
  346 #   if 0 /* DEBUG */
  347     if ((debug & DEBUG_NNTP) && verbose > 1) {
  348         if (*message_id && *a_message_id) { /* check if purposed ID matches returned ID */
  349             if (strcmp(message_id, a_message_id)) {
  350                 if (id_in_article)
  351                     debug_print_file("NNTP", "Returned Message-ID: %s doesn't match given Message-ID: %s", a_message_id, message_id);
  352                 else
  353                     debug_print_file("NNTP", "Returned Message-ID: %s doesn't match purposed Message-ID: %s", a_message_id, message_id);
  354             }
  355         }
  356     }
  357 #   endif /* 0 */
  358 
  359     if (*message_id && (id_in_article || !*a_message_id))
  360         strcpy(a_message_id, message_id);
  361 
  362     ret_code = TRUE;
  363 
  364     return ret_code;
  365 }
  366 #endif /* NNTP_INEWS */
  367 
  368 
  369 /*
  370  * Call submit_inews() if using built in inews, else invoke external inews
  371  * prog
  372  */
  373 t_bool
  374 submit_news_file(
  375     char *name,
  376     struct t_group *group,
  377     char *a_message_id)
  378 {
  379     char buf[PATH_LEN];
  380     char *cp = buf;
  381     char *fcc;
  382     t_bool ret_code;
  383     t_bool ismail = FALSE;
  384 
  385     a_message_id[0] = '\0';
  386 
  387     fcc = checknadd_headers(name, group);
  388     FreeIfNeeded(fcc); /* we don't use it at the moment */
  389 
  390     rfc15211522_encode(name, txt_mime_encodings[(group ? group->attribute->post_mime_encoding : tinrc.post_mime_encoding)], group, (group ? group->attribute->post_8bit_header : tinrc.post_8bit_header), ismail);
  391 
  392 #ifdef NNTP_INEWS
  393     if (read_news_via_nntp && !read_saved_news && !strcasecmp(tinrc.inews_prog, INTERNAL_CMD))
  394         ret_code = submit_inews(name, group, a_message_id);
  395     else
  396 #endif /* NNTP_INEWS */
  397         {
  398             /* use tinrc.inews_prog or 'inewsdir/inews -h' 'inews -h' */
  399             if (strcasecmp(tinrc.inews_prog, INTERNAL_CMD))
  400                 STRCPY(buf, tinrc.inews_prog);
  401             else {
  402                 if (*inewsdir)
  403                     joinpath(buf, sizeof(buf), inewsdir, "inews -h");
  404                 else
  405                     strcpy(buf, "inews -h");
  406             }
  407             cp += strlen(cp);
  408             sh_format(cp, sizeof(buf) - (size_t) (cp - buf), " < %s", name);
  409 
  410             ret_code = invoke_cmd(buf);
  411 
  412 #ifdef NNTP_INEWS
  413             if (!ret_code && read_news_via_nntp && !read_saved_news && strcasecmp(tinrc.inews_prog, INTERNAL_CMD)) {
  414                 if (prompt_yn(_(txt_post_via_builtin_inews), TRUE)) {
  415                     ret_code = submit_inews(name, group, a_message_id);
  416                     if (ret_code) {
  417                         if (prompt_yn(_(txt_post_via_builtin_inews_only), TRUE) == 1)
  418                             strcpy(tinrc.inews_prog, INTERNAL_CMD);
  419                     }
  420                 }
  421             }
  422 #endif /* NNTP_INEWS */
  423         }
  424     return ret_code;
  425 }
  426 
  427 
  428 /*
  429  * returnvalues:  1 = Sender needed
  430  *                0 = no Sender needed
  431  *               -1 = error (no '.' and/or '@' in From) [unused]
  432  *               -2 = error (no '.' and/or '@' in Sender)
  433  */
  434 #if defined(NNTP_INEWS) && !defined(FORGERY)
  435 static int
  436 sender_needed(
  437     char *from,
  438     char *sender,
  439     const char *charset)
  440 {
  441     char *from_at_pos;
  442     char *sender_at_pos;
  443     char *sender_dot_pos;
  444     char *p;
  445     char from_addr[HEADER_LEN];
  446     char sender_addr[HEADER_LEN];
  447     char sender_line[HEADER_LEN];
  448     char sender_name[HEADER_LEN];
  449     int r;
  450 
  451 #   ifdef DEBUG
  452     if (debug & DEBUG_MISC) {
  453         wait_message(3, "sender_needed From:=[%s]", from);
  454         wait_message(3, "sender_needed Sender:=[%s]", sender);
  455     }
  456 #   endif /* DEBUG */
  457 
  458     /* extract address */
  459     strip_name(from, from_addr);
  460 
  461     snprintf(sender_line, sizeof(sender_line), "Sender: %s", sender);
  462 
  463     p = rfc1522_encode(sender_line, charset, FALSE);
  464     r = gnksa_do_check_from(p + 8, sender_addr, sender_name);
  465     if (r > GNKSA_OK && r < GNKSA_MISSING_REALNAME) {
  466         free(p);
  467         return -2;
  468     }
  469     free(p);
  470 
  471     from_at_pos = strchr(from_addr, '@');
  472     if ((sender_at_pos = strchr(sender_addr, '@')))
  473         sender_dot_pos = strchr(sender_at_pos, '.');
  474     else /* this case is caught by the gnksa_do_check_from() code above; anyway ... */
  475         return -2;
  476 
  477     if (from_at_pos == NULL || sender_dot_pos == NULL) /* as we build From and check Sender above this shouldn't happen at all */
  478         return -2;
  479 
  480     if (strncasecmp(from_addr, sender_addr, (size_t) (from_at_pos - from_addr)))
  481         return 1; /* login differs */
  482 
  483     if (strcasecmp(from_at_pos, sender_at_pos) && (strcasecmp(from_at_pos + 1, sender_dot_pos + 1)))
  484         return 1; /* domainname differs */
  485 
  486     return 0;
  487 }
  488 #endif /* NNTP_INEWS && !FORGERY */