Synchronet now requires the libarchive development package (e.g. libarchive-dev on Debian-based Linux distros, libarchive.org for more info) to build successfully.

postmsg.cpp 21.1 KB
Newer Older
1 2 3 4 5 6
/* Synchronet user create/post public message routine */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
rswindell's avatar
rswindell committed
7
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
 *																			*
 * This program is free software; you can redistribute it and/or			*
 * modify it under the terms of the GNU General Public License				*
 * as published by the Free Software Foundation; either version 2			*
 * of the License, or (at your option) any later version.					*
 * See the GNU General Public License for more details: gpl.txt or			*
 * http://www.fsf.org/copyleft/gpl.html										*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

#include "sbbs.h"
23
#include "utf8.h"
24
#include "filedat.h"
25

26
int msgbase_open(scfg_t* cfg, smb_t* smb, unsigned int subnum, int* storage, long* dupechk_hashes, uint16_t* xlat)
27
{
28 29
	int i;

30 31 32
	*dupechk_hashes=SMB_HASH_SOURCE_DUPE;
	*xlat=XLAT_NONE;

33 34 35
	if((i=smb_open_sub(cfg, smb, subnum)) != SMB_SUCCESS)
		return i;

36 37 38 39 40 41 42
	if(smb->subnum==INVALID_SUB) {
		/* duplicate message-IDs must be allowed in mail database */
		*dupechk_hashes&=~(1<<SMB_HASH_SOURCE_MSG_ID);
	} else {
		if(cfg->sub[smb->subnum]->misc&SUB_LZH)
			*xlat=XLAT_LZH;
	}
43

44 45 46
	if(smb->status.max_crcs==0)	/* no CRC checking means no body text dupe checking */
		*dupechk_hashes&=~(1<<SMB_HASH_SOURCE_BODY);

47 48
	*storage=smb_storage_mode(cfg, smb);

49
	return i;
50 51
}

52 53 54 55 56 57 58 59 60 61
static uchar* findsig(char* msgbuf)
{
	char* tail = strstr(msgbuf, "\n-- \r\n");
	if(tail != NULL) {
		*tail = '\0';
		tail++;
		truncsp(msgbuf);
	}
	return (uchar*)tail;
}
62

