Fossil

Check-in [03888604]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Add the smtp_send_msg() function capable of encoding and sending a complete email message.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | smtp
Files: files | file ages | folders
SHA3-256:03888604076d42a27ea216886d0bf97f356c2ef7474bfca44043b1a1c91299cc
User & Date: drh 2018-06-28 21:38:32
Context
2018-06-29
03:12
Baseline implementation of the "smtp" command. check-in: be55fc60 user: drh tags: smtp
2018-06-28
21:38
Add the smtp_send_msg() function capable of encoding and sending a complete email message. check-in: 03888604 user: drh tags: smtp
17:15
The test-smtp-probe command is now working. check-in: 9281d52a user: drh tags: smtp
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/http_socket.c.

190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
  if( ai ) freeaddrinfo(ai);
  return rc;
}

/*
** Send content out over the open socket connection.
*/
size_t socket_send(void *NotUsed, void *pContent, size_t N){
  size_t sent;
  size_t total = 0;
  while( N>0 ){
    sent = send(iSocket, pContent, N, 0);
    if( sent<=0 ) break;
    total += sent;
    N -= sent;







|







190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
  if( ai ) freeaddrinfo(ai);
  return rc;
}

/*
** Send content out over the open socket connection.
*/
size_t socket_send(void *NotUsed, const void *pContent, size_t N){
  size_t sent;
  size_t total = 0;
  while( N>0 ){
    sent = send(iSocket, pContent, N, 0);
    if( sent<=0 ) break;
    total += sent;
    N -= sent;

Changes to src/smtp.c.

258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
...
318
319
320
321
322
323
324

325
326
327
328
329
330
331
...
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386















































































































































































      if( nDelay>100 ){
        blob_init(in, 0, 0);
        p->zErr = mprintf("timeout");
        socket_close();
        p->atEof = 1;
        return;
      }else{
        sqlite3_sleep(25);
      }
    }while( n<1 || z[n-1]!='\n' );
    blob_truncate(&p->inbuf, n);
    blob_line(&p->inbuf, in);
  }
  z = blob_buffer(in);
  n = blob_size(in);
................................................................................
  int iCode = 0;
  int bMore = 0;
  char *zArg = 0;
  smtp_send_line(p, "QUIT\r\n");
  do{
    smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
  }while( bMore );

  socket_close();
  return 0;
}

/*
** Begin a client SMTP session.  Wait for the initial 220 then send
** the EHLO and wait for a 250.
................................................................................
**
** Interact with the SMTP server for DOMAIN by setting up a connection
** and then immediately shutting it back down.  Log all interaction
** on the console.  Use ME as the domain name of the sender.
*/
void test_smtp_probe(void){
  SmtpSession *p;
  int rc;
  const char *zDomain;
  const char *zSelf;
  if( g.argc!=3 && g.argc!=4 ) usage("DOMAIN [ME]");
  zDomain = g.argv[2];
  zSelf = g.argc==4 ? g.argv[3] : "fossil-scm.org";
  p = smtp_session_new(zSelf, zDomain, SMTP_TRACE_STDOUT);
  if( p->zErr ){
    fossil_fatal("%s", p->zErr);
  }
  fossil_print("Connection to \"%s\"\n", p->zHostname);
  rc = smtp_client_startup(p);
  if( !rc ) smtp_client_quit(p);
  if( p->zErr ){
    fossil_fatal("ERROR: %s\n", p->zErr);
  }
  smtp_session_free(p);
}






















































































































































































|







 







>







 







<










|
|





>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
...
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
...
363
364
365
366
367
368
369

370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
      if( nDelay>100 ){
        blob_init(in, 0, 0);
        p->zErr = mprintf("timeout");
        socket_close();
        p->atEof = 1;
        return;
      }else{
        sqlite3_sleep(100);
      }
    }while( n<1 || z[n-1]!='\n' );
    blob_truncate(&p->inbuf, n);
    blob_line(&p->inbuf, in);
  }
  z = blob_buffer(in);
  n = blob_size(in);
................................................................................
  int iCode = 0;
  int bMore = 0;
  char *zArg = 0;
  smtp_send_line(p, "QUIT\r\n");
  do{
    smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
  }while( bMore );
  p->atEof = 1;
  socket_close();
  return 0;
}

/*
** Begin a client SMTP session.  Wait for the initial 220 then send
** the EHLO and wait for a 250.
................................................................................
**
** Interact with the SMTP server for DOMAIN by setting up a connection
** and then immediately shutting it back down.  Log all interaction
** on the console.  Use ME as the domain name of the sender.
*/
void test_smtp_probe(void){
  SmtpSession *p;

  const char *zDomain;
  const char *zSelf;
  if( g.argc!=3 && g.argc!=4 ) usage("DOMAIN [ME]");
  zDomain = g.argv[2];
  zSelf = g.argc==4 ? g.argv[3] : "fossil-scm.org";
  p = smtp_session_new(zSelf, zDomain, SMTP_TRACE_STDOUT);
  if( p->zErr ){
    fossil_fatal("%s", p->zErr);
  }
  fossil_print("Connection to \"%s\"\n", p->zHostname);
  smtp_client_startup(p);
  smtp_client_quit(p);
  if( p->zErr ){
    fossil_fatal("ERROR: %s\n", p->zErr);
  }
  smtp_session_free(p);
}

