1 /*
2      ATP QWK MAIL READER FOR READING AND REPLYING TO QWK MAIL PACKETS.
3      Copyright (C) 1992, 1993, 1997  Thomas McWilliams
4      Copyright (C) 1990  Rene Cougnenc
5 
6      This program is free software; you can redistribute it and/or modify
7      it under the terms of the GNU General Public License as published by
8      the Free Software Foundation; either version 2, or (at your option)
9      any later version.
10 
11      This program is distributed in the hope that it will be useful,
12      but WITHOUT ANY WARRANTY; without even the implied warranty of
13      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14      GNU General Public License for more details.
15 
16      You should have received a copy of the GNU General Public License
17      along with this program; if not, write to the Free Software
18      Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 */
20 
21 /*
22 reply.c
23 */
24 
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <fcntl.h>
31 
32 #include "reader.h"
33 #include "readlib.h"
34 #include "ansi.h"
35 #include "makemail.h"
36 #include "qlib.h"
37 
38 /************************* begin Reply() routines *********************/
39 
40 /* size for reply prompt buffer */
41 #define RPMSIZE 300
42 
43 #if defined(HAVE_LIBTERMCAP)
44 /*
45  * do_tc_prompt, called by reply_prompt() on termcap based systems.
46  */
47 static void
do_tc_prompt(char * rpmbuf,const int subject_length,const char * p)48 do_tc_prompt(char *rpmbuf, const int subject_length, const char *p)
49 {                                 /*@-strictops */
50     if (*cur_right != NUL_CHAR) { /*@=strictops */
51 	char *t;
52 	int k, i, buf_leng = 0;
53 	strcat(rpmbuf, "[ ");
54 	t = tputs_ptr(cur_right);
55 	for (i = 0; i < subject_length; i++)
56 	    strcat(rpmbuf, t);
57 	strcat(rpmbuf, " ]");
58 	buf_leng = strlen(rpmbuf);
59 	for (i = 0; i < RPMSIZE; i++)
60 	    rpmbuf[buf_leng + i] = (char) SPC_CHAR;
61 	rpmbuf[buf_leng] = NUL_CHAR;
62 	strcat(rpmbuf, "\r");
63 	k = strlen(p) + 2;
64 	for (i = 0; i < k; i++)
65 	    strcat(rpmbuf, t);
66     }
67 }
68 #endif
69 
70 static const char *prm_ansi[] =
71 {"error", PRMTTO, PRMTFR, PRMTSB, PRMTSL, PRMTSC, NULL};
72 
73 enum rep_prompt_type {
74     REPERR = 0, REPTO, REPFR, REPSB, REPSBL, REPSEC
75 };
76 
77 /*
78  * reply_prompt, create prompts for user when entering a message.
79  *
80  *  modes for reply prompts:  to, from, subject, long_subject, security.
81  */
82 static char *
reply_prompt(enum rep_prompt_type rpm)83 reply_prompt(enum rep_prompt_type rpm)
84 {
85     static char rpmbuf[RPMSIZE];
86     const char *p;
87     rpmbuf[0] = NUL_CHAR;
88     strcat(rpmbuf, "\r");
89     if (ansi) {
90 	strcat(rpmbuf, prm_ansi[(unsigned)rpm]);
91     } else {
92 #if defined(HAVE_LIBTERMCAP)
93 	int subject_length = SHORT_SUBJ_LEN;
94 #endif
95 	switch (rpm) {
96 	case REPTO:
97 	    p = PRTO;
98 	    break;
99 	case REPFR:
100 	    p = PFROM;
101 	    break;
102 	case REPSB:
103 	    p = PSUBJ;
104 	    break;
105 	case REPSBL:
106 	    p = PSUBJ;
107 #if defined(HAVE_LIBTERMCAP)
108 	    subject_length = LSUBJ_BODY_LEN;
109 #endif
110 	    break;
111 	case REPSEC:
112 	    p = PSECR;
113 	    break;
114 	default:
115 	    /* switch() case error */
116 	    error_switch_case((int)rpm, __FILE__, __LINE__);
117 	    p = " ERROR in reply_prompt() ";
118 	}
119 	strcat(rpmbuf, p);
120 #if defined(HAVE_LIBTERMCAP)
121 	do_tc_prompt(rpmbuf, subject_length, p);
122 #endif
123     }
124     return rpmbuf;
125 }
126 
127 
128 /*
129  * ask_speller, asks user if reply needs spelling check.
130  */
131 static atp_ERROR_T
ask_spell(const reply_type_t mode,const char * fname)132 ask_spell(const reply_type_t mode, const char *fname)
133 {
134     atp_ERROR_T ret_code = ATP_OK;
135     char CONSPTR speller = get_atprc_str(spellr);
136     char *intmp;                                     /*@-strictops */
137 
138     if (mode != XPOST && speller[0] != NUL_CHAR ) {  /*@=strictops */
139 	do {
140 	    intmp = readline("Check spelling [Y/n] ?", do_scroll);
141 	} while (intmp == NULL);
142 	StripDel(intmp);
143 	intmp[0] = tolower(intmp[0]);  /*@-strictops */
144 	if (intmp[0] != 'n') {         /*@=strictops */
145 	    fork_execvp(speller, fname);
146 	    restore_terminal();	/* restore terminal parameters */
147 #ifdef USE_FORK
148 	    if (get_reperror())
149 		ret_code = ATP_ERROR;
150 #endif
151 	}
152 	free_string(intmp);
153     }
154     free_string(speller);
155     return ret_code;
156 }
157 
158 
159 /*
160  * ask_user, confirm disposition of reply with the user.
161  * bufr *must* point to a buffer
162  */
163 static void
ask_user(const reply_type_t mode,char * bufr)164 ask_user(const reply_type_t mode, char *bufr)
165 {				/*@-strictops */
166     if (mode == XPOST)		/*@=strictops */
167 	bufr[0] = 'x';
168     else
169 	for (;;) {
170 	    char *intmp;
171 	    CLSCRN();
172 	    printf("\n");
173 	    if (get_tag_flag()) {
174 		strcpy(bufr, "tag ?");
175 		Tag(bufr);
176 		printf("Fido style tagline is %s.\n\n", fido ? "ON" : "OFF");
177 	    } else {
178 		printf("\nTaglines are currently disabled.\n");
179 		printf("Type \"tag on\" to enable them.\n");
180 	    }
181 	    printf("select:\n\t`Save' to save reply.\n");
182 	    printf("\t`Edit' to re-edit reply.\n");
183 	    printf("\t`Fido' to toggle tagline style and save reply.\n");
184 	    printf("\t`Tag' allows you to change tagline (see ATP man page)\n");
185 	    printf("\t`Abort' to cancel reply.\n\n");
186 	    do {
187 		intmp = readline("Choose: Save, Edit, Fido, Tag, Abort  [Save] ? ", do_scroll);
188 	    } while (intmp == NULL);
189 	    strcpy(bufr, intmp);
190 	    free_string(intmp);
191 	    StripDel(bufr);
192 	    StripLSpace(bufr);
193 	    bufr[0] = tolower(bufr[0]);   /*@-strictops */
194 	    if (bufr[0] == NUL_CHAR || bufr[0] == 's' || bufr[0] == 'f' || bufr[0] == 'a' || bufr[0] == 'e')
195 		break;                  /*@=strictops */
196 	    if (strnicmp(bufr, "tag", (size_t)3) == 0) {
197           	/* call tagline routines */
198 		tag_set_edit_reply_mode(TRUE);
199 		Tag(bufr);
200 		tag_set_edit_reply_mode(FALSE);
201 	    }
202 	}/* end of for(;;) loop */
203 }
204 
205 
206 /*
207  * scpy, copies len bytes to src while also deleting left spaces.
208  */
209 static void
scpy(char * dest,const char * src,const size_t len)210 scpy(char *dest, const char *src, const size_t len)
211 {
212     int i = (int) len ;
213     (void) memcpy( dest, src, len);
214     do {
215 	*(dest + i) = NUL_CHAR;
216 	i--;			/*@-strictops */
217     } while ( 0 <= i && *(dest + i) == SPC_CHAR);	/*@=strictops */
218 }
219 
220 /*
221  * set_lowcase, subroutine for get_from().
222  */
223 static atp_BOOL_T
set_lowcase(const reply_type_t mode,char * Header)224 set_lowcase(const reply_type_t mode, char *Header)
225 {
226     atp_BOOL_T lowcase = fido;		/* True or False boolean valued */
227     /*@-strictops */
228     if (mode == REPLY) {
229 	char CONSPTR sl_tmp = Header + HForWhom;
230 	size_t i = 0;
231 	lowcase = FALSE;
232 	for (; i < field_len; i++)
233 	    if (sl_tmp[i] >= 'a' && sl_tmp[i] <= 'z')
234 		lowcase = TRUE;
235     } else if (mode == ENTER) {         /*@=strictops */
236 	/* PCBoard likes uppercase to/from field */
237 	lowcase = get_caps() ? FALSE : TRUE  ;
238     }
239     return lowcase;
240 }
241 
242 /*
243  * error_too_long, prints message if author or recipient address is too long.
244  */
245 static void
error_too_long(void)246 error_too_long(void)
247 {
248     red();
249     printf("      %s", txt[34]);
250     green();
251     printf("\n");
252 }
253 
254 /*
255  * error_msg_aborted, message printed when reply is aborted.
256  */
257 static void
error_msg_aborted(void)258 error_msg_aborted(void)
259 {
260     blue();
261     printf("      %s.", txt[33]);
262     green();
263     printf("\n");
264 }
265 
266 
267 static char tmp[MAXPATHS], dummy[MAXPATHS];
268 /*
269  * get_from, get who the message is from.
270  */
271 static atp_ERROR_T
get_from(const reply_type_t mode,char * Qmail)272 get_from(const reply_type_t mode, char *Qmail)
273 {
274     atp_ERROR_T ret_code;
275     char CONSPTR UserName = get_cntrl_str(usrnm);
276     assert(UserName != NULL);
277     printf("\n");
278     for (;;) {
279 	atp_BOOL_T lowcase;
280 	char CONSPTR Header = rbuf;
281 	char *intmp = NULL;
282 	green();
283 	strcpy(dummy, UserName);
284 	luxptr = dummy;                        /*@-strictops */
285 	if (mode == CHANGE || mode == XPOST) { /*@=strictops */
286 	    scpy(dummy, Header + HAuthor, field_len);
287 	}
288 	lowcase = set_lowcase(mode, Header);
289 	if (!lowcase)
290 	    (void)strupr(dummy);
291 	do {
292 	    intmp = readline(reply_prompt(REPFR), do_scroll);
293 	} while (intmp == NULL);
294 	strcpy(tmp, intmp);
295 	add_history(intmp);
296 	free_string(intmp);
297 	luxptr = NULL;
298 	StripDel(tmp);
299 	if (strlen(tmp) <= field_len)
300 	    break;
301 	/* "Entry too long!  field_len chars max */
302 	error_too_long();
303 	printf("\n");
304     }
305     free_string(UserName);     /*@-strictops */
306     if (tmp[0] != NUL_CHAR) {  /*@=strictops */
307 	/* write name in the from field */
308 	str2mem(Qmail + HAuthor, tmp);
309 	ret_code = ATP_OK;
310     } else {
311 	ret_code = ATP_ERROR;
312 	/* message aborted! */
313 	error_msg_aborted();
314     }
315     return ret_code;
316 }
317 
318 /*
319  * get_to, get who the message is being sent to.
320  */
321 static atp_ERROR_T
get_to(const reply_type_t mode,char * Qmail)322 get_to(const reply_type_t mode, char *Qmail)
323 {
324     atp_ERROR_T ret_code;
325     for (;;) {
326 	char *intmp = NULL;
327 	char CONSPTR Header = rbuf;
328 	green();
329 	luxptr = dummy; /*@-strictops */
330 	if (mode == CHANGE || mode == XPOST)
331 	    scpy(dummy, Header + HForWhom, field_len);
332 	else if (mode != ENTER)
333 	    scpy(dummy, Header + HAuthor, field_len);
334 	else
335 	    strcpy(dummy, "All");
336 	if (!fido || (get_caps() && (mode == ENTER)))	/* PCBoard likes uppercase to/from field */
337 	    (void)strupr(dummy);
338 	do {
339 	    intmp = readline(reply_prompt(REPTO), do_scroll);
340 	} while (intmp == NULL);
341 	if (!fido || (get_caps() && (mode == ENTER)))	/* PCBoard likes uppercase to/from field */
342 	    (void)strupr(intmp); /*@=strictops */
343 	strcpy(tmp, intmp);
344 	add_history(intmp);
345 	free_string(intmp);
346 	luxptr = NULL;
347 	StripDel(tmp);
348 	if (strlen(tmp) <= field_len)
349 	    break;
350 	/* "Entry too long!  field_len chars max */
351 	error_too_long();
352 	printf("\n");
353     }
354     /*@-strictops */
355     if (stricmp(tmp, "N") == SUCCESS || tmp[0] == NUL_CHAR ) { /*@=strictops */
356 	ret_code = ATP_ERROR;
357 	/* "message aborted" */
358 	error_msg_aborted();
359     } else {
360 	ret_code = ATP_OK;
361 	str2mem(Qmail + HForWhom, tmp);
362     }
363     return ret_code;
364 }
365 
366 /* Long subject for PCBoard */
367 static char RepSubjBuf[100];
368 
369 /*
370  * get_reply_lsubj - strdups the reply long subject buffer.
371  */
372 char *
get_reply_lsubj(void)373 get_reply_lsubj(void)
374 {
375     char *sb = NULL;                 /*@-strictops */
376     if (RepSubjBuf[0] != NUL_CHAR ) {  /*@=strictops */
377 	sb = strdup(RepSubjBuf);
378 	test_fatal_malloc(sb, __FILE__, __LINE__);
379     }
380     return sb;
381 }
382 
383 
384 /*
385  * setup_long_subject, initializes reply subject buffer.
386  */
387 static void
setup_long_subject(const char * Header)388 setup_long_subject(const char *Header)
389 {
390     if (PCBLONG)
391 	Check4LongSubj();
392     if (subj_is_long()) {
393 	char CONSPTR tp = get_long_subj();
394 	assert( tp != NULL );
395 	strcpy(RepSubjBuf, tp);
396 	free_string(tp);
397     } else {
398 	scpy(RepSubjBuf, Header + HSubject, field_len);
399     }
400 }
401 
402 /*
403  * get_subject, get subject of the message.
404  */
405 static atp_ERROR_T
get_subj(const reply_type_t mode,char * Qmail)406 get_subj(const reply_type_t mode, char *Qmail)
407 {
408     atp_ERROR_T ret_code;
409     char *intmp = NULL;
410     char CONSPTR Header = rbuf;
411     green();               /*@-strictops */
412     if (mode != ENTER) {   /*@=strictops */
413 	setup_long_subject(Header);
414 	luxptr = RepSubjBuf;
415     }
416     do {
417 	intmp = readline(reply_prompt((PCBLONG ? REPSBL : REPSB)), do_scroll);
418     } while (intmp == NULL);
419     strcpy(tmp, intmp);
420     add_history(intmp);
421     free_string(intmp);
422     /* remember that luxptr is global so re-initialize ! */
423     luxptr = NULL;
424     StripDel(tmp);  /*@-strictops */
425     if (stricmp(tmp, "N") == SUCCESS || tmp[0] == NUL_CHAR ) {  /*@=strictops */
426 	/* "message aborted" */
427 	error_msg_aborted();
428 	ret_code = ATP_ERROR;
429     } else {
430 	ret_code = ATP_OK;
431 	/* length of long subject line */
432 	if (strlen(tmp) > (size_t) LSUBJ_BODY_LEN)
433 	    tmp[LSUBJ_BODY_LEN] = NUL_CHAR;
434 	/* make sure that it fits header field */
435 	if (strlen(tmp) > (size_t) SHORT_SUBJ_LEN) {
436 	    strcpy(RepSubjBuf, tmp);
437 	    tmp[SHORT_SUBJ_LEN] = NUL_CHAR;
438 	} else {
439 	    /* don't use long subject if length <= field_len */
440 	    RepSubjBuf[0] = NUL_CHAR;
441 	}
442 	str2mem(Qmail + HSubject, tmp);
443     }
444     return ret_code;
445 }
446 
447 /*
448  * get_security, get message security: public or private.
449  */
450 static atp_ERROR_T
get_security(const reply_type_t mode,char * Qmail)451 get_security(const reply_type_t mode, char *Qmail)
452 {
453     atp_ERROR_T ret_code;
454     char CONSPTR Header = rbuf;
455     for (;;) {
456 	char *intmp = NULL;
457 	green();	/*@-strictops */
458 	if ((mode != ENTER) &&
459 	    (Header[HStatus] == '+' || Header[HStatus] == '*')){/*@=strictops */
460 	    luxptr = "Receiver Only (private)";
461 	    add_history("None (public)");
462 	} else {
463 	    luxptr = "None (public)";
464 	    add_history("Receiver Only (private)");
465 	}
466 	do {
467 	    intmp = readline(reply_prompt(REPSEC), do_scroll);
468 	} while (intmp == NULL);
469 	strcpy(tmp, intmp);
470 	add_history(intmp);
471 	free_string(intmp);
472 	luxptr = NULL;
473 	StripDel(tmp);
474 	StripLSpace(tmp);
475 	if (strlen(tmp) <= field_len)
476 	    break;
477 	red();
478 	printf("%s", txt[35]);	/* "Too long! R=...etc..." */
479 	green();
480 	printf("\n");
481     }                           /*@-strictops */
482     if (tmp[0] != NUL_CHAR) {
483 	(void)strupr(tmp);
484 	if (tmp[0] == 'R' || strnicmp(tmp,"priv", (size_t)4) == SUCCESS )   /*@=strictops */
485 	    Qmail[HStatus] = (char) PRIVATE_MSG;
486 	else
487 	    Qmail[HStatus] = (char) PUBLIC_MSG;
488 	ret_code = ATP_OK;
489     } else {
490 	ret_code = ATP_ERROR;
491 	/* "message aborted" */
492 	error_msg_aborted();
493     }
494     return ret_code;
495 }
496 
497 /* index into array */
498 static int TargetConfIdx;
499 /*
500  * get_address_fin, finishes building message header for replies.
501  */
502 static void
get_address_fin(const reply_type_t mode,char * Qmail)503 get_address_fin(const reply_type_t mode, char *Qmail)
504 {
505     char CONSPTR Header = rbuf;
506     /* Msg goes to the target conference */
507     sprintf(dummy, "%-7d", ConfNumbers[TargetConfIdx]);
508     str2mem(Qmail + HConfNum, dummy);
509 
510     /* get reference number if available */          /*@-strictops */
511     if (mode == CHANGE )
512 	scpy(dummy, Header + HRefMsg, (size_t)7);
513     else if (mode == ENTER || mode == XPOST)         /*@=strictops */
514 	dummy[0] = NUL_CHAR;
515     else
516 	scpy(dummy, Header + HNumMsg, (size_t)7);
517     str2mem(Qmail + HRefMsg, dummy);
518 }
519 
520 
521 static const char *months[] =
522 {"none", "JAN", "FEB", "MAR", "APR", "MAY", "JUN",
523  "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
524 static const char *monums[] =
525 {"00-", "01-", "02-", "03-", "04-", "05-", "06-",
526  "07-", "08-", "09-", "10-", "11-", "12-"};
527 
528 /*
529  * le date et l'heure d'entete du message.
530  * tetedate, returns a pointer to the reply date/time string.
531  */
532 static char *
tetedate(void)533 tetedate(void)
534 {
535     int i;
536     time_t t;
537     char tbuf[30];
538     static char tbuf2[30];
539     t = time(NULL);
540     tzset();
541     /* this macro is used only by msdos and its relatives */
542     ADJUST_DOS_TIME
543     sprintf(tbuf, "%s", ctime(&t));
544     tbuf[7] = NUL_CHAR;
545     tbuf[10] = NUL_CHAR;
546     tbuf[16] = NUL_CHAR;
547     tbuf[21] = '-';
548     tbuf[24] = NUL_CHAR;
549     /* default case */
550     sprintf(tbuf2, "01-01-7001:00");
551     for (i = 1; i < 13; i++) {
552 	const int found_a_match = stricmp(tbuf+4, months[i]);
553 	if (found_a_match == SUCCESS) {
554 	    sprintf(tbuf2, "%s%s%s%s", monums[i], tbuf+8, tbuf+21, tbuf+11);
555 	    break;
556 	}
557     } /*@-strictops */
558     if (tbuf2[3] == SPC_CHAR) /*@=strictops */
559 	tbuf2[3] = '0';
560     return tbuf2;
561 }
562 
563 /*
564  * Remise du Header aux param�tres par d�faut
565  * ResetHeader, put the default parameters in the reply header.
566  */
567 static void
ResetHeader(char * Qmail)568 ResetHeader(char *Qmail)
569 {
570     const unsigned high_byte = 0xff00;
571     const unsigned low_byte = 0x00ff;
572     byte CONSPTR ptr = (byte *) Qmail;
573     byte word16[2];		/* space for 16 bit unsigned word  */
574     char qbuf[30];		/* temporary buffer                */
575 
576     /* Given index number, retrieve real conference number */
577     const unsigned RealConfN = (unsigned)ConfNumbers[TargetConfIdx];
578 #ifdef ATPDBG
579     const int temp = ConfNumbers[TargetConfIdx];
580     assert( -1 < temp );
581 #endif
582 
583     /* fill new header with spaces ; struct remplie d'espaces */
584     (void)memset((void *) Qmail, (int)SPC_CHAR, header_SIZE);
585 
586     /* Mark message as "public message, unread" */
587     Qmail[HStatus] = PUBLIC_MSG;
588 
589     /* Add ascii conference number to header */
590     sprintf(qbuf, "%-7d", ConfNumbers[TargetConfIdx]);
591     str2mem(Qmail + HConfNum, qbuf);
592 
593     /* Now enter the time as formatted by a call to tetedate() */
594     str2mem(Qmail + HMsgDate, tetedate());
595 
596     /* create a 16bit little endian unsigned conference number */
597     word16[0] = (byte) (RealConfN & low_byte );
598     word16[1] = (byte) ((RealConfN & high_byte ) >> 8);
599     (void)memcpy((void *) &Qmail[HBinConfN], (void *) word16, (size_t) 2);
600 
601     /* Mark message as "active", 0xE1 = active  0xE2 = non active  */
602     *(ptr + HMsgActiv) = ACTIVE_MSG;
603     *(ptr + HUnused1) = SPC_CHAR;	/* space */
604     *(ptr + HUnused2) = SPC_CHAR;	/* space */
605     *(ptr + HNetTag) = SPC_CHAR;	/* space */
606 }
607 
608 
609 /*
610  * get_address, build subject/author/recepient/security fields for message.
611  */
612 static atp_ERROR_T
get_address(const reply_type_t mode,char * Qmail)613 get_address(const reply_type_t mode, char *Qmail)
614 {
615     atp_ERROR_T ret_code = ATP_ERROR;
616     ResetHeader(Qmail);
617 
618     /* get who the message is from */      /*@-strictops */
619     if (get_from(mode, Qmail) == ATP_OK
620 
621     /* get who the message is being sent to */
622 	&& get_to(mode, Qmail) == ATP_OK
623 
624     /* get subject of message */
625 	&& get_subj(mode, Qmail) == ATP_OK
626 
627     /* get security of message */
628 	&& get_security(mode, Qmail) == ATP_OK) {
629 
630     /* finish up */
631 	get_address_fin(mode, Qmail);
632 	ret_code = ATP_OK;
633     }
634     return ret_code;
635 }
636 
637 
638 /*
639  * AddReply, add the reply to a Qmail reply.msg file.
640  */
641 static void
AddReply(const reply_type_t mode,const char * fname,char * Qmail)642 AddReply(const reply_type_t mode, const char *fname, char *Qmail)
643 {
644     if (OpenRepFile(add_reply) && KodeMessage(fname, Qmail) == ATP_OK) {
645 	do_unlink(fname);
646 	set_ReplyExist(TRUE, rexist_AddReply);
647 	if (autotag)
648 	    ChooseTag();
649 	if (get_saved_conf() == RCONF_IDX) {
650 	    const long total_messages = get_TotMsg() + 1L ;
651 	    set_TotMsg(total_messages, tm_AddReply);
652 	    /*    CurConf = RCONF_IDX; */
653 	    if (mode == CHANGE)
654 		ReadNext(NUKE);
655 	    if (mode == XPOST)             /*@=strictops */
656 		ReadNext(CROSS);
657 	    else {
658 		sprintf(dummy, "%ld", get_TotMsg());
659 		GoToNum(dummy);
660 	    }
661 	}
662     }
663 }
664 
665 
666 
667 /*
668  * Rep_conf_stoi, finds conference integer index based on string argument.
669  *
670  */
671 static int
Rep_conf_stoi(const char * cmdargs)672 Rep_conf_stoi(const char *cmdargs)
673 {
674     int i;
675     if (Numeric(cmdargs)) {
676 	i = findCindex(atoi(cmdargs));
677     } else if (stricmp(cmdargs, "MAIN") == SUCCESS) { /* not numeric */
678 	i = 0;
679     } else {
680 	/* search for match in conf list */
681 	const size_t cmdarg_len = strlen(cmdargs);
682 	for (i = 0; i <= LastConf; i++)
683 	    if (strnicmp(cmdargs, ConfNames[i], cmdarg_len) == SUCCESS)
684 		break;
685     }
686     return i;
687 }
688 
689 static const int not_valid_target  = (int)ATP_ERROR ;
690 /*
691  * Rep_valid_range, verifies that a conference index is in the proper range.
692  */
693 static int
Rep_valid_range(const int target)694 Rep_valid_range(const int target)
695 {
696     return ((0 <= target && target < RCONF_IDX) ? target : not_valid_target );
697 }
698 
699 static const int no_args = -1 ;
700 
701 /*
702  * Rep_mode_valid, verifies that requested Reply() operation is legal.
703  */
704 static atp_BOOL_T
Rep_mode_valid(const reply_type_t mode,const int target)705 Rep_mode_valid(const reply_type_t mode, const int target)
706 {
707     atp_BOOL_T is_valid = FALSE;
708     const int saved_conf = get_saved_conf();
709     assert(-1 < saved_conf);
710     if (target == no_args) {
711 	switch (mode) {
712 	case XPOST:
713 	    break;
714 	case ENTER:
715 	    is_valid = (saved_conf != PCONF_IDX && saved_conf != RCONF_IDX) ?
716 		TRUE : FALSE;
717 	    break;
718 	case CHANGE:
719 	    is_valid = (saved_conf == RCONF_IDX) ? TRUE : FALSE;
720 	    break;
721 	case REPLY:
722 	    is_valid = (saved_conf != RCONF_IDX) ? TRUE : FALSE;
723 	    break;
724 	default:
725 	    /* switch() case error */
726 	    error_switch_case((int)mode, __FILE__, __LINE__);
727 	}
728     } else
729 	is_valid = (Rep_valid_range(target) == not_valid_target) ?
730 	    FALSE : TRUE;
731     return is_valid;
732 }
733 
734 
735 /*
736  * Rep_conf_header, read conference number from header, find index and validate.
737  */
738 static int
Rep_conf_header(const char * Header)739 Rep_conf_header(const char *Header)
740 {
741     const int target = findCindex(readCnum(Header + HBinConfN));
742     return Rep_valid_range(target);
743 }
744 
745 /*
746  * get_target_conf, get proper conference number to inscribe in reply header.
747  */
748 static int
get_target_conf(const reply_type_t mode)749 get_target_conf(const reply_type_t mode)
750 {
751     int target = not_valid_target;
752     /*@-strictops */
753     if (mode == CHANGE || get_saved_conf() == PCONF_IDX) {
754 	/* rbuf points to message header */
755 	target = Rep_conf_header(rbuf);
756     } else if (mode == REPLY || mode == ENTER) { /*@=strictops */
757 	target = get_saved_conf();
758     } else {
759 	fprintf(stderr, "Unknown mode: %d  %s:%d\n", (int)mode, __FILE__, __LINE__);
760 	(void)sleep(3);
761     }
762     return target;
763 }
764 
765 /*
766  * Rep_mode_parse, initializes context for Reply().
767  */
768 static int
Rep_mode_parse(const reply_type_t mode,const char * cmdline)769 Rep_mode_parse(const reply_type_t mode, const char *cmdline)
770 {
771     int target;
772     /* check for command line arguments */
773     const atp_BOOL_T m_args = ((sscanf(cmdline, "%s %s", dummy, tmp)) > 1) ? TRUE : FALSE;
774 
775     if (m_args) {
776 	/* convert string to number */
777 	target = Rep_conf_stoi(tmp);
778 
779 	/* verify combination of mode and target */
780 	if (!Rep_mode_valid(mode, target)) {
781 	    target = not_valid_target;
782 	}
783     } else if (Rep_mode_valid(mode, no_args)) {
784 	target = get_target_conf(mode);
785     } else
786 	target = not_valid_target;
787     return target;
788 }
789 /* end of mode parse routines */
790 
791 
792 /*
793  * call_editor, invoke editor for reply.
794  */
795 static atp_ERROR_T
call_editor(const reply_type_t mode,const char * fname)796 call_editor(const reply_type_t mode, const char *fname)
797 {
798     atp_ERROR_T ret_code = ATP_OK;
799     /*@-strictops */
800     if (mode != XPOST) { /*@=strictops */
801 	char CONSPTR Editor = get_atprc_str(editr);
802 	assert( Editor != NULL );
803 	white();
804 	/* "calling editor" */
805 	printf("\n%s\n", txt[38]);
806 	fork_execvp(Editor, fname);
807 	free_string(Editor);
808 	/* reset terminal attributes */
809 	restore_terminal();
810 #ifdef USE_FORK
811 	if (get_reperror())
812 	    ret_code = ATP_ERROR;
813 #endif
814     }
815     return ret_code;
816 }
817 
818 
819 /*
820  * McAdjust, adjust for Mc prefix as in McWilliams.
821  */
822 ATP_INLINE void
McAdjust(size_t * i,const char ** str,char ** dst,const size_t len)823 McAdjust(size_t *i, const char **str, char **dst, const size_t len)
824 {
825     const char CONSPTR McPrefix = "Mc";
826     const size_t mick_len = (size_t) STRING_LEN("Mc");
827     const size_t too_far = (size_t) (len - mick_len);
828 
829     assert( len >= mick_len );
830     if (*i < too_far && strnicmp(((*str) + 1), McPrefix, mick_len) == SUCCESS) {
831 	strcpy(((*dst) + 1), McPrefix);
832 	(*i) += mick_len;
833 	(*dst) += (int) mick_len;
834 	(*str) += (int) mick_len;
835     }
836 }
837 
838 /*
839  **Formatte le nom en minuscules, avec les initiales Upcase, dans dst, null
840  **terminated.
841  * Initials, build and format author's name for reply headers.
842  */
843 static void
Initials(const char * str,char * dst,const size_t len)844 Initials(const char *str, char *dst, const size_t len)
845 {
846     atp_BOOL_T convert_to_upper = TRUE;
847     size_t i;
848 
849     /* build a name with first letter capitalized */
850     for (i = 0; i < len; i++, str++, dst++) {
851 	*dst = *str;		/*@-strictops */
852 	if (*dst == '.' || *dst == '-' || *dst == SPC_CHAR) {	/*@=strictops */
853 	    convert_to_upper = TRUE;	/* Next char converted to Upcase */
854 	    McAdjust(&i, &str, &dst, len);	/* adjust for "Mc" prefix */
855 	} else if (convert_to_upper) {
856 	    *dst = toupper(*dst);
857 	    convert_to_upper = FALSE;
858 	} else {
859 	    *dst = tolower(*dst);
860 	}
861     }
862 
863     /* build a null-terminated string */
864     do {
865 	*dst = NUL_CHAR;
866 	dst--;			/*@-strictops */
867     } while (*dst == SPC_CHAR);	/*@=strictops */
868 
869 }
870 
871 
872 /*
873  * Response modes for RefMessage():
874  *  John Doe wrote to all, John Doe wrote to me, John Doe wrote to Guy Beau.
875  */
876 enum ref_hdr_msg {
877   John_Doe_wrote_to_ALL, John_Doe_wrote_to_me, John_Doe_wrote_to_Guy_Beau
878 };
879 
880 
881 /*
882  * get_response_mode, get relationship of who is writing to whom.
883  */
884 static enum ref_hdr_msg
get_response_mode(const char * Msg)885 get_response_mode(const char *Msg)
886 {
887     enum ref_hdr_msg response_mode;
888     char CONSPTR UserName = get_cntrl_str(usrnm);
889     assert(UserName != NULL);
890 
891     if (strnicmp(Msg + HForWhom, "ALL", (size_t) 3) == SUCCESS)
892         response_mode = John_Doe_wrote_to_ALL;
893     else if (strnicmp(Msg + HForWhom, UserName, strlen(UserName)) == SUCCESS)
894         response_mode = John_Doe_wrote_to_me;
895     else
896         response_mode = John_Doe_wrote_to_Guy_Beau;
897     free_string(UserName);
898     return response_mode;
899 }
900 
901 
902 /*
903  * R�alise l'ent�te de la lettre...
904  * RefMessage, create a message reference string for a reply.
905  */
906 static void
RefMessage(FILE * fp,char * Msg)907 RefMessage(FILE * fp, char *Msg)
908 {
909     if (get_HeadLetter()) {
910 	enum ref_hdr_msg response_mode;
911 	int mon, day;
912 	char Destin[32];
913 	char Ref[32];
914 
915 	Msg[HMsgDate + 2] = '-';
916 	Msg[HMsgDate + 5] = '-';
917 	sscanf(Msg + HMsgDate, "%d-%d", &mon, &day);
918 	Initials(Msg + HAuthor, Destin, field_len);
919 	Destin[field_len] = NUL_CHAR;
920 	Initials(Msg + HForWhom, Ref, field_len);
921 	Ref[field_len] = NUL_CHAR;
922 
923 	response_mode = get_response_mode(Msg);
924 
925 	if (strnicmp(Msg + HAuthor, "UUCP", (size_t)4) != SUCCESS ) {
926 	    /* "Cher"           */
927 	    fprintf(fp, "%s %s,\n", txt[96], Destin);
928 	    fprintf(fp, "%s", txt[97]);
929 	    /* "Dans un msg du" */
930 	    fprintf(fp, " %d %s, ", day, Months[mon - 1]);
931 
932 	    switch (response_mode) {
933 	    case John_Doe_wrote_to_ALL:
934 		/* "vous �crivez" */
935 		fprintf(fp, "%s :\n\n", txt[98]);
936 		break;
937 	    case John_Doe_wrote_to_me:
938 		/* "vous m'�crivez" */
939 		fprintf(fp, "%s :\n\n", txt[99]);
940 		break;
941 	    case John_Doe_wrote_to_Guy_Beau:
942 		/* "destin� �" */
943 		fprintf(fp, "%s", txt[100]);
944 		fprintf(fp, " %s, ", Ref);
945 		/* "vous �crivez"   */
946 		fprintf(fp, "%s :\n\n", txt[98]);
947 		break;
948 	    default:
949 		/* switch() case error */
950 		error_switch_case((int)response_mode, __FILE__, __LINE__);
951 	    }
952 	}
953     }
954 }
955 
956 
957 
958 /*
959  * output_quoted_line, add quote delimiter character to start of reply line.
960  */
961 static void
output_quoted_line(unsigned long i,byte * ptr,const char * quote,FILE * fq)962 output_quoted_line(unsigned long i, byte * ptr, const char *quote, FILE * fq)
963 {
964     size_t col = strlen(quote);
965     const atp_CODE_T chrset = get_charset();
966     const unsigned long MsgSize = get_MsgSize();
967     fprintf(fq, "%s", quote);
968     while (i < MsgSize) {
969 	/*@-strictops */
970 	if (*ptr == (byte)'\n') {
971 	    fprintf(fq, "\n%s", quote);
972 	    col = strlen(quote);
973 	} else if (*ptr != (byte)NUL_CHAR) {
974 	    if (col < 78) {	/* Trunc quoted lines to 78 cols */
975 		if (chrset == ISOLAT1)
976 		    fprintf(fq, "%c", codelu[(unsigned) (*ptr)]);
977 		else if (chrset == CHR7BIT)
978 		    fprintf(fq, "%c", code7bit[(unsigned) (*ptr)]);
979 		else
980 		    fprintf(fq, "%c", *ptr == DOS_SPACE ? SPC_CHAR : *ptr);
981 		    /*@=strictops */
982 	    }
983 	    col++;
984 	}
985 	i++;
986 	ptr++;
987     }
988 }
989 
990 
991 /*
992  * QuoteDo, open reply file for quoting.
993  */
994 static void
QuoteDo(const char * quote,const char * tmprname)995 QuoteDo(const char *quote, const char *tmprname)
996 {
997     FILE *fq;
998     if ((fq = fopen(tmprname, "w")) == NULL) {
999 	/* "Unable to create file" */
1000 	printf("%s %s !\n", txt[50], tmprname);
1001     } else {
1002 	unsigned long start_of_text_offset = 0L;
1003 	char *ptr = rbuf;
1004 
1005 	/* Dear User, in a message to... */
1006 	RefMessage(fq, ptr);
1007 
1008 	/* point to start of message text */
1009 	ptr += header_SIZE;
1010 
1011 	/* adjust start of message for embedded long subject lines */
1012 	if (PCBLONG && subj_is_long()) {
1013 	    ptr = get_sot(); /* get start_of_text */ /*@-strictops */
1014 	    start_of_text_offset = (long) (ptr - (rbuf + header_SIZE)); /*@=strictops */
1015 	}
1016 	/* write quoted lines to file */
1017 	output_quoted_line(start_of_text_offset, (byte *) ptr, quote, fq);
1018 	fclose(fq);
1019     }
1020 }
1021 
1022 /*
1023  * QuoteMsg, copy the current msg in file tmprname with quotes.
1024  */
1025 static void
QuoteMsg(const char * tmprname)1026 QuoteMsg(const char *tmprname)
1027 {
1028     const char CONSPTR Msg = rbuf;
1029     char quote[10];
1030     if (strnicmp(Msg + HAuthor, "SYSOP", (size_t)5) == SUCCESS) {
1031 	strcpy(quote, "Sys> ");
1032     } else if (strnicmp(Msg + HAuthor, "UUCP", (size_t)4) == SUCCESS) {
1033 	strcpy(quote, "> ");
1034     } else {
1035 	int col;
1036 	char tmp1[50], tmp2[50], tmp3[50], tmp4[50], tmp5[50];
1037 	(void)memcpy(tmp1, Msg + HAuthor, field_len);
1038 	tmp1[field_len] = NUL_CHAR;
1039 	col = sscanf(tmp1, "%s %s %s %s", tmp2, tmp3, tmp4, tmp5);
1040 
1041 	assert( 0 <= col && col < 5 );
1042 	switch (col) {
1043 	case 0:
1044 	    strcpy(quote, " > ");
1045 	    break;
1046 	case 1:                            /*@-strictops */
1047 	    if (tmp2[1] == NUL_CHAR)       /*@=strictops */
1048 		sprintf(quote, "%c> ", tmp2[0]);
1049 	    else
1050 		sprintf(quote, "%c%c> ", tmp2[0], tmp2[1]);
1051 	    break;
1052 	case 3:
1053 	    sprintf(quote, "%c%c%c> ", tmp2[0], tmp3[0], tmp4[0]);	/* Initials.. */
1054 	    break;
1055 	case 4:
1056 	    sprintf(quote, "%c%c> ", tmp2[0], tmp5[0]);		/* Initials.. */
1057 	    break;
1058 	default:
1059 	    sprintf(quote, "%c%c> ", tmp2[0], tmp3[0]);		/* Initials.. */
1060 	}
1061     }
1062     printf("\n%s %s ...\n", txt[59], quote);	/* "Quoting message with " */
1063     QuoteDo(quote, tmprname);
1064 }
1065 
1066 
1067 /*
1068  * do_strip, copy old reply message minus the tagline, called by StripTag().
1069  */
1070 static void
do_strip(byte * ptr,FILE * fq,unsigned long i)1071 do_strip(byte *ptr, FILE * fq, unsigned long i)
1072 {
1073     const unsigned long MsgSize = get_MsgSize();
1074     const atp_CODE_T chrset = get_charset();
1075     while (i < MsgSize) {
1076 	/*@-strictops */
1077 	if (*ptr == DOS_SPACE) {
1078 	    fprintf(fq, "\n");
1079 	    break;
1080 	}
1081 	if (*ptr == (byte)'\n')
1082 	    fprintf(fq, "\n");
1083 	else if (*ptr == (byte)NUL_CHAR)/* do nothing, i.e. skip the zero character */
1084 	    ;			/* empty statement */
1085 	else if (chrset == ISOLAT1)
1086 	    fprintf(fq, "%c", codelu[(unsigned) (*ptr)]);
1087 	else if (chrset == CHR7BIT) /*@=strictops */
1088 	    fprintf(fq, "%c", code7bit[(unsigned) (*ptr)]);
1089 	else
1090 	    fprintf(fq, "%c", *ptr);
1091 	i++;
1092 	ptr++;
1093     }
1094 }
1095 
1096 
1097 /*
1098  * StripTag, strip tagline from re-edited reply.
1099  */
1100 static void
StripTag(const char * tmprname)1101 StripTag(const char *tmprname)
1102 {
1103     FILE *fq;
1104     if ((fq = fopen(tmprname, "w")) == NULL) {
1105 	/* "Unable to create file" */
1106 	printf("%s %s !\n", txt[50], tmprname);
1107 	(void)sleep(4);
1108     } else {
1109 	unsigned long i = 0;
1110 	byte *ptr = (byte *) (rbuf + header_SIZE);
1111 	/* adjust for long embedded subjects */
1112 	if (PCBLONG && subj_is_long()) {       /*@-strictops */
1113 	    assert( strlen(RepSubjBuf) > field_len ); /* ??? */
1114 	    while (*ptr != (byte)LF_CHAR)      /*@=strictops */
1115 		i++, ptr++;
1116 	    i++, ptr++;               /*@-strictops */
1117 	    if(*ptr == (byte)LF_CHAR) /*@=strictops */
1118 	        i++, ptr++;
1119 	}
1120 	/* setup is complete, now do the actual work */
1121 	do_strip(ptr, fq, i);
1122 	fflush(fq);
1123 	fclose(fq);
1124     }
1125 }
1126 
1127 
1128 /*
1129  * editpp_fname, pre-process reply file for quoting and tagline removal.
1130  */
1131 static void
editpp_fname(const reply_type_t mode,const char * fname,atp_BOOL_T reedit)1132 editpp_fname(const reply_type_t mode, const char *fname, atp_BOOL_T reedit)
1133 {
1134     /*@-strictops */
1135     if (mode == REPLY && !reedit)
1136 	/* Quote original new message */
1137 	QuoteMsg(fname);
1138     else if ((mode == CHANGE || mode == XPOST) && !reedit)
1139 	/* remove tagline from old message */
1140 	StripTag(fname);
1141     /*@=strictops */
1142 }
1143 
1144 
1145 /*
1146  * edit_fname, wrapper sets up housekeeping for call_editor().
1147  * returns either ATP_OK or ATP_ERROR
1148  */
1149 static atp_ERROR_T
edit_fname(const reply_type_t mode,const char * fname,char * Qmail)1150 edit_fname(const reply_type_t mode, const char *fname, char *Qmail)
1151 {
1152     atp_BOOL_T reedit = FALSE;
1153     atp_ERROR_T ret_code = ATP_ERROR;
1154 
1155     do {
1156 	/* get subject, author, recepient, and security for message */ /*@-strictops */
1157 	if (get_address(mode, Qmail) == ATP_OK) {
1158 
1159 	    /* pre-process reply: quote message or strip tagline */
1160 	    editpp_fname(mode, fname, reedit);
1161 
1162 	    /* finally, here is where we invoke the editor for the reply */
1163 	    if (call_editor(mode, fname) == ATP_OK
1164 
1165 	    /* here is where we ask if spelling needs checking */
1166 		&& ask_spell(mode, fname) == ATP_OK) {
1167                 /*@=strictops */
1168 		/* query user whether to save, abort or re-edit message */
1169 		ask_user(mode, tmp);
1170 		switch (tmp[0]) {
1171 		case 'e':
1172 		    /* re-edit message */
1173 		    reedit = TRUE;
1174 		    break;
1175 		case 'a':
1176 		    /* abort message */
1177 		    error_msg_aborted();
1178 		    do_unlink(fname);
1179 		    reedit = FALSE;
1180 		    ret_code = ATP_ERROR;
1181 		    break;
1182 		case 'f':
1183 		    /* flip tagline style */
1184 		    /*@-casebreak */ /* yes, we fall-through, no break */
1185 		    togltag();
1186 		default:
1187 		    reedit = FALSE;
1188 		    /*@=casebreak */
1189 		    ret_code = ATP_OK;
1190 		    /* adjust header for fido or not */
1191 		    if (!fido) {
1192 			int i = 0;
1193 			for (; i < 50; i++)
1194 			    Qmail[HForWhom + i] = toupper(Qmail[HForWhom + i]);
1195 		    }
1196 		}
1197 	    }
1198 	}
1199     } while (reedit);
1200     return ret_code;
1201 }
1202 
1203 /* initialize to guaranteed invalid conference index */
1204 static const int conf_is_invalid = -17715;
1205 static int SaveConf = -17715;
1206 
1207 /*
1208  * get_saved_conf - returns value of SaveConf variable.
1209  */
1210 int
get_saved_conf(void)1211 get_saved_conf(void)
1212 {
1213 	return SaveConf;
1214 }
1215 
1216 /*
1217  * Reply - reply to current message; enter message; or re-edit a message.
1218  */
1219 void
Reply(const reply_type_t mode,const char * line)1220 Reply(const reply_type_t mode, const char *line)
1221 {
1222     char *intmp, fname[MAXPATHS];
1223     char Qmail[HDRSIZE];
1224 
1225     /* save current conference */
1226     SaveConf = get_CurConf();
1227 
1228     /* determine which conference to post message in based on mode */
1229     if ((TargetConfIdx = Rep_mode_parse(mode, line)) != (int)ATP_ERROR) {
1230 #ifdef ATPDBG
1231 	assert( 0 <= TargetConfIdx && TargetConfIdx < RCONF_IDX );
1232 #endif
1233 	/* create temporary file for reply */
1234 	if ((intmp = tmpnam(NULL)) != NULL)
1235 	    strcpy(fname, intmp);
1236 	else
1237 	    sprintf(fname, "%s%c%s", WorkPath, SEP, "reply.tmp");
1238 
1239 	/* edit the reply file */                        /*@-strictops */
1240 	if (edit_fname(mode, fname, Qmail) == ATP_OK) {  /*@=strictops */
1241 
1242 	    /* add the reply to the reply conference database */
1243 	    AddReply(mode, fname, Qmail);
1244 	}
1245     }
1246     restore_terminal();
1247 #ifdef USE_FORK
1248     if (get_reperror()) {
1249 	CLSCRN();
1250 	/* clear error condition */
1251 	reset_reperror();
1252     }
1253 #endif
1254     /* restore current conference */
1255     /* CurConf = SaveConf; */
1256     /* invalidate saved conference */
1257     SaveConf = conf_is_invalid;
1258     return;
1259 }
1260 /********************** end of Reply() routines *******************/
1261 
1262