Fossil SCM

fossil-scm / src / smtp.c
Blame History Raw 700 lines
1
/*
2
** Copyright (c) 2018 D. Richard Hipp
3
**
4
** This program is free software; you can redistribute it and/or
5
** modify it under the terms of the Simplified BSD License (also
6
** known as the "2-Clause License" or "FreeBSD License".)
7
**
8
** This program is distributed in the hope that it will be useful,
9
** but without any warranty; without even the implied warranty of
10
** merchantability or fitness for a particular purpose.
11
**
12
** Author contact information:
13
** [email protected]
14
** http://www.hwaci.com/drh/
15
**
16
*******************************************************************************
17
**
18
** Implementation of SMTP (Simple Mail Transport Protocol) according
19
** to RFC 5321.
20
*/
21
#include "config.h"
22
#include "smtp.h"
23
#include <assert.h>
24
#if (HAVE_DN_EXPAND || HAVE___NS_NAME_UNCOMPRESS || HAVE_NS_NAME_UNCOMPRESS) \
25
&& (HAVE_NS_PARSERR || HAVE___NS_PARSERR) && !defined(FOSSIL_OMIT_DNS)
26
# include <sys/types.h>
27
# include <netinet/in.h>
28
# if defined(HAVE_BIND_RESOLV_H)
29
# include <bind/resolv.h>
30
# include <bind/arpa/nameser_compat.h>
31
# else
32
# include <arpa/nameser.h>
33
# include <resolv.h>
34
# endif
35
# if defined(HAVENS_NAME_UNCOMPRESS) && !defined(dn_expand)
36
# define dn_expand ns_name_uncompress
37
# endif
38
# if defined(HAVE__NS_NAME_UNCOMPRESS) && !defined(dn_expand)
39
# define dn_expand __ns_name_uncompress
40
# endif
41
# define FOSSIL_UNIX_STYLE_DNS 1
42
#endif
43
#if defined(_WIN32) && !defined(__MINGW32__) && !defined(__MINGW64__)
44
# include <windows.h>
45
# include <windns.h>
46
# define FOSSIL_WINDOWS_STYLE_DNS 1
47
#endif
48
49
50
/*
51
** Find the hostname for receiving email for the domain given
52
** in zDomain. Return NULL if not found or not implemented.
53
** If multiple email receivers are advertized, pick the one with
54
** the lowest preference number.
55
**
56
** The returned string is obtained from fossil_malloc()
57
** and should be released using fossil_free().
58
*/
59
char *smtp_mx_host(const char *zDomain){
60
#if defined(FOSSIL_UNIX_STYLE_DNS)
61
int nDns; /* Length of the DNS reply */
62
int rc; /* Return code from various APIs */
63
int i; /* Loop counter */
64
int iBestPriority = 9999999; /* Best priority */
65
int nRec; /* Number of answers */
66
ns_msg h; /* DNS reply parser */
67
const unsigned char *pBest = 0; /* RDATA for the best answer */
68
unsigned char aDns[5000]; /* Raw DNS reply content */
69
char zHostname[5000]; /* Hostname for the MX */
70
71
nDns = res_query(zDomain, C_IN, T_MX, aDns, sizeof(aDns));
72
if( nDns<=0 ) return 0;
73
res_init();
74
rc = ns_initparse(aDns,nDns,&h);
75
if( rc ) return 0;
76
nRec = ns_msg_count(h, ns_s_an);
77
for(i=0; i<nRec; i++){
78
ns_rr x;
79
int priority, sz;
80
const unsigned char *p;
81
rc = ns_parserr(&h, ns_s_an, i, &x);
82
if( rc ) continue;
83
p = ns_rr_rdata(x);
84
sz = ns_rr_rdlen(x);
85
if( sz>2 ){
86
priority = p[0]*256 + p[1];
87
if( priority<iBestPriority ){
88
pBest = p;
89
iBestPriority = priority;
90
}
91
}
92
}
93
if( pBest ){
94
dn_expand(aDns, aDns+nDns, pBest+2, zHostname, sizeof(zHostname));
95
return fossil_strdup(zHostname);
96
}
97
return 0;
98
#elif defined(FOSSIL_WINDOWS_STYLE_DNS)
99
DNS_STATUS status; /* Return status */
100
PDNS_RECORDA pDnsRecord, p; /* Pointer to DNS_RECORD structure */
101
int iBestPriority = 9999999; /* Best priority */
102
char *pBest = 0; /* RDATA for the best answer */
103
104
status = DnsQuery_UTF8(zDomain, /* Domain name */
105
DNS_TYPE_MX, /* DNS record type */
106
DNS_QUERY_STANDARD, /* Query options */
107
NULL, /* List of DNS servers */
108
&pDnsRecord, /* Query results */
109
NULL); /* Reserved */
110
if( status ) return NULL;
111
112
p = pDnsRecord;
113
while( p ){
114
if( p->Data.MX.wPreference<iBestPriority ){
115
iBestPriority = p->Data.MX.wPreference;
116
pBest = p->Data.MX.pNameExchange;
117
}
118
p = p->pNext;
119
}
120
if( pBest ){
121
pBest = fossil_strdup(pBest);
122
}
123
DnsRecordListFree(pDnsRecord, DnsFreeRecordListDeep);
124
return pBest;
125
#else
126
return 0;
127
#endif /* defined(FOSSIL_WINDOWS_STYLE_DNS) */
128
}
129
130
/*
131
** COMMAND: test-find-mx
132
**
133
** Usage: %fossil test-find-mx DOMAIN ...
134
**
135
** Do a DNS MX lookup to find the hostname for sending email for
136
** DOMAIN.
137
*/
138
void test_find_mx(void){
139
int i;
140
if( g.argc<=2 ){
141
usage("DOMAIN ...");
142
}
143
for(i=2; i<g.argc; i++){
144
char *z = smtp_mx_host(g.argv[i]);
145
fossil_print("%s: %s\n", g.argv[i], z);
146
fossil_free(z);
147
}
148
}
149
150
#if INTERFACE
151
/*
152
** Information about a single SMTP connection.
153
*/
154
struct SmtpSession {
155
const char *zFrom; /* Domain from which we are sending */
156
const char *zDest; /* Domain that will receive the email */
157
char *zHostname; /* Hostname of SMTP server for zDest */
158
u32 smtpFlags; /* Flags changing the operation */
159
FILE *logFile; /* Write session transcript to this log file */
160
Blob *pTranscript; /* Record session transcript here */
161
int bOpen; /* True if connection is Open */
162
int bFatal; /* Error is fatal. Do not retry */
163
char *zErr; /* Error message */
164
Blob inbuf; /* Input buffer */
165
UrlData url; /* Address of the server */
166
};
167
168
/* Allowed values for SmtpSession.smtpFlags */
169
#define SMTP_TRACE_STDOUT 0x00001 /* Debugging info to console */
170
#define SMTP_TRACE_FILE 0x00002 /* Debugging info to logFile */
171
#define SMTP_TRACE_BLOB 0x00004 /* Record transcript */
172
#define SMTP_DIRECT 0x00008 /* Skip the MX lookup */
173
#define SMTP_PORT 0x00010 /* Use an alternate port number */
174
175
#endif
176
177
/*
178
** Shutdown an SmtpSession
179
*/
180
void smtp_session_free(SmtpSession *pSession){
181
socket_close();
182
blob_reset(&pSession->inbuf);
183
fossil_free(pSession->zHostname);
184
fossil_free(pSession->zErr);
185
fossil_free(pSession);
186
}
187
188
/*
189
** Set an error message on the SmtpSession
190
*/
191
static void smtp_set_error(
192
SmtpSession *p, /* The SMTP context */
193
int bFatal, /* Fatal error. Reset and retry is pointless */
194
const char *zFormat, /* Error message. */
195
...
196
){
197
if( bFatal ) p->bFatal = 1;
198
if( p->zErr==0 ){
199
va_list ap;
200
va_start(ap, zFormat);
201
p->zErr = vmprintf(zFormat, ap);
202
va_end(ap);
203
}
204
if( p->bOpen ){
205
socket_close();
206
p->bOpen = 0;
207
}
208
}
209
210
/*
211
** Allocate a new SmtpSession object.
212
**
213
** Both zFrom and zDest must be specified. smtpFlags may not contain
214
** either SMTP_TRACE_FILE or SMTP_TRACE_BLOB as those settings must be
215
** added by a subsequent call to smtp_session_config().
216
**
217
** The iPort option is ignored unless SMTP_PORT is set in smtpFlags
218
*/
219
SmtpSession *smtp_session_new(
220
const char *zFrom, /* Domain for the client */
221
const char *zDest, /* Domain of the server */
222
u32 smtpFlags, /* Flags */
223
int iPort /* TCP port if the SMTP_PORT flags is present */
224
){
225
SmtpSession *p;
226
227
p = fossil_malloc( sizeof(*p) );
228
memset(p, 0, sizeof(*p));
229
p->zFrom = zFrom;
230
p->zDest = zDest;
231
p->smtpFlags = smtpFlags;
232
p->url.port = 25;
233
blob_init(&p->inbuf, 0, 0);
234
if( smtpFlags & SMTP_PORT ){
235
p->url.port = iPort;
236
}
237
if( (smtpFlags & SMTP_DIRECT)!=0 ){
238
int i;
239
p->zHostname = fossil_strdup(zDest);
240
for(i=0; p->zHostname[i] && p->zHostname[i]!=':'; i++){}
241
if( p->zHostname[i]==':' ){
242
p->zHostname[i] = 0;
243
p->url.port = atoi(&p->zHostname[i+1]);
244
}
245
}else{
246
p->zHostname = smtp_mx_host(zDest);
247
}
248
if( p->zHostname==0 ){
249
smtp_set_error(p, 1, "cannot locate SMTP server for \"%s\"", zDest);
250
return p;
251
}
252
p->url.name = p->zHostname;
253
socket_global_init();
254
p->bOpen = 0;
255
return p;
256
}
257
258
/*
259
** Configure debugging options on SmtpSession. Add all bits in
260
** smtpFlags to the settings. The following bits can be added:
261
**
262
** SMTP_FLAG_FILE: In which case pArg is the FILE* pointer to use
263
**
264
** SMTP_FLAG_BLOB: In which case pArg is the Blob* poitner to use.
265
*/
266
void smtp_session_config(SmtpSession *p, u32 smtpFlags, void *pArg){
267
p->smtpFlags = smtpFlags;
268
if( smtpFlags & SMTP_TRACE_FILE ){
269
p->logFile = (FILE*)pArg;
270
}else if( smtpFlags & SMTP_TRACE_BLOB ){
271
p->pTranscript = (Blob*)pArg;
272
}
273
}
274
275
/*
276
** Send a single line of output the SMTP client to the server.
277
*/
278
static void smtp_send_line(SmtpSession *p, const char *zFormat, ...){
279
Blob b = empty_blob;
280
va_list ap;
281
char *z;
282
int n;
283
if( !p->bOpen ) return;
284
va_start(ap, zFormat);
285
blob_vappendf(&b, zFormat, ap);
286
va_end(ap);
287
z = blob_buffer(&b);
288
n = blob_size(&b);
289
assert( n>=2 );
290
assert( z[n-1]=='\n' );
291
assert( z[n-2]=='\r' );
292
if( p->smtpFlags & SMTP_TRACE_STDOUT ){
293
fossil_print("C: %.*s\n", n-2, z);
294
}
295
if( p->smtpFlags & SMTP_TRACE_FILE ){
296
fprintf(p->logFile, "C: %.*s\n", n-2, z);
297
}
298
if( p->smtpFlags & SMTP_TRACE_BLOB ){
299
blob_appendf(p->pTranscript, "C: %.*s\n", n-2, z);
300
}
301
socket_send(0, z, n);
302
blob_reset(&b);
303
}
304
305
/*
306
** Read a line of input received from the SMTP server. Make in point
307
** to the next input line.
308
**
309
** Content is actually read into the p->in buffer. Then blob_line()
310
** is used to extract individual lines, passing each to "in".
311
*/
312
static void smtp_recv_line(SmtpSession *p, Blob *in){
313
int n = blob_size(&p->inbuf);
314
char *z = blob_buffer(&p->inbuf);
315
int i = blob_tell(&p->inbuf);
316
int nDelay = 0;
317
if( i<n && z[n-1]=='\n' ){
318
blob_line(&p->inbuf, in);
319
}else if( !p->bOpen ){
320
blob_init(in, 0, 0);
321
}else{
322
if( n>0 && i>=n ){
323
blob_truncate(&p->inbuf, 0);
324
blob_rewind(&p->inbuf);
325
n = 0;
326
}
327
do{
328
size_t got;
329
blob_resize(&p->inbuf, n+1000);
330
z = blob_buffer(&p->inbuf);
331
got = socket_receive(0, z+n, 1000, 1);
332
if( got>0 ){
333
in->nUsed += got;
334
n += got;
335
z[n] = 0;
336
if( n>0 && z[n-1]=='\n' ) break;
337
if( got==1000 ) continue;
338
}
339
nDelay++;
340
if( nDelay>100 ){
341
blob_init(in, 0, 0);
342
smtp_set_error(p, 1, "client times out waiting on server response");
343
return;
344
}else{
345
sqlite3_sleep(100);
346
}
347
}while( n<1 || z[n-1]!='\n' );
348
blob_truncate(&p->inbuf, n);
349
blob_line(&p->inbuf, in);
350
}
351
z = blob_buffer(in);
352
n = blob_size(in);
353
if( n && z[n-1]=='\n' ) n--;
354
if( n && z[n-1]=='\r' ) n--;
355
if( p->smtpFlags & SMTP_TRACE_STDOUT ){
356
fossil_print("S: %.*s\n", n, z);
357
}
358
if( p->smtpFlags & SMTP_TRACE_FILE ){
359
fprintf(p->logFile, "S: %.*s\n", n, z);
360
}
361
if( p->smtpFlags & SMTP_TRACE_BLOB ){
362
blob_appendf(p->pTranscript, "S: %.*s\n", n-2, z);
363
}
364
}
365
366
/*
367
** Capture a single-line server reply.
368
*/
369
static void smtp_get_reply_from_server(
370
SmtpSession *p, /* The SMTP connection */
371
Blob *in, /* Buffer used to hold the reply */
372
int *piCode, /* The return code */
373
int *pbMore, /* True if the reply is not complete */
374
char **pzArg /* Argument */
375
){
376
int n;
377
char *z;
378
blob_truncate(in, 0);
379
smtp_recv_line(p, in);
380
blob_trim(in);
381
z = blob_str(in);
382
n = blob_size(in);
383
if( z[0]=='#' ){
384
*piCode = 0;
385
*pbMore = 1;
386
*pzArg = z;
387
}else{
388
*piCode = atoi(z);
389
*pbMore = n>=4 && z[3]=='-';
390
*pzArg = n>=4 ? z+4 : "";
391
}
392
}
393
394
/*
395
** Have the client send a QUIT message.
396
*/
397
int smtp_client_quit(SmtpSession *p){
398
Blob in = BLOB_INITIALIZER;
399
int iCode = 0;
400
int bMore = 0;
401
char *zArg = 0;
402
if( p->bOpen ){
403
smtp_send_line(p, "QUIT\r\n");
404
do{
405
smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
406
}while( bMore );
407
p->bOpen = 0;
408
socket_close();
409
}
410
return 0;
411
}
412
413
/*
414
** Begin a client SMTP session. Wait for the initial 220 then send
415
** the EHLO and wait for a 250.
416
**
417
** Return 0 on success and non-zero for a failure.
418
*/
419
static int smtp_client_startup(SmtpSession *p){
420
Blob in = BLOB_INITIALIZER;
421
int iCode = 0;
422
int bMore = 0;
423
char *zArg = 0;
424
if( p==0 || p->bFatal ) return 1;
425
if( socket_open(&p->url) ){
426
smtp_set_error(p, 1, "can't open socket: %z", socket_errmsg());
427
return 1;
428
}
429
p->bOpen = 1;
430
do{
431
smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
432
}while( bMore );
433
if( iCode!=220 ){
434
smtp_set_error(p, 1, "conversation begins with: \"%d %s\"",iCode,zArg);
435
smtp_client_quit(p);
436
return 1;
437
}
438
smtp_send_line(p, "EHLO %s\r\n", p->zFrom);
439
do{
440
smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
441
}while( bMore );
442
if( iCode!=250 ){
443
smtp_set_error(p, 1, "reply to EHLO with: \"%d %s\"",iCode, zArg);
444
smtp_client_quit(p);
445
return 1;
446
}
447
fossil_free(p->zErr);
448
p->zErr = 0;
449
return 0;
450
}
451
452
/*
453
** COMMAND: test-smtp-probe
454
**
455
** Usage: %fossil test-smtp-probe DOMAIN [ME]
456
**
457
** Interact with the SMTP server for DOMAIN by setting up a connection
458
** and then immediately shutting it back down. Log all interaction
459
** on the console. Use ME as the domain name of the sender.
460
**
461
** Options:
462
** --direct Use DOMAIN directly without going through MX
463
** --port N Talk on TCP port N
464
*/
465
void test_smtp_probe(void){
466
SmtpSession *p;
467
const char *zDomain;
468
const char *zSelf;
469
const char *zPort;
470
int iPort = 25;
471
u32 smtpFlags = SMTP_TRACE_STDOUT|SMTP_PORT;
472
473
if( find_option("direct",0,0)!=0 ) smtpFlags |= SMTP_DIRECT;
474
zPort = find_option("port",0,1);
475
if( zPort ) iPort = atoi(zPort);
476
verify_all_options();
477
if( g.argc!=3 && g.argc!=4 ) usage("DOMAIN [ME]");
478
zDomain = g.argv[2];
479
zSelf = g.argc==4 ? g.argv[3] : "fossil-scm.org";
480
p = smtp_session_new(zSelf, zDomain, smtpFlags, iPort);
481
if( p->zErr ){
482
fossil_fatal("%s", p->zErr);
483
}
484
fossil_print("Connection to \"%s\"\n", p->zHostname);
485
smtp_client_startup(p);
486
smtp_client_quit(p);
487
if( p->zErr ){
488
fossil_fatal("ERROR: %s\n", p->zErr);
489
}
490
smtp_session_free(p);
491
}
492
493
/*
494
** Send the content of an email message followed by a single
495
** "." line. All lines must be \r\n terminated. Any isolated
496
** \n line terminators in the input must be converted. Also,
497
** a line beginning with "." must have the dot doubled per
498
** https://tools.ietf.org/html/rfc5321#section-4.5.2
499
*/
500
static void smtp_send_email_body(
501
const char *zMsg, /* Message to send */
502
size_t (*xSend)(void*,const void*,size_t), /* Sender callback function */
503
void *pArg /* First arg to sender */
504
){
505
Blob in;
506
Blob out = BLOB_INITIALIZER;
507
Blob line;
508
blob_init(&in, zMsg, -1);
509
while( blob_line(&in, &line) ){
510
char *z = blob_buffer(&line);
511
int n = blob_size(&line);
512
if( n==0 ) break;
513
n--;
514
if( n && z[n-1]=='\r' ) n--;
515
if( z[0]=='.' ){
516
blob_append(&out, "..", 2); /* RFC 5321 § 4.5.2 */
517
blob_append(&out, z+1, n-1);
518
}else{
519
blob_append(&out, z, n);
520
}
521
blob_append(&out, "\r\n", 2);
522
}
523
blob_append(&out, ".\r\n", 3);
524
xSend(pArg, blob_buffer(&out), blob_size(&out));
525
blob_reset(&out);
526
blob_reset(&line);
527
}
528
529
/* A sender function appropriate for use by smtp_send_email_body() to
530
** send all content to the console, for testing.
531
*/
532
static size_t smtp_test_sender(void *NotUsed, const void *pContent, size_t N){
533
return fwrite(pContent, 1, N, stdout);
534
}
535
536
/*
537
** COMMAND: test-smtp-senddata
538
**
539
** Usage: %fossil test-smtp-senddata FILE
540
**
541
** Read content from FILE, then send it to stdout encoded as if sent
542
** to the DATA portion of an SMTP session. This command is used to
543
** test the encoding logic.
544
*/
545
void test_smtp_senddata(void){
546
Blob f;
547
if( g.argc!=3 ) usage("FILE");
548
blob_read_from_file(&f, g.argv[2], ExtFILE);
549
smtp_send_email_body(blob_str(&f), smtp_test_sender, 0);
550
blob_reset(&f);
551
}
552
553
/*
554
** Send a single email message to the SMTP server.
555
**
556
** All email addresses (zFrom and azTo) must be plain "local@domain"
557
** format without the surrounding "<..>". This routine will add the
558
** necessary "<..>".
559
**
560
** The body of the email should be well-structured. This routine will
561
** convert any \n line endings into \r\n and will escape lines containing
562
** just ".", but will not make any other alterations or corrections to
563
** the message content.
564
**
565
** Return 0 on success. Otherwise an error code.
566
*/
567
int smtp_send_msg(
568
SmtpSession *p, /* The SMTP server to which the message is sent */
569
const char *zFrom, /* Who the message is from */
570
int nTo, /* Number of recipients */
571
const char **azTo, /* Email address of each recipient */
572
const char *zMsg /* Body of the message */
573
){
574
int i;
575
int iCode = 0;
576
int bMore = 0;
577
char *zArg = 0;
578
Blob in;
579
blob_init(&in, 0, 0);
580
if( !p->bOpen ){
581
if( !p->bFatal ) smtp_client_startup(p);
582
if( !p->bOpen ) return 1;
583
}
584
smtp_send_line(p, "MAIL FROM:<%s>\r\n", zFrom);
585
do{
586
smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
587
}while( bMore );
588
if( iCode!=250 ){
589
smtp_set_error(p, 0,"reply to MAIL FROM: \"%d %s\"",iCode,zArg);
590
return 1;
591
}
592
for(i=0; i<nTo; i++){
593
smtp_send_line(p, "RCPT TO:<%s>\r\n", azTo[i]);
594
do{
595
smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
596
}while( bMore );
597
if( iCode!=250 ){
598
smtp_set_error(p, 0,"reply to RCPT TO: \"%d %s\"",iCode,zArg);
599
return 1;
600
}
601
}
602
smtp_send_line(p, "DATA\r\n");
603
do{
604
smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
605
}while( bMore );
606
if( iCode!=354 ){
607
smtp_set_error(p, 0, "reply to DATA with: \"%d %s\"",iCode,zArg);
608
return 1;
609
}
610
smtp_send_email_body(zMsg, socket_send, 0);
611
if( p->smtpFlags & SMTP_TRACE_STDOUT ){
612
fossil_print("C: # message content\nC: .\n");
613
}
614
if( p->smtpFlags & SMTP_TRACE_FILE ){
615
fprintf(p->logFile, "C: # message content\nC: .\n");
616
}
617
if( p->smtpFlags & SMTP_TRACE_BLOB ){
618
blob_appendf(p->pTranscript, "C: # message content\nC: .\n");
619
}
620
do{
621
smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
622
}while( bMore );
623
if( iCode!=250 ){
624
smtp_set_error(p, 0, "reply to end-of-DATA with: \"%d %s\"",
625
iCode, zArg);
626
return 1;
627
}
628
return 0;
629
}
630
631
/*
632
** The input is a base email address of the form "local@domain".
633
** Return a pointer to just the "domain" part, or 0 if the string
634
** contains no "@".
635
*/
636
const char *domain_of_addr(const char *z){
637
while( z[0] && z[0]!='@' ) z++;
638
if( z[0]==0 ) return 0;
639
return z+1;
640
}
641
642
643
/*
644
** COMMAND: test-smtp-send
645
**
646
** Usage: %fossil test-smtp-send EMAIL FROM TO ...
647
**
648
** Use SMTP to send the email message contained in the file named EMAIL
649
** to the list of users TO. FROM is the sender of the email.
650
**
651
** Options:
652
** --direct Go directly to the TO domain. Bypass MX lookup
653
** --relayhost R Use R as relay host directly for delivery.
654
** --port N Use TCP port N instead of 25
655
** --trace Show the SMTP conversation on the console
656
*/
657
void test_smtp_send(void){
658
SmtpSession *p;
659
const char *zFrom;
660
int nTo;
661
const char *zToDomain;
662
const char *zFromDomain;
663
const char *zRelay;
664
const char **azTo;
665
int smtpPort = 25;
666
const char *zPort;
667
Blob body;
668
u32 smtpFlags = SMTP_PORT;
669
if( find_option("trace",0,0)!=0 ) smtpFlags |= SMTP_TRACE_STDOUT;
670
if( find_option("direct",0,0)!=0 ) smtpFlags |= SMTP_DIRECT;
671
zPort = find_option("port",0,1);
672
if( zPort ) smtpPort = atoi(zPort);
673
zRelay = find_option("relayhost",0,1);
674
verify_all_options();
675
if( g.argc<5 ) usage("EMAIL FROM TO ...");
676
blob_read_from_file(&body, g.argv[2], ExtFILE);
677
zFrom = g.argv[3];
678
nTo = g.argc-4;
679
azTo = (const char**)g.argv+4;
680
zFromDomain = domain_of_addr(zFrom);
681
if( zRelay!=0 && zRelay[0]!= 0) {
682
smtpFlags |= SMTP_DIRECT;
683
zToDomain = zRelay;
684
}else{
685
zToDomain = domain_of_addr(azTo[0]);
686
}
687
p = smtp_session_new(zFromDomain, zToDomain, smtpFlags, smtpPort);
688
if( p->zErr ){
689
fossil_fatal("%s", p->zErr);
690
}
691
fossil_print("Connection to \"%s\"\n", p->zHostname);
692
smtp_send_msg(p, zFrom, nTo, azTo, blob_str(&body));
693
smtp_client_quit(p);
694
if( p->zErr ){
695
fossil_fatal("ERROR: %s\n", p->zErr);
696
}
697
smtp_session_free(p);
698
blob_reset(&body);
699
}
700

Keyboard Shortcuts

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