1 /*
2 Bacula(R) - The Network Backup Solution
3
4 Copyright (C) 2000-2020 Kern Sibbald
5
6 The original author of Bacula is Kern Sibbald, with contributions
7 from many others, a complete list can be found in the file AUTHORS.
8
9 You may use this file and others of this release according to the
10 license defined in the LICENSE file, which includes the Affero General
11 Public License, v3.0 ("AGPLv3") and some additional permissions and
12 terms pursuant to its AGPLv3 Section 7.
13
14 This notice must be preserved when any source code is
15 conveyed and/or propagated.
16
17 Bacula(R) is a registered trademark of Kern Sibbald.
18 */
19 /*
20 Derived from smtp-orig.c
21
22 AUTHOR(S)
23 W.Z. Venema
24 Eindhoven University of Technology
25 Department of Mathematics and Computer Science
26 Den Dolech 2, P.O. Box 513, 5600 MB Eindhoven, The Netherlands
27 CREATION DATE
28 Wed Dec 1 14:51:13 MET 1993
29 LAST UPDATE
30 Fri Aug 11 12:29:23 MET DST 1995
31 COPYRIGHT
32 None specified.
33
34 Kern Sibbald, July 2001
35
36 Note, the original W.Z. Venema smtp.c had no license and no
37 copyright.
38 See:
39 http://archives.neohapsis.com/archives/postfix/2000-05/1520.html
40
41 In previous versions, I mistakenly believed that this code came from
42 Ralf S. Engelshall's smtpclient_main.c, but in fact 99% was
43 Wietse Venema's code, and the rest was mine.
44 */
45
46 #include "bacula.h"
47 #include "jcr.h"
48 #define MY_NAME "bsmtp"
49
50 #if defined(HAVE_WIN32)
51 #include <lmcons.h>
52 #endif
53
54 #ifndef MAXSTRING
55 #define MAXSTRING 254
56 #endif
57
58 enum resolv_type {
59 RESOLV_PROTO_ANY,
60 RESOLV_PROTO_IPV4,
61 RESOLV_PROTO_IPV6
62 };
63
64 static FILE *sfp;
65 static FILE *rfp;
66
67 static char *from_addr = NULL;
68 static char *cc_addr = NULL;
69 static char *subject = NULL;
70 static char *err_addr = NULL;
71 static const char *mailhost = NULL;
72 static char *reply_addr = NULL;
73 static char *sender_addr = NULL;
74 static char *custom_headers[10];
75 static int mailport = 25;
76 static char my_hostname[MAXSTRING];
77 static bool content_utf8 = false;
78 static resolv_type default_resolv_type = RESOLV_PROTO_IPV4;
79
80 /*
81 * Take input that may have names and other stuff and strip
82 * it down to the mail box address ... i.e. what is enclosed
83 * in < >. Otherwise add < >.
84 */
cleanup_addr(char * addr,char * buf,int buf_len)85 static char *cleanup_addr(char *addr, char *buf, int buf_len)
86 {
87 char *p, *q;
88
89 if ((p = strchr(addr, '<')) == NULL) {
90 snprintf(buf, buf_len, "<%s>", addr);
91 } else {
92 /* Copy <addr> */
93 for (q=buf; *p && *p!='>'; ) {
94 *q++ = *p++;
95 }
96 if (*p) {
97 *q++ = *p;
98 }
99 *q = 0;
100 }
101 Dmsg2(100, "cleanup in=%s out=%s\n", addr, buf);
102 return buf;
103 }
104
105 /*
106 * get_response - examine message from server
107 */
get_response(void)108 static void get_response(void)
109 {
110 char buf[1000];
111
112 Dmsg0(50, "Calling fgets on read socket rfp.\n");
113 buf[3] = 0;
114 while (fgets(buf, sizeof(buf), rfp)) {
115 int len = strlen(buf);
116 if (len > 0) {
117 buf[len-1] = 0;
118 }
119 if (debug_level >= 10) {
120 fprintf(stderr, "%s <-- %s\n", mailhost, buf);
121 }
122 Dmsg2(10, "%s --> %s\n", mailhost, buf);
123 if (!isdigit((int)buf[0]) || buf[0] > '3') {
124 Pmsg2(0, _("Fatal malformed reply from %s: %s\n"), mailhost, buf);
125 exit(1);
126 }
127 if (buf[3] != '-') {
128 break;
129 }
130 }
131 if (ferror(rfp)) {
132 fprintf(stderr, _("Fatal fgets error: ERR=%s\n"), strerror(errno));
133 }
134 return;
135 }
136
137 /*
138 * chat - say something to server and check the response
139 */
chat(const char * fmt,...)140 static void chat(const char *fmt, ...)
141 {
142 va_list ap;
143
144 va_start(ap, fmt);
145 vfprintf(sfp, fmt, ap);
146 va_end(ap);
147 if (debug_level >= 10) {
148 fprintf(stdout, "%s --> ", my_hostname);
149 va_start(ap, fmt);
150 vfprintf(stdout, fmt, ap);
151 va_end(ap);
152 }
153
154 /* Send message to server and parse its response. */
155 fflush(sfp);
156 if (debug_level >= 10) {
157 fflush(stdout);
158 }
159 get_response();
160 }
161
162 /*
163 * usage - explain and bail out
164 */
usage()165 static void usage()
166 {
167 fprintf(stderr,
168 _("\n"
169 "Usage: %s [-f from] [-h mailhost] [-s subject] [-c copy] [recipient ...]\n"
170 " -4 forces bsmtp to use IPv4 addresses only.\n"
171 #ifdef HAVE_IPV6
172 " -6 forces bsmtp to use IPv6 addresses only.\n"
173 #endif
174 " -8 set charset to UTF-8\n"
175 " -a use any ip protocol for address resolution\n"
176 " -c set the Cc: field\n"
177 " -d <nn> set debug level to <nn>\n"
178 " -dt print a timestamp in debug output\n"
179 " -f set the From: field\n"
180 " -h use mailhost:port as the SMTP server\n"
181 " -s set the Subject: field\n"
182 " -r set the Reply-To: field\n"
183 " -S set the Sender: field\n"
184 " -H add a custom line to the mail header\n"
185 " -l set the maximum number of lines to send (default: unlimited)\n"
186 " -? print this message.\n"
187 "\n"), MY_NAME);
188
189 exit(1);
190 }
191
192 /*
193 * Return the offset west from localtime to UTC in minutes
194 * Same as timezone.tz_minuteswest
195 * Unix tz_offset coded by: Attila Fülöp
196 */
tz_offset(time_t lnow,struct tm & tm)197 static long tz_offset(time_t lnow, struct tm &tm)
198 {
199 #if defined(HAVE_WIN32)
200 #if defined(HAVE_MINGW)
201 __MINGW_IMPORT long _dstbias;
202 #endif
203 #if defined(MINGW64)
204 # define _tzset tzset
205 #endif
206 /* Win32 code */
207 long offset;
208 _tzset();
209 offset = _timezone;
210 if (tm.tm_isdst) {
211 offset += _dstbias;
212 }
213 return offset /= 60;
214 #else
215
216 /* Unix/Linux code */
217 struct tm tm_utc;
218 time_t now = lnow;
219
220 (void)gmtime_r(&now, &tm_utc);
221 tm_utc.tm_isdst = tm.tm_isdst;
222 return (long)difftime(mktime(&tm_utc), now) / 60;
223 #endif
224 }
225
get_date_string(char * buf,int buf_len)226 static void get_date_string(char *buf, int buf_len)
227 {
228 time_t now = time(NULL);
229 struct tm tm;
230 char tzbuf[MAXSTRING];
231 long my_timezone;
232
233 /* Add RFC822 date */
234 (void)localtime_r(&now, &tm);
235
236 my_timezone = tz_offset(now, tm);
237 strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S", &tm);
238 snprintf(tzbuf, sizeof(tzbuf), " %+2.2ld%2.2u", -my_timezone / 60, (unsigned int)abs(my_timezone) % 60);
239 strcat(buf, tzbuf); /* add +0100 */
240 strftime(tzbuf, sizeof(tzbuf), " (%Z)", &tm);
241 strcat(buf, tzbuf); /* add (CEST) */
242 }
243
244 /*********************************************************************
245 *
246 * Program to send email
247 */
main(int argc,char * argv[])248 int main (int argc, char *argv[])
249 {
250 char buf[1000];
251 int i, ch;
252 unsigned long maxlines, lines;
253 #if defined(HAVE_WIN32)
254 SOCKET s;
255 #else
256 int s, r;
257 struct passwd *pwd;
258 #endif
259 char *cp, *p;
260 #ifdef HAVE_GETADDRINFO
261 int res;
262 struct addrinfo hints;
263 struct addrinfo *ai, *rp;
264 char mail_port[10];
265 #else
266 struct hostent *hp;
267 struct sockaddr_in sin;
268 #endif
269 #ifdef HAVE_IPV6
270 const char *options = "468ac:d:f:h:r:s:l:S:H:?";
271 #else
272 const char *options = "48ac:d:f:h:r:s:l:S:H:?";
273 #endif
274
275 int custom_header_count = 0;
276 int custom_header_max = (sizeof(custom_headers) / sizeof(char *));
277
278 setlocale(LC_ALL, "en_US");
279 bindtextdomain("bacula", LOCALEDIR);
280 textdomain("bacula");
281
282 my_name_is(argc, argv, "bsmtp");
283 maxlines = 0;
284
285 while ((ch = getopt(argc, argv, options)) != -1) {
286 switch (ch) {
287 case '4':
288 default_resolv_type = RESOLV_PROTO_IPV4;
289 break;
290
291 #ifdef HAVE_IPV6
292 case '6':
293 default_resolv_type = RESOLV_PROTO_IPV6;
294 break;
295 #endif
296
297 case '8':
298 content_utf8 = true;
299 break;
300
301 case 'a':
302 default_resolv_type = RESOLV_PROTO_ANY;
303 break;
304
305 case 'c':
306 Dmsg1(20, "cc=%s\n", optarg);
307 cc_addr = optarg;
308 break;
309
310 case 'd': /* set debug level */
311 if (*optarg == 't') {
312 dbg_timestamp = true;
313 } else {
314 debug_level = atoi(optarg);
315 if (debug_level <= 0) {
316 debug_level = 1;
317 }
318 }
319 Dmsg1(20, "Debug level = %d\n", debug_level);
320 break;
321
322 case 'f': /* from */
323 from_addr = optarg;
324 break;
325
326 case 'h': /* smtp host */
327 Dmsg1(20, "host=%s\n", optarg);
328 p = strchr(optarg, ':');
329 if (p) {
330 *p++ = 0;
331 mailport = atoi(p);
332 }
333 mailhost = optarg;
334 break;
335
336 case 's': /* subject */
337 Dmsg1(20, "subject=%s\n", optarg);
338 subject = optarg;
339 break;
340
341 case 'r': /* reply address */
342 reply_addr = optarg;
343 break;
344
345 case 'S': /* sender address */
346 sender_addr = optarg;
347 break;
348
349 case 'H': /* custom header "XXXX: YYYYYYY" */
350 if (custom_header_count<custom_header_max) {
351 custom_headers[custom_header_count++] = optarg;
352 } else {
353 fprintf(stderr, "Too many custom header, ignored\n");
354 }
355 break;
356
357 case 'l':
358 Dmsg1(20, "maxlines=%s\n", optarg);
359 maxlines = (unsigned long) atol(optarg);
360 break;
361
362 case '?':
363 default:
364 usage();
365
366 }
367 }
368 argc -= optind;
369 argv += optind;
370
371 if (argc < 1) {
372 Pmsg0(0, _("Fatal error: no recipient given.\n"));
373 usage();
374 exit(1);
375 }
376
377 /*
378 * Determine SMTP server
379 */
380 if (mailhost == NULL) {
381 if ((cp = getenv("SMTPSERVER")) != NULL) {
382 mailhost = cp;
383 } else {
384 mailhost = "localhost";
385 }
386 }
387
388 #if defined(HAVE_WIN32)
389 WSADATA wsaData;
390
391 _setmode(0, _O_BINARY);
392 WSAStartup(MAKEWORD(2,2), &wsaData);
393 #endif
394
395 /*
396 * Find out my own host name for HELO;
397 * if possible, get the FQDN - fully qualified domain name
398 */
399 if (gethostname(my_hostname, sizeof(my_hostname) - 1) < 0) {
400 Pmsg1(0, _("Fatal gethostname error: ERR=%s\n"), strerror(errno));
401 exit(1);
402 }
403 #ifdef HAVE_GETADDRINFO
404 memset(&hints, 0, sizeof(struct addrinfo));
405 hints.ai_family = AF_UNSPEC;
406 hints.ai_socktype = 0;
407 hints.ai_protocol = 0;
408 hints.ai_flags = AI_CANONNAME;
409
410 if ((res = getaddrinfo(my_hostname, NULL, &hints, &ai)) != 0) {
411 Pmsg2(0, _("Fatal getaddrinfo for myself failed \"%s\": ERR=%s\n"),
412 my_hostname, gai_strerror(res));
413 exit(1);
414 }
415 bstrncpy(my_hostname, ai->ai_canonname, sizeof(my_hostname));
416 freeaddrinfo(ai);
417 #else
418 if ((hp = gethostbyname(my_hostname)) == NULL) {
419 Pmsg2(0, _("Fatal gethostbyname for myself failed \"%s\": ERR=%s\n"),
420 my_hostname, strerror(errno));
421 exit(1);
422 }
423 bstrncpy(my_hostname, hp->h_name, sizeof(my_hostname));
424 #endif
425 Dmsg1(20, "My hostname is: %s\n", my_hostname);
426
427 /*
428 * Determine from address.
429 */
430 if (from_addr == NULL) {
431 #if defined(HAVE_WIN32)
432 DWORD dwSize = UNLEN + 1;
433 LPSTR lpszBuffer = (LPSTR)alloca(dwSize);
434
435 if (GetUserName(lpszBuffer, &dwSize)) {
436 snprintf(buf, sizeof(buf), "%s@%s", lpszBuffer, my_hostname);
437 } else {
438 snprintf(buf, sizeof(buf), "unknown-user@%s", my_hostname);
439 }
440 #else
441 if ((pwd = getpwuid(getuid())) == 0) {
442 snprintf(buf, sizeof(buf), "userid-%d@%s", (int)getuid(), my_hostname);
443 } else {
444 snprintf(buf, sizeof(buf), "%s@%s", pwd->pw_name, my_hostname);
445 }
446 #endif
447 from_addr = bstrdup(buf);
448 }
449 Dmsg1(20, "From addr=%s\n", from_addr);
450
451 /*
452 * Connect to smtp daemon on mailhost.
453 */
454 lookup_host:
455 #ifdef HAVE_GETADDRINFO
456 memset(&hints, 0, sizeof(struct addrinfo));
457 switch (default_resolv_type) {
458 case RESOLV_PROTO_ANY:
459 hints.ai_family = AF_UNSPEC;
460 break;
461 case RESOLV_PROTO_IPV4:
462 hints.ai_family = AF_INET;
463 break;
464 #ifdef HAVE_IPV6
465 case RESOLV_PROTO_IPV6:
466 hints.ai_family = AF_INET6;
467 break;
468 #endif
469 default:
470 hints.ai_family = AF_UNSPEC;
471 break;
472 }
473 hints.ai_socktype = SOCK_STREAM;
474 hints.ai_protocol = 0;
475 hints.ai_flags = 0;
476 snprintf(mail_port, sizeof(mail_port), "%d", mailport);
477
478 if ((res = getaddrinfo(mailhost, mail_port, &hints, &ai)) != 0) {
479 Pmsg2(0, _("Error unknown mail host \"%s\": ERR=%s\n"),
480 mailhost, gai_strerror(res));
481 if (strcasecmp(mailhost, "localhost")) {
482 Pmsg0(0, _("Retrying connection using \"localhost\".\n"));
483 mailhost = "localhost";
484 goto lookup_host;
485 }
486 exit(1);
487 }
488
489 for (rp = ai; rp != NULL; rp = rp->ai_next) {
490 #if defined(HAVE_WIN32)
491 s = WSASocket(rp->ai_family, rp->ai_socktype, rp->ai_protocol, NULL, 0, 0);
492 #else
493 s = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
494 #endif
495 if (s < 0) {
496 continue;
497 }
498
499 if (connect(s, rp->ai_addr, rp->ai_addrlen) != -1) {
500 break;
501 }
502
503 close(s);
504 }
505
506 if (!rp) {
507 Pmsg1(0, _("Failed to connect to mailhost %s\n"), mailhost);
508 exit(1);
509 }
510
511 freeaddrinfo(ai);
512 #else
513 if ((hp = gethostbyname(mailhost)) == NULL) {
514 Pmsg2(0, _("Error unknown mail host \"%s\": ERR=%s\n"), mailhost,
515 strerror(errno));
516 if (strcasecmp(mailhost, "localhost") != 0) {
517 Pmsg0(0, _("Retrying connection using \"localhost\".\n"));
518 mailhost = "localhost";
519 goto lookup_host;
520 }
521 exit(1);
522 }
523
524 if (hp->h_addrtype != AF_INET) {
525 Pmsg1(0, _("Fatal error: Unknown address family for smtp host: %d\n"), hp->h_addrtype);
526 exit(1);
527 }
528 memset((char *)&sin, 0, sizeof(sin));
529 memcpy((char *)&sin.sin_addr, hp->h_addr, hp->h_length);
530 sin.sin_family = hp->h_addrtype;
531 sin.sin_port = htons(mailport);
532 #if defined(HAVE_WIN32)
533 if ((s = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, 0)) < 0) {
534 Pmsg1(0, _("Fatal socket error: ERR=%s\n"), strerror(errno));
535 exit(1);
536 }
537 #else
538 if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
539 Pmsg1(0, _("Fatal socket error: ERR=%s\n"), strerror(errno));
540 exit(1);
541 }
542 #endif
543 if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
544 Pmsg2(0, _("Fatal connect error to %s: ERR=%s\n"), mailhost, strerror(errno));
545 exit(1);
546 }
547 Dmsg0(20, "Connected\n");
548 #endif
549
550 #if defined(HAVE_WIN32)
551 int fdSocket = _open_osfhandle(s, _O_RDWR | _O_BINARY);
552 if (fdSocket == -1) {
553 Pmsg1(0, _("Fatal _open_osfhandle error: ERR=%s\n"), strerror(errno));
554 exit(1);
555 }
556
557 int fdSocket2 = dup(fdSocket);
558
559 if ((sfp = fdopen(fdSocket, "wb")) == NULL) {
560 Pmsg1(0, _("Fatal fdopen error: ERR=%s\n"), strerror(errno));
561 exit(1);
562 }
563 if ((rfp = fdopen(fdSocket2, "rb")) == NULL) {
564 Pmsg1(0, _("Fatal fdopen error: ERR=%s\n"), strerror(errno));
565 exit(1);
566 }
567 #else
568 if ((r = dup(s)) < 0) {
569 Pmsg1(0, _("Fatal dup error: ERR=%s\n"), strerror(errno));
570 exit(1);
571 }
572 if ((sfp = fdopen(s, "w")) == 0) {
573 Pmsg1(0, _("Fatal fdopen error: ERR=%s\n"), strerror(errno));
574 exit(1);
575 }
576 if ((rfp = fdopen(r, "r")) == 0) {
577 Pmsg1(0, _("Fatal fdopen error: ERR=%s\n"), strerror(errno));
578 exit(1);
579 }
580 #endif
581
582 /*
583 * Send SMTP headers. Note, if any of the strings have a <
584 * in them already, we do not enclose the string in < >, otherwise
585 * we do.
586 */
587 get_response(); /* banner */
588 chat("HELO %s\r\n", my_hostname);
589 chat("MAIL FROM:%s\r\n", cleanup_addr(from_addr, buf, sizeof(buf)));
590
591 for (i = 0; i < argc; i++) {
592 Dmsg1(20, "rcpt to: %s\n", argv[i]);
593 chat("RCPT TO:%s\r\n", cleanup_addr(argv[i], buf, sizeof(buf)));
594 }
595
596 if (cc_addr) {
597 chat("RCPT TO:%s\r\n", cleanup_addr(cc_addr, buf, sizeof(buf)));
598 }
599 Dmsg0(20, "Data\n");
600 chat("DATA\r\n");
601
602 /*
603 * Send header
604 */
605 fprintf(sfp, "From: %s\r\n", from_addr);
606 Dmsg1(10, "From: %s\r\n", from_addr);
607 if (subject) {
608 fprintf(sfp, "Subject: %s\r\n", subject);
609 Dmsg1(10, "Subject: %s\r\n", subject);
610 }
611 if (reply_addr) {
612 fprintf(sfp, "Reply-To: %s\r\n", reply_addr);
613 Dmsg1(10, "Reply-To: %s\r\n", reply_addr);
614 }
615 if (err_addr) {
616 fprintf(sfp, "Errors-To: %s\r\n", err_addr);
617 Dmsg1(10, "Errors-To: %s\r\n", err_addr);
618 }
619
620 if (sender_addr) {
621 fprintf(sfp, "Sender: %s\r\n", sender_addr);
622 Dmsg1(10, "Sender: %s\r\n", sender_addr);
623 } else {
624
625 #if defined(HAVE_WIN32)
626 DWORD dwSize = UNLEN + 1;
627 LPSTR lpszBuffer = (LPSTR)alloca(dwSize);
628
629 if (GetUserName(lpszBuffer, &dwSize)) {
630 fprintf(sfp, "Sender: %s@%s\r\n", lpszBuffer, my_hostname);
631 Dmsg2(10, "Sender: %s@%s\r\n", lpszBuffer, my_hostname);
632 } else {
633 fprintf(sfp, "Sender: unknown-user@%s\r\n", my_hostname);
634 Dmsg1(10, "Sender: unknown-user@%s\r\n", my_hostname);
635 }
636 #else
637 if ((pwd = getpwuid(getuid())) == 0) {
638 fprintf(sfp, "Sender: userid-%d@%s\r\n", (int)getuid(), my_hostname);
639 Dmsg2(10, "Sender: userid-%d@%s\r\n", (int)getuid(), my_hostname);
640 } else {
641 fprintf(sfp, "Sender: %s@%s\r\n", pwd->pw_name, my_hostname);
642 Dmsg2(10, "Sender: %s@%s\r\n", pwd->pw_name, my_hostname);
643 }
644 #endif
645 }
646 /* Add custom header, if any */
647 for (i=0; i<custom_header_count; i++) {
648 fprintf(sfp, "%s\n", custom_headers[i]);
649 }
650
651 fprintf(sfp, "To: %s", argv[0]);
652 Dmsg1(10, "To: %s", argv[0]);
653 for (i = 1; i < argc; i++) {
654 fprintf(sfp, ",%s", argv[i]);
655 Dmsg1(10, ",%s", argv[i]);
656 }
657
658 fprintf(sfp, "\r\n");
659 Dmsg0(10, "\r\n");
660 if (cc_addr) {
661 fprintf(sfp, "Cc: %s\r\n", cc_addr);
662 Dmsg1(10, "Cc: %s\r\n", cc_addr);
663 }
664
665 if (content_utf8) {
666 fprintf(sfp, "Content-Type: text/plain; charset=UTF-8\r\n");
667 Dmsg0(10, "Content-Type: text/plain; charset=UTF-8\r\n");
668 }
669
670 get_date_string(buf, sizeof(buf));
671 fprintf(sfp, "Date: %s\r\n", buf);
672 Dmsg1(10, "Date: %s\r\n", buf);
673
674 fprintf(sfp, "\r\n");
675
676 /*
677 * Send message body
678 */
679 lines = 0;
680 while (fgets(buf, sizeof(buf), stdin)) {
681 if (maxlines > 0 && ++lines > maxlines) {
682 Dmsg1(20, "skip line because of maxlines limit: %lu\n", maxlines);
683 while (fgets(buf, sizeof(buf), stdin)) {
684 ++lines;
685 }
686 break;
687 }
688 buf[sizeof(buf)-1] = '\0';
689 buf[strlen(buf)-1] = '\0';
690 if (buf[0] == '.') { /* add extra . see RFC 2821 4.5.2 */
691 fputs(".", sfp);
692 }
693 fputs(buf, sfp);
694 fputs("\r\n", sfp);
695 }
696
697 if (lines > maxlines) {
698 Dmsg1(10, "hit maxlines limit: %lu\n", maxlines);
699 fprintf(sfp, "\r\n\r\n[maximum of %lu lines exceeded, skipped %lu lines of output]\r\n", maxlines, lines-maxlines);
700 }
701
702 /*
703 * Send SMTP quit command
704 */
705 chat(".\r\n");
706 chat("QUIT\r\n");
707 exit(0);
708 }
709