1 /*
2 ** Copyright 2000-2007 Double Precision, Inc.
3 ** See COPYING for distribution information.
4 */
5 
6 #include	"config.h"
7 #include	"cmlm.h"
8 #include	"cmlmsubunsubmsg.h"
9 #include	"afx/afx.h"
10 #include	"random128/random128.h"
11 #include	"dbobj.h"
12 #include	"mydirent.h"
13 #include	<iostream>
14 #include	<fstream>
15 #include	<ctype.h>
16 #include	<time.h>
17 #include	<errno.h>
18 #include	<sysexits.h>
19 #include	<string.h>
20 
21 static const char rcsid[]="$Id: cmlmsubunsubmsg.C,v 1.13 2007/12/17 12:09:18 mrsam Exp $";
22 //
23 //  Initial subscription/unsubscription request.
24 //
25 
26 static int dosubunsub(std::string, std::string,
27 	const char *, const char *, const char *, int, const char *);
28 
dosub(const char * address)29 int dosub(const char *address)
30 {
31 	std::string postoptions= cmdget_s("SUBSCRIBE");
32 	std::string msg(readmsg());
33 	std::string addr=returnaddr(msg, address);
34 
35 	if (postoptions == "mod")
36 		return (dosubunsub(msg, addr, "sub", "modsubconfirm",
37 			"sub4.tmpl", 1, 0));
38 
39 	return (dosubunsub(msg, addr, "sub", "subconfirm", "sub.tmpl", 0, 0));
40 }
41 
dounsub(const char * address)42 int dounsub(const char *address)
43 {
44 	std::string msg(readmsg());
45 	std::string addr=returnaddr(msg, address);
46 
47 	return (dosubunsub(msg, addr, "unsub", "unsubconfirm", "unsub.tmpl",
48 		0, 0));
49 }
50 
51 // Set a write-only alias.
52 
doalias(const char * address)53 int doalias(const char *address)
54 {
55 	std::string msg(readmsg());
56 	std::string addr=returnaddr(msg, address);
57 
58 	std::string x_alias="X-Alias: " + header_s(msg, "subject");
59 
60 	return (dosubunsub(msg, addr, "alias", "aliasconfirm", "sub.tmpl", 0,
61 			   x_alias.c_str()));
62 }
63 
64 // Moderated subscription approval
65 
domodsub(const char * address)66 int domodsub(const char *address)
67 {
68 	std::string msg(readmsg());
69 	std::string addr="";
70 
71 	std::string filename=address;
72 
73 	std::string::iterator b=filename.begin(), e=filename.end();
74 
75 	std::string::iterator p=
76 		std::find_if(b, e, std::not1(std::ptr_fun(::isalpha)));
77 
78 	if (p == e)
79 	{
80 		std::transform(b, e, b, std::ptr_fun(::toupper));
81 
82                 filename=COMMANDS "/sub." + filename;
83 
84 		std::ifstream	ifs(filename.c_str());
85 
86 		if (!std::getline(ifs, addr).good())
87                         addr="";
88         }
89 
90 	if (addr == "")
91 	{
92 		std::cerr << "Invalid address." << std::endl;
93 		return (EX_SOFTWARE);
94 	}
95 
96 	std::string token;
97 	std::string subj;
98 
99 {
100 CommandLock	cmd_lock;
101 
102 DbObj	dat;
103 
104 	if (dat.Open(COMMANDSDAT, "C"))
105 	{
106 		perror(COMMANDSDAT);
107 		return (EX_OSERR);
108 	}
109 
110 	std::ifstream	ifs;
111 
112 	std::string filename;
113 	std::string key="sub." + addr;
114 	std::string subfilename;
115 
116 	addrlower(key);
117 
118 	filename=dat.Fetch(key, "");
119 
120 	if (filename.size())
121 	{
122 		subfilename= COMMANDS "/" + filename;
123 
124 		ifs.open(subfilename.c_str());
125 	}
126 
127 	if (!ifs.is_open() || !std::getline(ifs, token).good())
128 	{
129 		std::cerr << "Invalid confirmation." << std::endl;
130 		return (EX_SOFTWARE);
131 	}
132 
133 	while (std::getline(ifs, token).good())
134 		if (token.size() == 0) break;
135 
136 	while (std::getline(ifs, token).good())
137 		if (token == MSGSEPARATOR)	break;
138 
139 	subj=header_s(msg, "subject");		// Original subject
140 
141 // Rebuild original msg.
142 
143 	msg="";
144 
145 	while (std::getline(ifs, token).good())
146 	{
147 		if (token.size() == 0)	continue;
148 		msg += token;
149 		msg += '\n';
150 	}
151 
152 	ifs.close();
153 	unlink(subfilename.c_str());
154 	dat.Delete(key);
155 }
156 
157 	TrimLeft(subj);
158 
159 	if (subj.substr(0, 2) == "no")
160 	{
161 		std::string owner=get_verp_return("owner");
162 
163 		pid_t	p;
164 		afxopipestream ack(sendmail_bcc(p, owner));
165 
166 		ack << "From: " << myname() << " <" << owner << ">" << std::endl
167 			<< "To: " << addr << std::endl
168 			<< "Bcc: " << addr << std::endl;
169 
170 		simple_template(ack, "sub5.tmpl", msg);
171 		ack.close();
172 		return (wait4sendmail(p));
173 	}
174 
175 	return (dosubunsub(msg, addr, "sub", "subconfirm", "sub.tmpl", 0, 0));
176 }
177 
dosubunsub(std::string msg,std::string addr,const char * cmdpfix,const char * retpfix,const char * tpl,int flag,const char * xh)178 static int dosubunsub(std::string msg,	// Message headers, for logging
179 		      std::string addr,	// Parsed address
180 		      const char *cmdpfix, // Prefix for the commands/ file
181 		      const char *retpfix, // Prefix on the return address
182 		      const char *tpl,	// Template for the acknowledgement
183 		      int flag,		// Non-true if moderator sub approval
184 		      const char * xh)	// Extra header on response, for aliases
185 {
186 	std::fstream	tokfile;
187 
188 	if (addr.size() == 0)
189 	{
190 		std::cerr << "Invalid address." << std::endl;
191 		return (EX_SOFTWARE);
192 	}
193 
194 CommandLock	cmd_lock;
195 
196 DbObj	dat;
197 
198 	if (dat.Open(COMMANDSDAT, "C"))
199 	{
200 		perror(COMMANDSDAT);
201 		return (EX_OSERR);
202 	}
203 
204 	std::string key(cmdpfix);
205 
206 	key += ".";
207 	key += addr;
208 
209 	addrlower(key);
210 
211 	std::string subfilename;
212 
213 	struct	stat stat_buf;
214 
215 	std::string filename;
216 
217 	filename=dat.Fetch(key, "");
218 
219 	if (filename.size())
220 	{
221 		// Already received previous command from this sender
222 
223 		subfilename= COMMANDS "/" + filename;
224 	}
225 	else
226 	{
227 		// Create new command file for this sender
228 
229 		std::string f;
230 
231 		do
232 		{
233 			f=cmdpfix;
234 			f += ".";
235 			f += random128_alpha();
236 
237 			subfilename = COMMANDS "/" + f;
238 
239 		} while (stat(subfilename.c_str(), &stat_buf) == 0);
240 
241 		if (dat.Store(key, f, "R"))
242 		{
243 			perror(COMMANDSDAT);
244 			return (EX_OSERR);
245 		}
246 	}
247 
248 time_t	curtime;
249 
250 	time(&curtime);
251 
252 //
253 //  At most, acknowledge a subscription for the same addresses no
254 //  more frequently then once per 30 minutes -- this stops a subscription bomb.
255 //
256 	if (stat(subfilename.c_str(), &stat_buf) == 0
257 	    && stat_buf.st_mtime >= curtime - 30 * 60)
258 		return (0);
259 
260 //
261 //  Into the token file we write: address, newline, token, newline, then the
262 //  confirmation request.
263 //
264 
265 	tokfile.open(subfilename.c_str(),
266 		     std::ios::in | std::ios::out | std::ios::trunc);
267 
268 	if (!tokfile.is_open())
269 	{
270 		perror("open");
271 		return (1);
272 	}
273 
274 	std::string me=retpfix;
275 
276 	me += "-";
277 	me += strrchr(subfilename.c_str(), '.')+1;
278 
279 	me=get_verp_return(me);
280 
281 	std::string owner=get_verp_return("owner");
282 
283 	// Use get_verp_return to compute the mailing list owner's address
284 
285 	std::string sendto=addr;
286 
287 
288 	if (flag)
289 		sendto=owner;
290 
291 	tokfile << addr << std::endl;
292 
293 	if (xh)
294 		tokfile << xh << std::endl;
295 
296 	tokfile << "From: " << myname() << " <" << owner << ">" << std::endl
297 		<< "Reply-To: " << me << std::endl
298 		<< "To: " << sendto << std::endl;
299 
300 	{
301 		std::ifstream ifs("confsubj.tmpl");
302 
303 		if (!ifs.bad())
304 		{
305 			std::string s;
306 
307 			if (std::getline(ifs, s).good())
308 			{
309 				tokfile << s << std::endl;
310 			}
311 		}
312 	}
313 
314 	copy_template(tokfile, "subjrequest.tmpl", "");
315 
316 	ack_template(tokfile, tpl,
317 		     std::string(retpfix) + "/" +
318 		     (strrchr(subfilename.c_str(), '.')+1), msg);
319 
320 	std::string buf;
321 
322 	tokfile.seekg(0);
323 	if (tokfile.bad())
324 	{
325 		perror("write");
326 		tokfile.close();
327 		unlink(subfilename.c_str());
328 		return (EX_OSERR);
329 	}
330 
331 	pid_t	p;
332 	int	nodsn= (cmdget_s("NODSN") == "1");
333 	afxopipestream	ack(sendmail_bcc(p, owner.c_str(), nodsn));
334 
335 	ack << "Bcc: " << sendto << std::endl;
336 
337 	std::getline(tokfile, buf); // Skip token number
338 
339 	while (std::getline(tokfile, buf).good())
340 		ack << buf << std::endl;
341 
342 	tokfile.close();
343 	ack.flush();
344 	if (ack.fail())
345 	{
346 		std::cerr << strerror(errno) << std::endl;
347 		exit(1);
348 	}
349 
350 	ack.close();
351 	return (wait4sendmail(p));
352 }
353 
354 //
355 //  Subscription confirmation.
356 //
357 
dosubunsubconfirm(const char * address,const char * pfix,int (* func)(const char *,std::string),const char * good_template,const char * bad_template,int donotsend)358 int dosubunsubconfirm(const char *address, const char *pfix,
359 		      int (*func)(const char *, std::string),
360 		      const char *good_template,
361 		      const char *bad_template,
362 		      int donotsend)
363 {
364 	std::string msg(readmsg());
365 	std::string addr="";
366 
367 	std::string filename=address;
368 
369 	std::string::iterator b=filename.begin(), e=filename.end();
370 
371 	std::string::iterator p=
372 		std::find_if(b, e, std::not1(std::ptr_fun(::isalpha)));
373 
374 	if (p == e)
375 	{
376 		std::transform(b, e, b, std::ptr_fun(::toupper));
377 
378 		filename=COMMANDS "/" + (pfix + ("." + filename));
379 
380 		std::ifstream	ifs(filename.c_str());
381 
382 		if (!std::getline(ifs, addr).good())
383 			addr="";
384 	}
385 
386 	if (addr != "")
387 	{
388 	CommandLock	cmd_lock;
389 
390 	DbObj	dat;
391 
392 		if (dat.Open(COMMANDSDAT, "C"))
393 		{
394 			perror(COMMANDSDAT);
395 			return (EX_OSERR);
396 		}
397 
398 		std::ifstream	ifs;
399 
400 		std::string filename;
401 		std::string key=pfix;
402 
403 		key += '.';
404 		key += addr;
405 
406 		std::string subfilename;
407 
408 		addrlower(key);
409 
410 		filename=dat.Fetch(key, "");
411 
412 		if (filename.size())
413 		{
414 			subfilename= COMMANDS "/" + filename;
415 
416 			ifs.open(subfilename.c_str());
417 		}
418 
419 		std::string token;
420 
421 		if (ifs.is_open() &&
422 		    std::getline(ifs, token).good() &&
423 		    checkconfirm(msg))
424 		{
425 //
426 //  We will save the original subscription request, and the acknowledgement,
427 //  in this luser's subscribtion record.
428 //
429 			std::string subbuf;
430 
431 			while (std::getline(ifs, token).good())
432 			{
433 				subbuf += token;
434 				subbuf += '\n';
435 			}
436 			subbuf += "\n*** ACKNOWLEDGEMENT ***\n";
437 			subbuf += msg;
438 			ifs.close();
439 
440 			int	rc=(*func)(addr.c_str(), subbuf);
441 
442 			if (rc == 0)
443 				donotsend=0;
444 
445 			if (rc == 0 || rc == 9)
446 			{
447 				unlink(subfilename.c_str());
448 				dat.Delete(key);
449 
450 				if (!donotsend)
451 				{
452 					pid_t	p;
453 					std::string owner=
454 						get_verp_return("owner");
455 
456 					afxopipestream ack(sendmail_bcc(p, owner));
457 
458 					owner= myname() + (" <" + owner + ">");
459 
460 					ack << "Bcc: " << addr << std::endl
461 						<< "From: " << owner << std::endl
462 						<< "Reply-To: " << owner
463 								<< std::endl
464 						<< "To: "
465 							<< addr
466 							<< std::endl;
467 
468 					copy_template(ack, "suback.tmpl", "");
469 					ack_template(ack,
470 						     rc ? bad_template:
471 						     good_template, "", msg);
472 					ack.flush();
473 					if (ack.fail())
474 					{
475 						std::cerr << strerror(errno) << std::endl;
476 						exit(1);
477 					}
478 					ack.close();
479 					rc=wait4sendmail(p);
480 				}
481 
482 			}
483 			dat.Close();
484 
485 		DIR	*dirp;
486 		struct	dirent	*de=0;
487 
488 			if ((dirp=opendir(COMMANDS)) != 0)
489 			{
490 				while ((de=readdir(dirp)) != 0)
491 					if ((de->d_name[0]) != '.')
492 						break;
493 				closedir(dirp);
494 			}
495 
496 			if (de == 0)	// Commands subdir is empty
497 				unlink(COMMANDSDAT);
498 			return (rc);
499 		}
500 	}
501 	std::cerr << "Invalid confirmation.\n" << std::endl;
502 	return (EX_NOPERM);
503 }
504