63
/****************************************************************************/
64 65
/* Posts a message on sub-board number 'subnum'								*/
/* Returns true if posted, false if not.                                    */
66
/****************************************************************************/
67
bool sbbs_t::postmsg(uint subnum, long wm_mode, smb_t* resmb, smbmsg_t* remsg)
68
{
69 70 71 72
	char	str[256];
	char	title[LEN_TITLE+1] = "";
	char	top[256] = "";
	char	touser[64] = "";
73
	char	from[64];
74
	char	tags[64] = "";
75 76
	const char*	editor=NULL;
	const char*	charset=NULL;
77
	char*	msgbuf=NULL;
deuce's avatar
64-bit  
deuce committed
78
	uint16_t xlat;
79
	ushort	msgattr = 0;
80 81 82 83
	int 	i,storage;
	long	dupechk_hashes;
	long	length;
	FILE*	fp;
84
	smbmsg_t msg;
85
	uint	reason;
86 87 88 89 90 91 92
	str_list_t names = NULL;

	/* Security checks */
	if(!can_user_post(&cfg,subnum,&useron,&client,&reason)) {
		bputs(text[reason]);
		return false;
	}
93 94

	if(remsg) {
95
		SAFECOPY(title, msghdr_field(remsg, remsg->subj, NULL, term_supports(UTF8)));
96
		if(remsg->hdr.attr&MSG_ANONYMOUS)
97 98
			SAFECOPY(from,text[Anonymous]);
		else
99
			SAFECOPY(from, msghdr_field(remsg, remsg->from, NULL, term_supports(UTF8)));
100
		// If user posted this message, reply to the original recipient again
101 102 103
		if(remsg->to != NULL
			&& ((remsg->from_ext != NULL && atoi(remsg->from_ext)==useron.number)
				|| stricmp(useron.alias,remsg->from) == 0 || stricmp(useron.name,remsg->from) == 0))
104
			SAFECOPY(touser, msghdr_field(remsg, remsg->to, NULL, term_supports(UTF8)));
105
		else
106
			SAFECOPY(touser,from);
107 108
		if(remsg->to != NULL)
			strListPush(&names, remsg->to);
109
		msgattr=(ushort)(remsg->hdr.attr&MSG_PRIVATE);
rswindell's avatar
rswindell committed
110 111 112
		sprintf(top,text[RegardingByToOn]
			,title
			,from
113
			,msghdr_field(remsg, remsg->to, NULL, term_supports(UTF8))
114
			,timestr(remsg->hdr.when_written.time)
115 116 117
			,smb_zonestr(remsg->hdr.when_written.zone,NULL));
		if(remsg->tags != NULL)
			SAFECOPY(tags, remsg->tags);
118
	}
119 120 121 122 123 124 125 126 127

	bprintf(text[Posting],cfg.grp[cfg.sub[subnum]->grp]->sname,cfg.sub[subnum]->lname);
	action=NODE_PMSG;
	nodesync();

	if(!(msgattr&MSG_PRIVATE) && (cfg.sub[subnum]->misc&SUB_PONLY
		|| (cfg.sub[subnum]->misc&SUB_PRIV && !noyes(text[PrivatePostQ]))))
		msgattr|=MSG_PRIVATE;

128 129
	if(sys_status&SS_ABORT) {
		strListFree(&names);
130
		return(false);
131
	}
132

133 134 135 136 137
	if(
#if 0	/* we *do* support internet posts to specific people July-11-2002 */
		!(cfg.sub[subnum]->misc&SUB_INET) &&	// Prompt for TO: user
#endif
		(cfg.sub[subnum]->misc&SUB_TOUSER || msgattr&MSG_PRIVATE || touser[0])) {
138
		if(!touser[0] && !(msgattr&MSG_PRIVATE))
139
			SAFECOPY(touser,"All");
140 141 142 143
		bputs(text[PostTo]);
		i=LEN_ALIAS;
		if(cfg.sub[subnum]->misc&SUB_QNET)
			i=25;
144
		if(cfg.sub[subnum]->misc&SUB_FIDO)
145
			i=FIDO_NAME_LEN-1;
146 147
		if(cfg.sub[subnum]->misc&(SUB_PNET|SUB_INET))
			i=60;
148
		getstr(touser,i,K_LINE|K_EDIT|K_AUTODEL|K_TRIM,names);
149
		if(stricmp(touser,"ALL")
150
			&& !(cfg.sub[subnum]->misc&(SUB_PNET|SUB_FIDO|SUB_QNET|SUB_INET|SUB_ANON))) {
151
			if(cfg.sub[subnum]->misc&SUB_NAME) {
152
				if(!userdatdupe(useron.number,U_NAME,LEN_NAME,touser)) {
153
					bputs(text[UnknownUser]);
154
					strListFree(&names);
155 156 157
					return(false); 
				} 
			}
158
			else {
159 160
				if((i=finduser(touser))==0) {
					strListFree(&names);
161
					return(false);
162
				}
163 164 165
				username(&cfg,i,touser); 
			} 
		}
166 167 168 169
		if(sys_status&SS_ABORT) {
			strListFree(&names);
			return(false);
		}
170
	}
171
	strListFree(&names);
172 173

	if(!touser[0])
174
		SAFECOPY(touser,"All");       // Default to ALL
175 176 177 178 179 180

	if(!stricmp(touser,"SYSOP") && !SYSOP)  // Change SYSOP to user #1
		username(&cfg,1,touser);

	if(msgattr&MSG_PRIVATE && !stricmp(touser,"ALL")) {
		bputs(text[NoToUser]);
181 182
		return(false); 
	}
183 184 185 186 187
	if(msgattr&MSG_PRIVATE)
		wm_mode|=WM_PRIVATE;

	if(cfg.sub[subnum]->misc&SUB_AONLY
		|| (cfg.sub[subnum]->misc&SUB_ANON && useron.exempt&FLAG('A')
188
			&& !noyes(text[AnonymousQ]))) {
189
		msgattr|=MSG_ANONYMOUS;
190 191
		wm_mode|=WM_ANON;
	}
192

193
	if(cfg.sub[subnum]->mod_ar[0] && chk_ar(cfg.sub[subnum]->mod_ar,&useron,&client))
194 195 196 197 198 199 200 201 202 203
		msgattr|=MSG_MODERATED;

	if(cfg.sub[subnum]->misc&SUB_SYSPERM && sub_op(subnum))
		msgattr|=MSG_PERMANENT;

	if(msgattr&MSG_PRIVATE)
		bputs(text[PostingPrivately]);

	if(msgattr&MSG_ANONYMOUS)
		bputs(text[PostingAnonymously]);
204
	else if(cfg.sub[subnum]->misc&SUB_NAME)
205 206
		bputs(text[UsingRealName]);

207
	msg_tmp_fname(useron.xedit, str, sizeof(str));
208

209
	if((i=smb_stack(&smb,SMB_STACK_PUSH))!=SMB_SUCCESS) {
210
		errormsg(WHERE,ERR_OPEN,cfg.sub[subnum]->code,i,smb.last_error);
211 212
		return(false); 
	}
213

214
	if((i=msgbase_open(&cfg,&smb,subnum,&storage,&dupechk_hashes,&xlat))!=SMB_SUCCESS) {
215
		errormsg(WHERE,ERR_OPEN,smb.file,i,smb.last_error);
216
		smb_stack(&smb,SMB_STACK_POP);
217 218
		return(false); 
	}
219

220 221 222
	if(remsg != NULL && resmb != NULL && !(wm_mode&WM_QUOTE)) {
		if(quotemsg(resmb, remsg))
			wm_mode |= WM_QUOTE;
223 224 225 226
	}

	if(!writemsg(str,top,title,wm_mode,subnum,touser
		,/* from: */cfg.sub[subnum]->misc&SUB_NAME ? useron.name : useron.alias
227
		,&editor, &charset)
228 229 230 231 232 233
		|| (length=(long)flength(str))<1) {	/* Bugfix Aug-20-2003: Reject negative length */
		bputs(text[Aborted]);
		smb_close(&smb);
		smb_stack(&smb,SMB_STACK_POP);
		return(false); 
	}
234 235 236 237 238
	if((cfg.sub[subnum]->misc&SUB_MSGTAGS)
		&& (tags[0] || text[TagMessageQ][0] == 0 || !noyes(text[TagMessageQ]))) {
		bputs(text[TagMessagePrompt]);
		getstr(tags, sizeof(tags)-1, K_EDIT|K_LINE|K_TRIM);
	}
239 240 241

	bputs(text[WritingIndx]);

242
	if((i=smb_locksmbhdr(&smb))!=SMB_SUCCESS) {
243 244
		smb_close(&smb);
		errormsg(WHERE,ERR_LOCK,smb.file,i,smb.last_error);
245
		smb_stack(&smb,SMB_STACK_POP);
246 247
		return(false); 
	}
248

249
	if((i=smb_getstatus(&smb))!=SMB_SUCCESS) {
250 251
		smb_close(&smb);
		errormsg(WHERE,ERR_READ,smb.file,i,smb.last_error);
252
		smb_stack(&smb,SMB_STACK_POP);
253 254
		return(false); 
	}
255

256
	if((msgbuf=(char*)calloc(length+1,sizeof(char))) == NULL) {
257
		smb_close(&smb);
258
		errormsg(WHERE,ERR_ALLOC,"msgbuf",length+1);
259
		smb_stack(&smb,SMB_STACK_POP);
260
		return(false);
261
	}
262

263 264
	if((fp=fopen(str,"rb"))==NULL) {
		free(msgbuf);
265 266
		smb_close(&smb);
		errormsg(WHERE,ERR_OPEN,str,O_RDONLY|O_BINARY);
267
		smb_stack(&smb,SMB_STACK_POP);
268 269
		return(false); 
	}
270

271 272 273 274 275 276 277 278
	i=fread(msgbuf,1,length,fp);
	fclose(fp);
	if(i != length) {
		free(msgbuf);
		smb_close(&smb);
		errormsg(WHERE,ERR_READ,str,length);
		smb_stack(&smb,SMB_STACK_POP);
		return(false);
279
	}
280
	truncsp(msgbuf);
281

282 283 284
	/* ToDo: split body/tail */

	memset(&msg,0,sizeof(msg));
285
	msg.hdr.attr=msgattr;
286
	msg.hdr.when_written.time=msg.hdr.when_imported.time=time32(NULL);
287
	msg.hdr.when_written.zone=msg.hdr.when_imported.zone=sys_timezone(&cfg);
288

289
	msg.hdr.number=smb.status.last_msg+1; /* this *should* be the new message number */
290

291
	smb_hfield_str(&msg,RECIPIENT,touser);
292

293
	SAFECOPY(str,cfg.sub[subnum]->misc&SUB_NAME ? useron.name : useron.alias);
294
	smb_hfield_str(&msg,SENDER,str);
295 296

	sprintf(str,"%u",useron.number);
297
	smb_hfield_str(&msg,SENDEREXT,str);
298

299
	/* Security logging */
300
	msg_client_hfields(&msg,&client);
301
	smb_hfield_str(&msg,SENDERSERVER, server_host_name());
302

303
	smb_hfield_str(&msg,SUBJECT,title);
304

305
	add_msg_ids(&cfg, &smb, &msg, remsg);
306

307
	editor_info_to_msg(&msg, editor, charset);
308 309 310
	
	if(tags[0])
		smb_hfield_str(&msg, SMB_TAGS, tags);
311

312
	i=smb_addmsg(&smb,&msg,storage,dupechk_hashes,xlat,(uchar*)msgbuf, findsig(msgbuf));
313 314 315 316 317 318 319
	free(msgbuf);

	if(i==SMB_DUPE_MSG) {
		attr(cfg.color[clr_err]);
		bprintf(text[CantPostMsg], smb.last_error);
	} else if(i!=SMB_SUCCESS)
		errormsg(WHERE,ERR_WRITE,smb.file,i,smb.last_error);
320 321 322 323

	smb_close(&smb);
	smb_stack(&smb,SMB_STACK_POP);
	smb_freemsgmem(&msg);
324
	if(i!=SMB_SUCCESS)
325
		return(false); 
326 327

	logon_posts++;
328
	user_posted_msg(&cfg, &useron, 1);
329 330
	bprintf(text[Posted],cfg.grp[cfg.sub[subnum]->grp]->sname
		,cfg.sub[subnum]->lname);
331 332
	sprintf(str,"posted on %s %s"
		,cfg.grp[cfg.sub[subnum]->grp]->sname,cfg.sub[subnum]->lname);
333
	logline("P+",str);
334

335 336 337
	if(!(msgattr & MSG_ANONYMOUS)
		&& stricmp(touser, "All") != 0
		&& (remsg == NULL || remsg->from_net.type == NET_NONE)) {
338 339 340 341 342 343 344 345 346 347 348 349 350
		if(cfg.sub[subnum]->misc&SUB_NAME)
			i = userdatdupe(0, U_NAME, LEN_NAME, touser);
		else
			i = matchuser(&cfg, touser, TRUE /* sysop_alias */);
		if(i > 0 && i != useron.number) {
			SAFEPRINTF4(str, text[MsgPostedToYouVia]
				,cfg.sub[subnum]->misc&SUB_NAME ? useron.name : useron.alias
				,connection
				,cfg.grp[cfg.sub[subnum]->grp]->sname,cfg.sub[subnum]->lname);
			putsmsg(&cfg, i, str);
		}
	}

351
	signal_sub_sem(&cfg,subnum);
352

353 354
	user_event(EVENT_POST);

355 356 357
	return(true);
}

