Fossil SCM
Baseline implementation of the "smtp" command.
Commit
be55fc60c07438490f94d9b56dd9204ef29258a9a6248f38ab7307e523e32ba3
Parent
03888604076d42a…
2 files changed
+1
-1
+215
-19
+1
-1
| --- src/main.c | ||
| +++ src/main.c | ||
| @@ -1205,11 +1205,11 @@ | ||
| 1205 | 1205 | ** |
| 1206 | 1206 | ** The noJail flag means that the chroot jail is not entered. But |
| 1207 | 1207 | ** privileges are still lowered to that of the user-id and group-id |
| 1208 | 1208 | ** of the repository file. |
| 1209 | 1209 | */ |
| 1210 | -static char *enter_chroot_jail(char *zRepo, int noJail){ | |
| 1210 | +char *enter_chroot_jail(char *zRepo, int noJail){ | |
| 1211 | 1211 | #if !defined(_WIN32) |
| 1212 | 1212 | if( getuid()==0 ){ |
| 1213 | 1213 | int i; |
| 1214 | 1214 | struct stat sStat; |
| 1215 | 1215 | Blob dir; |
| 1216 | 1216 |
| --- src/main.c | |
| +++ src/main.c | |
| @@ -1205,11 +1205,11 @@ | |
| 1205 | ** |
| 1206 | ** The noJail flag means that the chroot jail is not entered. But |
| 1207 | ** privileges are still lowered to that of the user-id and group-id |
| 1208 | ** of the repository file. |
| 1209 | */ |
| 1210 | static char *enter_chroot_jail(char *zRepo, int noJail){ |
| 1211 | #if !defined(_WIN32) |
| 1212 | if( getuid()==0 ){ |
| 1213 | int i; |
| 1214 | struct stat sStat; |
| 1215 | Blob dir; |
| 1216 |
| --- src/main.c | |
| +++ src/main.c | |
| @@ -1205,11 +1205,11 @@ | |
| 1205 | ** |
| 1206 | ** The noJail flag means that the chroot jail is not entered. But |
| 1207 | ** privileges are still lowered to that of the user-id and group-id |
| 1208 | ** of the repository file. |
| 1209 | */ |
| 1210 | char *enter_chroot_jail(char *zRepo, int noJail){ |
| 1211 | #if !defined(_WIN32) |
| 1212 | if( getuid()==0 ){ |
| 1213 | int i; |
| 1214 | struct stat sStat; |
| 1215 | Blob dir; |
| 1216 |
+215
-19
| --- src/smtp.c | ||
| +++ src/smtp.c | ||
| @@ -116,20 +116,21 @@ | ||
| 116 | 116 | const char *zDest; /* Domain that will receive the email */ |
| 117 | 117 | char *zHostname; /* Hostname of SMTP server for zDest */ |
| 118 | 118 | u32 smtpFlags; /* Flags changing the operation */ |
| 119 | 119 | FILE *logFile; /* Write session transcript to this log file */ |
| 120 | 120 | Blob *pTranscript; /* Record session transcript here */ |
| 121 | - const char *zLabel; /* Either "CS" or "SC" */ | |
| 122 | 121 | int atEof; /* True after connection closes */ |
| 123 | 122 | char *zErr; /* Error message */ |
| 124 | 123 | Blob inbuf; /* Input buffer */ |
| 125 | 124 | }; |
| 126 | 125 | |
| 127 | 126 | /* Allowed values for SmtpSession.smtpFlags */ |
| 128 | 127 | #define SMTP_TRACE_STDOUT 0x00001 /* Debugging info to console */ |
| 129 | 128 | #define SMTP_TRACE_FILE 0x00002 /* Debugging info to logFile */ |
| 130 | 129 | #define SMTP_TRACE_BLOB 0x00004 /* Record transcript */ |
| 130 | +#define SMTP_DIRECT 0x00008 /* Skip the MX lookup */ | |
| 131 | +#define SMTP_PORT 0x00010 /* Use an alternate port number */ | |
| 131 | 132 | |
| 132 | 133 | #endif |
| 133 | 134 | |
| 134 | 135 | /* |
| 135 | 136 | ** Shutdown an SmtpSession |
| @@ -143,16 +144,21 @@ | ||
| 143 | 144 | } |
| 144 | 145 | |
| 145 | 146 | /* |
| 146 | 147 | ** Allocate a new SmtpSession object. |
| 147 | 148 | ** |
| 148 | -** Both zFrom and zDest must be specified for a client side SMTP connection. | |
| 149 | -** For a server-side, specify only zFrom. | |
| 149 | +** Both zFrom and zDest must be specified. | |
| 150 | +** | |
| 151 | +** The ... arguments are in this order: | |
| 152 | +** | |
| 153 | +** SMTP_PORT: int | |
| 154 | +** SMTP_TRACE_FILE: FILE* | |
| 155 | +** SMTP_TRACE_BLOB: Blob* | |
| 150 | 156 | */ |
| 151 | 157 | SmtpSession *smtp_session_new( |
| 152 | - const char *zFrom, /* Domain name of our end. */ | |
| 153 | - const char *zDest, /* Domain of the other end. */ | |
| 158 | + const char *zFrom, /* Domain for the client */ | |
| 159 | + const char *zDest, /* Domain of the server */ | |
| 154 | 160 | u32 smtpFlags, /* Flags */ |
| 155 | 161 | ... /* Arguments depending on the flags */ |
| 156 | 162 | ){ |
| 157 | 163 | SmtpSession *p; |
| 158 | 164 | va_list ap; |
| @@ -160,29 +166,36 @@ | ||
| 160 | 166 | |
| 161 | 167 | p = fossil_malloc( sizeof(*p) ); |
| 162 | 168 | memset(p, 0, sizeof(*p)); |
| 163 | 169 | p->zFrom = zFrom; |
| 164 | 170 | p->zDest = zDest; |
| 165 | - p->zLabel = zDest==0 ? "CS" : "SC"; | |
| 166 | 171 | p->smtpFlags = smtpFlags; |
| 172 | + memset(&url, 0, sizeof(url)); | |
| 173 | + url.port = 25; | |
| 167 | 174 | blob_init(&p->inbuf, 0, 0); |
| 168 | 175 | va_start(ap, smtpFlags); |
| 176 | + if( smtpFlags & SMTP_PORT ){ | |
| 177 | + url.port = va_arg(ap, int); | |
| 178 | + } | |
| 169 | 179 | if( smtpFlags & SMTP_TRACE_FILE ){ |
| 170 | 180 | p->logFile = va_arg(ap, FILE*); |
| 171 | - }else if( smtpFlags & SMTP_TRACE_BLOB ){ | |
| 181 | + } | |
| 182 | + if( smtpFlags & SMTP_TRACE_BLOB ){ | |
| 172 | 183 | p->pTranscript = va_arg(ap, Blob*); |
| 173 | 184 | } |
| 174 | 185 | va_end(ap); |
| 175 | - p->zHostname = smtp_mx_host(zDest); | |
| 186 | + if( (smtpFlags & SMTP_DIRECT)!=0 ){ | |
| 187 | + p->zHostname = fossil_strdup(zDest); | |
| 188 | + }else{ | |
| 189 | + p->zHostname = smtp_mx_host(zDest); | |
| 190 | + } | |
| 176 | 191 | if( p->zHostname==0 ){ |
| 177 | 192 | p->atEof = 1; |
| 178 | 193 | p->zErr = mprintf("cannot locate SMTP server for \"%s\"", zDest); |
| 179 | 194 | return p; |
| 180 | 195 | } |
| 181 | - memset(&url, 0, sizeof(url)); | |
| 182 | 196 | url.name = p->zHostname; |
| 183 | - url.port = 25; | |
| 184 | 197 | socket_global_init(); |
| 185 | 198 | if( socket_open(&url) ){ |
| 186 | 199 | p->atEof = 1; |
| 187 | 200 | p->zErr = socket_errmsg(); |
| 188 | 201 | socket_close(); |
| @@ -206,17 +219,17 @@ | ||
| 206 | 219 | n = blob_size(&b); |
| 207 | 220 | assert( n>=2 ); |
| 208 | 221 | assert( z[n-1]=='\n' ); |
| 209 | 222 | assert( z[n-2]=='\r' ); |
| 210 | 223 | if( p->smtpFlags & SMTP_TRACE_STDOUT ){ |
| 211 | - fossil_print("%c: %.*s\n", p->zLabel[1], n-2, z); | |
| 224 | + fossil_print("C: %.*s\n", n-2, z); | |
| 212 | 225 | } |
| 213 | 226 | if( p->smtpFlags & SMTP_TRACE_FILE ){ |
| 214 | - fprintf(p->logFile, "%c: %.*s\n", p->zLabel[1], n-2, z); | |
| 227 | + fprintf(p->logFile, "C: %.*s\n", n-2, z); | |
| 215 | 228 | } |
| 216 | 229 | if( p->smtpFlags & SMTP_TRACE_BLOB ){ |
| 217 | - blob_appendf(p->pTranscript, "%c: %.*s\n", p->zLabel[1], n-2, z); | |
| 230 | + blob_appendf(p->pTranscript, "C: %.*s\n", n-2, z); | |
| 218 | 231 | } |
| 219 | 232 | socket_send(0, z, n); |
| 220 | 233 | blob_zero(&b); |
| 221 | 234 | } |
| 222 | 235 | |
| @@ -271,17 +284,17 @@ | ||
| 271 | 284 | z = blob_buffer(in); |
| 272 | 285 | n = blob_size(in); |
| 273 | 286 | if( n && z[n-1]=='\n' ) n--; |
| 274 | 287 | if( n && z[n-1]=='\r' ) n--; |
| 275 | 288 | if( p->smtpFlags & SMTP_TRACE_STDOUT ){ |
| 276 | - fossil_print("%c: %.*s\n", p->zLabel[0], n, z); | |
| 289 | + fossil_print("S: %.*s\n", n, z); | |
| 277 | 290 | } |
| 278 | 291 | if( p->smtpFlags & SMTP_TRACE_FILE ){ |
| 279 | - fprintf(p->logFile, "%c: %.*s\n", p->zLabel[0], n, z); | |
| 292 | + fprintf(p->logFile, "S: %.*s\n", n, z); | |
| 280 | 293 | } |
| 281 | 294 | if( p->smtpFlags & SMTP_TRACE_BLOB ){ |
| 282 | - blob_appendf(p->pTranscript, "%c: %.*s\n", p->zLabel[0], n-2, z); | |
| 295 | + blob_appendf(p->pTranscript, "S: %.*s\n", n-2, z); | |
| 283 | 296 | } |
| 284 | 297 | } |
| 285 | 298 | |
| 286 | 299 | /* |
| 287 | 300 | ** Capture a single-line server reply. |
| @@ -362,19 +375,32 @@ | ||
| 362 | 375 | ** Usage: %fossil test-smtp-probe DOMAIN [ME] |
| 363 | 376 | ** |
| 364 | 377 | ** Interact with the SMTP server for DOMAIN by setting up a connection |
| 365 | 378 | ** and then immediately shutting it back down. Log all interaction |
| 366 | 379 | ** on the console. Use ME as the domain name of the sender. |
| 380 | +** | |
| 381 | +** Options: | |
| 382 | +** | |
| 383 | +** --direct Use DOMAIN directly without going through MX | |
| 384 | +** --port N Talk on TCP port N | |
| 367 | 385 | */ |
| 368 | 386 | void test_smtp_probe(void){ |
| 369 | 387 | SmtpSession *p; |
| 370 | 388 | const char *zDomain; |
| 371 | 389 | const char *zSelf; |
| 390 | + const char *zPort; | |
| 391 | + int iPort = 25; | |
| 392 | + u32 smtpFlags = SMTP_TRACE_STDOUT|SMTP_PORT; | |
| 393 | + | |
| 394 | + if( find_option("direct",0,0)!=0 ) smtpFlags |= SMTP_DIRECT; | |
| 395 | + zPort = find_option("port",0,1); | |
| 396 | + if( zPort ) iPort = atoi(zPort); | |
| 397 | + verify_all_options(); | |
| 372 | 398 | if( g.argc!=3 && g.argc!=4 ) usage("DOMAIN [ME]"); |
| 373 | 399 | zDomain = g.argv[2]; |
| 374 | 400 | zSelf = g.argc==4 ? g.argv[3] : "fossil-scm.org"; |
| 375 | - p = smtp_session_new(zSelf, zDomain, SMTP_TRACE_STDOUT); | |
| 401 | + p = smtp_session_new(zSelf, zDomain, smtpFlags, iPort); | |
| 376 | 402 | if( p->zErr ){ |
| 377 | 403 | fossil_fatal("%s", p->zErr); |
| 378 | 404 | } |
| 379 | 405 | fossil_print("Connection to \"%s\"\n", p->zHostname); |
| 380 | 406 | smtp_client_startup(p); |
| @@ -523,31 +549,38 @@ | ||
| 523 | 549 | ** Use SMTP to send the email message contained in the file named EMAIL |
| 524 | 550 | ** to the list of users TO. FROM is the sender of the email. |
| 525 | 551 | ** |
| 526 | 552 | ** Options: |
| 527 | 553 | ** |
| 554 | +** --direct Go directly to the TO domain. Bypass MX lookup | |
| 555 | +** --port N Use TCP port N instead of 25 | |
| 528 | 556 | ** --trace Show the SMTP conversation on the console |
| 529 | 557 | */ |
| 530 | 558 | void test_smtp_send(void){ |
| 531 | 559 | SmtpSession *p; |
| 532 | 560 | const char *zFrom; |
| 533 | 561 | int nTo; |
| 534 | 562 | const char *zToDomain; |
| 535 | 563 | const char *zFromDomain; |
| 536 | 564 | const char **azTo; |
| 565 | + int smtpPort = 25; | |
| 566 | + const char *zPort; | |
| 537 | 567 | Blob body; |
| 538 | - u32 smtpFlags = 0; | |
| 568 | + u32 smtpFlags = SMTP_PORT; | |
| 539 | 569 | if( find_option("trace",0,0)!=0 ) smtpFlags |= SMTP_TRACE_STDOUT; |
| 570 | + if( find_option("direct",0,0)!=0 ) smtpFlags |= SMTP_DIRECT; | |
| 571 | + zPort = find_option("port",0,1); | |
| 572 | + if( zPort ) smtpPort = atoi(zPort); | |
| 540 | 573 | verify_all_options(); |
| 541 | 574 | if( g.argc<5 ) usage("EMAIL FROM TO ..."); |
| 542 | 575 | blob_read_from_file(&body, g.argv[2], ExtFILE); |
| 543 | 576 | zFrom = g.argv[3]; |
| 544 | 577 | nTo = g.argc-4; |
| 545 | 578 | azTo = (const char**)g.argv+4; |
| 546 | 579 | zFromDomain = domainOfAddr(zFrom); |
| 547 | 580 | zToDomain = domainOfAddr(azTo[0]); |
| 548 | - p = smtp_session_new(zFromDomain, zToDomain, smtpFlags); | |
| 581 | + p = smtp_session_new(zFromDomain, zToDomain, smtpFlags, smtpPort); | |
| 549 | 582 | if( p->zErr ){ |
| 550 | 583 | fossil_fatal("%s", p->zErr); |
| 551 | 584 | } |
| 552 | 585 | fossil_print("Connection to \"%s\"\n", p->zHostname); |
| 553 | 586 | smtp_client_startup(p); |
| @@ -557,5 +590,168 @@ | ||
| 557 | 590 | fossil_fatal("ERROR: %s\n", p->zErr); |
| 558 | 591 | } |
| 559 | 592 | smtp_session_free(p); |
| 560 | 593 | blob_zero(&body); |
| 561 | 594 | } |
| 595 | + | |
| 596 | +/***************************************************************************** | |
| 597 | +** Server implementation | |
| 598 | +*****************************************************************************/ | |
| 599 | + | |
| 600 | +#if LOCAL_INTERFACE | |
| 601 | +/* | |
| 602 | +** State information for the server | |
| 603 | +*/ | |
| 604 | +struct SmtpServer { | |
| 605 | + sqlite3 *db; /* Database into which the email is delivered */ | |
| 606 | + char *zEhlo; /* Client domain on the EHLO line */ | |
| 607 | + char *zFrom; /* MAIL FROM: argument */ | |
| 608 | + int nTo; /* Number of RCPT TO: lines seen */ | |
| 609 | + char **azTo; /* Address in each RCPT TO line */ | |
| 610 | + u32 srvrFlags; /* Control flags */ | |
| 611 | + Blob msg; /* Content following DATA */ | |
| 612 | + Blob transcript; /* Session transcript */ | |
| 613 | +}; | |
| 614 | + | |
| 615 | +#define SMTPSRV_CLEAR_MSG 1 /* smtp_server_clear() last message only */ | |
| 616 | +#define SMTPSRV_CLEAR_ALL 2 /* smtp_server_clear() everything */ | |
| 617 | +#define SMTPSRV_LOG 0x001 /* Record a transcript of the interaction */ | |
| 618 | + | |
| 619 | +#endif /* LOCAL_INTERFACE */ | |
| 620 | + | |
| 621 | +/* | |
| 622 | +** Clear the SmtpServer object. Deallocate resources. | |
| 623 | +** How much to clear depends on eHowMuch | |
| 624 | +*/ | |
| 625 | +static void smtp_server_clear(SmtpServer *p, int eHowMuch){ | |
| 626 | + int i; | |
| 627 | + if( eHowMuch>=SMTPSRV_CLEAR_MSG ){ | |
| 628 | + fossil_free(p->zFrom); | |
| 629 | + p->zFrom = 0; | |
| 630 | + for(i=0; i<p->nTo; i++) fossil_free(p->azTo[0]); | |
| 631 | + fossil_free(p->azTo); | |
| 632 | + p->azTo = 0; | |
| 633 | + p->nTo = 0; | |
| 634 | + blob_zero(&p->msg); | |
| 635 | + } | |
| 636 | + if( eHowMuch>=SMTPSRV_CLEAR_ALL ){ | |
| 637 | + blob_zero(&p->transcript); | |
| 638 | + sqlite3_close(p->db); | |
| 639 | + p->db = 0; | |
| 640 | + fossil_free(p->zEhlo); | |
| 641 | + p->zEhlo = 0; | |
| 642 | + } | |
| 643 | +} | |
| 644 | + | |
| 645 | +/* | |
| 646 | +** Turn raw memory into an SmtpServer object. | |
| 647 | +*/ | |
| 648 | +static void smtp_server_init(SmtpServer *p){ | |
| 649 | + memset(p, 0, sizeof(*p)); | |
| 650 | + blob_init(&p->msg, 0, 0); | |
| 651 | + blob_init(&p->transcript, 0, 0); | |
| 652 | +} | |
| 653 | + | |
| 654 | +/* | |
| 655 | +** Send a single line of output from the server to the client. | |
| 656 | +*/ | |
| 657 | +static void smtp_server_send(SmtpServer *p, const char *zFormat, ...){ | |
| 658 | + Blob b = empty_blob; | |
| 659 | + va_list ap; | |
| 660 | + char *z; | |
| 661 | + int n; | |
| 662 | + va_start(ap, zFormat); | |
| 663 | + blob_vappendf(&b, zFormat, ap); | |
| 664 | + va_end(ap); | |
| 665 | + z = blob_buffer(&b); | |
| 666 | + n = blob_size(&b); | |
| 667 | + assert( n>=2 ); | |
| 668 | + assert( z[n-1]=='\n' ); | |
| 669 | + assert( z[n-2]=='\r' ); | |
| 670 | + if( p->srvrFlags & SMTPSRV_LOG ){ | |
| 671 | + blob_appendf(&p->transcript, "S: %.*s\n", n-2, z); | |
| 672 | + } | |
| 673 | + fwrite(z, n, 1, stdout); | |
| 674 | + fflush(stdout); | |
| 675 | + blob_zero(&b); | |
| 676 | +} | |
| 677 | + | |
| 678 | +/* | |
| 679 | +** Read a single line from the client. | |
| 680 | +*/ | |
| 681 | +static int smtp_server_gets(SmtpServer *p, char *aBuf, int nBuf){ | |
| 682 | + int rc = fgets(aBuf, nBuf, stdin)!=0; | |
| 683 | + if( rc && (p->srvrFlags & SMTPSRV_LOG)!=0 ){ | |
| 684 | + blob_appendf(&p->transcript, "C: %s\n", aBuf); | |
| 685 | + } | |
| 686 | + return rc; | |
| 687 | +} | |
| 688 | + | |
| 689 | +/* | |
| 690 | +** Capture the incoming email data into the p->msg blob. Dequote | |
| 691 | +** lines of "..\r\n" into just ".\r\n". | |
| 692 | +*/ | |
| 693 | +static void smtp_server_capture_data(SmtpServer *p, char *z, int n){ | |
| 694 | + while( fgets(z, n, stdin) ){ | |
| 695 | + if( strncmp(z, ".\r\n", 3)==0 ) return; | |
| 696 | + if( strncmp(z, "..\r\n", 4)==0 ){ | |
| 697 | + memmove(z, z+1, 4); | |
| 698 | + } | |
| 699 | + blob_append(&p->msg, z, -1); | |
| 700 | + } | |
| 701 | +} | |
| 702 | + | |
| 703 | +/* | |
| 704 | +** COMMAND: smtp | |
| 705 | +** | |
| 706 | +** Usage: %fossil smtp [options] DBNAME | |
| 707 | +** | |
| 708 | +** Begin a SMTP conversation with a client using stdin/stdout. (This | |
| 709 | +** command is expected to be launched from xinetd or the equivalent.) | |
| 710 | +** Use information in the SQLite database at DBNAME to find configuration | |
| 711 | +** information and as a place to store the incoming content. | |
| 712 | +*/ | |
| 713 | +void smtp_server(void){ | |
| 714 | + char *zDbName; | |
| 715 | + const char *zDomain; | |
| 716 | + SmtpServer x; | |
| 717 | + char z[5000]; | |
| 718 | + | |
| 719 | + smtp_server_init(&x); | |
| 720 | + zDomain = find_option("domain",0,1); | |
| 721 | + if( zDomain==0 ) zDomain = "unspecified.domain"; | |
| 722 | + verify_all_options(); | |
| 723 | + if( g.argc!=3 ) usage("DBNAME"); | |
| 724 | + zDbName = g.argv[2]; | |
| 725 | + zDbName = enter_chroot_jail(zDbName, 0); | |
| 726 | + smtp_server_send(&x, "220 %s ESMTP Fossil ([%.*s] %s)\r\n", | |
| 727 | + zDomain, 16, MANIFEST_VERSION, MANIFEST_DATE); | |
| 728 | + while( smtp_server_gets(&x, z, sizeof(z)) ){ | |
| 729 | + if( strncmp(z, "EHLO ", 5)==0 ){ | |
| 730 | + smtp_server_send(&x, "250 ok\r\n"); | |
| 731 | + }else | |
| 732 | + if( strncmp(z, "HELO ", 5)==0 ){ | |
| 733 | + smtp_server_send(&x, "250 ok\r\n"); | |
| 734 | + }else | |
| 735 | + if( strncmp(z, "MAIL FROM:<", 11)==0 ){ | |
| 736 | + smtp_server_send(&x, "250 ok\r\n"); | |
| 737 | + }else | |
| 738 | + if( strncmp(z, "RCPT TO:<", 9)==0 ){ | |
| 739 | + smtp_server_send(&x, "250 ok\r\n"); | |
| 740 | + }else | |
| 741 | + if( strncmp(z, "DATA", 4)==0 ){ | |
| 742 | + smtp_server_send(&x, "354 ready\r\n"); | |
| 743 | + smtp_server_capture_data(&x, z, sizeof(z)); | |
| 744 | + smtp_server_send(&x, "250 ok\r\n"); | |
| 745 | + smtp_server_clear(&x, SMTPSRV_CLEAR_MSG); | |
| 746 | + }else | |
| 747 | + if( strncmp(z, "QUIT", 4)==0 ){ | |
| 748 | + smtp_server_send(&x, "221 closing connection\r\n"); | |
| 749 | + break; | |
| 750 | + }else | |
| 751 | + { | |
| 752 | + smtp_server_send(&x, "500 unknown command\r\n"); | |
| 753 | + } | |
| 754 | + } | |
| 755 | + fclose(stdin); | |
| 756 | + smtp_server_clear(&x, SMTPSRV_CLEAR_ALL); | |
| 757 | +} | |
| 562 | 758 |
| --- src/smtp.c | |
| +++ src/smtp.c | |
| @@ -116,20 +116,21 @@ | |
| 116 | const char *zDest; /* Domain that will receive the email */ |
| 117 | char *zHostname; /* Hostname of SMTP server for zDest */ |
| 118 | u32 smtpFlags; /* Flags changing the operation */ |
| 119 | FILE *logFile; /* Write session transcript to this log file */ |
| 120 | Blob *pTranscript; /* Record session transcript here */ |
| 121 | const char *zLabel; /* Either "CS" or "SC" */ |
| 122 | int atEof; /* True after connection closes */ |
| 123 | char *zErr; /* Error message */ |
| 124 | Blob inbuf; /* Input buffer */ |
| 125 | }; |
| 126 | |
| 127 | /* Allowed values for SmtpSession.smtpFlags */ |
| 128 | #define SMTP_TRACE_STDOUT 0x00001 /* Debugging info to console */ |
| 129 | #define SMTP_TRACE_FILE 0x00002 /* Debugging info to logFile */ |
| 130 | #define SMTP_TRACE_BLOB 0x00004 /* Record transcript */ |
| 131 | |
| 132 | #endif |
| 133 | |
| 134 | /* |
| 135 | ** Shutdown an SmtpSession |
| @@ -143,16 +144,21 @@ | |
| 143 | } |
| 144 | |
| 145 | /* |
| 146 | ** Allocate a new SmtpSession object. |
| 147 | ** |
| 148 | ** Both zFrom and zDest must be specified for a client side SMTP connection. |
| 149 | ** For a server-side, specify only zFrom. |
| 150 | */ |
| 151 | SmtpSession *smtp_session_new( |
| 152 | const char *zFrom, /* Domain name of our end. */ |
| 153 | const char *zDest, /* Domain of the other end. */ |
| 154 | u32 smtpFlags, /* Flags */ |
| 155 | ... /* Arguments depending on the flags */ |
| 156 | ){ |
| 157 | SmtpSession *p; |
| 158 | va_list ap; |
| @@ -160,29 +166,36 @@ | |
| 160 | |
| 161 | p = fossil_malloc( sizeof(*p) ); |
| 162 | memset(p, 0, sizeof(*p)); |
| 163 | p->zFrom = zFrom; |
| 164 | p->zDest = zDest; |
| 165 | p->zLabel = zDest==0 ? "CS" : "SC"; |
| 166 | p->smtpFlags = smtpFlags; |
| 167 | blob_init(&p->inbuf, 0, 0); |
| 168 | va_start(ap, smtpFlags); |
| 169 | if( smtpFlags & SMTP_TRACE_FILE ){ |
| 170 | p->logFile = va_arg(ap, FILE*); |
| 171 | }else if( smtpFlags & SMTP_TRACE_BLOB ){ |
| 172 | p->pTranscript = va_arg(ap, Blob*); |
| 173 | } |
| 174 | va_end(ap); |
| 175 | p->zHostname = smtp_mx_host(zDest); |
| 176 | if( p->zHostname==0 ){ |
| 177 | p->atEof = 1; |
| 178 | p->zErr = mprintf("cannot locate SMTP server for \"%s\"", zDest); |
| 179 | return p; |
| 180 | } |
| 181 | memset(&url, 0, sizeof(url)); |
| 182 | url.name = p->zHostname; |
| 183 | url.port = 25; |
| 184 | socket_global_init(); |
| 185 | if( socket_open(&url) ){ |
| 186 | p->atEof = 1; |
| 187 | p->zErr = socket_errmsg(); |
| 188 | socket_close(); |
| @@ -206,17 +219,17 @@ | |
| 206 | n = blob_size(&b); |
| 207 | assert( n>=2 ); |
| 208 | assert( z[n-1]=='\n' ); |
| 209 | assert( z[n-2]=='\r' ); |
| 210 | if( p->smtpFlags & SMTP_TRACE_STDOUT ){ |
| 211 | fossil_print("%c: %.*s\n", p->zLabel[1], n-2, z); |
| 212 | } |
| 213 | if( p->smtpFlags & SMTP_TRACE_FILE ){ |
| 214 | fprintf(p->logFile, "%c: %.*s\n", p->zLabel[1], n-2, z); |
| 215 | } |
| 216 | if( p->smtpFlags & SMTP_TRACE_BLOB ){ |
| 217 | blob_appendf(p->pTranscript, "%c: %.*s\n", p->zLabel[1], n-2, z); |
| 218 | } |
| 219 | socket_send(0, z, n); |
| 220 | blob_zero(&b); |
| 221 | } |
| 222 | |
| @@ -271,17 +284,17 @@ | |
| 271 | z = blob_buffer(in); |
| 272 | n = blob_size(in); |
| 273 | if( n && z[n-1]=='\n' ) n--; |
| 274 | if( n && z[n-1]=='\r' ) n--; |
| 275 | if( p->smtpFlags & SMTP_TRACE_STDOUT ){ |
| 276 | fossil_print("%c: %.*s\n", p->zLabel[0], n, z); |
| 277 | } |
| 278 | if( p->smtpFlags & SMTP_TRACE_FILE ){ |
| 279 | fprintf(p->logFile, "%c: %.*s\n", p->zLabel[0], n, z); |
| 280 | } |
| 281 | if( p->smtpFlags & SMTP_TRACE_BLOB ){ |
| 282 | blob_appendf(p->pTranscript, "%c: %.*s\n", p->zLabel[0], n-2, z); |
| 283 | } |
| 284 | } |
| 285 | |
| 286 | /* |
| 287 | ** Capture a single-line server reply. |
| @@ -362,19 +375,32 @@ | |
| 362 | ** Usage: %fossil test-smtp-probe DOMAIN [ME] |
| 363 | ** |
| 364 | ** Interact with the SMTP server for DOMAIN by setting up a connection |
| 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 | 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); |
| @@ -523,31 +549,38 @@ | |
| 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); |
| @@ -557,5 +590,168 @@ | |
| 557 | fossil_fatal("ERROR: %s\n", p->zErr); |
| 558 | } |
| 559 | smtp_session_free(p); |
| 560 | blob_zero(&body); |
| 561 | } |
| 562 |
| --- src/smtp.c | |
| +++ src/smtp.c | |
| @@ -116,20 +116,21 @@ | |
| 116 | const char *zDest; /* Domain that will receive the email */ |
| 117 | char *zHostname; /* Hostname of SMTP server for zDest */ |
| 118 | u32 smtpFlags; /* Flags changing the operation */ |
| 119 | FILE *logFile; /* Write session transcript to this log file */ |
| 120 | Blob *pTranscript; /* Record session transcript here */ |
| 121 | int atEof; /* True after connection closes */ |
| 122 | char *zErr; /* Error message */ |
| 123 | Blob inbuf; /* Input buffer */ |
| 124 | }; |
| 125 | |
| 126 | /* Allowed values for SmtpSession.smtpFlags */ |
| 127 | #define SMTP_TRACE_STDOUT 0x00001 /* Debugging info to console */ |
| 128 | #define SMTP_TRACE_FILE 0x00002 /* Debugging info to logFile */ |
| 129 | #define SMTP_TRACE_BLOB 0x00004 /* Record transcript */ |
| 130 | #define SMTP_DIRECT 0x00008 /* Skip the MX lookup */ |
| 131 | #define SMTP_PORT 0x00010 /* Use an alternate port number */ |
| 132 | |
| 133 | #endif |
| 134 | |
| 135 | /* |
| 136 | ** Shutdown an SmtpSession |
| @@ -143,16 +144,21 @@ | |
| 144 | } |
| 145 | |
| 146 | /* |
| 147 | ** Allocate a new SmtpSession object. |
| 148 | ** |
| 149 | ** Both zFrom and zDest must be specified. |
| 150 | ** |
| 151 | ** The ... arguments are in this order: |
| 152 | ** |
| 153 | ** SMTP_PORT: int |
| 154 | ** SMTP_TRACE_FILE: FILE* |
| 155 | ** SMTP_TRACE_BLOB: Blob* |
| 156 | */ |
| 157 | SmtpSession *smtp_session_new( |
| 158 | const char *zFrom, /* Domain for the client */ |
| 159 | const char *zDest, /* Domain of the server */ |
| 160 | u32 smtpFlags, /* Flags */ |
| 161 | ... /* Arguments depending on the flags */ |
| 162 | ){ |
| 163 | SmtpSession *p; |
| 164 | va_list ap; |
| @@ -160,29 +166,36 @@ | |
| 166 | |
| 167 | p = fossil_malloc( sizeof(*p) ); |
| 168 | memset(p, 0, sizeof(*p)); |
| 169 | p->zFrom = zFrom; |
| 170 | p->zDest = zDest; |
| 171 | p->smtpFlags = smtpFlags; |
| 172 | memset(&url, 0, sizeof(url)); |
| 173 | url.port = 25; |
| 174 | blob_init(&p->inbuf, 0, 0); |
| 175 | va_start(ap, smtpFlags); |
| 176 | if( smtpFlags & SMTP_PORT ){ |
| 177 | url.port = va_arg(ap, int); |
| 178 | } |
| 179 | if( smtpFlags & SMTP_TRACE_FILE ){ |
| 180 | p->logFile = va_arg(ap, FILE*); |
| 181 | } |
| 182 | if( smtpFlags & SMTP_TRACE_BLOB ){ |
| 183 | p->pTranscript = va_arg(ap, Blob*); |
| 184 | } |
| 185 | va_end(ap); |
| 186 | if( (smtpFlags & SMTP_DIRECT)!=0 ){ |
| 187 | p->zHostname = fossil_strdup(zDest); |
| 188 | }else{ |
| 189 | p->zHostname = smtp_mx_host(zDest); |
| 190 | } |
| 191 | if( p->zHostname==0 ){ |
| 192 | p->atEof = 1; |
| 193 | p->zErr = mprintf("cannot locate SMTP server for \"%s\"", zDest); |
| 194 | return p; |
| 195 | } |
| 196 | url.name = p->zHostname; |
| 197 | socket_global_init(); |
| 198 | if( socket_open(&url) ){ |
| 199 | p->atEof = 1; |
| 200 | p->zErr = socket_errmsg(); |
| 201 | socket_close(); |
| @@ -206,17 +219,17 @@ | |
| 219 | n = blob_size(&b); |
| 220 | assert( n>=2 ); |
| 221 | assert( z[n-1]=='\n' ); |
| 222 | assert( z[n-2]=='\r' ); |
| 223 | if( p->smtpFlags & SMTP_TRACE_STDOUT ){ |
| 224 | fossil_print("C: %.*s\n", n-2, z); |
| 225 | } |
| 226 | if( p->smtpFlags & SMTP_TRACE_FILE ){ |
| 227 | fprintf(p->logFile, "C: %.*s\n", n-2, z); |
| 228 | } |
| 229 | if( p->smtpFlags & SMTP_TRACE_BLOB ){ |
| 230 | blob_appendf(p->pTranscript, "C: %.*s\n", n-2, z); |
| 231 | } |
| 232 | socket_send(0, z, n); |
| 233 | blob_zero(&b); |
| 234 | } |
| 235 | |
| @@ -271,17 +284,17 @@ | |
| 284 | z = blob_buffer(in); |
| 285 | n = blob_size(in); |
| 286 | if( n && z[n-1]=='\n' ) n--; |
| 287 | if( n && z[n-1]=='\r' ) n--; |
| 288 | if( p->smtpFlags & SMTP_TRACE_STDOUT ){ |
| 289 | fossil_print("S: %.*s\n", n, z); |
| 290 | } |
| 291 | if( p->smtpFlags & SMTP_TRACE_FILE ){ |
| 292 | fprintf(p->logFile, "S: %.*s\n", n, z); |
| 293 | } |
| 294 | if( p->smtpFlags & SMTP_TRACE_BLOB ){ |
| 295 | blob_appendf(p->pTranscript, "S: %.*s\n", n-2, z); |
| 296 | } |
| 297 | } |
| 298 | |
| 299 | /* |
| 300 | ** Capture a single-line server reply. |
| @@ -362,19 +375,32 @@ | |
| 375 | ** Usage: %fossil test-smtp-probe DOMAIN [ME] |
| 376 | ** |
| 377 | ** Interact with the SMTP server for DOMAIN by setting up a connection |
| 378 | ** and then immediately shutting it back down. Log all interaction |
| 379 | ** on the console. Use ME as the domain name of the sender. |
| 380 | ** |
| 381 | ** Options: |
| 382 | ** |
| 383 | ** --direct Use DOMAIN directly without going through MX |
| 384 | ** --port N Talk on TCP port N |
| 385 | */ |
| 386 | void test_smtp_probe(void){ |
| 387 | SmtpSession *p; |
| 388 | const char *zDomain; |
| 389 | const char *zSelf; |
| 390 | const char *zPort; |
| 391 | int iPort = 25; |
| 392 | u32 smtpFlags = SMTP_TRACE_STDOUT|SMTP_PORT; |
| 393 | |
| 394 | if( find_option("direct",0,0)!=0 ) smtpFlags |= SMTP_DIRECT; |
| 395 | zPort = find_option("port",0,1); |
| 396 | if( zPort ) iPort = atoi(zPort); |
| 397 | verify_all_options(); |
| 398 | if( g.argc!=3 && g.argc!=4 ) usage("DOMAIN [ME]"); |
| 399 | zDomain = g.argv[2]; |
| 400 | zSelf = g.argc==4 ? g.argv[3] : "fossil-scm.org"; |
| 401 | p = smtp_session_new(zSelf, zDomain, smtpFlags, iPort); |
| 402 | if( p->zErr ){ |
| 403 | fossil_fatal("%s", p->zErr); |
| 404 | } |
| 405 | fossil_print("Connection to \"%s\"\n", p->zHostname); |
| 406 | smtp_client_startup(p); |
| @@ -523,31 +549,38 @@ | |
| 549 | ** Use SMTP to send the email message contained in the file named EMAIL |
| 550 | ** to the list of users TO. FROM is the sender of the email. |
| 551 | ** |
| 552 | ** Options: |
| 553 | ** |
| 554 | ** --direct Go directly to the TO domain. Bypass MX lookup |
| 555 | ** --port N Use TCP port N instead of 25 |
| 556 | ** --trace Show the SMTP conversation on the console |
| 557 | */ |
| 558 | void test_smtp_send(void){ |
| 559 | SmtpSession *p; |
| 560 | const char *zFrom; |
| 561 | int nTo; |
| 562 | const char *zToDomain; |
| 563 | const char *zFromDomain; |
| 564 | const char **azTo; |
| 565 | int smtpPort = 25; |
| 566 | const char *zPort; |
| 567 | Blob body; |
| 568 | u32 smtpFlags = SMTP_PORT; |
| 569 | if( find_option("trace",0,0)!=0 ) smtpFlags |= SMTP_TRACE_STDOUT; |
| 570 | if( find_option("direct",0,0)!=0 ) smtpFlags |= SMTP_DIRECT; |
| 571 | zPort = find_option("port",0,1); |
| 572 | if( zPort ) smtpPort = atoi(zPort); |
| 573 | verify_all_options(); |
| 574 | if( g.argc<5 ) usage("EMAIL FROM TO ..."); |
| 575 | blob_read_from_file(&body, g.argv[2], ExtFILE); |
| 576 | zFrom = g.argv[3]; |
| 577 | nTo = g.argc-4; |
| 578 | azTo = (const char**)g.argv+4; |
| 579 | zFromDomain = domainOfAddr(zFrom); |
| 580 | zToDomain = domainOfAddr(azTo[0]); |
| 581 | p = smtp_session_new(zFromDomain, zToDomain, smtpFlags, smtpPort); |
| 582 | if( p->zErr ){ |
| 583 | fossil_fatal("%s", p->zErr); |
| 584 | } |
| 585 | fossil_print("Connection to \"%s\"\n", p->zHostname); |
| 586 | smtp_client_startup(p); |
| @@ -557,5 +590,168 @@ | |
| 590 | fossil_fatal("ERROR: %s\n", p->zErr); |
| 591 | } |
| 592 | smtp_session_free(p); |
| 593 | blob_zero(&body); |
| 594 | } |
| 595 | |
| 596 | /***************************************************************************** |
| 597 | ** Server implementation |
| 598 | *****************************************************************************/ |
| 599 | |
| 600 | #if LOCAL_INTERFACE |
| 601 | /* |
| 602 | ** State information for the server |
| 603 | */ |
| 604 | struct SmtpServer { |
| 605 | sqlite3 *db; /* Database into which the email is delivered */ |
| 606 | char *zEhlo; /* Client domain on the EHLO line */ |
| 607 | char *zFrom; /* MAIL FROM: argument */ |
| 608 | int nTo; /* Number of RCPT TO: lines seen */ |
| 609 | char **azTo; /* Address in each RCPT TO line */ |
| 610 | u32 srvrFlags; /* Control flags */ |
| 611 | Blob msg; /* Content following DATA */ |
| 612 | Blob transcript; /* Session transcript */ |
| 613 | }; |
| 614 | |
| 615 | #define SMTPSRV_CLEAR_MSG 1 /* smtp_server_clear() last message only */ |
| 616 | #define SMTPSRV_CLEAR_ALL 2 /* smtp_server_clear() everything */ |
| 617 | #define SMTPSRV_LOG 0x001 /* Record a transcript of the interaction */ |
| 618 | |
| 619 | #endif /* LOCAL_INTERFACE */ |
| 620 | |
| 621 | /* |
| 622 | ** Clear the SmtpServer object. Deallocate resources. |
| 623 | ** How much to clear depends on eHowMuch |
| 624 | */ |
| 625 | static void smtp_server_clear(SmtpServer *p, int eHowMuch){ |
| 626 | int i; |
| 627 | if( eHowMuch>=SMTPSRV_CLEAR_MSG ){ |
| 628 | fossil_free(p->zFrom); |
| 629 | p->zFrom = 0; |
| 630 | for(i=0; i<p->nTo; i++) fossil_free(p->azTo[0]); |
| 631 | fossil_free(p->azTo); |
| 632 | p->azTo = 0; |
| 633 | p->nTo = 0; |
| 634 | blob_zero(&p->msg); |
| 635 | } |
| 636 | if( eHowMuch>=SMTPSRV_CLEAR_ALL ){ |
| 637 | blob_zero(&p->transcript); |
| 638 | sqlite3_close(p->db); |
| 639 | p->db = 0; |
| 640 | fossil_free(p->zEhlo); |
| 641 | p->zEhlo = 0; |
| 642 | } |
| 643 | } |
| 644 | |
| 645 | /* |
| 646 | ** Turn raw memory into an SmtpServer object. |
| 647 | */ |
| 648 | static void smtp_server_init(SmtpServer *p){ |
| 649 | memset(p, 0, sizeof(*p)); |
| 650 | blob_init(&p->msg, 0, 0); |
| 651 | blob_init(&p->transcript, 0, 0); |
| 652 | } |
| 653 | |
| 654 | /* |
| 655 | ** Send a single line of output from the server to the client. |
| 656 | */ |
| 657 | static void smtp_server_send(SmtpServer *p, const char *zFormat, ...){ |
| 658 | Blob b = empty_blob; |
| 659 | va_list ap; |
| 660 | char *z; |
| 661 | int n; |
| 662 | va_start(ap, zFormat); |
| 663 | blob_vappendf(&b, zFormat, ap); |
| 664 | va_end(ap); |
| 665 | z = blob_buffer(&b); |
| 666 | n = blob_size(&b); |
| 667 | assert( n>=2 ); |
| 668 | assert( z[n-1]=='\n' ); |
| 669 | assert( z[n-2]=='\r' ); |
| 670 | if( p->srvrFlags & SMTPSRV_LOG ){ |
| 671 | blob_appendf(&p->transcript, "S: %.*s\n", n-2, z); |
| 672 | } |
| 673 | fwrite(z, n, 1, stdout); |
| 674 | fflush(stdout); |
| 675 | blob_zero(&b); |
| 676 | } |
| 677 | |
| 678 | /* |
| 679 | ** Read a single line from the client. |
| 680 | */ |
| 681 | static int smtp_server_gets(SmtpServer *p, char *aBuf, int nBuf){ |
| 682 | int rc = fgets(aBuf, nBuf, stdin)!=0; |
| 683 | if( rc && (p->srvrFlags & SMTPSRV_LOG)!=0 ){ |
| 684 | blob_appendf(&p->transcript, "C: %s\n", aBuf); |
| 685 | } |
| 686 | return rc; |
| 687 | } |
| 688 | |
| 689 | /* |
| 690 | ** Capture the incoming email data into the p->msg blob. Dequote |
| 691 | ** lines of "..\r\n" into just ".\r\n". |
| 692 | */ |
| 693 | static void smtp_server_capture_data(SmtpServer *p, char *z, int n){ |
| 694 | while( fgets(z, n, stdin) ){ |
| 695 | if( strncmp(z, ".\r\n", 3)==0 ) return; |
| 696 | if( strncmp(z, "..\r\n", 4)==0 ){ |
| 697 | memmove(z, z+1, 4); |
| 698 | } |
| 699 | blob_append(&p->msg, z, -1); |
| 700 | } |
| 701 | } |
| 702 | |
| 703 | /* |
| 704 | ** COMMAND: smtp |
| 705 | ** |
| 706 | ** Usage: %fossil smtp [options] DBNAME |
| 707 | ** |
| 708 | ** Begin a SMTP conversation with a client using stdin/stdout. (This |
| 709 | ** command is expected to be launched from xinetd or the equivalent.) |
| 710 | ** Use information in the SQLite database at DBNAME to find configuration |
| 711 | ** information and as a place to store the incoming content. |
| 712 | */ |
| 713 | void smtp_server(void){ |
| 714 | char *zDbName; |
| 715 | const char *zDomain; |
| 716 | SmtpServer x; |
| 717 | char z[5000]; |
| 718 | |
| 719 | smtp_server_init(&x); |
| 720 | zDomain = find_option("domain",0,1); |
| 721 | if( zDomain==0 ) zDomain = "unspecified.domain"; |
| 722 | verify_all_options(); |
| 723 | if( g.argc!=3 ) usage("DBNAME"); |
| 724 | zDbName = g.argv[2]; |
| 725 | zDbName = enter_chroot_jail(zDbName, 0); |
| 726 | smtp_server_send(&x, "220 %s ESMTP Fossil ([%.*s] %s)\r\n", |
| 727 | zDomain, 16, MANIFEST_VERSION, MANIFEST_DATE); |
| 728 | while( smtp_server_gets(&x, z, sizeof(z)) ){ |
| 729 | if( strncmp(z, "EHLO ", 5)==0 ){ |
| 730 | smtp_server_send(&x, "250 ok\r\n"); |
| 731 | }else |
| 732 | if( strncmp(z, "HELO ", 5)==0 ){ |
| 733 | smtp_server_send(&x, "250 ok\r\n"); |
| 734 | }else |
| 735 | if( strncmp(z, "MAIL FROM:<", 11)==0 ){ |
| 736 | smtp_server_send(&x, "250 ok\r\n"); |
| 737 | }else |
| 738 | if( strncmp(z, "RCPT TO:<", 9)==0 ){ |
| 739 | smtp_server_send(&x, "250 ok\r\n"); |
| 740 | }else |
| 741 | if( strncmp(z, "DATA", 4)==0 ){ |
| 742 | smtp_server_send(&x, "354 ready\r\n"); |
| 743 | smtp_server_capture_data(&x, z, sizeof(z)); |
| 744 | smtp_server_send(&x, "250 ok\r\n"); |
| 745 | smtp_server_clear(&x, SMTPSRV_CLEAR_MSG); |
| 746 | }else |
| 747 | if( strncmp(z, "QUIT", 4)==0 ){ |
| 748 | smtp_server_send(&x, "221 closing connection\r\n"); |
| 749 | break; |
| 750 | }else |
| 751 | { |
| 752 | smtp_server_send(&x, "500 unknown command\r\n"); |
| 753 | } |
| 754 | } |
| 755 | fclose(stdin); |
| 756 | smtp_server_clear(&x, SMTPSRV_CLEAR_ALL); |
| 757 | } |
| 758 |