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