Fossil SCM

Add the smtp_send_msg() function capable of encoding and sending a complete email message.

drh 2018-06-28 21:38 UTC smtp
Commit 03888604076d42a27ea216886d0bf97f356c2ef7474bfca44043b1a1c91299cc
2 files changed +1 -1 +179 -4
--- src/http_socket.c
+++ src/http_socket.c
@@ -192,11 +192,11 @@
192192
}
193193
194194
/*
195195
** Send content out over the open socket connection.
196196
*/
197
-size_t socket_send(void *NotUsed, void *pContent, size_t N){
197
+size_t socket_send(void *NotUsed, const void *pContent, size_t N){
198198
size_t sent;
199199
size_t total = 0;
200200
while( N>0 ){
201201
sent = send(iSocket, pContent, N, 0);
202202
if( sent<=0 ) break;
203203
--- src/http_socket.c
+++ src/http_socket.c
@@ -192,11 +192,11 @@
192 }
193
194 /*
195 ** Send content out over the open socket connection.
196 */
197 size_t socket_send(void *NotUsed, void *pContent, size_t N){
198 size_t sent;
199 size_t total = 0;
200 while( N>0 ){
201 sent = send(iSocket, pContent, N, 0);
202 if( sent<=0 ) break;
203
--- src/http_socket.c
+++ src/http_socket.c
@@ -192,11 +192,11 @@
192 }
193
194 /*
195 ** Send content out over the open socket connection.
196 */
197 size_t socket_send(void *NotUsed, const void *pContent, size_t N){
198 size_t sent;
199 size_t total = 0;
200 while( N>0 ){
201 sent = send(iSocket, pContent, N, 0);
202 if( sent<=0 ) break;
203
+179 -4
--- src/smtp.c
+++ src/smtp.c
@@ -260,11 +260,11 @@
260260
p->zErr = mprintf("timeout");
261261
socket_close();
262262
p->atEof = 1;
263263
return;
264264
}else{
265
- sqlite3_sleep(25);
265
+ sqlite3_sleep(100);
266266
}
267267
}while( n<1 || z[n-1]!='\n' );
268268
blob_truncate(&p->inbuf, n);
269269
blob_line(&p->inbuf, in);
270270
}
@@ -320,10 +320,11 @@
320320
char *zArg = 0;
321321
smtp_send_line(p, "QUIT\r\n");
322322
do{
323323
smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
324324
}while( bMore );
325
+ p->atEof = 1;
325326
socket_close();
326327
return 0;
327328
}
328329
329330
/*
@@ -364,11 +365,10 @@
364365
** and then immediately shutting it back down. Log all interaction
365366
** on the console. Use ME as the domain name of the sender.
366367
*/
367368
void test_smtp_probe(void){
368369
SmtpSession *p;
369
- int rc;
370370
const char *zDomain;
371371
const char *zSelf;
372372
if( g.argc!=3 && g.argc!=4 ) usage("DOMAIN [ME]");
373373
zDomain = g.argv[2];
374374
zSelf = g.argc==4 ? g.argv[3] : "fossil-scm.org";
@@ -375,12 +375,187 @@
375375
p = smtp_session_new(zSelf, zDomain, SMTP_TRACE_STDOUT);
376376
if( p->zErr ){
377377
fossil_fatal("%s", p->zErr);
378378
}
379379
fossil_print("Connection to \"%s\"\n", p->zHostname);
380
- rc = smtp_client_startup(p);
381
- if( !rc ) smtp_client_quit(p);
380
+ smtp_client_startup(p);
381
+ smtp_client_quit(p);
382
+ if( p->zErr ){
383
+ fossil_fatal("ERROR: %s\n", p->zErr);
384
+ }
385
+ smtp_session_free(p);
386
+}
387
+
388
+/*
389
+** Send the content of an email message followed by a single
390
+** "." line. All lines must be \r\n terminated. Any isolated
391
+** \n line terminators in the input must be converted. Also,
392
+** an line contain using "." should be converted to "..".
393
+*/
394
+static void smtp_send_email_body(
395
+ const char *zMsg, /* Message to send */
396
+ size_t (*xSend)(void*,const void*,size_t), /* Sender callback function */
397
+ void *pArg /* First arg to sender */
398
+){
399
+ Blob in;
400
+ Blob out = BLOB_INITIALIZER;
401
+ Blob line;
402
+ blob_init(&in, zMsg, -1);
403
+ while( blob_line(&in, &line) ){
404
+ char *z = blob_buffer(&line);
405
+ int n = blob_size(&line);
406
+ if( n==0 ) break;
407
+ n--;
408
+ if( n && z[n-1]=='\r' ) n--;
409
+ if( n==1 && z[0]=='.' ){
410
+ blob_append(&out, "..\r\n", 4);
411
+ }else{
412
+ blob_append(&out, z, n);
413
+ blob_append(&out, "\r\n", 2);
414
+ }
415
+ }
416
+ blob_append(&out, ".\r\n", 3);
417
+ xSend(pArg, blob_buffer(&out), blob_size(&out));
418
+ blob_zero(&out);
419
+ blob_zero(&line);
420
+}
421
+
422
+/* A sender function appropriate for use by smtp_send_email_body() to
423
+** send all content to the console, for testing.
424
+*/
425
+static size_t smtp_test_sender(void *NotUsed, const void *pContent, size_t N){
426
+ return fwrite(pContent, 1, N, stdout);
427
+}
428
+
429
+/*
430
+** COMMAND: test-smtp-senddata
431
+**
432
+** Usage: %fossil test-smtp-senddata FILE
433
+**
434
+** Read content from FILE, then send it to stdout encoded as if sent
435
+** to the DATA portion of an SMTP session. This command is used to
436
+** test the encoding logic.
437
+*/
438
+void test_smtp_senddata(void){
439
+ Blob f;
440
+ if( g.argc!=3 ) usage("FILE");
441
+ blob_read_from_file(&f, g.argv[2], ExtFILE);
442
+ smtp_send_email_body(blob_str(&f), smtp_test_sender, 0);
443
+ blob_zero(&f);
444
+}
445
+
446
+/*
447
+** Send a single email message to the SMTP server.
448
+**
449
+** All email addresses (zFrom and azTo) must be plain "local@domain"
450
+** format with the surrounding "<..>". This routine will add the
451
+** necessary "<..>".
452
+**
453
+** The body of the email should be well-structured. This routine will
454
+** convert any \n line endings into \r\n and will escape lines containing
455
+** just ".", but will not make any other alterations or corrections to
456
+** the message content.
457
+**
458
+** Return 0 on success. Otherwise an error code.
459
+*/
460
+int smtp_send_msg(
461
+ SmtpSession *p, /* The SMTP server to which the message is sent */
462
+ const char *zFrom, /* Who the message is from */
463
+ int nTo, /* Number of receipients */
464
+ const char **azTo, /* Email address of each recipient */
465
+ const char *zMsg /* Body of the message */
466
+){
467
+ int i;
468
+ int iCode = 0;
469
+ int bMore = 0;
470
+ char *zArg = 0;
471
+ Blob in;
472
+ blob_init(&in, 0, 0);
473
+ smtp_send_line(p, "MAIL FROM:<%s>\r\n", zFrom);
474
+ do{
475
+ smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
476
+ }while( bMore );
477
+ if( iCode!=250 ) return 1;
478
+ for(i=0; i<nTo; i++){
479
+ smtp_send_line(p, "RCPT TO:<%s>\r\n", azTo[i]);
480
+ do{
481
+ smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
482
+ }while( bMore );
483
+ if( iCode!=250 ) return 1;
484
+ }
485
+ smtp_send_line(p, "DATA\r\n");
486
+ do{
487
+ smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
488
+ }while( bMore );
489
+ if( iCode!=354 ) return 1;
490
+ smtp_send_email_body(zMsg, socket_send, 0);
491
+ if( p->smtpFlags & SMTP_TRACE_STDOUT ){
492
+ fossil_print("C: # message content\nC: .\n");
493
+ }
494
+ if( p->smtpFlags & SMTP_TRACE_FILE ){
495
+ fprintf(p->logFile, "C: # message content\nC: .\n");
496
+ }
497
+ if( p->smtpFlags & SMTP_TRACE_BLOB ){
498
+ blob_appendf(p->pTranscript, "C: # message content\nC: .\n");
499
+ }
500
+ do{
501
+ smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
502
+ }while( bMore );
503
+ if( iCode!=250 ) return 1;
504
+ return 0;
505
+}
506
+
507
+/*
508
+** The input is a base email address of the form "local@domain".
509
+** Return a pointer to just the "domain" part.
510
+*/
511
+static const char *domainOfAddr(const char *z){
512
+ while( z[0] && z[0]!='@' ) z++;
513
+ if( z[0]==0 ) return 0;
514
+ return z+1;
515
+}
516
+
517
+
518
+/*
519
+** COMMAND: test-smtp-send
520
+**
521
+** Usage: %fossil test-smtp-send EMAIL FROM TO ...
522
+**
523
+** Use SMTP to send the email message contained in the file named EMAIL
524
+** to the list of users TO. FROM is the sender of the email.
525
+**
526
+** Options:
527
+**
528
+** --trace Show the SMTP conversation on the console
529
+*/
530
+void test_smtp_send(void){
531
+ SmtpSession *p;
532
+ const char *zFrom;
533
+ int nTo;
534
+ const char *zToDomain;
535
+ const char *zFromDomain;
536
+ const char **azTo;
537
+ Blob body;
538
+ u32 smtpFlags = 0;
539
+ if( find_option("trace",0,0)!=0 ) smtpFlags |= SMTP_TRACE_STDOUT;
540
+ verify_all_options();
541
+ if( g.argc<5 ) usage("EMAIL FROM TO ...");
542
+ blob_read_from_file(&body, g.argv[2], ExtFILE);
543
+ zFrom = g.argv[3];
544
+ nTo = g.argc-4;
545
+ azTo = (const char**)g.argv+4;
546
+ zFromDomain = domainOfAddr(zFrom);
547
+ zToDomain = domainOfAddr(azTo[0]);
548
+ p = smtp_session_new(zFromDomain, zToDomain, smtpFlags);
549
+ if( p->zErr ){
550
+ fossil_fatal("%s", p->zErr);
551
+ }
552
+ fossil_print("Connection to \"%s\"\n", p->zHostname);
553
+ smtp_client_startup(p);
554
+ smtp_send_msg(p, zFrom, nTo, azTo, blob_str(&body));
555
+ smtp_client_quit(p);
382556
if( p->zErr ){
383557
fossil_fatal("ERROR: %s\n", p->zErr);
384558
}
385559
smtp_session_free(p);
560
+ blob_zero(&body);
386561
}
387562
--- src/smtp.c
+++ src/smtp.c
@@ -260,11 +260,11 @@
260 p->zErr = mprintf("timeout");
261 socket_close();
262 p->atEof = 1;
263 return;
264 }else{
265 sqlite3_sleep(25);
266 }
267 }while( n<1 || z[n-1]!='\n' );
268 blob_truncate(&p->inbuf, n);
269 blob_line(&p->inbuf, in);
270 }
@@ -320,10 +320,11 @@
320 char *zArg = 0;
321 smtp_send_line(p, "QUIT\r\n");
322 do{
323 smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
324 }while( bMore );
 