Rob Swindell's avatar
Rob Swindell committed
358
extern "C" void signal_sub_sem(scfg_t* cfg, uint subnum)
359
{
360
	char str[MAX_PATH+1];
361

362
	if(subnum==INVALID_SUB || subnum>=cfg->total_subs)	/* e-mail? */
363 364 365
		return;

	/* signal semaphore files */
366
	if(cfg->sub[subnum]->misc&SUB_FIDO && cfg->echomail_sem[0])		
367
		ftouch(cmdstr(cfg,NULL,cfg->echomail_sem,nulstr,nulstr,str,sizeof(str)));
368
	if(cfg->sub[subnum]->post_sem[0]) 
369
		ftouch(cmdstr(cfg,NULL,cfg->sub[subnum]->post_sem,nulstr,nulstr,str,sizeof(str)));
370
}
371

Rob Swindell's avatar
Rob Swindell committed
372
extern "C" int msg_client_hfields(smbmsg_t* msg, client_t* client)
373
{
374 375
	int		i;
	char	port[16];
376
	char	date[64];
377

378 379 380
	if(client==NULL)
		return(-1);

381
	if(client->user!=NULL && client->usernum && (i=smb_hfield_str(msg,SENDERUSERID,client->user))!=SMB_SUCCESS)
382
		return(i);
383 384
	if(client->time
		&& (i=smb_hfield_str(msg,SENDERTIME,xpDateTime_to_isoDateTimeStr(gmtime_to_xpDateTime(client->time)
385 386 387
		,/* separators: */"","","", /* precision: */0
		,date,sizeof(date))))!=SMB_SUCCESS)
		return(i);
388 389
	if(*client->addr
		&& (i=smb_hfield_str(msg,SENDERIPADDR,client->addr))!=SMB_SUCCESS)
390
		return(i);
391 392
	if(*client->host
		&& (i=smb_hfield_str(msg,SENDERHOSTNAME,client->host))!=SMB_SUCCESS)
393
		return(i);
394
	if(client->protocol!=NULL && (i=smb_hfield_str(msg,SENDERPROTOCOL,client->protocol))!=SMB_SUCCESS)
395
		return(i);
396 397 398 399 400
	if(client->port) {
		SAFEPRINTF(port,"%u",client->port);
		return smb_hfield_str(msg,SENDERPORT,port);
	}
	return SMB_SUCCESS;
401 402
}

