Fossil SCM

Rework the internal email sending logic so that it is connection-oriented. This makes it more efficient and makes it easier to add support for an SMTP sending method at a later date.

drh 2018-06-23 14:24 trunk
Commit b42189878e66cfd569298fa26a4a73a3face1fd4a3c4ec193ab13b4da408339f
1 file changed +200 -104
+200 -104
--- src/email.c
+++ src/email.c
@@ -284,19 +284,147 @@
284284
# undef popen
285285
# define popen _popen
286286
# undef pclose
287287
# define pclose _pclose
288288
#endif
289
+
290
+#if INTERFACE
291
+/*
292
+** An instance of the following object is used to send emails.
293
+*/
294
+struct EmailSender {
295
+ sqlite3 *db; /* Database emails are sent to */
296
+ sqlite3_stmt *pStmt; /* Stmt to insert into the database */
297
+ const char *zDest; /* How to send email. */
298
+ const char *zDb; /* Name of database file */
299
+ const char *zDir; /* Directory in which to store as email files */
300
+ const char *zCmd; /* Command to run for each email */
301
+ const char *zFrom; /* Emails come from here */
302
+ char *zErr; /* Error message */
303
+ int bImmediateFail; /* On any error, call fossil_fatal() */
304
+};
305
+#endif /* INTERFACE */
306
+
307
+/*
308
+** Shutdown an emailer. Clear all information other than the error message.
309
+*/
310
+static void emailerShutdown(EmailSender *p){
311
+ sqlite3_finalize(p->pStmt);
312
+ p->pStmt = 0;
313
+ sqlite3_free(p->db);
314
+ p->db = 0;
315
+ p->zDb = 0;
316
+ p->zDir = 0;
317
+ p->zCmd = 0;
318
+ p->zDest = "off";
319
+}
320
+
321
+/*
322
+** Put the EmailSender into an error state.
323
+*/
324
+static void emailerError(EmailSender *p, const char *zFormat, ...){
325
+ va_list ap;
326
+ fossil_free(p->zErr);
327
+ va_start(ap, zFormat);
328
+ p->zErr = vmprintf(zFormat, ap);
329
+ va_end(ap);
330
+ emailerShutdown(p);
331
+ if( p->bImmediateFail ){
332
+ fossil_fatal("%s", p->zErr);
333
+ }
334
+}
335
+
336
+/*
337
+** Free an email sender object
338
+*/
339
+void email_sender_free(EmailSender *p){
340
+ emailerShutdown(p);
341
+ fossil_free(p->zErr);
342
+ fossil_free(p);
343
+}
344
+
345
+/*
346
+** Get an email setting value. Report an error if not configured.
347
+** Return 0 on success and one if there is an error.
348
+*/
349
+static int emailerGetSetting(
350
+ EmailSender *p, /* Where to report the error */
351
+ const char **pzVal, /* Write the setting value here */
352
+ const char *zName /* Name of the setting */
353
+){
354
+ const char *z = db_get(zName, 0);
355
+ int rc = 0;
356
+ if( z==0 || z[0]==0 ){
357
+ emailerError(p, "missing \"%s\" setting", zName);
358
+ rc = 1;
359
+ }else{
360
+ *pzVal = z;
361
+ }
362
+ return rc;
363
+}
289364
290365
/*
291
-** Send an email message using whatever sending mechanism is configured
292
-** by these settings:
366
+** Create a new EmailSender object.
367
+**
368
+** The method used for sending email is determined by various email-*
369
+** settings, and especially email-send-method. The repository
370
+** email-send-method can be overridden by the zAltDest argument to
371
+** cause a different sending mechanism to be used. Pass "stdout" to
372
+** zAltDest to cause all emails to be printed to the console for
373
+** debugging purposes.
293374
**
294
-** email-send-method "off" Do not send any emails
295
-** "pipe" Pipe the email to email-send-command
296
-** "db" Store the mail in database email-send-db
297
-** "file" Store the email as a file in email-send-dir
375
+** The EmailSender object returned must be freed using email_sender_free().
376
+*/
377
+EmailSender *email_sender_new(const char *zAltDest, int bImmediateFail){
378
+ EmailSender *p;
379
+ char *zMissing = 0;
380
+
381
+ p = fossil_malloc(sizeof(*p));
382
+ memset(p, 0, sizeof(*p));
383
+ p->bImmediateFail = bImmediateFail;
384
+ if( zAltDest ){
385
+ p->zDest = zAltDest;
386
+ }else{
387
+ p->zDest = db_get("email-send-method","off");
388
+ }
389
+ if( fossil_strcmp(p->zDest,"off")==0 ) return p;
390
+ if( emailerGetSetting(p, &p->zFrom, "email-self") ) return p;
391
+ if( fossil_strcmp(p->zDest,"db")==0 ){
392
+ char *zErr;
393
+ int rc;
394
+ if( emailerGetSetting(p, &p->zDb, "email-send-db") ) return p;
395
+ rc = sqlite3_open(p->zDb, &p->db);
396
+ if( rc ){
397
+ emailerError(p, "unable to open output database file \"%s\": %s",
398
+ p->zDb, sqlite3_errmsg(p->db));
399
+ return p;
400
+ }
401
+ rc = sqlite3_exec(p->db, "CREATE TABLE IF NOT EXISTS email(\n"
402
+ " emailid INTEGER PRIMARY KEY,\n"
403
+ " msg TEXT\n);", 0, 0, &zErr);
404
+ if( zErr ){
405
+ emailerError(p, "CREATE TABLE failed with \"%s\"", zErr);
406
+ sqlite3_free(zErr);
407
+ return p;
408
+ }
409
+ rc = sqlite3_prepare_v2(p->db, "INSERT INTO email(msg) VALUES(?1)", -1,
410
+ &p->pStmt, 0);
411
+ if( rc ){
412
+ emailerError(p, "cannot prepare INSERT statement: %s",
413
+ sqlite3_errmsg(p->db));
414
+ return p;
415
+ }
416
+ }else if( fossil_strcmp(p->zDest, "pipe")==0 ){
417
+ emailerGetSetting(p, &p->zCmd, "email-send-command");
418
+ }else if( fossil_strcmp(p->zDest, "dir")==0 ){
419
+ emailerGetSetting(p, &p->zDir, "email-send-dir");
420
+ }
421
+ return p;
422
+}
423
+
424
+/*
425
+** Send a single email message.
298426
**
299427
** The recepient(s) must be specified using "To:" or "Cc:" or "Bcc:" fields
300428
** in the header. Likewise, the header must contains a "Subject:" line.
301429
** The header might also include fields like "Message-Id:" or
302430
** "In-Reply-To:".
@@ -305,97 +433,50 @@
305433
**
306434
** From:
307435
** Content-Type:
308436
** Content-Transfer-Encoding:
309437
**
310
-** At least one body must be supplied.
311
-**
312438
** The caller maintains ownership of the input Blobs. This routine will
313439
** read the Blobs and send them onward to the email system, but it will
314440
** not free them.
315
-**
316
-** If zDest is not NULL then it is an overwrite for the email-send-method.
317
-** zDest can be "stdout" to send output to the console for debugging.
318441
*/
319
-void email_send(Blob *pHdr, Blob *pPlain, Blob *pHtml, const char *zDest){
320
- const char *zFrom = db_get("email-self", 0);
321
- char *zBoundary = 0;
442
+void email_send(EmailSender *p, Blob *pHdr, Blob *pBody){
322443
Blob all;
323
- if( zFrom==0 ){
324
- fossil_warning("Missing configuration: \"email-self\"");
325
- return;
326
- }
327
- if( zDest==0 ) zDest = db_get("email-send-method", "off");
328
- if( strcmp(zDest, "off")==0 ){
444
+ if( fossil_strcmp(p->zDest, "off")==0 ){
329445
return;
330446
}
331447
blob_init(&all, 0, 0);
332448
blob_append(&all, blob_buffer(pHdr), blob_size(pHdr));
333
- blob_appendf(&all, "From: %s\r\n", zFrom);
334
- if( pPlain && pHtml ){
335
- blob_appendf(&all, "MIME-Version: 1.0\r\n");
336
- zBoundary = db_text(0, "SELECT hex(randomblob(20))");
337
- blob_appendf(&all, "Content-Type: multipart/alternative;"
338
- " boundary=\"%s\"\r\n", zBoundary);
339
- }
340
- if( pPlain ){
341
- blob_add_final_newline(pPlain);
342
- if( zBoundary ){
343
- blob_appendf(&all, "\r\n--%s\r\n", zBoundary);
344
- }
345
- blob_appendf(&all,"Content-Type: text/plain\r\n");
346
- blob_appendf(&all, "Content-Transfer-Encoding: base64\r\n\r\n");
347
- append_base64(&all, pPlain);
348
- }
349
- if( pHtml ){
350
- blob_add_final_newline(pHtml);
351
- if( zBoundary ){
352
- blob_appendf(&all, "--%s\r\n", zBoundary);
353
- }
354
- blob_appendf(&all,"Content-Type: text/html\r\n");
355
- blob_appendf(&all, "Content-Transfer-Encoding: base64\r\n\r\n");
356
- append_base64(&all, pHtml);
357
- }
358
- if( zBoundary ){
359
- blob_appendf(&all, "--%s--\r\n", zBoundary);
360
- fossil_free(zBoundary);
361
- zBoundary = 0;
362
- }
363
- if( strcmp(zDest, "db")==0 ){
364
- sqlite3 *db;
365
- sqlite3_stmt *pStmt;
366
- int rc;
367
- const char *zDb = db_get("email-send-db",0);
368
- rc = sqlite3_open(zDb, &db);
369
- if( rc==SQLITE_OK ){
370
- sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS email(\n"
371
- " emailid INTEGER PRIMARY KEY,\n"
372
- " msg TEXT\n);", 0, 0, 0);
373
- rc = sqlite3_prepare_v2(db, "INSERT INTO email(msg) VALUES(?1)", -1,
374
- &pStmt, 0);
375
- if( rc==SQLITE_OK ){
376
- sqlite3_bind_text(pStmt, 1, blob_str(&all), -1, SQLITE_TRANSIENT);
377
- sqlite3_step(pStmt);
378
- sqlite3_finalize(pStmt);
379
- }
380
- sqlite3_close(db);
381
- }
382
- }else if( strcmp(zDest, "pipe")==0 ){
383
- const char *zCmd = db_get("email-send-command", 0);
384
- if( zCmd ){
385
- FILE *out = popen(zCmd, "w");
386
- if( out ){
387
- fwrite(blob_buffer(&all), 1, blob_size(&all), out);
388
- fclose(out);
389
- }
390
- }
391
- }else if( strcmp(zDest, "dir")==0 ){
392
- const char *zDir = db_get("email-send-dir","./");
393
- char *zFile = emailTempFilename(zDir);
449
+ blob_appendf(&all, "From: %s\r\n", p->zFrom);
450
+ blob_add_final_newline(pBody);
451
+ blob_appendf(&all,"Content-Type: text/plain\r\n");
452
+ blob_appendf(&all, "Content-Transfer-Encoding: base64\r\n\r\n");
453
+ append_base64(&all, pBody);
454
+ if( p->pStmt ){
455
+ int i, rc;
456
+ sqlite3_bind_text(p->pStmt, 1, blob_str(&all), -1, SQLITE_TRANSIENT);
457
+ for(i=0; i<100 && sqlite3_step(p->pStmt)==SQLITE_BUSY; i++){
458
+ sqlite3_sleep(10);
459
+ }
460
+ rc = sqlite3_reset(p->pStmt);
461
+ if( rc!=SQLITE_OK ){
462
+ emailerError(p, "Failed to insert email message into output queue.\n"
463
+ "%s", sqlite3_errmsg(p->db));
464
+ }
465
+ }else if( p->zCmd ){
466
+ FILE *out = popen(p->zCmd, "w");
467
+ if( out ){
468
+ fwrite(blob_buffer(&all), 1, blob_size(&all), out);
469
+ fclose(out);
470
+ }else{
471
+ emailerError(p, "Could not open output pipe \"%s\"", p->zCmd);
472
+ }
473
+ }else if( p->zDir ){
474
+ char *zFile = emailTempFilename(p->zDir);
394475
blob_write_to_file(&all, zFile);
395476
fossil_free(zFile);
396
- }else if( strcmp(zDest, "stdout")==0 ){
477
+ }else if( strcmp(p->zDest, "stdout")==0 ){
397478
fossil_print("%s\n", blob_str(&all));
398479
}
399480
blob_zero(&all);
400481
}
401482
@@ -483,11 +564,10 @@
483564
** email sending mechanism is currently configured.
484565
** Use this for testing the email configuration.
485566
** Options:
486567
**
487568
** --body FILENAME
488
-** --html
489569
** --stdout
490570
** --subject|-S SUBJECT
491571
**
492572
** settings [NAME VALUE] With no arguments, list all email settings.
493573
** Or change the value of a single email setting.
@@ -556,16 +636,15 @@
556636
email_schema();
557637
}
558638
}else
559639
if( strncmp(zCmd, "send", nCmd)==0 ){
560640
Blob prompt, body, hdr;
561
- int sendAsBoth = find_option("both",0,0)!=0;
562
- int sendAsHtml = find_option("html",0,0)!=0;
563641
const char *zDest = find_option("stdout",0,0)!=0 ? "stdout" : 0;
564642
int i;
565643
const char *zSubject = find_option("subject", "S", 1);
566644
const char *zSource = find_option("body", 0, 1);
645
+ EmailSender *pSender;
567646
verify_all_options();
568647
blob_init(&prompt, 0, 0);
569648
blob_init(&body, 0, 0);
570649
blob_init(&hdr, 0, 0);
571650
for(i=3; i<g.argc; i++){
@@ -578,21 +657,13 @@
578657
blob_read_from_file(&body, zSource, ExtFILE);
579658
}else{
580659
prompt_for_user_comment(&body, &prompt);
581660
}
582661
blob_add_final_newline(&body);
583
- if( sendAsHtml ){
584
- email_send(&hdr, 0, &body, zDest);
585
- }else if( sendAsBoth ){
586
- Blob html;
587
- blob_init(&html, 0, 0);
588
- blob_appendf(&html, "<pre>\n%h</pre>\n", blob_str(&body));
589
- email_send(&hdr, &body, &html, zDest);
590
- blob_zero(&html);
591
- }else{
592
- email_send(&hdr, &body, 0, zDest);
593
- }
662
+ pSender = email_sender_new(zDest, 1);
663
+ email_send(pSender, &hdr, &body);
664
+ email_sender_free(pSender);
594665
blob_zero(&hdr);
595666
blob_zero(&body);
596667
blob_zero(&prompt);
597668
}else
598669
if( strncmp(zCmd, "settings", nCmd)==0 ){
@@ -822,21 +893,32 @@
822893
cgi_redirectf("%R/alerts/%s", zCode);
823894
return;
824895
}else{
825896
/* We need to send a verification email */
826897
Blob hdr, body;
898
+ EmailSender *pSender = email_sender_new(0,0);
827899
blob_init(&hdr,0,0);
828900
blob_init(&body,0,0);
829901
blob_appendf(&hdr, "To: %s\n", zEAddr);
830902
blob_appendf(&hdr, "Subject: Subscription verification\n");
831903
blob_appendf(&body, zConfirmMsg/*works-like:"%s%s%s"*/,
832904
g.zBaseURL, g.zBaseURL, zCode);
833
- email_send(&hdr, &body, 0, 0);
905
+ email_send(pSender, &hdr, &body);
834906
style_header("Email Alert Verification");
835
- @ <p>An email has been sent to "%h(zEAddr)". That email contains a
836
- @ hyperlink that you must click on in order to activate your
837
- @ subscription.</p>
907
+ if( pSender->zErr ){
908
+ @ <h1>Internal Error</h1>
909
+ @ <p>The following internal error was encountered while trying
910
+ @ to send the confirmation email:
911
+ @ <blockquote><pre>
912
+ @ %h(pSender->zErr)
913
+ @ </pre></blockquote>
914
+ }else{
915
+ @ <p>An email has been sent to "%h(zEAddr)". That email contains a
916
+ @ hyperlink that you must click on in order to activate your
917
+ @ subscription.</p>
918
+ }
919
+ email_sender_free(pSender);
838920
style_footer();
839921
}
840922
return;
841923
}
842924
style_header("Signup For Email Alerts");
@@ -1213,20 +1295,31 @@
12131295
}
12141296
if( bSubmit ){
12151297
/* If we get this far, it means that a valid unsubscribe request has
12161298
** been submitted. Send the appropriate email. */
12171299
Blob hdr, body;
1300
+ EmailSender *pSender = email_sender_new(0,0);
12181301
blob_init(&hdr,0,0);
12191302
blob_init(&body,0,0);
12201303
blob_appendf(&hdr, "To: %s\n", zEAddr);
12211304
blob_appendf(&hdr, "Subject: Unsubscribe Instructions\n");
12221305
blob_appendf(&body, zUnsubMsg/*works-like:"%s%s%s%s%s%s"*/,
12231306
g.zBaseURL, g.zBaseURL, zCode, g.zBaseURL, g.zBaseURL, zCode);
1224
- email_send(&hdr, &body, 0, 0);
1307
+ email_send(pSender, &hdr, &body);
12251308
style_header("Unsubscribe Instructions Sent");
1226
- @ <p>An email has been sent to "%h(zEAddr)" that explains how to
1227
- @ unsubscribe and/or modify your subscription settings</p>
1309
+ if( pSender->zErr ){
1310
+ @ <h1>Internal Error</h1>
1311
+ @ <p>The following error was encountered while trying to send an
1312
+ @ email to %h(zEAddr):
1313
+ @ <blockquote><pre>
1314
+ @ %h(pSender->zErr)
1315
+ @ </pre></blockquote>
1316
+ }else{
1317
+ @ <p>An email has been sent to "%h(zEAddr)" that explains how to
1318
+ @ unsubscribe and/or modify your subscription settings</p>
1319
+ }
1320
+ email_sender_free(pSender);
12281321
style_footer();
12291322
return;
12301323
}
12311324
12321325
/* Non-logged-in users have to enter an email address to which is
@@ -1525,19 +1618,21 @@
15251618
Blob hdr, body;
15261619
const char *zUrl;
15271620
const char *zRepoName;
15281621
const char *zFrom;
15291622
const char *zDest = (flags & SENDALERT_STDOUT) ? "stdout" : 0;
1623
+ EmailSender *pSender = 0;
15301624
15311625
db_begin_transaction();
15321626
if( !email_enabled() ) goto send_alerts_done;
15331627
zUrl = db_get("email-url",0);
15341628
if( zUrl==0 ) goto send_alerts_done;
15351629
zRepoName = db_get("email-subname",0);
15361630
if( zRepoName==0 ) goto send_alerts_done;
15371631
zFrom = db_get("email-self",0);
15381632
if( zFrom==0 ) goto send_alerts_done;
1633
+ pSender = email_sender_new(zDest, 0);
15391634
db_multi_exec(
15401635
"DROP TABLE IF EXISTS temp.wantalert;"
15411636
"CREATE TEMP TABLE wantalert(eventId TEXT);"
15421637
);
15431638
if( flags & SENDALERT_DIGEST ){
@@ -1587,11 +1682,11 @@
15871682
blob_append(&body, blob_buffer(&p->txt), blob_size(&p->txt));
15881683
}
15891684
if( nHit==0 ) continue;
15901685
blob_appendf(&body,"\n%.72c\nSubscription info: %s/alerts/%s\n",
15911686
'-', zUrl, zCode);
1592
- email_send(&hdr,&body,0,zDest);
1687
+ email_send(pSender,&hdr,&body);
15931688
blob_truncate(&hdr);
15941689
blob_truncate(&body);
15951690
}
15961691
blob_zero(&hdr);
15971692
blob_zero(&body);
@@ -1604,7 +1699,8 @@
16041699
db_multi_exec("UPDATE pending_alert SET sentSep=true");
16051700
}
16061701
db_multi_exec("DELETE FROM pending_alert WHERE sentDigest AND sentSep");
16071702
}
16081703
send_alerts_done:
1704
+ email_sender_free(pSender);
16091705
db_end_transaction(0);
16101706
}
16111707
--- src/email.c
+++ src/email.c
@@ -284,19 +284,147 @@
284 # undef popen
285 # define popen _popen
286 # undef pclose
287 # define pclose _pclose
288 #endif
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
289
290 /*
291 ** Send an email message using whatever sending mechanism is configured
292 ** by these settings:
 
 
 
 
 
 
293 **
294 ** email-send-method "off" Do not send any emails
295 ** "pipe" Pipe the email to email-send-command
296 ** "db" Store the mail in database email-send-db
297 ** "file" Store the email as a file in email-send-dir
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
298 **
299 ** The recepient(s) must be specified using "To:" or "Cc:" or "Bcc:" fields
300 ** in the header. Likewise, the header must contains a "Subject:" line.
301 ** The header might also include fields like "Message-Id:" or
302 ** "In-Reply-To:".
@@ -305,97 +433,50 @@
305 **
306 ** From:
307 ** Content-Type:
308 ** Content-Transfer-Encoding:
309 **
310 ** At least one body must be supplied.
311 **
312 ** The caller maintains ownership of the input Blobs. This routine will
313 ** read the Blobs and send them onward to the email system, but it will
314 ** not free them.
315 **
316 ** If zDest is not NULL then it is an overwrite for the email-send-method.
317 ** zDest can be "stdout" to send output to the console for debugging.
318 */
319 void email_send(Blob *pHdr, Blob *pPlain, Blob *pHtml, const char *zDest){
320 const char *zFrom = db_get("email-self", 0);
321 char *zBoundary = 0;
322 Blob all;
323 if( zFrom==0 ){
324 fossil_warning("Missing configuration: \"email-self\"");
325 return;
326 }
327 if( zDest==0 ) zDest = db_get("email-send-method", "off");
328 if( strcmp(zDest, "off")==0 ){
329 return;
330 }
331 blob_init(&all, 0, 0);
332 blob_append(&all, blob_buffer(pHdr), blob_size(pHdr));
333 blob_appendf(&all, "From: %s\r\n", zFrom);
334 if( pPlain && pHtml ){
335 blob_appendf(&all, "MIME-Version: 1.0\r\n");
336 zBoundary = db_text(0, "SELECT hex(randomblob(20))");
337 blob_appendf(&all, "Content-Type: multipart/alternative;"
338 " boundary=\"%s\"\r\n", zBoundary);
339 }
340 if( pPlain ){
341 blob_add_final_newline(pPlain);
342 if( zBoundary ){
343 blob_appendf(&all, "\r\n--%s\r\n", zBoundary);
344 }
345 blob_appendf(&all,"Content-Type: text/plain\r\n");
346 blob_appendf(&all, "Content-Transfer-Encoding: base64\r\n\r\n");
347 append_base64(&all, pPlain);
348 }
349 if( pHtml ){
350 blob_add_final_newline(pHtml);
351 if( zBoundary ){
352 blob_appendf(&all, "--%s\r\n", zBoundary);
353 }
354 blob_appendf(&all,"Content-Type: text/html\r\n");
355 blob_appendf(&all, "Content-Transfer-Encoding: base64\r\n\r\n");
356 append_base64(&all, pHtml);
357 }
358 if( zBoundary ){
359 blob_appendf(&all, "--%s--\r\n", zBoundary);
360 fossil_free(zBoundary);
361 zBoundary = 0;
362 }
363 if( strcmp(zDest, "db")==0 ){
364 sqlite3 *db;
365 sqlite3_stmt *pStmt;
366 int rc;
367 const char *zDb = db_get("email-send-db",0);
368 rc = sqlite3_open(zDb, &db);
369 if( rc==SQLITE_OK ){
370 sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS email(\n"
371 " emailid INTEGER PRIMARY KEY,\n"
372 " msg TEXT\n);", 0, 0, 0);
373 rc = sqlite3_prepare_v2(db, "INSERT INTO email(msg) VALUES(?1)", -1,
374 &pStmt, 0);
375 if( rc==SQLITE_OK ){
376 sqlite3_bind_text(pStmt, 1, blob_str(&all), -1, SQLITE_TRANSIENT);
377 sqlite3_step(pStmt);
378 sqlite3_finalize(pStmt);
379 }
380 sqlite3_close(db);
381 }
382 }else if( strcmp(zDest, "pipe")==0 ){
383 const char *zCmd = db_get("email-send-command", 0);
384 if( zCmd ){
385 FILE *out = popen(zCmd, "w");
386 if( out ){
387 fwrite(blob_buffer(&all), 1, blob_size(&all), out);
388 fclose(out);
389 }
390 }
391 }else if( strcmp(zDest, "dir")==0 ){
392 const char *zDir = db_get("email-send-dir","./");
393 char *zFile = emailTempFilename(zDir);
394 blob_write_to_file(&all, zFile);
395 fossil_free(zFile);
396 }else if( strcmp(zDest, "stdout")==0 ){
397 fossil_print("%s\n", blob_str(&all));
398 }
399 blob_zero(&all);
400 }
401
@@ -483,11 +564,10 @@
483 ** email sending mechanism is currently configured.
484 ** Use this for testing the email configuration.
485 ** Options:
486 **
487 ** --body FILENAME
488 ** --html
489 ** --stdout
490 ** --subject|-S SUBJECT
491 **
492 ** settings [NAME VALUE] With no arguments, list all email settings.
493 ** Or change the value of a single email setting.
@@ -556,16 +636,15 @@
556 email_schema();
557 }
558 }else
559 if( strncmp(zCmd, "send", nCmd)==0 ){
560 Blob prompt, body, hdr;
561 int sendAsBoth = find_option("both",0,0)!=0;
562 int sendAsHtml = find_option("html",0,0)!=0;
563 const char *zDest = find_option("stdout",0,0)!=0 ? "stdout" : 0;
564 int i;
565 const char *zSubject = find_option("subject", "S", 1);
566 const char *zSource = find_option("body", 0, 1);
 
567 verify_all_options();
568 blob_init(&prompt, 0, 0);
569 blob_init(&body, 0, 0);
570 blob_init(&hdr, 0, 0);
571 for(i=3; i<g.argc; i++){
@@ -578,21 +657,13 @@
578 blob_read_from_file(&body, zSource, ExtFILE);
579 }else{
580 prompt_for_user_comment(&body, &prompt);
581 }
582 blob_add_final_newline(&body);
583 if( sendAsHtml ){
584 email_send(&hdr, 0, &body, zDest);
585 }else if( sendAsBoth ){
586 Blob html;
587 blob_init(&html, 0, 0);
588 blob_appendf(&html, "<pre>\n%h</pre>\n", blob_str(&body));
589 email_send(&hdr, &body, &html, zDest);
590 blob_zero(&html);
591 }else{
592 email_send(&hdr, &body, 0, zDest);
593 }
594 blob_zero(&hdr);
595 blob_zero(&body);
596 blob_zero(&prompt);
597 }else
598 if( strncmp(zCmd, "settings", nCmd)==0 ){
@@ -822,21 +893,32 @@
822 cgi_redirectf("%R/alerts/%s", zCode);
823 return;
824 }else{
825 /* We need to send a verification email */
826 Blob hdr, body;
 
827 blob_init(&hdr,0,0);
828 blob_init(&body,0,0);
829 blob_appendf(&hdr, "To: %s\n", zEAddr);
830 blob_appendf(&hdr, "Subject: Subscription verification\n");
831 blob_appendf(&body, zConfirmMsg/*works-like:"%s%s%s"*/,
832 g.zBaseURL, g.zBaseURL, zCode);
833 email_send(&hdr, &body, 0, 0);
834 style_header("Email Alert Verification");
835 @ <p>An email has been sent to "%h(zEAddr)". That email contains a
836 @ hyperlink that you must click on in order to activate your
837 @ subscription.</p>
 
 
 
 
 
 
 
 
 
 
838 style_footer();
839 }
840 return;
841 }
842 style_header("Signup For Email Alerts");
@@ -1213,20 +1295,31 @@
1213 }
1214 if( bSubmit ){
1215 /* If we get this far, it means that a valid unsubscribe request has
1216 ** been submitted. Send the appropriate email. */
1217 Blob hdr, body;
 
1218 blob_init(&hdr,0,0);
1219 blob_init(&body,0,0);
1220 blob_appendf(&hdr, "To: %s\n", zEAddr);
1221 blob_appendf(&hdr, "Subject: Unsubscribe Instructions\n");
1222 blob_appendf(&body, zUnsubMsg/*works-like:"%s%s%s%s%s%s"*/,
1223 g.zBaseURL, g.zBaseURL, zCode, g.zBaseURL, g.zBaseURL, zCode);
1224 email_send(&hdr, &body, 0, 0);
1225 style_header("Unsubscribe Instructions Sent");
1226 @ <p>An email has been sent to "%h(zEAddr)" that explains how to
1227 @ unsubscribe and/or modify your subscription settings</p>
 
 
 
 
 
 
 
 
 
 
1228 style_footer();
1229 return;
1230 }
1231
1232 /* Non-logged-in users have to enter an email address to which is
@@ -1525,19 +1618,21 @@
1525 Blob hdr, body;
1526 const char *zUrl;
1527 const char *zRepoName;
1528 const char *zFrom;
1529 const char *zDest = (flags & SENDALERT_STDOUT) ? "stdout" : 0;
 
1530
1531 db_begin_transaction();
1532 if( !email_enabled() ) goto send_alerts_done;
1533 zUrl = db_get("email-url",0);
1534 if( zUrl==0 ) goto send_alerts_done;
1535 zRepoName = db_get("email-subname",0);
1536 if( zRepoName==0 ) goto send_alerts_done;
1537 zFrom = db_get("email-self",0);
1538 if( zFrom==0 ) goto send_alerts_done;
 
1539 db_multi_exec(
1540 "DROP TABLE IF EXISTS temp.wantalert;"
1541 "CREATE TEMP TABLE wantalert(eventId TEXT);"
1542 );
1543 if( flags & SENDALERT_DIGEST ){
@@ -1587,11 +1682,11 @@
1587 blob_append(&body, blob_buffer(&p->txt), blob_size(&p->txt));
1588 }
1589 if( nHit==0 ) continue;
1590 blob_appendf(&body,"\n%.72c\nSubscription info: %s/alerts/%s\n",
1591 '-', zUrl, zCode);
1592 email_send(&hdr,&body,0,zDest);
1593 blob_truncate(&hdr);
1594 blob_truncate(&body);
1595 }
1596 blob_zero(&hdr);
1597 blob_zero(&body);
@@ -1604,7 +1699,8 @@
1604 db_multi_exec("UPDATE pending_alert SET sentSep=true");
1605 }
1606 db_multi_exec("DELETE FROM pending_alert WHERE sentDigest AND sentSep");
1607 }
1608 send_alerts_done:
 
1609 db_end_transaction(0);
1610 }
1611
--- src/email.c
+++ src/email.c
@@ -284,19 +284,147 @@
284 # undef popen
285 # define popen _popen
286 # undef pclose
287 # define pclose _pclose
288 #endif
289
290 #if INTERFACE
291 /*
292 ** An instance of the following object is used to send emails.
293 */
294 struct EmailSender {
295 sqlite3 *db; /* Database emails are sent to */
296 sqlite3_stmt *pStmt; /* Stmt to insert into the database */
297 const char *zDest; /* How to send email. */
298 const char *zDb; /* Name of database file */
299 const char *zDir; /* Directory in which to store as email files */
300 const char *zCmd; /* Command to run for each email */
301 const char *zFrom; /* Emails come from here */
302 char *zErr; /* Error message */
303 int bImmediateFail; /* On any error, call fossil_fatal() */
304 };
305 #endif /* INTERFACE */
306
307 /*
308 ** Shutdown an emailer. Clear all information other than the error message.
309 */
310 static void emailerShutdown(EmailSender *p){
311 sqlite3_finalize(p->pStmt);
312 p->pStmt = 0;
313 sqlite3_free(p->db);
314 p->db = 0;
315 p->zDb = 0;
316 p->zDir = 0;
317 p->zCmd = 0;
318 p->zDest = "off";
319 }
320
321 /*
322 ** Put the EmailSender into an error state.
323 */
324 static void emailerError(EmailSender *p, const char *zFormat, ...){
325 va_list ap;
326 fossil_free(p->zErr);
327 va_start(ap, zFormat);
328 p->zErr = vmprintf(zFormat, ap);
329 va_end(ap);
330 emailerShutdown(p);
331 if( p->bImmediateFail ){
332 fossil_fatal("%s", p->zErr);
333 }
334 }
335
336 /*
337 ** Free an email sender object
338 */
339 void email_sender_free(EmailSender *p){
340 emailerShutdown(p);
341 fossil_free(p->zErr);
342 fossil_free(p);
343 }
344
345 /*
346 ** Get an email setting value. Report an error if not configured.
347 ** Return 0 on success and one if there is an error.
348 */
349 static int emailerGetSetting(
350 EmailSender *p, /* Where to report the error */
351 const char **pzVal, /* Write the setting value here */
352 const char *zName /* Name of the setting */
353 ){
354 const char *z = db_get(zName, 0);
355 int rc = 0;
356 if( z==0 || z[0]==0 ){
357 emailerError(p, "missing \"%s\" setting", zName);
358 rc = 1;
359 }else{
360 *pzVal = z;
361 }
362 return rc;
363 }
364
365 /*
366 ** Create a new EmailSender object.
367 **
368 ** The method used for sending email is determined by various email-*
369 ** settings, and especially email-send-method. The repository
370 ** email-send-method can be overridden by the zAltDest argument to
371 ** cause a different sending mechanism to be used. Pass "stdout" to
372 ** zAltDest to cause all emails to be printed to the console for
373 ** debugging purposes.
374 **
375 ** The EmailSender object returned must be freed using email_sender_free().
376 */
377 EmailSender *email_sender_new(const char *zAltDest, int bImmediateFail){
378 EmailSender *p;
379 char *zMissing = 0;
380
381 p = fossil_malloc(sizeof(*p));
382 memset(p, 0, sizeof(*p));
383 p->bImmediateFail = bImmediateFail;
384 if( zAltDest ){
385 p->zDest = zAltDest;
386 }else{
387 p->zDest = db_get("email-send-method","off");
388 }
389 if( fossil_strcmp(p->zDest,"off")==0 ) return p;
390 if( emailerGetSetting(p, &p->zFrom, "email-self") ) return p;
391 if( fossil_strcmp(p->zDest,"db")==0 ){
392 char *zErr;
393 int rc;
394 if( emailerGetSetting(p, &p->zDb, "email-send-db") ) return p;
395 rc = sqlite3_open(p->zDb, &p->db);
396 if( rc ){
397 emailerError(p, "unable to open output database file \"%s\": %s",
398 p->zDb, sqlite3_errmsg(p->db));
399 return p;
400 }
401 rc = sqlite3_exec(p->db, "CREATE TABLE IF NOT EXISTS email(\n"
402 " emailid INTEGER PRIMARY KEY,\n"
403 " msg TEXT\n);", 0, 0, &zErr);
404 if( zErr ){
405 emailerError(p, "CREATE TABLE failed with \"%s\"", zErr);
406 sqlite3_free(zErr);
407 return p;
408 }
409 rc = sqlite3_prepare_v2(p->db, "INSERT INTO email(msg) VALUES(?1)", -1,
410 &p->pStmt, 0);
411 if( rc ){
412 emailerError(p, "cannot prepare INSERT statement: %s",
413 sqlite3_errmsg(p->db));
414 return p;
415 }
416 }else if( fossil_strcmp(p->zDest, "pipe")==0 ){
417 emailerGetSetting(p, &p->zCmd, "email-send-command");
418 }else if( fossil_strcmp(p->zDest, "dir")==0 ){
419 emailerGetSetting(p, &p->zDir, "email-send-dir");
420 }
421 return p;
422 }
423
424 /*
425 ** Send a single email message.
426 **
427 ** The recepient(s) must be specified using "To:" or "Cc:" or "Bcc:" fields
428 ** in the header. Likewise, the header must contains a "Subject:" line.
429 ** The header might also include fields like "Message-Id:" or
430 ** "In-Reply-To:".
@@ -305,97 +433,50 @@
433 **
434 ** From:
435 ** Content-Type:
436 ** Content-Transfer-Encoding:
437 **
 
 
438 ** The caller maintains ownership of the input Blobs. This routine will
439 ** read the Blobs and send them onward to the email system, but it will
440 ** not free them.
 
 
 
441 */
442 void email_send(EmailSender *p, Blob *pHdr, Blob *pBody){
 
 
443 Blob all;
444 if( fossil_strcmp(p->zDest, "off")==0 ){
 
 
 
 
 
445 return;
446 }
447 blob_init(&all, 0, 0);
448 blob_append(&all, blob_buffer(pHdr), blob_size(pHdr));
449 blob_appendf(&all, "From: %s\r\n", p->zFrom);
450 blob_add_final_newline(pBody);
451 blob_appendf(&all,"Content-Type: text/plain\r\n");
452 blob_appendf(&all, "Content-Transfer-Encoding: base64\r\n\r\n");
453 append_base64(&all, pBody);
454 if( p->pStmt ){
455 int i, rc;
456 sqlite3_bind_text(p->pStmt, 1, blob_str(&all), -1, SQLITE_TRANSIENT);
457 for(i=0; i<100 && sqlite3_step(p->pStmt)==SQLITE_BUSY; i++){
458 sqlite3_sleep(10);
459 }
460 rc = sqlite3_reset(p->pStmt);
461 if( rc!=SQLITE_OK ){
462 emailerError(p, "Failed to insert email message into output queue.\n"
463 "%s", sqlite3_errmsg(p->db));
464 }
465 }else if( p->zCmd ){
466 FILE *out = popen(p->zCmd, "w");
467 if( out ){
468 fwrite(blob_buffer(&all), 1, blob_size(&all), out);
469 fclose(out);
470 }else{
471 emailerError(p, "Could not open output pipe \"%s\"", p->zCmd);
472 }
473 }else if( p->zDir ){
474 char *zFile = emailTempFilename(p->zDir);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
475 blob_write_to_file(&all, zFile);
476 fossil_free(zFile);
477 }else if( strcmp(p->zDest, "stdout")==0 ){
478 fossil_print("%s\n", blob_str(&all));
479 }
480 blob_zero(&all);
481 }
482
@@ -483,11 +564,10 @@
564 ** email sending mechanism is currently configured.
565 ** Use this for testing the email configuration.
566 ** Options:
567 **
568 ** --body FILENAME
 