325 socket_close();
326 return 0;
327 }
328
329 /*
@@ -364,11 +365,10 @@
364 ** and then immediately shutting it back down. Log all interaction
365 ** on the console. Use ME as the domain name of the sender.
366 */
367 void test_smtp_probe(void){
368 SmtpSession *p;
369 int rc;
370 const char *zDomain;
371 const char *zSelf;
372 if( g.argc!=3 && g.argc!=4 ) usage("DOMAIN [ME]");
373 zDomain = g.argv[2];
374 zSelf = g.argc==4 ? g.argv[3] : "fossil-scm.org";
@@ -375,12 +375,187 @@
375 p = smtp_session_new(zSelf, zDomain, SMTP_TRACE_STDOUT);
376 if( p->zErr ){
377 fossil_fatal("%s", p->zErr);
378 }
379 fossil_print("Connection to \"%s\"\n", p->zHostname);
380 rc = smtp_client_startup(p);
381 if( !rc ) smtp_client_quit(p);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
382 if( p->zErr ){
383 fossil_fatal("ERROR: %s\n", p->zErr);
384 }
385 smtp_session_free(p);
 
386 }
387
--- src/smtp.c
+++ src/smtp.c
@@ -260,11 +260,11 @@
260 p->zErr = mprintf("timeout");
261 socket_close();
262 p->atEof = 1;
263 return;
264 }else{
265 sqlite3_sleep(100);
266 }
267 }while( n<1 || z[n-1]!='\n' );
268 blob_truncate(&p->inbuf, n);
269 blob_line(&p->inbuf, in);
270 }
@@ -320,10 +320,11 @@
320 char *zArg = 0;
321 smtp_send_line(p, "QUIT\r\n");
322 do{
323 smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
324 }while( bMore );
325 p->atEof = 1;
326 socket_close();
327 return 0;
328 }
329
330 /*
@@ -364,11 +365,10 @@
365 ** and then immediately shutting it back down. Log all interaction
366 ** on the console. Use ME as the domain name of the sender.
367 */
368 void test_smtp_probe(void){
369 SmtpSession *p;
 
370 const char *zDomain;
371 const char *zSelf;
372 if( g.argc!=3 && g.argc!=4 ) usage("DOMAIN [ME]");
373 zDomain = g.argv[2];
374 zSelf = g.argc==4 ? g.argv[3] : "fossil-scm.org";
@@ -375,12 +375,187 @@
375 p = smtp_session_new(zSelf, zDomain, SMTP_TRACE_STDOUT);
376 if( p->zErr ){
377 fossil_fatal("%s", p->zErr);
378 }
379 fossil_print("Connection to \"%s\"\n", p->zHostname);
380 smtp_client_startup(p);
381 smtp_client_quit(p);
382 if( p->zErr ){
383 fossil_fatal("ERROR: %s\n", p->zErr);
384 }
385 smtp_session_free(p);
386 }
387
388 /*
389 ** Send the content of an email message followed by a single
390 ** "." line. All lines must be \r\n terminated. Any isolated
391 ** \n line terminators in the input must be converted. Also,
392 ** an line contain using "." should be converted to "..".
393 */
394 static void smtp_send_email_body(
395 const char *zMsg, /* Message to send */
396 size_t (*xSend)(void*,const void*,size_t), /* Sender callback function */
397 void *pArg /* First arg to sender */
398 ){
399 Blob in;
400 Blob out = BLOB_INITIALIZER;
401 Blob line;
402 blob_init(&in, zMsg, -1);
403 while( blob_line(&in, &line) ){
404 char *z = blob_buffer(&line);
405 int n = blob_size(&line);
406 if( n==0 ) break;
407 n--;
408 if( n && z[n-1]=='\r' ) n--;
409 if( n==1 && z[0]=='.' ){
410 blob_append(&out, "..\r\n", 4);
411 }else{
412 blob_append(&out, z, n);
413 blob_append(&out, "\r\n", 2);
414 }
415 }
416 blob_append(&out, ".\r\n", 3);
417 xSend(pArg, blob_buffer(&out), blob_size(&out));
418 blob_zero(&out);
419 blob_zero(&line);
420 }
421
422 /* A sender function appropriate for use by smtp_send_email_body() to
423 ** send all content to the console, for testing.
424 */
425 static size_t smtp_test_sender(void *NotUsed, const void *pContent, size_t N){
426 return fwrite(pContent, 1, N, stdout);
427 }
428
429 /*
430 ** COMMAND: test-smtp-senddata
431 **
432 ** Usage: %fossil test-smtp-senddata FILE
433 **
434 ** Read content from FILE, then send it to stdout encoded as if sent
435 ** to the DATA portion of an SMTP session. This command is used to
436 ** test the encoding logic.
437 */
438 void test_smtp_senddata(void){
439 Blob f;
440 if( g.argc!=3 ) usage("FILE");
441 blob_read_from_file(&f, g.argv[2], ExtFILE);
442 smtp_send_email_body(blob_str(&f), smtp_test_sender, 0);
443 blob_zero(&f);
444 }
445
446 /*
447 ** Send a single email message to the SMTP server.
448 **
449 ** All email addresses (zFrom and azTo) must be plain "local@domain"
450 ** format with the surrounding "<..>". This routine will add the
451 ** necessary "<..>".
452 **
453 ** The body of the email should be well-structured. This routine will
454 ** convert any \n line endings into \r\n and will escape lines containing
455 ** just ".", but will not make any other alterations or corrections to
456 ** the message content.
457 **
458 ** Return 0 on success. Otherwise an error code.
459 */
460 int smtp_send_msg(
461 SmtpSession *p, /* The SMTP server to which the message is sent */
462 const char *zFrom, /* Who the message is from */
463 int nTo, /* Number of receipients */
464 const char **azTo, /* Email address of each recipient */
465 const char *zMsg /* Body of the message */
466 ){
467 int i;
468 int iCode = 0;
469 int bMore = 0;
470 char *zArg = 0;
471 Blob in;
472 blob_init(&in, 0, 0);
473 smtp_send_line(p, "MAIL FROM:<%s>\r\n", zFrom);
474 do{
475 smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
476 }while( bMore );
477 if( iCode!=250 ) return 1;
478 for(i=0; i<nTo; i++){
479 smtp_send_line(p, "RCPT TO:<%s>\r\n", azTo[i]);
480 do{
481 smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
482 }while( bMore );
483 if( iCode!=250 ) return 1;
484 }
485 smtp_send_line(p, "DATA\r\n");
486 do{
487 smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
488 }while( bMore );
489 if( iCode!=354 ) return 1;
490 smtp_send_email_body(zMsg, socket_send, 0);
491 if( p->smtpFlags & SMTP_TRACE_STDOUT ){
492 fossil_print("C: # message content\nC: .\n");
493 }
494 if( p->smtpFlags & SMTP_TRACE_FILE ){
495 fprintf(p->logFile, "C: # message content\nC: .\n");
496 }
497 if( p->smtpFlags & SMTP_TRACE_BLOB ){
498 blob_appendf(p->pTranscript, "C: # message content\nC: .\n");
499 }
500 do{
501 smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
502 }while( bMore );
503 if( iCode!=250 ) return 1;
504 return 0;
505 }
506
507 /*
508 ** The input is a base email address of the form "local@domain".
509 ** Return a pointer to just the "domain" part.
510 */
511 static const char *domainOfAddr(const char *z){
512 while( z[0] && z[0]!='@' ) z++;
513 if( z[0]==0 ) return 0;
514 return z+1;
515 }
516
517
518 /*
519 ** COMMAND: test-smtp-send
520 **
521 ** Usage: %fossil test-smtp-send EMAIL FROM TO ...
522 **
523 ** Use SMTP to send the email message contained in the file named EMAIL
524 ** to the list of users TO. FROM is the sender of the email.
525 **
526 ** Options:
527 **
528 ** --trace Show the SMTP conversation on the console
529 */
530 void test_smtp_send(void){
531 SmtpSession *p;
532 const char *zFrom;
533 int nTo;
534 const char *zToDomain;
535 const char *zFromDomain;
536 const char **azTo;
537 Blob body;
538 u32 smtpFlags = 0;
539 if( find_option("trace",0,0)!=0 ) smtpFlags |= SMTP_TRACE_STDOUT;
540 verify_all_options();
541 if( g.argc<5 ) usage("EMAIL FROM TO ...");
542 blob_read_from_file(&body, g.argv[2], ExtFILE);
543 zFrom = g.argv[3];
544 nTo = g.argc-4;
545 azTo = (const char**)g.argv+4;
546 zFromDomain = domainOfAddr(zFrom);
547 zToDomain = domainOfAddr(azTo[0]);
548 p = smtp_session_new(zFromDomain, zToDomain, smtpFlags);
549 if( p->zErr ){
550 fossil_fatal("%s", p->zErr);
551 }
552 fossil_print("Connection to \"%s\"\n", p->zHostname);
553 smtp_client_startup(p);
554 smtp_send_msg(p, zFrom, nTo, azTo, blob_str(&body));
555 smtp_client_quit(p);
556 if( p->zErr ){
557 fossil_fatal("ERROR: %s\n", p->zErr);
558 }
559 smtp_session_free(p);
560 blob_zero(&body);
561 }
562

Keyboard Shortcuts

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