403
/* Note: finds signature delimiter automatically and (if applicable) separates msgbuf into body and tail */
404
/* Adds/generates Message-IDs when needed */
405 406
/* Auto-sets the UTF-8 indicators for UTF-8 encoded header fields and body text */
/* If you want to save a message body with CP437 chars that also happen to be valid UTF-8 sequences, you'll need to preset the ftn_charset header */
Rob Swindell's avatar
Rob Swindell committed
407
extern "C" int savemsg(scfg_t* cfg, smb_t* smb, smbmsg_t* msg, client_t* client, const char* server, char* msgbuf, smbmsg_t* remsg)
408
{
409
	ushort	xlat=XLAT_NONE;
410
	int 	i;
411
	long	dupechk_hashes=SMB_HASH_SOURCE_DUPE;
412

413 414 415
	if(msg==NULL)
		return(SMB_FAILURE);

416
	if(!SMB_IS_OPEN(smb)) {
417
		if(smb->subnum==INVALID_SUB)
418 419
			sprintf(smb->file,"%smail",cfg->data_dir);
		else
420
			sprintf(smb->file,"%s%s",cfg->sub[smb->subnum]->data_dir,cfg->sub[smb->subnum]->code);
421
		smb->retry_time=cfg->smb_retry_time;
422
		if((i=smb_open(smb))!=SMB_SUCCESS)
423 424 425
			return(i);
	}

426 427 428 429 430 431 432 433 434 435
	/* Lock the msgbase early to preserve our message number (used in MSG-IDs) */
	if(!smb->locked && smb_locksmbhdr(smb)!=SMB_SUCCESS)
		return(SMB_ERR_LOCK);

	if(filelength(fileno(smb->shd_fp))>0 && (i=smb_getstatus(smb))!=SMB_SUCCESS) {
		if(smb->locked)
			smb_unlocksmbhdr(smb);
		return(i);
	}

436
	if(smb->subnum==INVALID_SUB) {	/* e-mail */
437

438 439 440 441
		smb->status.max_crcs=cfg->mail_maxcrcs;
		smb->status.max_age=cfg->mail_maxage;
		smb->status.max_msgs=0;	/* unlimited */
		smb->status.attr=SMB_EMAIL;
442

443 444 445
		/* duplicate message-IDs must be allowed in mail database */
		dupechk_hashes&=~(1<<SMB_HASH_SOURCE_MSG_ID);

446
	} else {	/* sub-board */
447

448 449 450 451
		smb->status.max_crcs=cfg->sub[smb->subnum]->maxcrcs;
		smb->status.max_msgs=cfg->sub[smb->subnum]->maxmsgs;
		smb->status.max_age=cfg->sub[smb->subnum]->maxage;
		smb->status.attr=0;
452

453
		if(cfg->sub[smb->subnum]->misc&SUB_LZH)
454
			xlat=XLAT_LZH;
455

456 457 458 459 460
		/* enforce anonymous/private posts only */
 		if(cfg->sub[smb->subnum]->misc&SUB_PONLY)
 			msg->hdr.attr|=MSG_PRIVATE;
 		if(cfg->sub[smb->subnum]->misc&SUB_AONLY)
 			msg->hdr.attr|=MSG_ANONYMOUS;
461 462
	}

463
	if(msg->hdr.when_imported.time==0) {
464
		msg->hdr.when_imported.time=time32(NULL);
465 466
		msg->hdr.when_imported.zone=sys_timezone(cfg);
	}
467 468
	if(msg->hdr.when_written.time==0)	/* Uninitialized */
		msg->hdr.when_written = msg->hdr.when_imported;
469

470
	msg->hdr.number=smb->status.last_msg+1;		/* needed for MSG-ID generation */
471

472 473 474 475 476
	if(smb->status.max_crcs==0)	/* no CRC checking means no body text dupe checking */
		dupechk_hashes&=~(1<<SMB_HASH_SOURCE_BODY);

	if(client!=NULL)
		msg_client_hfields(msg,client);
477 478
	if(server!=NULL)
		smb_hfield_str(msg,SENDERSERVER,server);
479
 
480
	add_msg_ids(cfg, smb, msg, remsg);
481

482 483 484 485 486
	if((msg->to != NULL && !str_is_ascii(msg->to) && utf8_str_is_valid(msg->to))
		|| (msg->from != NULL && !str_is_ascii(msg->from) && utf8_str_is_valid(msg->from))
		|| (msg->subj != NULL && !str_is_ascii(msg->subj) && utf8_str_is_valid(msg->subj)))
		msg->hdr.auxattr |= MSG_HFIELDS_UTF8;

487 488 489
	if(msg->ftn_charset == NULL && !str_is_ascii(msgbuf) && utf8_str_is_valid(msgbuf))
		smb_hfield_str(msg, FIDOCHARSET, FIDO_CHARSET_UTF8);

490 491 492 493
	msgbuf = strdup(msgbuf);
	if(msgbuf == NULL)
		return SMB_FAILURE;
	if((i=smb_addmsg(smb,msg,smb_storage_mode(cfg, smb),dupechk_hashes,xlat,(uchar*)msgbuf, findsig(msgbuf)))==SMB_SUCCESS
rswindell's avatar
rswindell committed
494 495
		&& msg->to!=NULL	/* no recipient means no header created at this stage */) {
		if(smb->subnum == INVALID_SUB) {
496 497 498 499
			if(msg->to_net.type == NET_FIDO && cfg->netmail_sem[0]) {
				char tmp[MAX_PATH + 1];
				ftouch(cmdstr(cfg,NULL,cfg->netmail_sem,nulstr,nulstr,tmp, sizeof(tmp)));
			}
rswindell's avatar
rswindell committed
500 501
		} else
			signal_sub_sem(cfg,smb->subnum);
502 503

		if(msg->to_net.type == NET_NONE && !(msg->hdr.attr & MSG_ANONYMOUS) && cfg->text != NULL) {
504
			int usernum = 0;
505
			if(msg->to_ext != NULL)
506
				usernum = atoi(msg->to_ext);
507
			else if(smb->subnum != INVALID_SUB && (cfg->sub[smb->subnum]->misc & SUB_NAME))
508
				usernum = userdatdupe(cfg, 0, U_NAME, LEN_NAME, msg->to, /* del: */FALSE, /* next: */FALSE, NULL, NULL);
509
			else
510 511
				usernum = matchuser(cfg, msg->to, TRUE /* sysop_alias */);
			if(usernum > 0 && (client == NULL || usernum != (int)client->usernum)) {
512 513 514
				char str[256];
				if(smb->subnum == INVALID_SUB) {
					safe_snprintf(str, sizeof(str), cfg->text[UserSentYouMail], msg->from);
515
					putsmsg(cfg, usernum, str);
516 517 518 519 520 521 522 523 524
				} else {
					char fido_buf[64];
					const char* via = smb_netaddrstr(&msg->from_net, fido_buf);
					if(via == NULL)
						via = (client == NULL) ? "" : client->protocol;
					safe_snprintf(str, sizeof(str), cfg->text[MsgPostedToYouVia]
						,msg->from
						,via
						,cfg->grp[cfg->sub[smb->subnum]->grp]->sname,cfg->sub[smb->subnum]->lname);
525
					putsmsg(cfg, usernum, str);
526 527 528
				}
			}
		}
rswindell's avatar
rswindell committed
529
	}
530
	free(msgbuf);
531 532
	return(i);
}
533

