tin  2.6.1
About: TIN is a threaded NNTP and spool based UseNet newsreader.
  Fossies Dox: tin-2.6.1.tar.xz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

inews.c
Go to the documentation of this file.
1/*
2 * Project : tin - a Usenet reader
3 * Module : inews.c
4 * Author : I. Lea
5 * Created : 1992-03-17
6 * Updated : 2021-02-23
7 * Notes : NNTP built in version of inews
8 *
9 * Copyright (c) 1991-2022 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#if 0
60# ifdef HAVE_NETDB_H
61# include <netdb.h>
62# endif /* HAVE_NETDB_H */
63
64# ifdef HAVE_SYS_SOCKET_H
65# include <sys/socket.h>
66# endif /* HAVE_SYS_SOCKET_H */
67# ifdef HAVE_NETINET_IN_H
68# include <netinet/in.h>
69# endif /* HAVE_NETINET_IN_H */
70#endif /* 0 */
71
72
73/*
74 * Submit an article using the NNTP POST command
75 *
76 * TODO: remove mailheaders (To, Cc, Bcc, ...)?
77 */
78#ifdef NNTP_INEWS
79static t_bool
80submit_inews(
81 char *name,
82 struct t_group *group,
83 char *a_message_id)
84{
85 FILE *fp;
86 char *line;
87 char *ptr, *ptr2;
88 char buf[HEADER_LEN];
89 char from_name[HEADER_LEN];
90 char message_id[HEADER_LEN];
91 char response[NNTP_STRLEN];
92 int auth_error = 0;
93 int respcode;
94 t_bool leave_loop;
95 t_bool id_in_article = FALSE;
97# ifndef FORGERY
98 char sender_hdr[HEADER_LEN];
99 int sender = 0;
100 t_bool ismail = FALSE;
101# endif /* !FORGERY */
102# ifdef USE_CANLOCK
103 t_bool can_lock_in_article = FALSE;
104# endif /* USE_CANLOCK */
105
106 if ((fp = fopen(name, "r")) == NULL) {
108 return ret_code;
109 }
110
111 from_name[0] = '\0';
112 message_id[0] = '\0';
113
114 while ((line = tin_fgets(fp, TRUE)) != NULL) {
115 if (line[0] == '\0') /* end of headers */
116 break;
117
118 if ((ptr = strchr(line, ':'))) {
119 if (ptr - line == 4 && !strncasecmp(line, "From", 4)) {
120 STRCPY(from_name, line);
121 }
122
123 if (ptr - line == 10 && !strncasecmp(line, "Message-ID", 10)) {
124 STRCPY(message_id, ptr + 2);
125 id_in_article = TRUE;
126 }
127# ifdef USE_CANLOCK
128 if (ptr - line == 11 && !strncasecmp(line, "Cancel-Lock", 11))
129 can_lock_in_article = TRUE;
130# endif /* USE_CANLOCK */
131 }
132 }
133
134 if ((from_name[0] == '\0') || (from_name[6] == '\0')) {
135 /* we could silently add a From: line here if we want to... */
136 error_message(2, _(txt_error_no_from));
137 fclose(fp);
138 return ret_code;
139 }
140
141# ifndef FORGERY
142 /*
143 * we should only skip the gnksa_check_from() test if we are going to
144 * post a forged cancel, but inews.c doesn't know anything about the
145 * message type, so we skip the test if FORGERY is set.
146 *
147 * TODO: check at least the local- and domainpart if post_8bit_header
148 * is set
149 *
150 * check for valid From: line
151 */
152 respcode = gnksa_check_from(from_name + 6);
153 if (!(group ? group->attribute->post_8bit_header : tinrc.post_8bit_header) && respcode > GNKSA_OK && respcode < GNKSA_MISSING_REALNAME) { /* error in address */
154 error_message(2, "inews158%s", _(txt_invalid_from), from_name + 6);
155 fclose(fp);
156 return ret_code;
157 }
158# endif /* !FORGERY */
159
160 do {
161 rewind(fp);
162
163# ifndef FORGERY
164 if (!disable_sender && (ptr = build_sender())) {
165# ifdef CHARSET_CONVERSION
166 const char *charset = group ? txt_mime_charsets[group->attribute->mm_network_charset] : txt_mime_charsets[tinrc.mm_network_charset];
167# else
168 const char *charset = tinrc.mm_charset;
169# endif /* CHARSET_CONVERSION */
170
171 sender = sender_needed(from_name + 6, ptr, charset);
172 switch (sender) {
173 case -2: /* can't build Sender: */
174 error_message(2, _(txt_invalid_sender), ptr);
175 fclose(fp);
176 return ret_code;
177 /* NOTREACHED */
178 break;
179
180 case -1: /* illegal From: (can't happen as check is done above already) */
181 error_message(2, _(txt_invalid_from), from_name + 6);
182 fclose(fp);
183 return ret_code;
184 /* NOTREACHED */
185 break;
186
187 case 1: /* insert Sender */
188 snprintf(sender_hdr, sizeof(sender_hdr), "Sender: %s", ptr);
189# ifdef CHARSET_CONVERSION
190 buffer_to_network(sender_hdr, group ? group->attribute->mm_network_charset : tinrc.mm_network_charset);
191# endif /* CHARSET_CONVERSION */
192 if (!(group ? group->attribute->post_8bit_header : tinrc.post_8bit_header)) {
193 char *p = rfc1522_encode(sender_hdr, charset, ismail);
194
195 STRCPY(sender_hdr, p);
196 free(p);
197 }
198 break;
199
200 case 0: /* no sender needed */
201 default:
202 break;
203 }
204 }
205# endif /* !FORGERY */
206
207 /*
208 * Send POST command to NNTP server
209 * Receive CONT_POST or ERROR response code from NNTP server
210 */
211 if (nntp_command("POST", CONT_POST, response, sizeof(response)) == NULL) {
212 error_message(2, "%s", response);
213 fclose(fp);
214 return ret_code;
215 }
216
217 /*
218 * check article if it contains a Message-ID header
219 * if not scan response line if it contains a Message-ID
220 * if it's present: use it.
221 */
222 if (message_id[0] == '\0') {
223 /* simple syntax check - locate last '<' */
224 if ((ptr = strrchr(response, '<')) != NULL) {
225 /* search next '>' */
226 if ((ptr2 = strchr(ptr, '>')) != NULL) {
227 /* terminate string */
228 *++ptr2 = '\0';
229 /* check for @ and no whitespaces */
230 if ((strchr(ptr, '@') != NULL) && (strpbrk(ptr, " \t") == NULL))
231 my_strncpy(message_id, ptr, sizeof(message_id) - 1); /* copy Message-ID */
232 }
233 }
234 }
235
236# ifndef FORGERY
237 /*
238 * Send Path: (and Sender: if needed) headers
239 */
240 snprintf(buf, sizeof(buf), "Path: %s", PATHMASTER);
241 u_put_server(buf);
242 u_put_server("\r\n");
243
244 if (sender == 1) {
245 u_put_server(sender_hdr);
246 u_put_server("\r\n");
247 }
248# endif /* !FORGERY */
249
250 /*
251 * check if Message-ID comes from the server
252 */
253 if (*message_id) {
254 if (!id_in_article) {
255 snprintf(buf, sizeof(buf), "Message-ID: %s", message_id);
256 u_put_server(buf);
257 u_put_server("\r\n");
258 }
259# ifdef USE_CANLOCK
260 if (tinrc.cancel_lock_algo && !can_lock_in_article) {
261 char lock[1024];
262 char *lptr;
263
264 lock[0] = '\0';
265 if ((lptr = build_canlock(message_id, get_secret())) != NULL) {
266 STRCPY(lock, lptr);
267 free(lptr);
268 snprintf(buf, sizeof(buf), "Cancel-Lock: %s", lock);
269 u_put_server(buf);
270 u_put_server("\r\n");
271 }
272 }
273# endif /* USE_CANLOCK */
274 }
275
276 /*
277 * Send article 1 line at a time ending with "."
278 */
279 while ((line = tin_fgets(fp, FALSE)) != NULL) {
280 /*
281 * If line starts with a '.' add another '.' to stop truncation
282 */
283 if (line[0] == '.')
284 u_put_server(".");
285
286# ifdef USE_CANLOCK
287 /* skip any bogus Cancel-Locks */
288 if (!strlen(line))
289 can_lock_in_article = FALSE; /* don't touch the body */
290
291 if (can_lock_in_article && !id_in_article) {
292 ptr = strchr(line, ':');
293 if (ptr - line != 11 || strncasecmp(line, "Cancel-Lock", 11)) {
294 u_put_server(line);
295 u_put_server("\r\n");
296 }
297 /* TODO: silently add a new Cancel-Lock if message_id is now known? */
298 } else
299# endif /* USE_CANLOCK */
300 {
301 u_put_server(line);
302 u_put_server("\r\n");
303 }
304 }
305
306 u_put_server(".\r\n");
307 put_server(""); /* flush */
308
309 /*
310 * Receive OK_POSTED or ERROR response code from NNTP server
311 * Don't use get_respcode at this point, because then we would not
312 * recognize if posting has failed due to missing authentication in
313 * which case the complete posting has to be resent.
314 */
315 respcode = get_only_respcode(response, sizeof(response));
316 leave_loop = TRUE;
317
318 /*
319 * Don't leave this loop if we only tried once to post and an
320 * authentication request was received. Leave loop on any other
321 * response or any further authentication requests.
322 *
323 * TODO: add 483 (RFC 3977) support
324 */
325 if (((respcode == ERR_NOAUTH) || (respcode == NEED_AUTHINFO)) && (auth_error++ < 1) && (authenticate(nntp_server, userid, FALSE)))
326 leave_loop = FALSE;
327 } while (!leave_loop);
328
329 fclose(fp);
330
331 /*
332 * FIXME: The displayed message may be wrong if authentication has
333 * failed. (The message will be sth. like "Authentication required"
334 * which is not really wrong but misleading. The problem is that
335 * authenticate() does only return a bool value and not the server
336 * response.)
337 */
338 if (respcode != OK_POSTED) {
339 /* TODO: -> lang.c */
340 error_message(2, "Posting failed (%s)", str_trim(response));
341 return ret_code;
342 }
343
344 /*
345 * scan line if it contains a Message-ID
346 */
347 /* simple syntax check - locate last '<' */
348 if ((ptr = strrchr(response, '<')) != NULL) {
349 /* search next '>' */
350 if ((ptr2 = strchr(response, '>')) != NULL) {
351 /* terminate string */
352 *++ptr2 = '\0';
353 /* check for @ and no whitespaces */
354 if ((strchr(ptr, '@') != NULL) && (strpbrk(ptr, " \t") == NULL))
355 strcpy(a_message_id, ptr); /* copy Message-ID */
356 }
357 }
358
359# if 0
360 if (*message_id && *a_message_id) { /* check if returned ID matches purposed ID */
361 if (strcmp(message_id, a_message_id)) {
362 ; /* shouldn't happen - warn user? */
363 }
364 }
365# endif /* 0 */
366
367 if (*message_id && (id_in_article || !*a_message_id))
368 strcpy(a_message_id, message_id);
369
370 ret_code = TRUE;
371
372 return ret_code;
373}
374#endif /* NNTP_INEWS */
375
376
377/*
378 * Call submit_inews() if using built in inews, else invoke external inews
379 * prog
380 */
381t_bool
383 char *name,
384 struct t_group *group,
385 char *a_message_id)
386{
387 char buf[PATH_LEN];
388 char *cp = buf;
389 char *fcc;
391 t_bool ismail = FALSE;
392
393 a_message_id[0] = '\0';
394
395 fcc = checknadd_headers(name, group);
396 FreeIfNeeded(fcc); /* we don't use it at the moment */
397
399
400#ifdef NNTP_INEWS
402 ret_code = submit_inews(name, group, a_message_id);
403 else
404#endif /* NNTP_INEWS */
405 {
406 /* use tinrc.inews_prog or 'inewsdir/inews -h' 'inews -h' */
409 else {
410 if (*inewsdir)
411 joinpath(buf, sizeof(buf), inewsdir, "inews -h");
412 else
413 strcpy(buf, "inews -h");
414 }
415 cp += strlen(cp);
416 sh_format(cp, sizeof(buf) - (size_t) (cp - buf), " < %s", name);
417
419
420#ifdef NNTP_INEWS
422 if (prompt_yn(_(txt_post_via_builtin_inews), TRUE)) {
423 ret_code = submit_inews(name, group, a_message_id);
424 if (ret_code) {
425 if (prompt_yn(_(txt_post_via_builtin_inews_only), TRUE) == 1)
427 }
428 }
429 }
430#endif /* NNTP_INEWS */
431 }
432 return ret_code;
433}
434
435
436/*
437 * returnvalues: 1 = Sender needed
438 * 0 = no Sender needed
439 * -1 = error (no '.' and/or '@' in From) [unused]
440 * -2 = error (no '.' and/or '@' in Sender)
441 */
442#if defined(NNTP_INEWS) && !defined(FORGERY)
443static int
444sender_needed(
445 char *from,
446 char *sender,
447 const char *charset)
448{
449 char *from_at_pos;
450 char *sender_at_pos;
451 char *sender_dot_pos;
452 char *p;
453 char from_addr[HEADER_LEN];
454 char sender_addr[HEADER_LEN];
455 char sender_line[HEADER_LEN];
456 char sender_name[HEADER_LEN];
457 int r;
458
459# ifdef DEBUG
460 if (debug & DEBUG_MISC) {
461 wait_message(3, "sender_needed From:=[%s]", from);
462 wait_message(3, "sender_needed Sender:=[%s]", sender);
463 }
464# endif /* DEBUG */
465
466 /* extract address */
467 strip_name(from, from_addr);
468
469 snprintf(sender_line, sizeof(sender_line), "Sender: %s", sender);
470
471 p = rfc1522_encode(sender_line, charset, FALSE);
472 r = gnksa_do_check_from(p + 8, sender_addr, sender_name);
473 if (r > GNKSA_OK && r < GNKSA_MISSING_REALNAME) {
474 free(p);
475 return -2;
476 }
477 free(p);
478
479 from_at_pos = strchr(from_addr, '@');
480 if ((sender_at_pos = strchr(sender_addr, '@')))
481 sender_dot_pos = strchr(sender_at_pos, '.');
482 else /* this case is caught by the gnksa_do_check_from() code above; anyway ... */
483 return -2;
484
485 if (from_at_pos == NULL || sender_dot_pos == NULL) /* as we build From and check Sender above this shouldn't happen at all */
486 return -2;
487
488 if (strncasecmp(from_addr, sender_addr, (size_t) (from_at_pos - from_addr)))
489 return 1; /* login differs */
490
491 if (strcasecmp(from_at_pos, sender_at_pos) && (strcasecmp(from_at_pos + 1, sender_dot_pos + 1)))
492 return 1; /* domainname differs */
493
494 return 0;
495}
496#endif /* NNTP_INEWS && !FORGERY */
unsigned t_bool
Definition: bool.h:77
#define TRUE
Definition: bool.h:74
#define FALSE
Definition: bool.h:70
#define DEBUG_MISC
Definition: debug.h:54
t_bool disable_sender
Definition: init.c:133
constext * txt_mime_encodings[]
Definition: lang.c:1456
char inewsdir[PATH_LEN]
Definition: init.c:82
char * nntp_server
Definition: nntplib.c:28
t_bool read_saved_news
Definition: init.c:152
#define GNKSA_OK
Definition: extern.h:1590
constext txt_cannot_open[]
Definition: lang.c:128
char userid[PATH_LEN]
Definition: init.c:107
struct t_config tinrc
Definition: init.c:192
unsigned short debug
Definition: debug.c:51
t_bool read_news_via_nntp
Definition: init.c:151
constext txt_invalid_from[]
Definition: lang.c:583
#define GNKSA_MISSING_REALNAME
Definition: extern.h:1622
static int ret_code
Definition: group.c:86
t_bool submit_news_file(char *name, struct t_group *group, char *a_message_id)
Definition: inews.c:382
static char buf[16]
Definition: langinfo.c:50
#define NNTP_STRLEN
Definition: nntplib.h:155
#define CONT_POST
Definition: nntplib.h:118
#define ERR_NOAUTH
Definition: nntplib.h:139
#define OK_POSTED
Definition: nntplib.h:110
#define NEED_AUTHINFO
Definition: nntplib.h:119
char * strpbrk(const char *str1, const char *str2)
Definition: string.c:312
void rfc15211522_encode(const char *filename, constext *mime_encoding, struct t_group *group, t_bool allow_8bit_header, t_bool ismail)
Definition: rfc2047.c:1045
char * str_trim(char *string)
Definition: string.c:539
void error_message(unsigned int sdelay, const char *fmt,...)
Definition: screen.c:224
void perror_message(const char *fmt,...)
Definition: screen.c:260
char * rfc1522_encode(char *s, const char *charset, t_bool ismail)
Definition: rfc2047.c:855
int sh_format(char *dst, size_t len, const char *fmt,...)
Definition: string.c:677
char * checknadd_headers(const char *infile, struct t_group *group)
Definition: post.c:4881
int gnksa_check_from(char *from)
Definition: misc.c:3616
t_bool invoke_cmd(const char *nam)
Definition: misc.c:813
void joinpath(char *result, size_t result_size, const char *dir, const char *file)
Definition: joinpath.c:50
int gnksa_do_check_from(const char *from, char *address, char *realname)
Definition: misc.c:3529
char * tin_fgets(FILE *fp, t_bool header)
Definition: read.c:317
void wait_message(unsigned int sdelay, const char *fmt,...)
Definition: screen.c:133
void my_strncpy(char *p, const char *q, size_t n)
Definition: string.c:196
int strncasecmp(const char *p, const char *q, size_t n)
Definition: string.c:491
int strcasecmp(const char *p, const char *q)
Definition: string.c:475
void strip_name(const char *from, char *address)
Definition: misc.c:2390
int prompt_yn(const char *prompt, t_bool default_answer)
Definition: prompt.c:165
char * build_sender(void)
Definition: header.c:333
const char * name
Definition: signal.c:117
unsigned post_mime_encoding
Definition: tin.h:1667
unsigned post_8bit_header
Definition: tin.h:1666
t_bool post_8bit_header
Definition: tinrc.h:234
char inews_prog[PATH_LEN]
Definition: tinrc.h:262
char mm_charset[LEN]
Definition: tinrc.h:111
int post_mime_encoding
Definition: tinrc.h:153
Definition: tin.h:1816
struct t_attribute * attribute
Definition: tin.h:1834
#define STRCPY(dst, src)
Definition: tin.h:820
#define PATHMASTER
Definition: tin.h:601
#define FreeIfNeeded(p)
Definition: tin.h:2252
#define _(Text)
Definition: tin.h:94
#define PATH_LEN
Definition: tin.h:843
#define INTERNAL_CMD
Definition: tin.h:574
#define snprintf
Definition: tin.h:2464
#define HEADER_LEN
Definition: tin.h:863
static unsigned char * get_secret(size_t *sec_size, size_t *buf_size)
Read secret data from stdin.
Definition: canlock.c:148