/*
** Send the content of an email message followed by a single
** "." line.  All lines must be \r\n terminated.  Any isolated
** \n line terminators in the input must be converted.  Also,
** an line contain using "." should be converted to "..".
*/
static void smtp_send_email_body(
  const char *zMsg,                          /* Message to send */
  size_t (*xSend)(void*,const void*,size_t), /* Sender callback function */
  void *pArg                                 /* First arg to sender */
){
  Blob in;
  Blob out = BLOB_INITIALIZER;
  Blob line;
  blob_init(&in, zMsg, -1);
  while( blob_line(&in, &line) ){
    char *z = blob_buffer(&line);
    int n = blob_size(&line);
    if( n==0 ) break;
    n--;
    if( n && z[n-1]=='\r' ) n--;
    if( n==1 && z[0]=='.' ){
      blob_append(&out, "..\r\n", 4);
    }else{
      blob_append(&out, z, n);
      blob_append(&out, "\r\n", 2);
    }
  }
  blob_append(&out, ".\r\n", 3);
  xSend(pArg, blob_buffer(&out), blob_size(&out));
  blob_zero(&out);
  blob_zero(&line);
}

/* A sender function appropriate for use by smtp_send_email_body() to
** send all content to the console, for testing.
*/
static size_t smtp_test_sender(void *NotUsed, const void *pContent, size_t N){
  return fwrite(pContent, 1, N, stdout);
}

/*
** COMMAND: test-smtp-senddata
**
** Usage: %fossil test-smtp-senddata FILE
**
** Read content from FILE, then send it to stdout encoded as if sent
** to the DATA portion of an SMTP session.  This command is used to
** test the encoding logic.
*/
void test_smtp_senddata(void){
  Blob f;
  if( g.argc!=3 ) usage("FILE");
  blob_read_from_file(&f, g.argv[2], ExtFILE);
  smtp_send_email_body(blob_str(&f), smtp_test_sender, 0);
  blob_zero(&f);
}

/*
** Send a single email message to the SMTP server.
**
** All email addresses (zFrom and azTo) must be plain "local@domain"
** format with the surrounding "<..>".  This routine will add the
** necessary "<..>".
**
** The body of the email should be well-structured.  This routine will
** convert any \n line endings into \r\n and will escape lines containing
** just ".", but will not make any other alterations or corrections to
** the message content.
**
** Return 0 on success.  Otherwise an error code.
*/
int smtp_send_msg(
  SmtpSession *p,        /* The SMTP server to which the message is sent */
  const char *zFrom,     /* Who the message is from */
  int nTo,               /* Number of receipients */
  const char **azTo,     /* Email address of each recipient */
  const char *zMsg       /* Body of the message */
){
  int i;
  int iCode = 0;
  int bMore = 0;
  char *zArg = 0;
  Blob in;
  blob_init(&in, 0, 0);
  smtp_send_line(p, "MAIL FROM:<%s>\r\n", zFrom);
  do{
    smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
  }while( bMore );
  if( iCode!=250 ) return 1;
  for(i=0; i<nTo; i++){
    smtp_send_line(p, "RCPT TO:<%s>\r\n", azTo[i]);
    do{
      smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
    }while( bMore );
    if( iCode!=250 ) return 1;
  }
  smtp_send_line(p, "DATA\r\n");
  do{
    smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
  }while( bMore );
  if( iCode!=354 ) return 1;
  smtp_send_email_body(zMsg, socket_send, 0);
  if( p->smtpFlags & SMTP_TRACE_STDOUT ){
    fossil_print("C: # message content\nC: .\n");
  }
  if( p->smtpFlags & SMTP_TRACE_FILE ){
    fprintf(p->logFile, "C: # message content\nC: .\n");
  }
  if( p->smtpFlags & SMTP_TRACE_BLOB ){
    blob_appendf(p->pTranscript, "C: # message content\nC: .\n");
  }
  do{
    smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
  }while( bMore );
  if( iCode!=250 ) return 1;
  return 0;
}

/*
** The input is a base email address of the form "local@domain".
** Return a pointer to just the "domain" part.
*/
static const char *domainOfAddr(const char *z){
  while( z[0] && z[0]!='@' ) z++;
  if( z[0]==0 ) return 0;
  return z+1;
}


/*
** COMMAND: test-smtp-send
**
** Usage: %fossil test-smtp-send EMAIL FROM TO ...
**
** Use SMTP to send the email message contained in the file named EMAIL
** to the list of users TO.  FROM is the sender of the email.
**
** Options:
**
**      --trace               Show the SMTP conversation on the console
*/
void test_smtp_send(void){
  SmtpSession *p;
  const char *zFrom;
  int nTo;
  const char *zToDomain;
  const char *zFromDomain;
  const char **azTo;
  Blob body;
  u32 smtpFlags = 0;
  if( find_option("trace",0,0)!=0 ) smtpFlags |= SMTP_TRACE_STDOUT;
  verify_all_options();
  if( g.argc<5 ) usage("EMAIL FROM TO ...");
  blob_read_from_file(&body, g.argv[2], ExtFILE);
  zFrom = g.argv[3];
  nTo = g.argc-4;
  azTo = (const char**)g.argv+4;
  zFromDomain = domainOfAddr(zFrom);
  zToDomain = domainOfAddr(azTo[0]);
  p = smtp_session_new(zFromDomain, zToDomain, smtpFlags);
  if( p->zErr ){
    fossil_fatal("%s", p->zErr);
  }
  fossil_print("Connection to \"%s\"\n", p->zHostname);
  smtp_client_startup(p);
  smtp_send_msg(p, zFrom, nTo, azTo, blob_str(&body));
  smtp_client_quit(p);
  if( p->zErr ){
    fossil_fatal("ERROR: %s\n", p->zErr);
  }
  smtp_session_free(p);
  blob_zero(&body);
}