Rob Swindell's avatar
Rob Swindell committed
534
extern "C" int votemsg(scfg_t* cfg, smb_t* smb, smbmsg_t* msg, const char* smsgfmt, const char* votefmt)
535 536 537 538 539 540
{
	int result;
	smbmsg_t remsg;

	ZERO_VAR(remsg);

541 542 543 544 545 546 547
	if(msg->hdr.when_imported.time == 0) {
		msg->hdr.when_imported.time = time32(NULL);
		msg->hdr.when_imported.zone = sys_timezone(cfg);
	}
	if(msg->hdr.when_written.time == 0)	/* Uninitialized */
		msg->hdr.when_written = msg->hdr.when_imported;

548
	add_msg_ids(cfg, smb, msg, /* remsg: */NULL);
549

550 551 552
	/* Look-up thread_back if RFC822 Reply-ID was specified */
	if(msg->hdr.thread_back == 0 && msg->reply_id != NULL) {
		if(smb_getmsgidx_by_msgid(smb, &remsg, msg->reply_id) == SMB_SUCCESS)
rswindell's avatar
rswindell committed
553
			msg->hdr.thread_back = remsg.idx.number;	/* poll or message being voted on */
554
	}
555 556 557 558 559
	if(msg->hdr.thread_back == 0) {
		safe_snprintf(smb->last_error, sizeof(smb->last_error), "%s thread_back field is zero (reply_id=%s, ftn_reply=%s)"
			,__FUNCTION__, msg->reply_id, msg->ftn_reply);
		return SMB_ERR_HDR_FIELD;
	}
560 561
	if(smb_voted_already(smb, msg->hdr.thread_back, msg->from, (enum smb_net_type)msg->from_net.type, msg->from_net.addr))
		return SMB_DUPE_MSG;
rswindell's avatar
rswindell committed
562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578
	remsg.hdr.number = msg->hdr.thread_back;
	if((result = smb_getmsgidx(smb, &remsg)) != SMB_SUCCESS)
		return result;
	if((result = smb_getmsghdr(smb, &remsg)) != SMB_SUCCESS)
		return result;
	if(remsg.hdr.auxattr&POLL_CLOSED)
		result = SMB_CLOSED;
	else
		result = smb_addvote(smb, msg, smb_storage_mode(cfg, smb));
	if(result == SMB_SUCCESS && smsgfmt != NULL && remsg.from_ext != NULL) {
		user_t user;
		ZERO_VAR(user);
		user.number = atoi(remsg.from_ext);
		if(getuserdat(cfg, &user) == 0 && 
			(stricmp(remsg.from, user.alias) == 0 || stricmp(remsg.from, user.name) == 0)) {
			char from[256];
			char tstr[128];
579 580
			char smsg[4000];
			char votes[3000] = "";
rswindell's avatar
rswindell committed
581 582 583 584
			if(msg->from_net.type)
				safe_snprintf(from, sizeof(from), "%s (%s)", msg->from, smb_netaddr(&msg->from_net));
			else
				SAFECOPY(from, msg->from);
585
			if(remsg.hdr.type == SMB_MSG_TYPE_POLL && votefmt != NULL) {
586 587 588 589 590
				int answers = 0;
				for(int i=0; i<remsg.total_hfields; i++) {
					if(remsg.hfield[i].type == SMB_POLL_ANSWER) {
						if(msg->hdr.votes&(1<<answers)) {
							char vote[128];
591
							SAFEPRINTF(vote, votefmt, (char*)remsg.hfield_dat[i]);
592 593 594 595 596 597
							SAFECAT(votes, vote);
						}
						answers++;
					}
				}
			}
rswindell's avatar
rswindell committed
598 599 600 601 602
			safe_snprintf(smsg, sizeof(smsg), smsgfmt
				,timestr(cfg, msg->hdr.when_written.time, tstr)
				,cfg->grp[cfg->sub[smb->subnum]->grp]->sname
				,cfg->sub[smb->subnum]->sname
				,from
603 604
				,remsg.subj);
			SAFECAT(smsg, votes);
rswindell's avatar
rswindell committed
605
			putsmsg(cfg, user.number, smsg);
606 607
		}
	}
rswindell's avatar
rswindell committed
608 609 610 611
	smb_freemsgmem(&remsg);
	return result;
}

