| | @@ -646,1043 +646,5 @@ |
| 646 | 646 | fossil_fatal("ERROR: %s\n", p->zErr); |
| 647 | 647 | } |
| 648 | 648 | smtp_session_free(p); |
| 649 | 649 | blob_reset(&body); |
| 650 | 650 | } |
| 651 | | - |
| 652 | | -/***************************************************************************** |
| 653 | | -** Server implementation |
| 654 | | -*****************************************************************************/ |
| 655 | | - |
| 656 | | -/* |
| 657 | | -** Schema used by the email processing system. |
| 658 | | -*/ |
| 659 | | -static const char zEmailSchema[] = |
| 660 | | -@ -- bulk storage is in this table. This table can store either |
| 661 | | -@ -- the body of email messages or transcripts of an smtp session. |
| 662 | | -@ CREATE TABLE IF NOT EXISTS repository.emailblob( |
| 663 | | -@ emailid INTEGER PRIMARY KEY AUTOINCREMENT, -- numeric idea for the entry |
| 664 | | -@ enref INT, -- Number of references to this blob |
| 665 | | -@ ets INT, -- Corresponding transcript, or NULL |
| 666 | | -@ etime INT, -- insertion time, secs since 1970 |
| 667 | | -@ esz INT, -- uncompressed content size |
| 668 | | -@ etxt TEXT -- content of this entry |
| 669 | | -@ ); |
| 670 | | -@ |
| 671 | | -@ -- One row for each mailbox entry. All users emails are stored in |
| 672 | | -@ -- this same table. |
| 673 | | -@ CREATE TABLE IF NOT EXISTS repository.emailbox( |
| 674 | | -@ ebid INTEGER PRIMARY KEY, -- Unique id for each mailbox entry |
| 675 | | -@ euser TEXT, -- User who received this email |
| 676 | | -@ edate INT, -- Date received. Seconds since 1970 |
| 677 | | -@ efrom TEXT, -- Who is the email from |
| 678 | | -@ emsgid INT, -- Raw email text |
| 679 | | -@ estate INT, -- 0: Unread, 1: read, 2: trash 3: sent |
| 680 | | -@ esubject TEXT, -- Subject line for display |
| 681 | | -@ etags TEXT -- zero or more tags |
| 682 | | -@ ); |
| 683 | | -@ |
| 684 | | -@ -- Information on how to deliver incoming email. |
| 685 | | -@ CREATE TABLE IF NOT EXISTS repository.emailroute( |
| 686 | | -@ eaddr TEXT PRIMARY KEY, -- Email address |
| 687 | | -@ epolicy TEXT -- How to handle email sent to this address |
| 688 | | -@ ) WITHOUT ROWID; |
| 689 | | -@ |
| 690 | | -@ -- Outgoing email queue |
| 691 | | -@ CREATE TABLE IF NOT EXISTS repository.emailoutq( |
| 692 | | -@ edomain TEXT, -- Destination domain. (ex: "fossil-scm.org") |
| 693 | | -@ efrom TEXT, -- Sender email address (envelope "from") |
| 694 | | -@ eto TEXT, -- Recipient email address (envelope "to") |
| 695 | | -@ emsgid INT, -- Message body in the emailblob table |
| 696 | | -@ ectime INT, -- Time enqueued. Seconds since 1970 |
| 697 | | -@ emtime INT, -- Time of last send attempt. Sec since 1970 |
| 698 | | -@ ensend INT, -- Number of send attempts |
| 699 | | -@ ets INT -- Transcript of last failed attempt |
| 700 | | -@ ); |
| 701 | | -@ |
| 702 | | -@ -- Triggers to automatically keep the emailblob.enref field up to date |
| 703 | | -@ -- as entries in the emailblob, emailbox, and emailoutq tables are |
| 704 | | -@ -- deleted. |
| 705 | | -@ CREATE TRIGGER IF NOT EXISTS repository.emailblob_d1 |
| 706 | | -@ AFTER DELETE ON emailblob BEGIN |
| 707 | | -@ UPDATE emailblob SET enref=enref-1 WHERE emailid=old.ets; |
| 708 | | -@ END; |
| 709 | | -@ CREATE TRIGGER IF NOT EXISTS repository.emailbox_d1 |
| 710 | | -@ AFTER DELETE ON emailbox BEGIN |
| 711 | | -@ UPDATE emailblob SET enref=enref-1 WHERE emailid=old.emsgid; |
| 712 | | -@ END; |
| 713 | | -@ CREATE TRIGGER IF NOT EXISTS repository.emailoutq_d1 |
| 714 | | -@ AFTER DELETE ON emailoutq BEGIN |
| 715 | | -@ UPDATE emailblob SET enref=enref-1 WHERE emailid IN (old.ets,old.emsgid); |
| 716 | | -@ END; |
| 717 | | -@ |
| 718 | | -@ -- An index on the emailblob entries which are unreferenced. |
| 719 | | -@ CREATE INDEX IF NOT EXISTS repository.emailblob_nref ON emailblob(enref) |
| 720 | | -@ WHERE enref<=0; |
| 721 | | -; |
| 722 | | - |
| 723 | | -/* |
| 724 | | -** Code used to delete the email tables. |
| 725 | | -*/ |
| 726 | | -static const char zEmailDrop[] = |
| 727 | | -@ DROP TABLE IF EXISTS emailblob; |
| 728 | | -@ DROP TABLE IF EXISTS emailbox; |
| 729 | | -@ DROP TABLE IF EXISTS emailroute; |
| 730 | | -@ DROP TABLE IF EXISTS emailqueue; |
| 731 | | -; |
| 732 | | - |
| 733 | | -#if INTERFACE |
| 734 | | -/* |
| 735 | | -** Mailbox message states |
| 736 | | -*/ |
| 737 | | -#define MSG_UNREAD 0 |
| 738 | | -#define MSG_READ 1 |
| 739 | | -#define MSG_TRASH 2 |
| 740 | | -#endif /* INTERFACE */ |
| 741 | | - |
| 742 | | - |
| 743 | | -/* |
| 744 | | -** Populate the schema of a database. |
| 745 | | -** |
| 746 | | -** eForce==0 Fast |
| 747 | | -** eForce==1 Run CREATE TABLE statements every time |
| 748 | | -** eForce==2 DROP then rerun CREATE TABLE |
| 749 | | -*/ |
| 750 | | -void smtp_server_schema(int eForce){ |
| 751 | | - if( eForce==2 ){ |
| 752 | | - db_multi_exec(zEmailDrop/*works-like:""*/); |
| 753 | | - } |
| 754 | | - if( eForce==1 || !db_table_exists("repository","emailblob") ){ |
| 755 | | - db_multi_exec(zEmailSchema/*works-like:""*/); |
| 756 | | - } |
| 757 | | -} |
| 758 | | - |
| 759 | | -/* |
| 760 | | -** WEBPAGE: setup_smtp |
| 761 | | -** |
| 762 | | -** Administrative page for configuring and controlling inbound email and |
| 763 | | -** output email queuing. This page is available to administrators |
| 764 | | -** only via the /Admin/EmailServer menu. |
| 765 | | -*/ |
| 766 | | -void setup_smtp(void){ |
| 767 | | - Stmt q; |
| 768 | | - login_check_credentials(); |
| 769 | | - if( !g.perm.Setup ){ |
| 770 | | - login_needed(0); |
| 771 | | - return; |
| 772 | | - } |
| 773 | | - db_begin_transaction(); |
| 774 | | - style_set_current_feature("smtp"); |
| 775 | | - style_header("Email Server Setup"); |
| 776 | | - if( db_table_exists("repository","emailroute") ){ |
| 777 | | - style_submenu_element("emailblob table", "%R/emailblob"); |
| 778 | | - style_submenu_element("emailoutq table", "%R/emailoutq"); |
| 779 | | - db_prepare(&q, "SELECT eaddr, epolicy FROM emailroute ORDER BY 1"); |
| 780 | | - }else{ |
| 781 | | - db_prepare(&q, "SELECT null, null WHERE false"); |
| 782 | | - } |
| 783 | | - @ <h1>Email Routing Table</h1> |
| 784 | | - @ <table class="emailroutetab" cellpadding="5" border="1" cellspacing="0"> |
| 785 | | - @ <thead> |
| 786 | | - @ <tr> |
| 787 | | - @ <th>Email Address |
| 788 | | - @ <th>Routing |
| 789 | | - @ <th> |
| 790 | | - @ </tr> |
| 791 | | - @ </thead><tbody> |
| 792 | | - while( db_step(&q)==SQLITE_ROW ){ |
| 793 | | - const char *zEAddr = db_column_text(&q, 0); |
| 794 | | - const char *zEPolicy = db_column_text(&q, 1); |
| 795 | | - @ <tr> |
| 796 | | - @ <td valign="top">%h(zEAddr)</td> |
| 797 | | - @ <td valign="top"><span style="white-space:pre;">%h(zEPolicy)</span></td> |
| 798 | | - @ <td valign="top"><form method="POST" action="%R/setup_smtp_route"> |
| 799 | | - @ <input type="hidden" name="oaddr" value="%h(zEAddr)"> |
| 800 | | - @ <input type="submit" value="Edit"> |
| 801 | | - @ </form> |
| 802 | | - } |
| 803 | | - db_finalize(&q); |
| 804 | | - @ <tr> |
| 805 | | - @ <td colspan="3"> |
| 806 | | - @ <form method="POST" action="%R/setup_smtp_route"> |
| 807 | | - @ <input type="submit" value="New"> |
| 808 | | - @ ← Add a new email address |
| 809 | | - @ </form> |
| 810 | | - @ </table> |
| 811 | | - style_finish_page(); |
| 812 | | - db_end_transaction(0); |
| 813 | | -} |
| 814 | | - |
| 815 | | -/* |
| 816 | | -** WEBPAGE: setup_smtp_route |
| 817 | | -** |
| 818 | | -** Edit a single entry in the emailroute table. |
| 819 | | -** Query parameters: |
| 820 | | -** |
| 821 | | -** eaddr=ADDR ADDR is the email address as edited. |
| 822 | | -** |
| 823 | | -** oaddr=ADDR The original email address prior to editing. |
| 824 | | -** Omit to add a new address. |
| 825 | | -** |
| 826 | | -** epolicy=TXT The routing policy. |
| 827 | | -*/ |
| 828 | | -void setup_smtp_route(void){ |
| 829 | | - char *zEAddr = PT("eaddr"); /* new email address */ |
| 830 | | - char *zEPolicy = PT("epolicy"); /* new routing policy */ |
| 831 | | - char *zOAddr = PT("oaddr"); /* original email address */ |
| 832 | | - char *zErr = 0; |
| 833 | | - int iErr = 0; |
| 834 | | - login_check_credentials(); |
| 835 | | - if( !g.perm.Setup ){ |
| 836 | | - login_needed(0); |
| 837 | | - return; |
| 838 | | - } |
| 839 | | - style_set_current_feature("smtp"); |
| 840 | | - style_header("Email Route Editor"); |
| 841 | | - |
| 842 | | - if( P("edit") && cgi_csrf_safe(1) && zEAddr!=0 && zEPolicy!=0 ){ |
| 843 | | - smtp_server_schema(0); |
| 844 | | - if( (zOAddr==0 || fossil_strcmp(zEAddr,zOAddr)!=0) ){ |
| 845 | | - /* New or changed email address */ |
| 846 | | - if( db_exists("SELECT 1 FROM emailroute WHERE eaddr=%Q",zEAddr) ){ |
| 847 | | - iErr = 1; |
| 848 | | - zErr = mprintf("email address \"%h(zEAddr)\" already exists",zEAddr); |
| 849 | | - goto smtp_route_edit; |
| 850 | | - } |
| 851 | | - if( zEPolicy[0]==0 ){ |
| 852 | | - iErr = 2; |
| 853 | | - zErr = mprintf("empty route"); |
| 854 | | - goto smtp_route_edit; |
| 855 | | - } |
| 856 | | - } |
| 857 | | - /* If the email address has changed, or if the new policy is blank, |
| 858 | | - ** delete the old address and route information |
| 859 | | - */ |
| 860 | | - db_begin_transaction(); |
| 861 | | - if( (zOAddr && fossil_strcmp(zEAddr,zOAddr)!=0) || zEPolicy[0]==0 ){ |
| 862 | | - db_multi_exec("DELETE FROM emailroute WHERE eaddr=%Q", zOAddr); |
| 863 | | - } |
| 864 | | - if( zEPolicy[0] ){ |
| 865 | | - /* Insert the new address and route */ |
| 866 | | - db_multi_exec( |
| 867 | | - "REPLACE INTO emailroute(eaddr,epolicy) VALUES(%Q,%Q)", |
| 868 | | - zEAddr, zEPolicy |
| 869 | | - ); |
| 870 | | - } |
| 871 | | - db_end_transaction(0); |
| 872 | | - cgi_redirectf("%R/setup_smtp"); |
| 873 | | - } |
| 874 | | - if( P("cancel")!=0 ){ |
| 875 | | - cgi_redirectf("%R/setup_smtp"); |
| 876 | | - } |
| 877 | | - |
| 878 | | -smtp_route_edit: |
| 879 | | - if( zEAddr==0 ) zEAddr = zOAddr; |
| 880 | | - if( zEPolicy==0 && db_table_exists("repository","emailroute") ){ |
| 881 | | - zEPolicy = db_text(0, "SELECT epolicy FROM emailroute WHERE eaddr=%Q", |
| 882 | | - zEAddr); |
| 883 | | - } |
| 884 | | - if( zEPolicy==0 ) zEPolicy = ""; |
| 885 | | - @ <form method="POST" action="%R/setup_smtp_route"> |
| 886 | | - if( zOAddr ){ |
| 887 | | - @ <input type="hidden" name="oaddr" value="%h(zOAddr)"> |
| 888 | | - } |
| 889 | | - @ <table class="label-value"> |
| 890 | | - @ <tr> |
| 891 | | - @ <th>Email Address:</th> |
| 892 | | - @ <td><input type="text" size=30 name="eaddr" value="%h(zEAddr)"> |
| 893 | | - if( iErr==1 ){ |
| 894 | | - @ <td><span class="generalError">← %z(zErr)</span> |
| 895 | | - } |
| 896 | | - @ </tr> |
| 897 | | - if( zOAddr && fossil_strcmp(zOAddr,zEAddr)!=0 ){ |
| 898 | | - @ <tr> |
| 899 | | - @ <th>Original Address:</th> |
| 900 | | - @ <td>%h(zOAddr) |
| 901 | | - @ </tr> |
| 902 | | - } |
| 903 | | - @ <tr> |
| 904 | | - @ <th>Routing:</th> |
| 905 | | - @ <td><textarea name="epolicy" rows="3" cols="40">%h(zEPolicy)</textarea> |
| 906 | | - if( iErr==2 ){ |
| 907 | | - @ <td valign="top"><span class="generalError">← %z(zErr)</span> |
| 908 | | - } |
| 909 | | - @ </tr> |
| 910 | | - @ <tr> |
| 911 | | - @ <td> |
| 912 | | - @ <td><input type="submit" name="edit" value="Apply"> |
| 913 | | - @ <input type="submit" name="cancel" value="Cancel"> |
| 914 | | - @ </tr> |
| 915 | | - @ </table> |
| 916 | | - @ <hr> |
| 917 | | - @ <h1>Instructions</h1> |
| 918 | | - @ |
| 919 | | - @ <p>The "Routing" field consists of zero or more lines where each |
| 920 | | - @ line is an "action" followed by an "argument". Available actions: |
| 921 | | - @ <ul> |
| 922 | | - @ <li><p><b>forward</b> <i>email-address</i> |
| 923 | | - @ <p>Forward the message to <i>email-address</i>. |
| 924 | | - @ <li><p><b>mbox</b> <i>login-name</i> |
| 925 | | - @ <p>Store the message in the local mailbox for the user |
| 926 | | - @ with USER.LOGIN=<i>login-name</i>. |
| 927 | | - @ </ul> |
| 928 | | - @ |
| 929 | | - @ <p>To delete a route → erase all text from the "Routing" field then |
| 930 | | - @ press the "Apply" button. |
| 931 | | - style_finish_page(); |
| 932 | | -} |
| 933 | | - |
| 934 | | -#if LOCAL_INTERFACE |
| 935 | | -/* |
| 936 | | -** State information for the server |
| 937 | | -*/ |
| 938 | | -struct SmtpServer { |
| 939 | | - sqlite3_int64 idTranscript; /* Transcript ID number */ |
| 940 | | - sqlite3_int64 idMsg; /* Message ID number */ |
| 941 | | - const char *zIpAddr; /* Remote IP address */ |
| 942 | | - char *zEhlo; /* Client domain on the EHLO line */ |
| 943 | | - char *zFrom; /* MAIL FROM: argument */ |
| 944 | | - int nTo; /* Number of RCPT TO: lines seen */ |
| 945 | | - struct SmtpTo { |
| 946 | | - char *z; /* Address in each RCPT TO line */ |
| 947 | | - int okRemote; /* zTo can be in another domain */ |
| 948 | | - } *aTo; |
| 949 | | - u32 srvrFlags; /* Control flags */ |
| 950 | | - int nEts; /* Number of references to the transcript */ |
| 951 | | - int nRef; /* Number of references to idMsg */ |
| 952 | | - Blob msg; /* Content following DATA */ |
| 953 | | - Blob transcript; /* Session transcript */ |
| 954 | | -}; |
| 955 | | - |
| 956 | | -#define SMTPSRV_CLEAR_MSG 1 /* smtp_server_clear() last message only */ |
| 957 | | -#define SMTPSRV_CLEAR_ALL 2 /* smtp_server_clear() everything */ |
| 958 | | -#define SMTPSRV_LOG 0x001 /* Record a transcript of the interaction */ |
| 959 | | -#define SMTPSRV_STDERR 0x002 /* Transcription written to stderr */ |
| 960 | | -#define SMTPSRV_DRYRUN 0x004 /* Do not record anything in database */ |
| 961 | | - |
| 962 | | -#endif /* LOCAL_INTERFACE */ |
| 963 | | - |
| 964 | | -/* |
| 965 | | -** Clear the SmtpServer object. Deallocate resources. |
| 966 | | -** How much to clear depends on eHowMuch |
| 967 | | -*/ |
| 968 | | -static void smtp_server_clear(SmtpServer *p, int eHowMuch){ |
| 969 | | - int i; |
| 970 | | - if( eHowMuch>=SMTPSRV_CLEAR_MSG ){ |
| 971 | | - fossil_free(p->zFrom); |
| 972 | | - p->zFrom = 0; |
| 973 | | - for(i=0; i<p->nTo; i++) fossil_free(p->aTo[i].z); |
| 974 | | - fossil_free(p->aTo); |
| 975 | | - p->aTo = 0; |
| 976 | | - p->nTo = 0; |
| 977 | | - blob_reset(&p->msg); |
| 978 | | - p->idMsg = 0; |
| 979 | | - } |
| 980 | | - if( eHowMuch>=SMTPSRV_CLEAR_ALL ){ |
| 981 | | - blob_reset(&p->transcript); |
| 982 | | - p->idTranscript = 0; |
| 983 | | - fossil_free(p->zEhlo); |
| 984 | | - p->zEhlo = 0; |
| 985 | | - } |
| 986 | | -} |
| 987 | | - |
| 988 | | -/* |
| 989 | | -** Turn raw memory into an SmtpServer object. |
| 990 | | -*/ |
| 991 | | -static void smtp_server_init(SmtpServer *p){ |
| 992 | | - memset(p, 0, sizeof(*p)); |
| 993 | | - blob_init(&p->msg, 0, 0); |
| 994 | | - blob_init(&p->transcript, 0, 0); |
| 995 | | -} |
| 996 | | - |
| 997 | | -/* |
| 998 | | -** Append a new TO entry to the SmtpServer object. Do not do the |
| 999 | | -** append if the same entry is already on the list. |
| 1000 | | -** |
| 1001 | | -** The zAddr argument is obtained from fossil_malloc(). This |
| 1002 | | -** routine assumes ownership of the allocation. |
| 1003 | | -*/ |
| 1004 | | -static void smtp_append_to(SmtpServer *p, char *zAddr, int okRemote){ |
| 1005 | | - int i; |
| 1006 | | - for(i=0; zAddr[i]; i++){ zAddr[i] = fossil_tolower(zAddr[i]); } |
| 1007 | | - for(i=0; i<p->nTo; i++){ |
| 1008 | | - if( strcmp(zAddr, p->aTo[i].z)==0 ){ |
| 1009 | | - fossil_free(zAddr); |
| 1010 | | - if( p->aTo[i].okRemote==0 ) p->aTo[i].okRemote = okRemote; |
| 1011 | | - return; |
| 1012 | | - } |
| 1013 | | - } |
| 1014 | | - p->aTo = fossil_realloc(p->aTo, (p->nTo+1)*sizeof(p->aTo[0])); |
| 1015 | | - p->aTo[p->nTo].z = zAddr; |
| 1016 | | - p->aTo[p->nTo].okRemote = okRemote; |
| 1017 | | - p->nTo++; |
| 1018 | | -} |
| 1019 | | - |
| 1020 | | -/* |
| 1021 | | -** Send a single line of output from the server to the client. |
| 1022 | | -*/ |
| 1023 | | -static void smtp_server_send(SmtpServer *p, const char *zFormat, ...){ |
| 1024 | | - Blob b = empty_blob; |
| 1025 | | - va_list ap; |
| 1026 | | - char *z; |
| 1027 | | - int n; |
| 1028 | | - va_start(ap, zFormat); |
| 1029 | | - blob_vappendf(&b, zFormat, ap); |
| 1030 | | - va_end(ap); |
| 1031 | | - z = blob_buffer(&b); |
| 1032 | | - n = blob_size(&b); |
| 1033 | | - assert( n>=2 ); |
| 1034 | | - assert( z[n-1]=='\n' ); |
| 1035 | | - assert( z[n-2]=='\r' ); |
| 1036 | | - if( p->srvrFlags & SMTPSRV_LOG ){ |
| 1037 | | - blob_appendf(&p->transcript, "S: %.*s\n", n-2, z); |
| 1038 | | - } |
| 1039 | | - if( p->srvrFlags & SMTPSRV_STDERR ){ |
| 1040 | | - fprintf(stderr, "S: %.*s\n", n-2, z); |
| 1041 | | - } |
| 1042 | | - fwrite(z, n, 1, stdout); |
| 1043 | | - fflush(stdout); |
| 1044 | | - blob_reset(&b); |
| 1045 | | -} |
| 1046 | | - |
| 1047 | | -/* |
| 1048 | | -** Read a single line from the client. |
| 1049 | | -*/ |
| 1050 | | -static int smtp_server_gets(SmtpServer *p, char *aBuf, int nBuf){ |
| 1051 | | - int rc = fgets(aBuf, nBuf, stdin)!=0; |
| 1052 | | - if( rc ){ |
| 1053 | | - if( (p->srvrFlags & SMTPSRV_LOG)!=0 ){ |
| 1054 | | - blob_appendf(&p->transcript, "C: %s", aBuf); |
| 1055 | | - } |
| 1056 | | - if( (p->srvrFlags & SMTPSRV_STDERR)!=0 ){ |
| 1057 | | - fprintf(stderr, "C: %s", aBuf); |
| 1058 | | - } |
| 1059 | | - } |
| 1060 | | - return rc; |
| 1061 | | -} |
| 1062 | | - |
| 1063 | | -/* |
| 1064 | | -** RFC-5321 requires certain content be prepended to an email header |
| 1065 | | -** as that email is received. |
| 1066 | | -*/ |
| 1067 | | -static void smtp_server_prepend_header_lines(SmtpServer *p){ |
| 1068 | | - blob_appendf(&p->msg, "Received: from %s by Fossil-smtp\r\n", p->zIpAddr); |
| 1069 | | -} |
| 1070 | | - |
| 1071 | | -/* |
| 1072 | | -** Capture the incoming email data into the p->msg blob. Dequote |
| 1073 | | -** lines of "..\r\n" into just ".\r\n". |
| 1074 | | -*/ |
| 1075 | | -static void smtp_server_capture_data(SmtpServer *p, char *z, int n){ |
| 1076 | | - int nLine = 0; |
| 1077 | | - while( fgets(z, n, stdin) ){ |
| 1078 | | - if( strncmp(z, ".\r\n", 3)==0 || strncmp(z, ".\n",2)==0 ) break; |
| 1079 | | - nLine++; |
| 1080 | | - if( strncmp(z, "..\r\n", 4)==0 || strncmp(z, "..\n",3)==0 ){ |
| 1081 | | - memmove(z, z+1, 4); |
| 1082 | | - } |
| 1083 | | - blob_append(&p->msg, z, -1); |
| 1084 | | - } |
| 1085 | | - if( p->srvrFlags & SMTPSRV_LOG ){ |
| 1086 | | - blob_appendf(&p->transcript, "C: # %d lines, %d bytes of content\n", |
| 1087 | | - nLine, blob_size(&p->msg)); |
| 1088 | | - } |
| 1089 | | - if( p->srvrFlags & SMTPSRV_STDERR ){ |
| 1090 | | - fprintf(stderr, "C: # %d lines, %d bytes of content\n", |
| 1091 | | - nLine, blob_size(&p->msg)); |
| 1092 | | - } |
| 1093 | | -} |
| 1094 | | - |
| 1095 | | -/* |
| 1096 | | -** Send an email to a single email addess that is registered with |
| 1097 | | -** this system, according to the instructions in emailroute. If |
| 1098 | | -** zAddr is not in the emailroute table, then this routine is a |
| 1099 | | -** no-op. Or if zAddr has already been processed, then this |
| 1100 | | -** routine is a no-op. |
| 1101 | | -*/ |
| 1102 | | -static void smtp_server_send_one_user( |
| 1103 | | - SmtpServer *p, /* The current inbound email */ |
| 1104 | | - const char *zAddr, /* Who to forward this to */ |
| 1105 | | - int okRemote /* True if ok to foward to another domain */ |
| 1106 | | -){ |
| 1107 | | - char *zPolicy; |
| 1108 | | - Blob policy, line, token, tail; |
| 1109 | | - |
| 1110 | | - zPolicy = db_text(0, |
| 1111 | | - "SELECT epolicy FROM emailroute WHERE eaddr=%Q", zAddr); |
| 1112 | | - if( zPolicy==0 ){ |
| 1113 | | - if( okRemote ){ |
| 1114 | | - int i; |
| 1115 | | - for(i=0; zAddr[i] && zAddr[i]!='@'; i++){} |
| 1116 | | - if( zAddr[i]=='@' && zAddr[i+1]!=0 ){ |
| 1117 | | - db_multi_exec( |
| 1118 | | - "INSERT INTO emailoutq(edomain,efrom,eto,emsgid,ectime," |
| 1119 | | - "emtime,ensend)" |
| 1120 | | - "VALUES(%Q,%Q,%Q,%lld,now(),0,0)", |
| 1121 | | - zAddr+i+1, p->zFrom, zAddr, p->idMsg |
| 1122 | | - ); |
| 1123 | | - p->nRef++; |
| 1124 | | - } |
| 1125 | | - } |
| 1126 | | - return; |
| 1127 | | - } |
| 1128 | | - blob_init(&policy, zPolicy, -1); |
| 1129 | | - while( blob_line(&policy, &line) ){ |
| 1130 | | - blob_trim(&line); |
| 1131 | | - blob_token(&line, &token); |
| 1132 | | - blob_tail(&line, &tail); |
| 1133 | | - if( blob_size(&tail)==0 ) continue; |
| 1134 | | - if( blob_eq_str(&token, "mbox", 4) ){ |
| 1135 | | - Blob subj; |
| 1136 | | - email_header_value(&p->msg, "subject", &subj); |
| 1137 | | - db_multi_exec( |
| 1138 | | - "INSERT INTO emailbox(euser,edate,efrom,emsgid,estate,esubject)" |
| 1139 | | - " VALUES(%Q,now(),%Q,%lld,0,%Q)", |
| 1140 | | - blob_str(&tail), p->zFrom, p->idMsg, |
| 1141 | | - blob_str(&subj) |
| 1142 | | - ); |
| 1143 | | - blob_reset(&subj); |
| 1144 | | - p->nRef++; |
| 1145 | | - } |
| 1146 | | - if( blob_eq_str(&token, "forward", 7) ){ |
| 1147 | | - smtp_append_to(p, fossil_strdup(blob_str(&tail)), 1); |
| 1148 | | - } |
| 1149 | | - blob_reset(&tail); |
| 1150 | | - } |
| 1151 | | -} |
| 1152 | | - |
| 1153 | | -/* |
| 1154 | | -** The SmtpServer object contains a complete incoming email. |
| 1155 | | -** Add this email to the database. |
| 1156 | | -*/ |
| 1157 | | -static void smtp_server_route_incoming(SmtpServer *p, int bFinish){ |
| 1158 | | - Stmt s; |
| 1159 | | - int i; |
| 1160 | | - int nEtsStart = p->nEts; |
| 1161 | | - if( p->zFrom |
| 1162 | | - && p->nTo |
| 1163 | | - && blob_size(&p->msg) |
| 1164 | | - && (p->srvrFlags & SMTPSRV_DRYRUN)==0 |
| 1165 | | - ){ |
| 1166 | | - db_begin_write(); |
| 1167 | | - if( p->idTranscript==0 ) smtp_server_schema(0); |
| 1168 | | - p->nRef = 0; |
| 1169 | | - db_prepare(&s, |
| 1170 | | - "INSERT INTO emailblob(ets,etime,etxt,enref,esz)" |
| 1171 | | - " VALUES(:ets,now(),compress(:etxt),0,:esz)" |
| 1172 | | - ); |
| 1173 | | - p->nEts++; |
| 1174 | | - if( !bFinish && p->idTranscript==0 ){ |
| 1175 | | - db_bind_null(&s, ":ets"); |
| 1176 | | - db_bind_null(&s, ":etxt"); |
| 1177 | | - db_bind_null(&s, ":esz"); |
| 1178 | | - db_step(&s); |
| 1179 | | - db_reset(&s); |
| 1180 | | - p->idTranscript = db_last_insert_rowid(); |
| 1181 | | - }else if( bFinish ){ |
| 1182 | | - if( p->idTranscript ){ |
| 1183 | | - db_multi_exec( |
| 1184 | | - "UPDATE emailblob SET etxt=compress(%Q), enref=%d, esz=%d" |
| 1185 | | - " WHERE emailid=%lld", |
| 1186 | | - blob_str(&p->transcript), p->nEts, blob_size(&p->transcript), |
| 1187 | | - p->idTranscript); |
| 1188 | | - }else{ |
| 1189 | | - db_bind_null(&s, ":ets"); |
| 1190 | | - db_bind_str(&s, ":etxt", &p->transcript); |
| 1191 | | - db_bind_int(&s, ":esz", blob_size(&p->transcript)); |
| 1192 | | - db_step(&s); |
| 1193 | | - db_reset(&s); |
| 1194 | | - p->idTranscript = db_last_insert_rowid(); |
| 1195 | | - db_multi_exec( |
| 1196 | | - "UPDATE emailblob SET enref=%d WHERE emailid=%lld", |
| 1197 | | - p->nEts, p->idTranscript); |
| 1198 | | - } |
| 1199 | | - /* smtp_server_send(p, "221-Transcript id %lld nref %d\r\n", |
| 1200 | | - ** p->idTranscript, p->nEts); */ |
| 1201 | | - } |
| 1202 | | - db_bind_int64(&s, ":ets", p->idTranscript); |
| 1203 | | - db_bind_str(&s, ":etxt", &p->msg); |
| 1204 | | - db_bind_int(&s, ":esz", blob_size(&p->msg)); |
| 1205 | | - db_step(&s); |
| 1206 | | - db_finalize(&s); |
| 1207 | | - p->idMsg = db_last_insert_rowid(); |
| 1208 | | - |
| 1209 | | - /* make entries in emailbox and emailoutq */ |
| 1210 | | - for(i=0; i<p->nTo; i++){ |
| 1211 | | - int okRemote = p->aTo[i].okRemote; |
| 1212 | | - p->aTo[i].okRemote = 1; |
| 1213 | | - smtp_server_send_one_user(p, p->aTo[i].z, okRemote); |
| 1214 | | - } |
| 1215 | | - |
| 1216 | | - /* Fix up the emailblob.enref field of the email message body */ |
| 1217 | | - if( p->nRef ){ |
| 1218 | | - db_multi_exec( |
| 1219 | | - "UPDATE emailblob SET enref=%d WHERE emailid=%lld", |
| 1220 | | - p->nRef, p->idMsg |
| 1221 | | - ); |
| 1222 | | - }else{ |
| 1223 | | - db_multi_exec( |
| 1224 | | - "DELETE FROM emailblob WHERE emailid=%lld", p->idMsg |
| 1225 | | - ); |
| 1226 | | - p->nEts = nEtsStart; |
| 1227 | | - } |
| 1228 | | - |
| 1229 | | - /* Clean out legacy entries */ |
| 1230 | | - if( bFinish ){ |
| 1231 | | - db_multi_exec("DELETE FROM emailblob WHERE enref<=0"); |
| 1232 | | - } |
| 1233 | | - |
| 1234 | | - /* Finish the transaction after all changes are implemented */ |
| 1235 | | - db_commit_transaction(); |
| 1236 | | - } |
| 1237 | | - smtp_server_clear(p, SMTPSRV_CLEAR_MSG); |
| 1238 | | -} |
| 1239 | | - |
| 1240 | | -/* |
| 1241 | | -** Remove stale content from the emailblob table. |
| 1242 | | -*/ |
| 1243 | | -int smtp_cleanup(void){ |
| 1244 | | - int nAction = 0; |
| 1245 | | - if( db_table_exists("repository","emailblob") ){ |
| 1246 | | - db_begin_transaction(); |
| 1247 | | - db_multi_exec( |
| 1248 | | - "UPDATE emailblob SET ets=NULL WHERE enref<=0;" |
| 1249 | | - "DELETE FROM emailblob WHERE enref<=0;" |
| 1250 | | - ); |
| 1251 | | - nAction = db_changes(); |
| 1252 | | - db_end_transaction(0); |
| 1253 | | - } |
| 1254 | | - return nAction; |
| 1255 | | -} |
| 1256 | | - |
| 1257 | | -/* |
| 1258 | | -** COMMAND: test-emailblob-refcheck |
| 1259 | | -** |
| 1260 | | -** Usage: %fossil test-emailblob-refcheck [--repair] [--full] [--clean] |
| 1261 | | -** |
| 1262 | | -** Verify that the emailblob.enref field is correct. Report any errors. |
| 1263 | | -** Use the --repair command to fix up the enref field. The --full option |
| 1264 | | -** gives a full report showing the enref value on all entries in the |
| 1265 | | -** emailblob table. If the --clean flags is used together with --repair, |
| 1266 | | -** then emailblob table entires with enref==0 are removed. |
| 1267 | | -*/ |
| 1268 | | -void test_refcheck_emailblob(void){ |
| 1269 | | - int doRepair; |
| 1270 | | - int fullReport; |
| 1271 | | - int doClean; |
| 1272 | | - Blob sql; |
| 1273 | | - Stmt q; |
| 1274 | | - int nErr = 0; |
| 1275 | | - db_find_and_open_repository(0, 0); |
| 1276 | | - fullReport = find_option("full",0,0)!=0; |
| 1277 | | - doRepair = find_option("repair",0,0)!=0; |
| 1278 | | - doClean = find_option("clean",0,0)!=0; |
| 1279 | | - verify_all_options(); |
| 1280 | | - if( !db_table_exists("repository","emailblob") ){ |
| 1281 | | - fossil_print("emailblob table is not configured - nothing to check\n"); |
| 1282 | | - return; |
| 1283 | | - } |
| 1284 | | - db_multi_exec( |
| 1285 | | - "CREATE TEMP TABLE refcnt(id INTEGER PRIMARY KEY, n);" |
| 1286 | | - "INSERT INTO refcnt SELECT ets, count(*) FROM (" |
| 1287 | | - " SELECT ets FROM emailblob" |
| 1288 | | - " UNION ALL" |
| 1289 | | - " SELECT emsgid FROM emailbox" |
| 1290 | | - " UNION ALL" |
| 1291 | | - " SELECT emsgid FROM emailoutq" |
| 1292 | | - ") WHERE ets IS NOT NULL GROUP BY 1;" |
| 1293 | | - "INSERT OR IGNORE INTO refcnt(id,n) SELECT emailid, 0 FROM emailblob;" |
| 1294 | | - ); |
| 1295 | | - if( doRepair ){ |
| 1296 | | - db_multi_exec( |
| 1297 | | - "UPDATE emailblob SET enref=(SELECT n FROM refcnt WHERE id=emailid)" |
| 1298 | | - ); |
| 1299 | | - if( doClean ){ |
| 1300 | | - smtp_cleanup(); |
| 1301 | | - } |
| 1302 | | - } |
| 1303 | | - blob_init(&sql, 0, 0); |
| 1304 | | - blob_append_sql(&sql, |
| 1305 | | - "SELECT a.emailid, a.enref, b.n" |
| 1306 | | - " FROM emailblob AS a JOIN refcnt AS b ON a.emailid=b.id" |
| 1307 | | - ); |
| 1308 | | - if( !fullReport ){ |
| 1309 | | - blob_append_sql(&sql, " WHERE a.enref!=b.n"); |
| 1310 | | - } |
| 1311 | | - db_prepare_blob(&q, &sql); |
| 1312 | | - blob_reset(&sql); |
| 1313 | | - while( db_step(&q)==SQLITE_ROW ){ |
| 1314 | | - sqlite3_int64 id = db_column_int64(&q,0); |
| 1315 | | - int n1 = db_column_int(&q, 1); |
| 1316 | | - int n2 = db_column_int(&q, 2); |
| 1317 | | - if( n1!=n2 ) nErr++; |
| 1318 | | - fossil_print("%12lld %4d %4d%s\n", id, n1, n2, n1!=n2 ? " ERROR" : ""); |
| 1319 | | - } |
| 1320 | | - db_finalize(&q); |
| 1321 | | - if( nErr ){ |
| 1322 | | - fossil_print("Number of incorrect emailblob.enref values: %d\n",nErr); |
| 1323 | | - } |
| 1324 | | -} |
| 1325 | | - |
| 1326 | | - |
| 1327 | | -/* |
| 1328 | | -** COMMAND: smtpd* |
| 1329 | | -** |
| 1330 | | -** Usage: %fossil smtpd [OPTIONS] REPOSITORY |
| 1331 | | -** |
| 1332 | | -** Begin a SMTP conversation with a client using stdin/stdout. The |
| 1333 | | -** received email is stored in REPOSITORY. |
| 1334 | | -** |
| 1335 | | -** Options: |
| 1336 | | -** |
| 1337 | | -** --dryrun Do not record any emails in the database |
| 1338 | | -** |
| 1339 | | -** --trace Print a transcript of the conversation on stderr |
| 1340 | | -** for debugging and analysis |
| 1341 | | -** |
| 1342 | | -** --ipaddr ADDR The SMTP connection originates at ADDR. Or if ADDR |
| 1343 | | -** is the name of an environment variable, the address |
| 1344 | | -** is taken from that environment variable. |
| 1345 | | -*/ |
| 1346 | | -void smtp_server(void){ |
| 1347 | | - char *zDbName; |
| 1348 | | - const char *zDomain; |
| 1349 | | - SmtpServer x; |
| 1350 | | - char z[5000]; |
| 1351 | | - |
| 1352 | | - smtp_server_init(&x); |
| 1353 | | - zDomain = find_option("domain",0,1); |
| 1354 | | - if( zDomain==0 ) zDomain = ""; |
| 1355 | | - x.srvrFlags = SMTPSRV_LOG; |
| 1356 | | - if( find_option("trace",0,0)!=0 ) x.srvrFlags |= SMTPSRV_STDERR; |
| 1357 | | - if( find_option("dryrun",0,0)!=0 ) x.srvrFlags |= SMTPSRV_DRYRUN; |
| 1358 | | - x.zIpAddr = find_option("ipaddr",0,1); |
| 1359 | | - if( x.zIpAddr ){ |
| 1360 | | - const char *zNew = fossil_getenv(x.zIpAddr); |
| 1361 | | - if( zNew && zNew[0] ) x.zIpAddr = zNew; |
| 1362 | | - } |
| 1363 | | - if( x.zIpAddr==0 ){ |
| 1364 | | - x.zIpAddr = cgi_remote_ip(0); |
| 1365 | | - if( x.zIpAddr==0 ) x.zIpAddr = "?.?.?.?"; |
| 1366 | | - } |
| 1367 | | - verify_all_options(); |
| 1368 | | - if( g.argc!=3 ) usage("DBNAME"); |
| 1369 | | - zDbName = g.argv[2]; |
| 1370 | | - zDbName = enter_chroot_jail(zDbName, 0); |
| 1371 | | - db_open_repository(zDbName); |
| 1372 | | - add_content_sql_commands(g.db); |
| 1373 | | - smtp_server_send(&x, "220 %s ESMTP https://fossil-scm.org/ %s\r\n", |
| 1374 | | - zDomain, MANIFEST_VERSION); |
| 1375 | | - while( smtp_server_gets(&x, z, sizeof(z)) ){ |
| 1376 | | - if( strncmp(z, "EHLO", 4)==0 && fossil_isspace(z[4]) ){ |
| 1377 | | - smtp_server_send(&x, "250 ok\r\n"); |
| 1378 | | - }else |
| 1379 | | - if( strncmp(z, "HELO", 4)==0 && fossil_isspace(z[4]) ){ |
| 1380 | | - smtp_server_send(&x, "250 ok\r\n"); |
| 1381 | | - }else |
| 1382 | | - if( strncmp(z, "MAIL FROM:<", 11)==0 ){ |
| 1383 | | - smtp_server_route_incoming(&x, 0); |
| 1384 | | - smtp_server_clear(&x, SMTPSRV_CLEAR_MSG); |
| 1385 | | - x.zFrom = email_copy_addr(z+11,'>'); |
| 1386 | | - if( x.zFrom==0 ){ |
| 1387 | | - smtp_server_send(&x, "500 unacceptable email address\r\n"); |
| 1388 | | - }else{ |
| 1389 | | - smtp_server_send(&x, "250 ok\r\n"); |
| 1390 | | - } |
| 1391 | | - }else |
| 1392 | | - if( strncmp(z, "RCPT TO:<", 9)==0 ){ |
| 1393 | | - char *zAddr; |
| 1394 | | - if( x.zFrom==0 ){ |
| 1395 | | - smtp_server_send(&x, "500 missing MAIL FROM\r\n"); |
| 1396 | | - continue; |
| 1397 | | - } |
| 1398 | | - zAddr = email_copy_addr(z+9, '>'); |
| 1399 | | - if( zAddr==0 ){ |
| 1400 | | - smtp_server_send(&x, "505 no such user\r\n"); |
| 1401 | | - continue; |
| 1402 | | - } |
| 1403 | | - smtp_append_to(&x, zAddr, 0); |
| 1404 | | - if( x.nTo>=100 ){ |
| 1405 | | - smtp_server_send(&x, "452 too many recipients\r\n"); |
| 1406 | | - continue; |
| 1407 | | - } |
| 1408 | | - smtp_server_send(&x, "250 ok\r\n"); |
| 1409 | | - }else |
| 1410 | | - if( strncmp(z, "DATA", 4)==0 && fossil_isspace(z[4]) ){ |
| 1411 | | - if( x.zFrom==0 || x.nTo==0 ){ |
| 1412 | | - smtp_server_send(&x, "500 missing RCPT TO\r\n"); |
| 1413 | | - continue; |
| 1414 | | - } |
| 1415 | | - smtp_server_send(&x, "354 ready\r\n"); |
| 1416 | | - smtp_server_prepend_header_lines(&x); |
| 1417 | | - smtp_server_capture_data(&x, z, sizeof(z)); |
| 1418 | | - smtp_server_send(&x, "250 ok\r\n"); |
| 1419 | | - }else |
| 1420 | | - if( strncmp(z, "QUIT", 4)==0 && fossil_isspace(z[4]) ){ |
| 1421 | | - smtp_server_route_incoming(&x, 1); |
| 1422 | | - smtp_server_send(&x, "221 closing connection\r\n"); |
| 1423 | | - break; |
| 1424 | | - }else |
| 1425 | | - { |
| 1426 | | - smtp_server_send(&x, "500 unknown command\r\n"); |
| 1427 | | - } |
| 1428 | | - } |
| 1429 | | - smtp_server_clear(&x, SMTPSRV_CLEAR_ALL); |
| 1430 | | -} |
| 1431 | | - |
| 1432 | | -/* |
| 1433 | | -** Zero-terminate the argument. Return a pointer the start of the |
| 1434 | | -** next argument, or to NULL if there are no more arguments. |
| 1435 | | -*/ |
| 1436 | | -static char *pop3d_arg(char *z){ |
| 1437 | | - if( z[0]==0 || fossil_isspace(z[0]) ){ |
| 1438 | | - return 0; |
| 1439 | | - } |
| 1440 | | - z++; |
| 1441 | | - while( z[0] && !fossil_isspace(z[0]) ){ z++; } |
| 1442 | | - if( z[0]==0 ) return 0; |
| 1443 | | - z[0] = 0; |
| 1444 | | - z++; |
| 1445 | | - if( z[0]==0 || fossil_isspace(z[0]) ) return 0; |
| 1446 | | - return z; |
| 1447 | | -} |
| 1448 | | - |
| 1449 | | -/* |
| 1450 | | -** Write formatted output back to the pop3 client, and also to the |
| 1451 | | -** log file, if there is a log file. |
| 1452 | | -*/ |
| 1453 | | -static void pop3_print(FILE *pLog, const char *zFormat, ...){ |
| 1454 | | - va_list ap; |
| 1455 | | - char zLine[500]; |
| 1456 | | - va_start(ap, zFormat); |
| 1457 | | - sqlite3_vsnprintf(sizeof(zLine),zLine,zFormat,ap); |
| 1458 | | - va_end(ap); |
| 1459 | | - printf("%s\r\n", zLine); |
| 1460 | | - fflush(stdout); |
| 1461 | | - if( pLog ) fprintf(pLog, "S: %s\n", zLine); |
| 1462 | | -} |
| 1463 | | - |
| 1464 | | -/* |
| 1465 | | -** Try to log in for zUser and zPass. |
| 1466 | | -** |
| 1467 | | -** zUser can either point to a Fossil user name or to an email address |
| 1468 | | -** found in the user table's info field, in angle brackets. |
| 1469 | | -*/ |
| 1470 | | -static int pop3_login(const char *zUser, char *zPass){ |
| 1471 | | - return login_search_uid(&zUser, zPass) != 0; |
| 1472 | | -} |
| 1473 | | - |
| 1474 | | -/* |
| 1475 | | -** COMMAND: pop3d* |
| 1476 | | -** |
| 1477 | | -** Usage: %fossil pop3d [OPTIONS] REPOSITORY |
| 1478 | | -** |
| 1479 | | -** Begin a POP3 conversation with a client using stdin/stdout using |
| 1480 | | -** the mailboxes stored in REPOSITORY. |
| 1481 | | -** |
| 1482 | | -** If launched as root, the process first enters a chroot jail using |
| 1483 | | -** the directory of REPOSITORY as root, then drops all privileges and |
| 1484 | | -** assumes the user and group of REPOSITORY before reading any content |
| 1485 | | -** off of the wire. |
| 1486 | | -** |
| 1487 | | -** --logdir DIR Each pop3d session creates a new logfile |
| 1488 | | -** in the directory DIR and records a transcript |
| 1489 | | -** of the session there. The logfile is opened |
| 1490 | | -** before entering the chroot jail. |
| 1491 | | -*/ |
| 1492 | | -void pop3d_command(void){ |
| 1493 | | - char *zDbName; |
| 1494 | | - char *zA1, *zA2, *zCmd, *z; |
| 1495 | | - int inAuth = 1; |
| 1496 | | - int i; |
| 1497 | | - FILE *pLog = 0; |
| 1498 | | - const char *zDir; |
| 1499 | | - Stmt q; |
| 1500 | | - char zIn[1000]; |
| 1501 | | - char zUser[100]; |
| 1502 | | - zDir = find_option("logdir",0,1); |
| 1503 | | - if( zDir ){ |
| 1504 | | - char *zFile = file_time_tempname(zDir, ".txt"); |
| 1505 | | - pLog = fossil_fopen(zFile, "w"); |
| 1506 | | - fossil_free(zFile); |
| 1507 | | - } |
| 1508 | | - verify_all_options(); |
| 1509 | | - if( g.argc!=3 ) usage("DBNAME"); |
| 1510 | | - zDbName = g.argv[2]; |
| 1511 | | - zDbName = enter_chroot_jail(zDbName, 0); |
| 1512 | | - db_open_repository(zDbName); |
| 1513 | | - add_content_sql_commands(g.db); |
| 1514 | | - pop3_print(pLog, "+OK POP3 server ready"); |
| 1515 | | - while( fgets(zIn, sizeof(zIn), stdin) ){ |
| 1516 | | - if( pLog ) fprintf(pLog, "C: %s", zIn); |
| 1517 | | - zCmd = zIn; |
| 1518 | | - zA1 = pop3d_arg(zCmd); |
| 1519 | | - zA2 = zA1 ? pop3d_arg(zA1) : 0; |
| 1520 | | - for(i=0; zCmd[i]; i++){ zCmd[i] = fossil_tolower(zCmd[i]); } |
| 1521 | | - if( strcmp(zCmd,"quit")==0 ){ |
| 1522 | | - if( !inAuth ){ |
| 1523 | | - db_multi_exec( |
| 1524 | | - "UPDATE emailbox SET estate=2" |
| 1525 | | - " WHERE estate<2 AND ebid IN (SELECT ebid FROM pop3 WHERE isDel);" |
| 1526 | | - ); |
| 1527 | | - } |
| 1528 | | - pop3_print(pLog, "+OK"); |
| 1529 | | - break; |
| 1530 | | - } |
| 1531 | | - if( strcmp(zCmd,"capa")==0 ){ |
| 1532 | | - static const char *const azCap[] = { |
| 1533 | | - "TOP", "USER", "UIDL", |
| 1534 | | - }; |
| 1535 | | - int i; |
| 1536 | | - pop3_print(pLog, "+OK"); |
| 1537 | | - for(i=0; i<sizeof(azCap)/sizeof(azCap[0]); i++){ |
| 1538 | | - pop3_print(pLog, "%s", azCap[i]); |
| 1539 | | - } |
| 1540 | | - pop3_print(pLog, "."); |
| 1541 | | - continue; |
| 1542 | | - } |
| 1543 | | - if( inAuth ){ |
| 1544 | | - if( strcmp(zCmd,"user")==0 ){ |
| 1545 | | - if( zA1==0 || zA2!=0 ) goto cmd_error; |
| 1546 | | - sqlite3_snprintf(sizeof(zUser),zUser,"%s",zA1); |
| 1547 | | - goto cmd_ok; |
| 1548 | | - } |
| 1549 | | - if( strcmp(zCmd,"pass")==0 ){ |
| 1550 | | - if( zA1==0 || zA2!=0 ) goto cmd_error; |
| 1551 | | - if( pop3_login(zUser,zA1)==0 ){ |
| 1552 | | - goto cmd_error; |
| 1553 | | - }else{ |
| 1554 | | - inAuth = 0; |
| 1555 | | - db_multi_exec( |
| 1556 | | - "CREATE TEMP TABLE pop3(" |
| 1557 | | - " id INTEGER PRIMARY KEY," |
| 1558 | | - " emailid INT," |
| 1559 | | - " ebid INT," |
| 1560 | | - " isDel INT," |
| 1561 | | - " esz INT" |
| 1562 | | - ");" |
| 1563 | | - "INSERT INTO pop3(id,emailid,ebid,isDel,esz)" |
| 1564 | | - " SELECT NULL, emailid, ebid, 0, esz FROM emailblob, emailbox" |
| 1565 | | - " WHERE emailid=emsgid AND euser=%Q AND estate<=1" |
| 1566 | | - " ORDER BY edate;", |
| 1567 | | - zUser |
| 1568 | | - ); |
| 1569 | | - goto cmd_ok; |
| 1570 | | - } |
| 1571 | | - } |
| 1572 | | - /* Fossil cannot process APOP since the users clear-text password is |
| 1573 | | - ** unknown. */ |
| 1574 | | - goto cmd_error; |
| 1575 | | - }else{ |
| 1576 | | - if( strcmp(zCmd,"stat")==0 ){ |
| 1577 | | - db_prepare(&q, "SELECT count(*), sum(esz) FROM pop3 WHERE NOT isDel"); |
| 1578 | | - if( db_step(&q)==SQLITE_ROW ){ |
| 1579 | | - pop3_print(pLog, "+OK %d %d", |
| 1580 | | - db_column_int(&q,0), db_column_int(&q,1)); |
| 1581 | | - }else{ |
| 1582 | | - pop3_print(pLog,"-ERR"); |
| 1583 | | - } |
| 1584 | | - db_finalize(&q); |
| 1585 | | - continue; |
| 1586 | | - } |
| 1587 | | - if( strcmp(zCmd,"list")==0 ){ |
| 1588 | | - if( zA1 ){ |
| 1589 | | - db_prepare(&q, "SELECT id, esz FROM pop3" |
| 1590 | | - " WHERE id=%d AND NOT isDel", atoi(zA1)); |
| 1591 | | - if( db_step(&q)==SQLITE_ROW ){ |
| 1592 | | - pop3_print(pLog, "+OK %d %d", |
| 1593 | | - db_column_int(&q,0), db_column_int(&q,1)); |
| 1594 | | - }else{ |
| 1595 | | - pop3_print(pLog, "-ERR"); |
| 1596 | | - } |
| 1597 | | - }else{ |
| 1598 | | - pop3_print(pLog, "+OK"); |
| 1599 | | - db_prepare(&q, "SELECT id, esz FROM pop3 WHERE NOT isDel"); |
| 1600 | | - while( db_step(&q)==SQLITE_ROW ){ |
| 1601 | | - pop3_print(pLog, "%d %d", |
| 1602 | | - db_column_int(&q,0), db_column_int(&q,1)); |
| 1603 | | - } |
| 1604 | | - pop3_print(pLog, "."); |
| 1605 | | - } |
| 1606 | | - db_finalize(&q); |
| 1607 | | - continue; |
| 1608 | | - } |
| 1609 | | - if( strcmp(zCmd,"retr")==0 || strcmp(zCmd,"top")==0 ){ |
| 1610 | | - Blob all, line; |
| 1611 | | - int nLine = 0; |
| 1612 | | - int iLimit; |
| 1613 | | - int hdrPending = 1; |
| 1614 | | - if( zA1==0 ) goto cmd_error; |
| 1615 | | - iLimit = zA2 ? atoi(zA2) : 2147483647; |
| 1616 | | - if( iLimit<0 ) goto cmd_error; |
| 1617 | | - z = db_text(0, "SELECT decompress(emailblob.etxt) " |
| 1618 | | - " FROM emailblob, pop3" |
| 1619 | | - " WHERE emailblob.emailid=pop3.emailid" |
| 1620 | | - " AND pop3.id=%d AND NOT pop3.isDel", |
| 1621 | | - atoi(zA1)); |
| 1622 | | - if( z==0 ) goto cmd_error; |
| 1623 | | - pop3_print(pLog, "+OK"); |
| 1624 | | - blob_init(&all, z, -1); |
| 1625 | | - while( (hdrPending || iLimit>0) && blob_line(&all, &line) ){ |
| 1626 | | - char c = blob_buffer(&line)[0]; |
| 1627 | | - if( c=='.' ){ |
| 1628 | | - fputc('.', stdout); |
| 1629 | | - }else if( c=='\r' || c=='\n' ){ |
| 1630 | | - hdrPending = 0; |
| 1631 | | - } |
| 1632 | | - fwrite(blob_buffer(&line), 1, blob_size(&line), stdout); |
| 1633 | | - nLine++; |
| 1634 | | - if( !hdrPending ) iLimit--; |
| 1635 | | - } |
| 1636 | | - if( pLog ) fprintf(pLog, "S: # %d lines of content\n", nLine); |
| 1637 | | - pop3_print(pLog, "."); |
| 1638 | | - fossil_free(z); |
| 1639 | | - blob_reset(&all); |
| 1640 | | - blob_reset(&line); |
| 1641 | | - fflush(stdout); |
| 1642 | | - continue; |
| 1643 | | - } |
| 1644 | | - if( strcmp(zCmd,"dele")==0 ){ |
| 1645 | | - if( zA1==0 ) goto cmd_error; |
| 1646 | | - db_multi_exec("UPDATE pop3 SET isDel=1 WHERE id=%d",atoi(zA1)); |
| 1647 | | - goto cmd_ok; |
| 1648 | | - } |
| 1649 | | - if( strcmp(zCmd,"rset")==0 ){ |
| 1650 | | - db_multi_exec("UPDATE pop3 SET isDel=0"); |
| 1651 | | - goto cmd_ok; |
| 1652 | | - } |
| 1653 | | - if( strcmp(zCmd,"uidl")==0 ){ |
| 1654 | | - if( zA1 ){ |
| 1655 | | - db_prepare(&q, "SELECT id, emailid FROM pop3" |
| 1656 | | - " WHERE id=%d AND NOT isDel", atoi(zA1)); |
| 1657 | | - if( db_step(&q)==SQLITE_ROW ){ |
| 1658 | | - pop3_print(pLog, "+OK %d %d", |
| 1659 | | - db_column_int(&q,0), db_column_int(&q,1)); |
| 1660 | | - }else{ |
| 1661 | | - pop3_print(pLog,"-ERR"); |
| 1662 | | - } |
| 1663 | | - }else{ |
| 1664 | | - pop3_print(pLog, "+OK"); |
| 1665 | | - db_prepare(&q, "SELECT id, emailid FROM pop3 WHERE NOT isDel"); |
| 1666 | | - while( db_step(&q)==SQLITE_ROW ){ |
| 1667 | | - pop3_print(pLog, "%d %d", |
| 1668 | | - db_column_int(&q,0), db_column_int(&q,1)); |
| 1669 | | - } |
| 1670 | | - pop3_print(pLog, "."); |
| 1671 | | - } |
| 1672 | | - db_finalize(&q); |
| 1673 | | - continue; |
| 1674 | | - } |
| 1675 | | - if( strcmp(zCmd,"noop")==0 ){ |
| 1676 | | - goto cmd_ok; |
| 1677 | | - } |
| 1678 | | - /* Else, fall through into cmd_error */ |
| 1679 | | - } |
| 1680 | | - cmd_error: |
| 1681 | | - pop3_print(pLog, "-ERR"); |
| 1682 | | - continue; |
| 1683 | | - cmd_ok: |
| 1684 | | - pop3_print(pLog, "+OK"); |
| 1685 | | - continue; |
| 1686 | | - } |
| 1687 | | - if( pLog ) fclose(pLog); |
| 1688 | | -} |
| 1689 | 651 | |