Fossil SCM
Add "SMTP relay" as a new method for sending alert emails.
Commit
b96415f047707df245270cbe40bd72f4389e463a4a504182dc800a2db24b5216
Parent
4f30802a8842549…
2 files changed
+172
-11
+9
-97
+172
-11
| --- src/email.c | ||
| +++ src/email.c | ||
| @@ -188,14 +188,15 @@ | ||
| 188 | 188 | ** Administrative page for configuring and controlling email notification. |
| 189 | 189 | ** Normally accessible via the /Admin/Email menu. |
| 190 | 190 | */ |
| 191 | 191 | void setup_email(void){ |
| 192 | 192 | static const char *const azSendMethods[] = { |
| 193 | - "off", "Disabled", | |
| 194 | - "pipe", "Pipe to a command", | |
| 195 | - "db", "Store in a database", | |
| 196 | - "dir", "Store in a directory" | |
| 193 | + "off", "Disabled", | |
| 194 | + "pipe", "Pipe to a command", | |
| 195 | + "db", "Store in a database", | |
| 196 | + "dir", "Store in a directory", | |
| 197 | + "relay", "SMTP relay" | |
| 197 | 198 | }; |
| 198 | 199 | login_check_credentials(); |
| 199 | 200 | if( !g.perm.Setup ){ |
| 200 | 201 | login_needed(0); |
| 201 | 202 | return; |
| @@ -244,17 +245,14 @@ | ||
| 244 | 245 | @ (Property: "email-autoexec")</p> |
| 245 | 246 | @ <hr> |
| 246 | 247 | |
| 247 | 248 | multiple_choice_attribute("Email Send Method", "email-send-method", "esm", |
| 248 | 249 | "off", count(azSendMethods)/2, azSendMethods); |
| 249 | - @ <p>How to send email. The "Pipe to a command" | |
| 250 | - @ method is the usual choice in production. | |
| 251 | - @ (Property: "email-send-method")</p> | |
| 252 | - @ <hr> | |
| 250 | + @ <p>How to send email. Requires auxiliary information from the fields | |
| 251 | + @ that follow. (Property: "email-send-method")</p> | |
| 253 | 252 | email_schema(1); |
| 254 | - | |
| 255 | - entry_attribute("Command To Pipe Email To", 80, "email-send-command", | |
| 253 | + entry_attribute("Command To Pipe Email To", 60, "email-send-command", | |
| 256 | 254 | "ecmd", "sendmail -t", 0); |
| 257 | 255 | @ <p>When the send method is "pipe to a command", this is the command |
| 258 | 256 | @ that is run. Email messages are piped into the standard input of this |
| 259 | 257 | @ command. The command is expected to extract the sender address, |
| 260 | 258 | @ recepient addresses, and subject from the header of the piped email |
| @@ -269,10 +267,19 @@ | ||
| 269 | 267 | entry_attribute("Directory In Which To Store Email", 60, "email-send-dir", |
| 270 | 268 | "esdir", "", 0); |
| 271 | 269 | @ <p>When the send method is "store in a directory", each email message is |
| 272 | 270 | @ stored as a separate file in the directory shown here. |
| 273 | 271 | @ (Property: "email-send-dir")</p> |
| 272 | + | |
| 273 | + entry_attribute("SMTP relay host", 60, "email-send-relayhost", | |
| 274 | + "esrh", "", 0); | |
| 275 | + @ <p>When the send method is "SMTP relay", each email message is | |
| 276 | + @ transmitted via the SMTP protocol (rfc5321) to a "Mail Submission | |
| 277 | + @ Agent" or "MSA" (rfc4409) at the hostname shown here. Optionally | |
| 278 | + @ append a colon and TCP port number (ex: smtp.example.com:587). | |
| 279 | + @ The default TCP port number is 25. | |
| 280 | + @ (Property: "email-send-relayhost")</p> | |
| 274 | 281 | @ <hr> |
| 275 | 282 | |
| 276 | 283 | entry_attribute("Administrator email address", 40, "email-admin", |
| 277 | 284 | "eadmin", "", 0); |
| 278 | 285 | @ <p>This is the email for the human administrator for the system. |
| @@ -376,10 +383,11 @@ | ||
| 376 | 383 | const char *zDest; /* How to send email. */ |
| 377 | 384 | const char *zDb; /* Name of database file */ |
| 378 | 385 | const char *zDir; /* Directory in which to store as email files */ |
| 379 | 386 | const char *zCmd; /* Command to run for each email */ |
| 380 | 387 | const char *zFrom; /* Emails come from here */ |
| 388 | + SmtpSession *pSmtp; /* SMTP relay connection */ | |
| 381 | 389 | Blob out; /* For zDest=="blob" */ |
| 382 | 390 | char *zErr; /* Error message */ |
| 383 | 391 | int bImmediateFail; /* On any error, call fossil_fatal() */ |
| 384 | 392 | }; |
| 385 | 393 | #endif /* INTERFACE */ |
| @@ -393,11 +401,14 @@ | ||
| 393 | 401 | sqlite3_close(p->db); |
| 394 | 402 | p->db = 0; |
| 395 | 403 | p->zDb = 0; |
| 396 | 404 | p->zDir = 0; |
| 397 | 405 | p->zCmd = 0; |
| 398 | - p->zDest = "off"; | |
| 406 | + if( p->pSmtp ){ | |
| 407 | + smtp_session_free(p->pSmtp); | |
| 408 | + p->pSmtp = 0; | |
| 409 | + } | |
| 399 | 410 | blob_reset(&p->out); |
| 400 | 411 | } |
| 401 | 412 | |
| 402 | 413 | /* |
| 403 | 414 | ** Put the EmailSender into an error state. |
| @@ -460,10 +471,11 @@ | ||
| 460 | 471 | EmailSender *email_sender_new(const char *zAltDest, int bImmediateFail){ |
| 461 | 472 | EmailSender *p; |
| 462 | 473 | |
| 463 | 474 | p = fossil_malloc(sizeof(*p)); |
| 464 | 475 | memset(p, 0, sizeof(*p)); |
| 476 | + blob_init(&p->out, 0, 0); | |
| 465 | 477 | p->bImmediateFail = bImmediateFail; |
| 466 | 478 | if( zAltDest ){ |
| 467 | 479 | p->zDest = zAltDest; |
| 468 | 480 | }else{ |
| 469 | 481 | p->zDest = db_get("email-send-method","off"); |
| @@ -499,13 +511,146 @@ | ||
| 499 | 511 | emailerGetSetting(p, &p->zCmd, "email-send-command"); |
| 500 | 512 | }else if( fossil_strcmp(p->zDest, "dir")==0 ){ |
| 501 | 513 | emailerGetSetting(p, &p->zDir, "email-send-dir"); |
| 502 | 514 | }else if( fossil_strcmp(p->zDest, "blob")==0 ){ |
| 503 | 515 | blob_init(&p->out, 0, 0); |
| 516 | + }else if( fossil_strcmp(p->zDest, "relay")==0 ){ | |
| 517 | + const char *zRelay = 0; | |
| 518 | + emailerGetSetting(p, &zRelay, "email-send-relayhost"); | |
| 519 | + if( zRelay ){ | |
| 520 | + p->pSmtp = smtp_session_new(p->zFrom, zRelay, SMTP_DIRECT); | |
| 521 | + } | |
| 504 | 522 | } |
| 505 | 523 | return p; |
| 506 | 524 | } |
| 525 | + | |
| 526 | +/* | |
| 527 | +** Scan the header of the email message in pMsg looking for the | |
| 528 | +** (first) occurrance of zField. Fill pValue with the content of | |
| 529 | +** that field. | |
| 530 | +** | |
| 531 | +** This routine initializes pValue. Any prior content of pValue is | |
| 532 | +** discarded (leaked). | |
| 533 | +** | |
| 534 | +** Return non-zero on success. Return 0 if no instance of the header | |
| 535 | +** is found. | |
| 536 | +*/ | |
| 537 | +int email_header_value(Blob *pMsg, const char *zField, Blob *pValue){ | |
| 538 | + int nField = (int)strlen(zField); | |
| 539 | + Blob line; | |
| 540 | + blob_rewind(pMsg); | |
| 541 | + blob_init(pValue,0,0); | |
| 542 | + while( blob_line(pMsg, &line) ){ | |
| 543 | + int n, i; | |
| 544 | + char *z; | |
| 545 | + blob_trim(&line); | |
| 546 | + n = blob_size(&line); | |
| 547 | + if( n==0 ) return 0; | |
| 548 | + if( n<nField+1 ) continue; | |
| 549 | + z = blob_buffer(&line); | |
| 550 | + if( sqlite3_strnicmp(z, zField, nField)==0 && z[nField]==':' ){ | |
| 551 | + for(i=nField+1; i<n && fossil_isspace(z[i]); i++){} | |
| 552 | + blob_init(pValue, z+i, n-i); | |
| 553 | + while( blob_line(pMsg, &line) ){ | |
| 554 | + blob_trim(&line); | |
| 555 | + n = blob_size(&line); | |
| 556 | + if( n==0 ) break; | |
| 557 | + z = blob_buffer(&line); | |
| 558 | + if( !fossil_isspace(z[0]) ) break; | |
| 559 | + for(i=1; i<n && fossil_isspace(z[i]); i++){} | |
| 560 | + blob_append(pValue, " ", 1); | |
| 561 | + blob_append(pValue, z+i, n-i); | |
| 562 | + } | |
| 563 | + return 1; | |
| 564 | + } | |
| 565 | + } | |
| 566 | + return 0; | |
| 567 | +} | |
| 568 | + | |
| 569 | +/* | |
| 570 | +** Make a copy of the input string up to but not including the | |
| 571 | +** first ">" character. | |
| 572 | +** | |
| 573 | +** Verify that the string really that is to be copied really is a | |
| 574 | +** valid email address. If it is not, then return NULL. | |
| 575 | +** | |
| 576 | +** This routine is more restrictive than necessary. It does not | |
| 577 | +** allow comments, IP address, quoted strings, or certain uncommon | |
| 578 | +** characters. The only non-alphanumerics allowed in the local | |
| 579 | +** part are "_", "+", "-" and "+". | |
| 580 | +*/ | |
| 581 | +char *email_copy_addr(const char *z){ | |
| 582 | + int i; | |
| 583 | + int nAt = 0; | |
| 584 | + int nDot = 0; | |
| 585 | + char c; | |
| 586 | + if( z[0]=='.' ) return 0; /* Local part cannot begin with "." */ | |
| 587 | + for(i=0; (c = z[i])!=0 && c!='>'; i++){ | |
| 588 | + if( fossil_isalnum(c) ){ | |
| 589 | + /* Alphanumerics are always ok */ | |
| 590 | + }else if( c=='@' ){ | |
| 591 | + if( nAt ) return 0; /* Only a single "@" allowed */ | |
| 592 | + if( i>64 ) return 0; /* Local part too big */ | |
| 593 | + nAt = 1; | |
| 594 | + nDot = 0; | |
| 595 | + if( i==0 ) return 0; /* Disallow empty local part */ | |
| 596 | + if( z[i-1]=='.' ) return 0; /* Last char of local cannot be "." */ | |
| 597 | + if( z[i+1]=='.' || z[i+1]=='-' ){ | |
| 598 | + return 0; /* Domain cannot begin with "." or "-" */ | |
| 599 | + } | |
| 600 | + }else if( c=='-' ){ | |
| 601 | + if( z[i+1]=='>' ) return 0; /* Last character cannot be "-" */ | |
| 602 | + }else if( c=='.' ){ | |
| 603 | + if( z[i+1]=='.' ) return 0; /* Do not allow ".." */ | |
| 604 | + if( z[i+1]=='>' ) return 0; /* Domain may not end with . */ | |
| 605 | + nDot++; | |
| 606 | + }else if( (c=='_' || c=='+') && nAt==0 ){ | |
| 607 | + /* _ and + are ok in the local part */ | |
| 608 | + }else{ | |
| 609 | + return 0; /* Anything else is an error */ | |
| 610 | + } | |
| 611 | + } | |
| 612 | + if( c!='>' ) return 0; /* Missing final ">" */ | |
| 613 | + if( nAt==0 ) return 0; /* No "@" found anywhere */ | |
| 614 | + if( nDot==0 ) return 0; /* No "." in the domain */ | |
| 615 | + | |
| 616 | + /* If we reach this point, the email address is valid */ | |
| 617 | + return mprintf("%.*s", i, z); | |
| 618 | +} | |
| 619 | + | |
| 620 | +/* | |
| 621 | +** Extract all To: header values from the email header supplied. | |
| 622 | +** Store them in the array list. | |
| 623 | +*/ | |
| 624 | +void email_header_to(Blob *pMsg, int *pnTo, char ***pazTo){ | |
| 625 | + int nTo = 0; | |
| 626 | + char **azTo = 0; | |
| 627 | + Blob v; | |
| 628 | + char *z, *zAddr; | |
| 629 | + int i; | |
| 630 | + | |
| 631 | + email_header_value(pMsg, "to", &v); | |
| 632 | + z = blob_str(&v); | |
| 633 | + for(i=0; z[i]; i++){ | |
| 634 | + if( z[i]=='<' && (zAddr = email_copy_addr(&z[i+1]))!=0 ){ | |
| 635 | + azTo = fossil_realloc(azTo, sizeof(azTo[0])*(nTo+1) ); | |
| 636 | + azTo[nTo++] = zAddr; | |
| 637 | + } | |
| 638 | + } | |
| 639 | + *pnTo = nTo; | |
| 640 | + *pazTo = azTo; | |
| 641 | +} | |
| 642 | + | |
| 643 | +/* | |
| 644 | +** Free a list of To addresses obtained from a prior call to | |
| 645 | +** email_header_to() | |
| 646 | +*/ | |
| 647 | +void email_header_to_free(int nTo, char **azTo){ | |
| 648 | + int i; | |
| 649 | + for(i=0; i<nTo; i++) fossil_free(azTo[i]); | |
| 650 | + fossil_free(azTo); | |
| 651 | +} | |
| 507 | 652 | |
| 508 | 653 | /* |
| 509 | 654 | ** Send a single email message. |
| 510 | 655 | ** |
| 511 | 656 | ** The recepient(s) must be specified using "To:" or "Cc:" or "Bcc:" fields |
| @@ -579,11 +724,27 @@ | ||
| 579 | 724 | } |
| 580 | 725 | }else if( p->zDir ){ |
| 581 | 726 | char *zFile = emailTempFilename(p->zDir); |
| 582 | 727 | blob_write_to_file(&all, zFile); |
| 583 | 728 | fossil_free(zFile); |
| 729 | + }else if( p->pSmtp ){ | |
| 730 | + char **azTo = 0; | |
| 731 | + int nTo = 0; | |
| 732 | + email_header_to(pHdr, &nTo, &azTo); | |
| 733 | + if( nTo>0 ){ | |
| 734 | + smtp_send_msg(p->pSmtp, p->zFrom, nTo, azTo, blob_str(&all)); | |
| 735 | + email_header_to_free(nTo, azTo); | |
| 736 | + } | |
| 584 | 737 | }else if( strcmp(p->zDest, "stdout")==0 ){ |
| 738 | + char **azTo = 0; | |
| 739 | + int nTo = 0; | |
| 740 | + int i; | |
| 741 | + email_header_to(pHdr, &nTo, &azTo); | |
| 742 | + for(i=0; i<nTo; i++){ | |
| 743 | + fossil_print("X-To-Test-%d: [%s]\r\n", i, azTo[i]); | |
| 744 | + } | |
| 745 | + email_header_to_free(nTo, azTo); | |
| 585 | 746 | blob_add_final_newline(&all); |
| 586 | 747 | fossil_print("%s", blob_str(&all)); |
| 587 | 748 | } |
| 588 | 749 | blob_reset(&all); |
| 589 | 750 | } |
| 590 | 751 |
| --- src/email.c | |
| +++ src/email.c | |
| @@ -188,14 +188,15 @@ | |
| 188 | ** Administrative page for configuring and controlling email notification. |
| 189 | ** Normally accessible via the /Admin/Email menu. |
| 190 | */ |
| 191 | void setup_email(void){ |
| 192 | static const char *const azSendMethods[] = { |
| 193 | "off", "Disabled", |
| 194 | "pipe", "Pipe to a command", |
| 195 | "db", "Store in a database", |
| 196 | "dir", "Store in a directory" |
| 197 | }; |
| 198 | login_check_credentials(); |
| 199 | if( !g.perm.Setup ){ |
| 200 | login_needed(0); |
| 201 | return; |
| @@ -244,17 +245,14 @@ | |
| 244 | @ (Property: "email-autoexec")</p> |
| 245 | @ <hr> |
| 246 | |
| 247 | multiple_choice_attribute("Email Send Method", "email-send-method", "esm", |
| 248 | "off", count(azSendMethods)/2, azSendMethods); |
| 249 | @ <p>How to send email. The "Pipe to a command" |
| 250 | @ method is the usual choice in production. |
| 251 | @ (Property: "email-send-method")</p> |
| 252 | @ <hr> |
| 253 | email_schema(1); |
| 254 | |
| 255 | entry_attribute("Command To Pipe Email To", 80, "email-send-command", |
| 256 | "ecmd", "sendmail -t", 0); |
| 257 | @ <p>When the send method is "pipe to a command", this is the command |
| 258 | @ that is run. Email messages are piped into the standard input of this |
| 259 | @ command. The command is expected to extract the sender address, |
| 260 | @ recepient addresses, and subject from the header of the piped email |
| @@ -269,10 +267,19 @@ | |
| 269 | entry_attribute("Directory In Which To Store Email", 60, "email-send-dir", |
| 270 | "esdir", "", 0); |
| 271 | @ <p>When the send method is "store in a directory", each email message is |
| 272 | @ stored as a separate file in the directory shown here. |
| 273 | @ (Property: "email-send-dir")</p> |
| 274 | @ <hr> |
| 275 | |
| 276 | entry_attribute("Administrator email address", 40, "email-admin", |
| 277 | "eadmin", "", 0); |
| 278 | @ <p>This is the email for the human administrator for the system. |
| @@ -376,10 +383,11 @@ | |
| 376 | const char *zDest; /* How to send email. */ |
| 377 | const char *zDb; /* Name of database file */ |
| 378 | const char *zDir; /* Directory in which to store as email files */ |
| 379 | const char *zCmd; /* Command to run for each email */ |
| 380 | const char *zFrom; /* Emails come from here */ |
| 381 | Blob out; /* For zDest=="blob" */ |
| 382 | char *zErr; /* Error message */ |
| 383 | int bImmediateFail; /* On any error, call fossil_fatal() */ |
| 384 | }; |
| 385 | #endif /* INTERFACE */ |
| @@ -393,11 +401,14 @@ | |
| 393 | sqlite3_close(p->db); |
| 394 | p->db = 0; |
| 395 | p->zDb = 0; |
| 396 | p->zDir = 0; |
| 397 | p->zCmd = 0; |
| 398 | p->zDest = "off"; |
| 399 | blob_reset(&p->out); |
| 400 | } |
| 401 | |
| 402 | /* |
| 403 | ** Put the EmailSender into an error state. |
| @@ -460,10 +471,11 @@ | |
| 460 | EmailSender *email_sender_new(const char *zAltDest, int bImmediateFail){ |
| 461 | EmailSender *p; |
| 462 | |
| 463 | p = fossil_malloc(sizeof(*p)); |
| 464 | memset(p, 0, sizeof(*p)); |
| 465 | p->bImmediateFail = bImmediateFail; |
| 466 | if( zAltDest ){ |
| 467 | p->zDest = zAltDest; |
| 468 | }else{ |
| 469 | p->zDest = db_get("email-send-method","off"); |
| @@ -499,13 +511,146 @@ | |
| 499 | emailerGetSetting(p, &p->zCmd, "email-send-command"); |
| 500 | }else if( fossil_strcmp(p->zDest, "dir")==0 ){ |
| 501 | emailerGetSetting(p, &p->zDir, "email-send-dir"); |
| 502 | }else if( fossil_strcmp(p->zDest, "blob")==0 ){ |
| 503 | blob_init(&p->out, 0, 0); |
| 504 | } |
| 505 | return p; |
| 506 | } |
| 507 | |
| 508 | /* |
| 509 | ** Send a single email message. |
| 510 | ** |
| 511 | ** The recepient(s) must be specified using "To:" or "Cc:" or "Bcc:" fields |
| @@ -579,11 +724,27 @@ | |
| 579 | } |
| 580 | }else if( p->zDir ){ |
| 581 | char *zFile = emailTempFilename(p->zDir); |
| 582 | blob_write_to_file(&all, zFile); |
| 583 | fossil_free(zFile); |
| 584 | }else if( strcmp(p->zDest, "stdout")==0 ){ |
| 585 | blob_add_final_newline(&all); |
| 586 | fossil_print("%s", blob_str(&all)); |
| 587 | } |
| 588 | blob_reset(&all); |
| 589 | } |
| 590 |
| --- src/email.c | |
| +++ src/email.c | |
| @@ -188,14 +188,15 @@ | |
| 188 | ** Administrative page for configuring and controlling email notification. |
| 189 | ** Normally accessible via the /Admin/Email menu. |
| 190 | */ |
| 191 | void setup_email(void){ |
| 192 | static const char *const azSendMethods[] = { |
| 193 | "off", "Disabled", |
| 194 | "pipe", "Pipe to a command", |
| 195 | "db", "Store in a database", |
| 196 | "dir", "Store in a directory", |
| 197 | "relay", "SMTP relay" |
| 198 | }; |
| 199 | login_check_credentials(); |
| 200 | if( !g.perm.Setup ){ |
| 201 | login_needed(0); |
| 202 | return; |
| @@ -244,17 +245,14 @@ | |
| 245 | @ (Property: "email-autoexec")</p> |
| 246 | @ <hr> |
| 247 | |
| 248 | multiple_choice_attribute("Email Send Method", "email-send-method", "esm", |
| 249 | "off", count(azSendMethods)/2, azSendMethods); |
| 250 | @ <p>How to send email. Requires auxiliary information from the fields |
| 251 | @ that follow. (Property: "email-send-method")</p> |
| 252 | email_schema(1); |
| 253 | entry_attribute("Command To Pipe Email To", 60, "email-send-command", |
| 254 | "ecmd", "sendmail -t", 0); |
| 255 | @ <p>When the send method is "pipe to a command", this is the command |
| 256 | @ that is run. Email messages are piped into the standard input of this |
| 257 | @ command. The command is expected to extract the sender address, |
| 258 | @ recepient addresses, and subject from the header of the piped email |
| @@ -269,10 +267,19 @@ | |
| 267 | entry_attribute("Directory In Which To Store Email", 60, "email-send-dir", |
| 268 | "esdir", "", 0); |
| 269 | @ <p>When the send method is "store in a directory", each email message is |
| 270 | @ stored as a separate file in the directory shown here. |
| 271 | @ (Property: "email-send-dir")</p> |
| 272 | |
| 273 | entry_attribute("SMTP relay host", 60, "email-send-relayhost", |
| 274 | "esrh", "", 0); |
| 275 | @ <p>When the send method is "SMTP relay", each email message is |
| 276 | @ transmitted via the SMTP protocol (rfc5321) to a "Mail Submission |
| 277 | @ Agent" or "MSA" (rfc4409) at the hostname shown here. Optionally |
| 278 | @ append a colon and TCP port number (ex: smtp.example.com:587). |
| 279 | @ The default TCP port number is 25. |
| 280 | @ (Property: "email-send-relayhost")</p> |
| 281 | @ <hr> |
| 282 | |
| 283 | entry_attribute("Administrator email address", 40, "email-admin", |
| 284 | "eadmin", "", 0); |
| 285 | @ <p>This is the email for the human administrator for the system. |
| @@ -376,10 +383,11 @@ | |
| 383 | const char *zDest; /* How to send email. */ |
| 384 | const char *zDb; /* Name of database file */ |
| 385 | const char *zDir; /* Directory in which to store as email files */ |
| 386 | const char *zCmd; /* Command to run for each email */ |
| 387 | const char *zFrom; /* Emails come from here */ |
| 388 | SmtpSession *pSmtp; /* SMTP relay connection */ |
| 389 | Blob out; /* For zDest=="blob" */ |
| 390 | char *zErr; /* Error message */ |
| 391 | int bImmediateFail; /* On any error, call fossil_fatal() */ |
| 392 | }; |
| 393 | #endif /* INTERFACE */ |
| @@ -393,11 +401,14 @@ | |
| 401 | sqlite3_close(p->db); |
| 402 | p->db = 0; |
| 403 | p->zDb = 0; |
| 404 | p->zDir = 0; |
| 405 | p->zCmd = 0; |
| 406 | if( p->pSmtp ){ |
| 407 | smtp_session_free(p->pSmtp); |
| 408 | p->pSmtp = 0; |
| 409 | } |
| 410 | blob_reset(&p->out); |
| 411 | } |
| 412 | |
| 413 | /* |
| 414 | ** Put the EmailSender into an error state. |
| @@ -460,10 +471,11 @@ | |
| 471 | EmailSender *email_sender_new(const char *zAltDest, int bImmediateFail){ |
| 472 | EmailSender *p; |
| 473 | |
| 474 | p = fossil_malloc(sizeof(*p)); |
| 475 | memset(p, 0, sizeof(*p)); |
| 476 | blob_init(&p->out, 0, 0); |
| 477 | p->bImmediateFail = bImmediateFail; |
| 478 | if( zAltDest ){ |
| 479 | p->zDest = zAltDest; |
| 480 | }else{ |
| 481 | p->zDest = db_get("email-send-method","off"); |
| @@ -499,13 +511,146 @@ | |
| 511 | emailerGetSetting(p, &p->zCmd, "email-send-command"); |
| 512 | }else if( fossil_strcmp(p->zDest, "dir")==0 ){ |
| 513 | emailerGetSetting(p, &p->zDir, "email-send-dir"); |
| 514 | }else if( fossil_strcmp(p->zDest, "blob")==0 ){ |
| 515 | blob_init(&p->out, 0, 0); |
| 516 | }else if( fossil_strcmp(p->zDest, "relay")==0 ){ |
| 517 | const char *zRelay = 0; |
| 518 | emailerGetSetting(p, &zRelay, "email-send-relayhost"); |
| 519 | if( zRelay ){ |
| 520 | p->pSmtp = smtp_session_new(p->zFrom, zRelay, SMTP_DIRECT); |
| 521 | } |
| 522 | } |
| 523 | return p; |
| 524 | } |
| 525 | |
| 526 | /* |
| 527 | ** Scan the header of the email message in pMsg looking for the |
| 528 | ** (first) occurrance of zField. Fill pValue with the content of |
| 529 | ** that field. |
| 530 | ** |
| 531 | ** This routine initializes pValue. Any prior content of pValue is |
| 532 | ** discarded (leaked). |
| 533 | ** |
| 534 | ** Return non-zero on success. Return 0 if no instance of the header |
| 535 | ** is found. |
| 536 | */ |
| 537 | int email_header_value(Blob *pMsg, const char *zField, Blob *pValue){ |
| 538 | int nField = (int)strlen(zField); |
| 539 | Blob line; |
| 540 | blob_rewind(pMsg); |
| 541 | blob_init(pValue,0,0); |
| 542 | while( blob_line(pMsg, &line) ){ |
| 543 | int n, i; |
| 544 | char *z; |
| 545 | blob_trim(&line); |
| 546 | n = blob_size(&line); |
| 547 | if( n==0 ) return 0; |
| 548 | if( n<nField+1 ) continue; |
| 549 | z = blob_buffer(&line); |
| 550 | if( sqlite3_strnicmp(z, zField, nField)==0 && z[nField]==':' ){ |
| 551 | for(i=nField+1; i<n && fossil_isspace(z[i]); i++){} |
| 552 | blob_init(pValue, z+i, n-i); |
| 553 | while( blob_line(pMsg, &line) ){ |
| 554 | blob_trim(&line); |
| 555 | n = blob_size(&line); |
| 556 | if( n==0 ) break; |
| 557 | z = blob_buffer(&line); |
| 558 | if( !fossil_isspace(z[0]) ) break; |
| 559 | for(i=1; i<n && fossil_isspace(z[i]); i++){} |
| 560 | blob_append(pValue, " ", 1); |
| 561 | blob_append(pValue, z+i, n-i); |
| 562 | } |
| 563 | return 1; |
| 564 | } |
| 565 | } |
| 566 | return 0; |
| 567 | } |
| 568 | |
| 569 | /* |
| 570 | ** Make a copy of the input string up to but not including the |
| 571 | ** first ">" character. |
| 572 | ** |
| 573 | ** Verify that the string really that is to be copied really is a |
| 574 | ** valid email address. If it is not, then return NULL. |
| 575 | ** |
| 576 | ** This routine is more restrictive than necessary. It does not |
| 577 | ** allow comments, IP address, quoted strings, or certain uncommon |
| 578 | ** characters. The only non-alphanumerics allowed in the local |
| 579 | ** part are "_", "+", "-" and "+". |
| 580 | */ |
| 581 | char *email_copy_addr(const char *z){ |
| 582 | int i; |
| 583 | int nAt = 0; |
| 584 | int nDot = 0; |
| 585 | char c; |
| 586 | if( z[0]=='.' ) return 0; /* Local part cannot begin with "." */ |
| 587 | for(i=0; (c = z[i])!=0 && c!='>'; i++){ |
| 588 | if( fossil_isalnum(c) ){ |
| 589 | /* Alphanumerics are always ok */ |
| 590 | }else if( c=='@' ){ |
| 591 | if( nAt ) return 0; /* Only a single "@" allowed */ |
| 592 | if( i>64 ) return 0; /* Local part too big */ |
| 593 | nAt = 1; |
| 594 | nDot = 0; |
| 595 | if( i==0 ) return 0; /* Disallow empty local part */ |
| 596 | if( z[i-1]=='.' ) return 0; /* Last char of local cannot be "." */ |
| 597 | if( z[i+1]=='.' || z[i+1]=='-' ){ |
| 598 | return 0; /* Domain cannot begin with "." or "-" */ |
| 599 | } |
| 600 | }else if( c=='-' ){ |
| 601 | if( z[i+1]=='>' ) return 0; /* Last character cannot be "-" */ |
| 602 | }else if( c=='.' ){ |
| 603 | if( z[i+1]=='.' ) return 0; /* Do not allow ".." */ |
| 604 | if( z[i+1]=='>' ) return 0; /* Domain may not end with . */ |
| 605 | nDot++; |
| 606 | }else if( (c=='_' || c=='+') && nAt==0 ){ |
| 607 | /* _ and + are ok in the local part */ |
| 608 | }else{ |
| 609 | return 0; /* Anything else is an error */ |
| 610 | } |
| 611 | } |
| 612 | if( c!='>' ) return 0; /* Missing final ">" */ |
| 613 | if( nAt==0 ) return 0; /* No "@" found anywhere */ |
| 614 | if( nDot==0 ) return 0; /* No "." in the domain */ |
| 615 | |
| 616 | /* If we reach this point, the email address is valid */ |
| 617 | return mprintf("%.*s", i, z); |
| 618 | } |
| 619 | |
| 620 | /* |
| 621 | ** Extract all To: header values from the email header supplied. |
| 622 | ** Store them in the array list. |
| 623 | */ |
| 624 | void email_header_to(Blob *pMsg, int *pnTo, char ***pazTo){ |
| 625 | int nTo = 0; |
| 626 | char **azTo = 0; |
| 627 | Blob v; |
| 628 | char *z, *zAddr; |
| 629 | int i; |
| 630 | |
| 631 | email_header_value(pMsg, "to", &v); |
| 632 | z = blob_str(&v); |
| 633 | for(i=0; z[i]; i++){ |
| 634 | if( z[i]=='<' && (zAddr = email_copy_addr(&z[i+1]))!=0 ){ |
| 635 | azTo = fossil_realloc(azTo, sizeof(azTo[0])*(nTo+1) ); |
| 636 | azTo[nTo++] = zAddr; |
| 637 | } |
| 638 | } |
| 639 | *pnTo = nTo; |
| 640 | *pazTo = azTo; |
| 641 | } |
| 642 | |
| 643 | /* |
| 644 | ** Free a list of To addresses obtained from a prior call to |
| 645 | ** email_header_to() |
| 646 | */ |
| 647 | void email_header_to_free(int nTo, char **azTo){ |
| 648 | int i; |
| 649 | for(i=0; i<nTo; i++) fossil_free(azTo[i]); |
| 650 | fossil_free(azTo); |
| 651 | } |
| 652 | |
| 653 | /* |
| 654 | ** Send a single email message. |
| 655 | ** |
| 656 | ** The recepient(s) must be specified using "To:" or "Cc:" or "Bcc:" fields |
| @@ -579,11 +724,27 @@ | |
| 724 | } |
| 725 | }else if( p->zDir ){ |
| 726 | char *zFile = emailTempFilename(p->zDir); |
| 727 | blob_write_to_file(&all, zFile); |
| 728 | fossil_free(zFile); |
| 729 | }else if( p->pSmtp ){ |
| 730 | char **azTo = 0; |
| 731 | int nTo = 0; |
| 732 | email_header_to(pHdr, &nTo, &azTo); |
| 733 | if( nTo>0 ){ |
| 734 | smtp_send_msg(p->pSmtp, p->zFrom, nTo, azTo, blob_str(&all)); |
| 735 | email_header_to_free(nTo, azTo); |
| 736 | } |
| 737 | }else if( strcmp(p->zDest, "stdout")==0 ){ |
| 738 | char **azTo = 0; |
| 739 | int nTo = 0; |
| 740 | int i; |
| 741 | email_header_to(pHdr, &nTo, &azTo); |
| 742 | for(i=0; i<nTo; i++){ |
| 743 | fossil_print("X-To-Test-%d: [%s]\r\n", i, azTo[i]); |
| 744 | } |
| 745 | email_header_to_free(nTo, azTo); |
| 746 | blob_add_final_newline(&all); |
| 747 | fossil_print("%s", blob_str(&all)); |
| 748 | } |
| 749 | blob_reset(&all); |
| 750 | } |
| 751 |
+9
-97
| --- src/smtp.c | ||
| +++ src/smtp.c | ||
| @@ -182,11 +182,17 @@ | ||
| 182 | 182 | if( smtpFlags & SMTP_TRACE_BLOB ){ |
| 183 | 183 | p->pTranscript = va_arg(ap, Blob*); |
| 184 | 184 | } |
| 185 | 185 | va_end(ap); |
| 186 | 186 | if( (smtpFlags & SMTP_DIRECT)!=0 ){ |
| 187 | + int i; | |
| 187 | 188 | p->zHostname = fossil_strdup(zDest); |
| 189 | + for(i=0; p->zHostname[i] && p->zHostname[i]!=':'; i++){} | |
| 190 | + if( p->zHostname[i]==':' ){ | |
| 191 | + p->zHostname[i] = 0; | |
| 192 | + url.port = atoi(&p->zHostname[i+1]); | |
| 193 | + } | |
| 188 | 194 | }else{ |
| 189 | 195 | p->zHostname = smtp_mx_host(zDest); |
| 190 | 196 | } |
| 191 | 197 | if( p->zHostname==0 ){ |
| 192 | 198 | p->atEof = 1; |
| @@ -471,11 +477,11 @@ | ||
| 471 | 477 | |
| 472 | 478 | /* |
| 473 | 479 | ** Send a single email message to the SMTP server. |
| 474 | 480 | ** |
| 475 | 481 | ** All email addresses (zFrom and azTo) must be plain "local@domain" |
| 476 | -** format with the surrounding "<..>". This routine will add the | |
| 482 | +** format without the surrounding "<..>". This routine will add the | |
| 477 | 483 | ** necessary "<..>". |
| 478 | 484 | ** |
| 479 | 485 | ** The body of the email should be well-structured. This routine will |
| 480 | 486 | ** convert any \n line endings into \r\n and will escape lines containing |
| 481 | 487 | ** just ".", but will not make any other alterations or corrections to |
| @@ -595,53 +601,10 @@ | ||
| 595 | 601 | |
| 596 | 602 | /***************************************************************************** |
| 597 | 603 | ** Server implementation |
| 598 | 604 | *****************************************************************************/ |
| 599 | 605 | |
| 600 | -/* | |
| 601 | -** Scan the header of the email message in pMsg looking for the | |
| 602 | -** (first) occurrance of zField. Fill pValue with the content of | |
| 603 | -** that field. | |
| 604 | -** | |
| 605 | -** This routine initializes pValue. Any prior content of pValue is | |
| 606 | -** discarded (leaked). | |
| 607 | -** | |
| 608 | -** Return non-zero on success. Return 0 if no instance of the header | |
| 609 | -** is found. | |
| 610 | -*/ | |
| 611 | -int email_header_value(Blob *pMsg, const char *zField, Blob *pValue){ | |
| 612 | - int nField = (int)strlen(zField); | |
| 613 | - Blob line; | |
| 614 | - blob_rewind(pMsg); | |
| 615 | - blob_init(pValue,0,0); | |
| 616 | - while( blob_line(pMsg, &line) ){ | |
| 617 | - int n, i; | |
| 618 | - char *z; | |
| 619 | - blob_trim(&line); | |
| 620 | - n = blob_size(&line); | |
| 621 | - if( n==0 ) return 0; | |
| 622 | - if( n<nField+1 ) continue; | |
| 623 | - z = blob_buffer(&line); | |
| 624 | - if( sqlite3_strnicmp(z, zField, nField)==0 && z[nField]==':' ){ | |
| 625 | - for(i=nField+1; i<n && fossil_isspace(z[i]); i++){} | |
| 626 | - blob_init(pValue, z+i, n-i); | |
| 627 | - while( blob_line(pMsg, &line) ){ | |
| 628 | - blob_trim(&line); | |
| 629 | - n = blob_size(&line); | |
| 630 | - if( n==0 ) break; | |
| 631 | - z = blob_buffer(&line); | |
| 632 | - if( !fossil_isspace(z[0]) ) break; | |
| 633 | - for(i=1; i<n && fossil_isspace(z[i]); i++){} | |
| 634 | - blob_append(pValue, " ", 1); | |
| 635 | - blob_append(pValue, z+i, n-i); | |
| 636 | - } | |
| 637 | - return 1; | |
| 638 | - } | |
| 639 | - } | |
| 640 | - return 0; | |
| 641 | -} | |
| 642 | - | |
| 643 | 606 | /* |
| 644 | 607 | ** Schema used by the email processing system. |
| 645 | 608 | */ |
| 646 | 609 | static const char zEmailSchema[] = |
| 647 | 610 | @ -- bulk storage is in a separate table. This table can store either |
| @@ -969,61 +932,10 @@ | ||
| 969 | 932 | db_end_transaction(0); |
| 970 | 933 | } |
| 971 | 934 | smtp_server_clear(p, SMTPSRV_CLEAR_MSG); |
| 972 | 935 | } |
| 973 | 936 | |
| 974 | -/* | |
| 975 | -** Make a copy of the input string up to but not including the | |
| 976 | -** first ">" character. | |
| 977 | -** | |
| 978 | -** Verify that the string really that is to be copied really is a | |
| 979 | -** valid email address. If it is not, then return NULL. | |
| 980 | -** | |
| 981 | -** This routine is more restrictive than necessary. It does not | |
| 982 | -** allow comments, IP address, quoted strings, or certain uncommon | |
| 983 | -** characters. The only non-alphanumerics allowed in the local | |
| 984 | -** part are "_", "+", "-" and "+". | |
| 985 | -*/ | |
| 986 | -static char *extractEmail(const char *z){ | |
| 987 | - int i; | |
| 988 | - int nAt = 0; | |
| 989 | - int nDot = 0; | |
| 990 | - char c; | |
| 991 | - if( z[0]=='.' ) return 0; /* Local part cannot begin with "." */ | |
| 992 | - for(i=0; (c = z[i])!=0 && c!='>'; i++){ | |
| 993 | - if( fossil_isalnum(c) ){ | |
| 994 | - /* Alphanumerics are always ok */ | |
| 995 | - }else if( c=='@' ){ | |
| 996 | - if( nAt ) return 0; /* Only a single "@" allowed */ | |
| 997 | - if( i>64 ) return 0; /* Local part too big */ | |
| 998 | - nAt = 1; | |
| 999 | - nDot = 0; | |
| 1000 | - if( i==0 ) return 0; /* Disallow empty local part */ | |
| 1001 | - if( z[i-1]=='.' ) return 0; /* Last char of local cannot be "." */ | |
| 1002 | - if( z[i+1]=='.' || z[i+1]=='-' ){ | |
| 1003 | - return 0; /* Domain cannot begin with "." or "-" */ | |
| 1004 | - } | |
| 1005 | - }else if( c=='-' ){ | |
| 1006 | - if( z[i+1]=='>' ) return 0; /* Last character cannot be "-" */ | |
| 1007 | - }else if( c=='.' ){ | |
| 1008 | - if( z[i+1]=='.' ) return 0; /* Do not allow ".." */ | |
| 1009 | - if( z[i+1]=='>' ) return 0; /* Domain may not end with . */ | |
| 1010 | - nDot++; | |
| 1011 | - }else if( (c=='_' || c=='+') && nAt==0 ){ | |
| 1012 | - /* _ and + are ok in the local part */ | |
| 1013 | - }else{ | |
| 1014 | - return 0; /* Anything else is an error */ | |
| 1015 | - } | |
| 1016 | - } | |
| 1017 | - if( c!='>' ) return 0; /* Missing final ">" */ | |
| 1018 | - if( nAt==0 ) return 0; /* No "@" found anywhere */ | |
| 1019 | - if( nDot==0 ) return 0; /* No "." in the domain */ | |
| 1020 | - | |
| 1021 | - /* If we reach this point, the email address is valid */ | |
| 1022 | - return mprintf("%.*s", i, z); | |
| 1023 | -} | |
| 1024 | - | |
| 1025 | 937 | /* |
| 1026 | 938 | ** COMMAND: smtpd |
| 1027 | 939 | ** |
| 1028 | 940 | ** Usage: %fossil smtpd [OPTIONS] REPOSITORY |
| 1029 | 941 | ** |
| @@ -1064,11 +976,11 @@ | ||
| 1064 | 976 | smtp_server_send(&x, "250 ok\r\n"); |
| 1065 | 977 | }else |
| 1066 | 978 | if( strncmp(z, "MAIL FROM:<", 11)==0 ){ |
| 1067 | 979 | smtp_server_route_incoming(&x, 0); |
| 1068 | 980 | smtp_server_clear(&x, SMTPSRV_CLEAR_MSG); |
| 1069 | - x.zFrom = extractEmail(z+11); | |
| 981 | + x.zFrom = email_copy_addr(z+11); | |
| 1070 | 982 | if( x.zFrom==0 ){ |
| 1071 | 983 | smtp_server_send(&x, "500 unacceptable email address\r\n"); |
| 1072 | 984 | }else{ |
| 1073 | 985 | smtp_server_send(&x, "250 ok\r\n"); |
| 1074 | 986 | } |
| @@ -1077,11 +989,11 @@ | ||
| 1077 | 989 | char *zAddr; |
| 1078 | 990 | if( x.zFrom==0 ){ |
| 1079 | 991 | smtp_server_send(&x, "500 missing MAIL FROM\r\n"); |
| 1080 | 992 | continue; |
| 1081 | 993 | } |
| 1082 | - zAddr = extractEmail(z+9); | |
| 994 | + zAddr = email_copy_addr(z+9); | |
| 1083 | 995 | if( zAddr==0 ){ |
| 1084 | 996 | smtp_server_send(&x, "505 no such user\r\n"); |
| 1085 | 997 | continue; |
| 1086 | 998 | } |
| 1087 | 999 | smtp_append_to(&x, zAddr, 0); |
| 1088 | 1000 |
| --- src/smtp.c | |
| +++ src/smtp.c | |
| @@ -182,11 +182,17 @@ | |
| 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; |
| @@ -471,11 +477,11 @@ | |
| 471 | |
| 472 | /* |
| 473 | ** Send a single email message to the SMTP server. |
| 474 | ** |
| 475 | ** All email addresses (zFrom and azTo) must be plain "local@domain" |
| 476 | ** format with the surrounding "<..>". This routine will add the |
| 477 | ** necessary "<..>". |
| 478 | ** |
| 479 | ** The body of the email should be well-structured. This routine will |
| 480 | ** convert any \n line endings into \r\n and will escape lines containing |
| 481 | ** just ".", but will not make any other alterations or corrections to |
| @@ -595,53 +601,10 @@ | |
| 595 | |
| 596 | /***************************************************************************** |
| 597 | ** Server implementation |
| 598 | *****************************************************************************/ |
| 599 | |
| 600 | /* |
| 601 | ** Scan the header of the email message in pMsg looking for the |
| 602 | ** (first) occurrance of zField. Fill pValue with the content of |
| 603 | ** that field. |
| 604 | ** |
| 605 | ** This routine initializes pValue. Any prior content of pValue is |
| 606 | ** discarded (leaked). |
| 607 | ** |
| 608 | ** Return non-zero on success. Return 0 if no instance of the header |
| 609 | ** is found. |
| 610 | */ |
| 611 | int email_header_value(Blob *pMsg, const char *zField, Blob *pValue){ |
| 612 | int nField = (int)strlen(zField); |
| 613 | Blob line; |
| 614 | blob_rewind(pMsg); |
| 615 | blob_init(pValue,0,0); |
| 616 | while( blob_line(pMsg, &line) ){ |
| 617 | int n, i; |
| 618 | char *z; |
| 619 | blob_trim(&line); |
| 620 | n = blob_size(&line); |
| 621 | if( n==0 ) return 0; |
| 622 | if( n<nField+1 ) continue; |
| 623 | z = blob_buffer(&line); |
| 624 | if( sqlite3_strnicmp(z, zField, nField)==0 && z[nField]==':' ){ |
| 625 | for(i=nField+1; i<n && fossil_isspace(z[i]); i++){} |
| 626 | blob_init(pValue, z+i, n-i); |
| 627 | while( blob_line(pMsg, &line) ){ |
| 628 | blob_trim(&line); |
| 629 | n = blob_size(&line); |
| 630 | if( n==0 ) break; |
| 631 | z = blob_buffer(&line); |
| 632 | if( !fossil_isspace(z[0]) ) break; |
| 633 | for(i=1; i<n && fossil_isspace(z[i]); i++){} |
| 634 | blob_append(pValue, " ", 1); |
| 635 | blob_append(pValue, z+i, n-i); |
| 636 | } |
| 637 | return 1; |
| 638 | } |
| 639 | } |
| 640 | return 0; |
| 641 | } |
| 642 | |
| 643 | /* |
| 644 | ** Schema used by the email processing system. |
| 645 | */ |
| 646 | static const char zEmailSchema[] = |
| 647 | @ -- bulk storage is in a separate table. This table can store either |
| @@ -969,61 +932,10 @@ | |
| 969 | db_end_transaction(0); |
| 970 | } |
| 971 | smtp_server_clear(p, SMTPSRV_CLEAR_MSG); |
| 972 | } |
| 973 | |
| 974 | /* |
| 975 | ** Make a copy of the input string up to but not including the |
| 976 | ** first ">" character. |
| 977 | ** |
| 978 | ** Verify that the string really that is to be copied really is a |
| 979 | ** valid email address. If it is not, then return NULL. |
| 980 | ** |
| 981 | ** This routine is more restrictive than necessary. It does not |
| 982 | ** allow comments, IP address, quoted strings, or certain uncommon |
| 983 | ** characters. The only non-alphanumerics allowed in the local |
| 984 | ** part are "_", "+", "-" and "+". |
| 985 | */ |
| 986 | static char *extractEmail(const char *z){ |
| 987 | int i; |
| 988 | int nAt = 0; |
| 989 | int nDot = 0; |
| 990 | char c; |
| 991 | if( z[0]=='.' ) return 0; /* Local part cannot begin with "." */ |
| 992 | for(i=0; (c = z[i])!=0 && c!='>'; i++){ |
| 993 | if( fossil_isalnum(c) ){ |
| 994 | /* Alphanumerics are always ok */ |
| 995 | }else if( c=='@' ){ |
| 996 | if( nAt ) return 0; /* Only a single "@" allowed */ |
| 997 | if( i>64 ) return 0; /* Local part too big */ |
| 998 | nAt = 1; |
| 999 | nDot = 0; |
| 1000 | if( i==0 ) return 0; /* Disallow empty local part */ |
| 1001 | if( z[i-1]=='.' ) return 0; /* Last char of local cannot be "." */ |
| 1002 | if( z[i+1]=='.' || z[i+1]=='-' ){ |
| 1003 | return 0; /* Domain cannot begin with "." or "-" */ |
| 1004 | } |
| 1005 | }else if( c=='-' ){ |
| 1006 | if( z[i+1]=='>' ) return 0; /* Last character cannot be "-" */ |
| 1007 | }else if( c=='.' ){ |
| 1008 | if( z[i+1]=='.' ) return 0; /* Do not allow ".." */ |
| 1009 | if( z[i+1]=='>' ) return 0; /* Domain may not end with . */ |
| 1010 | nDot++; |
| 1011 | }else if( (c=='_' || c=='+') && nAt==0 ){ |
| 1012 | /* _ and + are ok in the local part */ |
| 1013 | }else{ |
| 1014 | return 0; /* Anything else is an error */ |
| 1015 | } |
| 1016 | } |
| 1017 | if( c!='>' ) return 0; /* Missing final ">" */ |
| 1018 | if( nAt==0 ) return 0; /* No "@" found anywhere */ |
| 1019 | if( nDot==0 ) return 0; /* No "." in the domain */ |
| 1020 | |
| 1021 | /* If we reach this point, the email address is valid */ |
| 1022 | return mprintf("%.*s", i, z); |
| 1023 | } |
| 1024 | |
| 1025 | /* |
| 1026 | ** COMMAND: smtpd |
| 1027 | ** |
| 1028 | ** Usage: %fossil smtpd [OPTIONS] REPOSITORY |
| 1029 | ** |
| @@ -1064,11 +976,11 @@ | |
| 1064 | smtp_server_send(&x, "250 ok\r\n"); |
| 1065 | }else |
| 1066 | if( strncmp(z, "MAIL FROM:<", 11)==0 ){ |
| 1067 | smtp_server_route_incoming(&x, 0); |
| 1068 | smtp_server_clear(&x, SMTPSRV_CLEAR_MSG); |
| 1069 | x.zFrom = extractEmail(z+11); |
| 1070 | if( x.zFrom==0 ){ |
| 1071 | smtp_server_send(&x, "500 unacceptable email address\r\n"); |
| 1072 | }else{ |
| 1073 | smtp_server_send(&x, "250 ok\r\n"); |
| 1074 | } |
| @@ -1077,11 +989,11 @@ | |
| 1077 | char *zAddr; |
| 1078 | if( x.zFrom==0 ){ |
| 1079 | smtp_server_send(&x, "500 missing MAIL FROM\r\n"); |
| 1080 | continue; |
| 1081 | } |
| 1082 | zAddr = extractEmail(z+9); |
| 1083 | if( zAddr==0 ){ |
| 1084 | smtp_server_send(&x, "505 no such user\r\n"); |
| 1085 | continue; |
| 1086 | } |
| 1087 | smtp_append_to(&x, zAddr, 0); |
| 1088 |
| --- src/smtp.c | |
| +++ src/smtp.c | |
| @@ -182,11 +182,17 @@ | |
| 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 | int i; |
| 188 | p->zHostname = fossil_strdup(zDest); |
| 189 | for(i=0; p->zHostname[i] && p->zHostname[i]!=':'; i++){} |
| 190 | if( p->zHostname[i]==':' ){ |
| 191 | p->zHostname[i] = 0; |
| 192 | url.port = atoi(&p->zHostname[i+1]); |
| 193 | } |
| 194 | }else{ |
| 195 | p->zHostname = smtp_mx_host(zDest); |
| 196 | } |
| 197 | if( p->zHostname==0 ){ |
| 198 | p->atEof = 1; |
| @@ -471,11 +477,11 @@ | |
| 477 | |
| 478 | /* |
| 479 | ** Send a single email message to the SMTP server. |
| 480 | ** |
| 481 | ** All email addresses (zFrom and azTo) must be plain "local@domain" |
| 482 | ** format without the surrounding "<..>". This routine will add the |
| 483 | ** necessary "<..>". |
| 484 | ** |
| 485 | ** The body of the email should be well-structured. This routine will |
| 486 | ** convert any \n line endings into \r\n and will escape lines containing |
| 487 | ** just ".", but will not make any other alterations or corrections to |
| @@ -595,53 +601,10 @@ | |
| 601 | |
| 602 | /***************************************************************************** |
| 603 | ** Server implementation |
| 604 | *****************************************************************************/ |
| 605 | |
| 606 | /* |
| 607 | ** Schema used by the email processing system. |
| 608 | */ |
| 609 | static const char zEmailSchema[] = |
| 610 | @ -- bulk storage is in a separate table. This table can store either |
| @@ -969,61 +932,10 @@ | |
| 932 | db_end_transaction(0); |
| 933 | } |
| 934 | smtp_server_clear(p, SMTPSRV_CLEAR_MSG); |
| 935 | } |
| 936 | |
| 937 | /* |
| 938 | ** COMMAND: smtpd |
| 939 | ** |
| 940 | ** Usage: %fossil smtpd [OPTIONS] REPOSITORY |
| 941 | ** |
| @@ -1064,11 +976,11 @@ | |
| 976 | smtp_server_send(&x, "250 ok\r\n"); |
| 977 | }else |
| 978 | if( strncmp(z, "MAIL FROM:<", 11)==0 ){ |
| 979 | smtp_server_route_incoming(&x, 0); |
| 980 | smtp_server_clear(&x, SMTPSRV_CLEAR_MSG); |
| 981 | x.zFrom = email_copy_addr(z+11); |
| 982 | if( x.zFrom==0 ){ |
| 983 | smtp_server_send(&x, "500 unacceptable email address\r\n"); |
| 984 | }else{ |
| 985 | smtp_server_send(&x, "250 ok\r\n"); |
| 986 | } |
| @@ -1077,11 +989,11 @@ | |
| 989 | char *zAddr; |
| 990 | if( x.zFrom==0 ){ |
| 991 | smtp_server_send(&x, "500 missing MAIL FROM\r\n"); |
| 992 | continue; |
| 993 | } |
| 994 | zAddr = email_copy_addr(z+9); |
| 995 | if( zAddr==0 ){ |
| 996 | smtp_server_send(&x, "505 no such user\r\n"); |
| 997 | continue; |
| 998 | } |
| 999 | smtp_append_to(&x, zAddr, 0); |
| 1000 |