Rob Swindell's avatar
Rob Swindell committed
612
extern "C" int closepoll(scfg_t* cfg, smb_t* smb, uint32_t msgnum, const char* username)
rswindell's avatar
rswindell committed
613 614 615 616 617 618 619 620 621 622 623
{
	int result;
	smbmsg_t msg;

	ZERO_VAR(msg);

	msg.hdr.when_imported.time = time32(NULL);
	msg.hdr.when_imported.zone = sys_timezone(cfg);
	msg.hdr.when_written = msg.hdr.when_imported;
	msg.hdr.thread_back = msgnum;
	smb_hfield_str(&msg, SENDER, username);
624

625
	add_msg_ids(cfg, smb, &msg, /* remsg: */NULL);
626

rswindell's avatar
rswindell committed
627 628 629
	result = smb_addpollclosure(smb, &msg, smb_storage_mode(cfg, smb));

	smb_freemsgmem(&msg);
630
	return result;
rswindell's avatar
rswindell committed
631
}
632

Rob Swindell's avatar
Rob Swindell committed
633
extern "C" int postpoll(scfg_t* cfg, smb_t* smb, smbmsg_t* msg)
634 635 636 637 638 639 640 641
{
	if(msg->hdr.when_imported.time == 0) {
		msg->hdr.when_imported.time = time32(NULL);
		msg->hdr.when_imported.zone = sys_timezone(cfg);
	}
	if(msg->hdr.when_written.time == 0)
		msg->hdr.when_written = msg->hdr.when_imported;

642 643
	add_msg_ids(cfg, smb, msg, /* remsg: */NULL);

644 645
	return smb_addpoll(smb, msg, smb_storage_mode(cfg, smb));
}
646 647