569 ** --stdout
570 ** --subject|-S SUBJECT
571 **
572 ** settings [NAME VALUE] With no arguments, list all email settings.
573 ** Or change the value of a single email setting.
@@ -556,16 +636,15 @@
636 email_schema();
637 }
638 }else
639 if( strncmp(zCmd, "send", nCmd)==0 ){
640 Blob prompt, body, hdr;
 
 
641 const char *zDest = find_option("stdout",0,0)!=0 ? "stdout" : 0;
642 int i;
643 const char *zSubject = find_option("subject", "S", 1);
644 const char *zSource = find_option("body", 0, 1);
645 EmailSender *pSender;
646 verify_all_options();
647 blob_init(&prompt, 0, 0);
648 blob_init(&body, 0, 0);
649 blob_init(&hdr, 0, 0);
650 for(i=3; i<g.argc; i++){
@@ -578,21 +657,13 @@
657 blob_read_from_file(&body, zSource, ExtFILE);
658 }else{
659 prompt_for_user_comment(&body, &prompt);
660 }
661 blob_add_final_newline(&body);
662 pSender = email_sender_new(zDest, 1);
663 email_send(pSender, &hdr, &body);
664 email_sender_free(pSender);
 
 
 
 
 
 
 
 
665 blob_zero(&hdr);
666 blob_zero(&body);
667 blob_zero(&prompt);
668 }else
669 if( strncmp(zCmd, "settings", nCmd)==0 ){
@@ -822,21 +893,32 @@
893 cgi_redirectf("%R/alerts/%s", zCode);
894 return;
895 }else{
896 /* We need to send a verification email */
897 Blob hdr, body;
898 EmailSender *pSender = email_sender_new(0,0);
899 blob_init(&hdr,0,0);
900 blob_init(&body,0,0);
901 blob_appendf(&hdr, "To: %s\n", zEAddr);
902 blob_appendf(&hdr, "Subject: Subscription verification\n");
903 blob_appendf(&body, zConfirmMsg/*works-like:"%s%s%s"*/,
904 g.zBaseURL, g.zBaseURL, zCode);
905 email_send(pSender, &hdr, &body);
906 style_header("Email Alert Verification");
907 if( pSender->zErr ){
908 @ <h1>Internal Error</h1>
909 @ <p>The following internal error was encountered while trying
910 @ to send the confirmation email:
911 @ <blockquote><pre>
912 @ %h(pSender->zErr)
913 @ </pre></blockquote>
914 }else{
915 @ <p>An email has been sent to "%h(zEAddr)". That email contains a
916 @ hyperlink that you must click on in order to activate your
917 @ subscription.</p>
918 }
919 email_sender_free(pSender);
920 style_footer();
921 }
922 return;
923 }
924 style_header("Signup For Email Alerts");
@@ -1213,20 +1295,31 @@
1295 }
1296 if( bSubmit ){
1297 /* If we get this far, it means that a valid unsubscribe request has
1298 ** been submitted. Send the appropriate email. */
1299 Blob hdr, body;
1300 EmailSender *pSender = email_sender_new(0,0);
1301 blob_init(&hdr,0,0);
1302 blob_init(&body,0,0);
1303 blob_appendf(&hdr, "To: %s\n", zEAddr);
1304 blob_appendf(&hdr, "Subject: Unsubscribe Instructions\n");
1305 blob_appendf(&body, zUnsubMsg/*works-like:"%s%s%s%s%s%s"*/,
1306 g.zBaseURL, g.zBaseURL, zCode, g.zBaseURL, g.zBaseURL, zCode);
1307 email_send(pSender, &hdr, &body);
1308 style_header("Unsubscribe Instructions Sent");
1309 if( pSender->zErr ){
1310 @ <h1>Internal Error</h1>
1311 @ <p>The following error was encountered while trying to send an
1312 @ email to %h(zEAddr):
1313 @ <blockquote><pre>
1314 @ %h(pSender->zErr)
1315 @ </pre></blockquote>
1316 }else{
1317 @ <p>An email has been sent to "%h(zEAddr)" that explains how to
1318 @ unsubscribe and/or modify your subscription settings</p>
1319 }
1320 email_sender_free(pSender);
1321 style_footer();
1322 return;
1323 }
1324
1325 /* Non-logged-in users have to enter an email address to which is
@@ -1525,19 +1618,21 @@
1618 Blob hdr, body;
1619 const char *zUrl;
1620 const char *zRepoName;
1621 const char *zFrom;
1622 const char *zDest = (flags & SENDALERT_STDOUT) ? "stdout" : 0;
1623 EmailSender *pSender = 0;
1624
1625 db_begin_transaction();
1626 if( !email_enabled() ) goto send_alerts_done;
1627 zUrl = db_get("email-url",0);
1628 if( zUrl==0 ) goto send_alerts_done;
1629 zRepoName = db_get("email-subname",0);
1630 if( zRepoName==0 ) goto send_alerts_done;
1631 zFrom = db_get("email-self",0);
1632 if( zFrom==0 ) goto send_alerts_done;
1633 pSender = email_sender_new(zDest, 0);
1634 db_multi_exec(
1635 "DROP TABLE IF EXISTS temp.wantalert;"
1636 "CREATE TEMP TABLE wantalert(eventId TEXT);"
1637 );
1638 if( flags & SENDALERT_DIGEST ){
@@ -1587,11 +1682,11 @@
1682 blob_append(&body, blob_buffer(&p->txt), blob_size(&p->txt));
1683 }
1684 if( nHit==0 ) continue;
1685 blob_appendf(&body,"\n%.72c\nSubscription info: %s/alerts/%s\n",
1686 '-', zUrl, zCode);
1687 email_send(pSender,&hdr,&body);
1688 blob_truncate(&hdr);
1689 blob_truncate(&body);
1690 }
1691 blob_zero(&hdr);
1692 blob_zero(&body);
@@ -1604,7 +1699,8 @@
1699 db_multi_exec("UPDATE pending_alert SET sentSep=true");
1700 }
1701 db_multi_exec("DELETE FROM pending_alert WHERE sentDigest AND sentSep");
1702 }
1703 send_alerts_done:
1704 email_sender_free(pSender);
1705 db_end_transaction(0);
1706 }
1707

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button