// Send an email and a short-message to a local user about something important (e.g. a system error)
Rob Swindell's avatar
Rob Swindell committed
648
extern "C" int notify(scfg_t* cfg, uint usernumber, const char* subject, const char* text)
649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686
{
	int			i;
	smb_t		smb = {0};
	uint16_t	xlat;
	int			storage;
	long		dupechk_hashes;
	uint16_t	agent = AGENT_PROCESS;
	uint16_t	nettype = NET_UNKNOWN;
	smbmsg_t	msg = {0};
	user_t		user = {0};
	char		str[128];

	user.number = usernumber;
	if((i = getuserdat(cfg, &user)) != 0)
		return i;

	msg.hdr.when_imported.time = time32(NULL);
	msg.hdr.when_imported.zone = sys_timezone(cfg);
	msg.hdr.when_written = msg.hdr.when_imported;
	smb_hfield(&msg, SENDERAGENT, sizeof(agent), &agent);
	smb_hfield_str(&msg, SENDER, cfg->sys_name);
	smb_hfield_str(&msg, RECIPIENT, user.alias);
	if(cfg->sys_misc&SM_FWDTONET && user.misc&NETMAIL && user.netmail[0]) {
		smb_hfield_netaddr(&msg, RECIPIENTNETADDR, user.netmail, &nettype);
		smb_hfield_bin(&msg, RECIPIENTNETTYPE, nettype);
	} else {
		SAFEPRINTF(str, "%u", usernumber);
		smb_hfield_str(&msg, RECIPIENTEXT, str);
	}
	smb_hfield_str(&msg, SUBJECT, subject);
	add_msg_ids(cfg, &smb, &msg, /* remsg: */NULL);
	if(msgbase_open(cfg, &smb, INVALID_SUB, &storage, &dupechk_hashes, &xlat) == SMB_SUCCESS) {
		smb_addmsg(&smb, &msg, storage, dupechk_hashes, xlat, (uchar*)text, /* tail: */NULL);
		smb_close(&smb);
	}
	smb_freemsgmem(&msg);

	char smsg[1024];
687 688 689 690 691 692 693 694 695
	if(text != NULL)
		safe_snprintf(smsg, sizeof(smsg),"\1n\1h%s \1r%s:\r\n%s\1n\r\n"
			,timestr(cfg, msg.hdr.when_imported.time, str)
			,subject
			,text);
	else
		safe_snprintf(smsg, sizeof(smsg),"\1n\1h%s \1r%s\1n\r\n"
			,timestr(cfg, msg.hdr.when_imported.time, str)
			,subject);
696 697
	return putsmsg(cfg, usernumber, smsg);
}