{ pkgs, lib, config, vaculib, ... }: let inherit (builtins) isString isList length head all isInt isAttrs isFloat isBool ; inherit (lib) concatStrings concatStringsSep splitString match replaceStrings reverseList elemAt mapAttrsToList ; mapConcat = f: xs: concatStrings (map f xs); mapConcatSep = sep: f: xs: concatStringsSep sep (map f xs); mapConcatLines = f: xs: mapConcatSep "\n" f xs; isListWhere = xs: f: (isList xs) && (all f xs); stringOrList = val: (isString val) || ((isListWhere val isString) && (length val) > 0); listify = val: if isList val then val else [ val ]; is_match = regex: s: (match regex s) != null; is_not_match = regex: s: !(is_match regex s); only_printable_ascii = s: is_match "[ -~\r\n]*" s; has_vars = s: lib.hasInfix ("$" + "{") s; sieve_raw_escape_string = s: if !only_printable_ascii s then builtins.trace s throw "s failed only_printable_ascii check" else replaceStrings [ ''"'' ''\'' "\n" "\r" ] [ ''\"'' ''\\'' ''\n'' ''\r'' ] s; sieve_encode_string = { allow_vars, for_debug_comment, with_quotes, }: s: assert isString s; assert allow_vars || for_debug_comment || (!has_vars s); let a = sieve_raw_escape_string s; b = if for_debug_comment then replaceStrings [ ''*/'' ] [ ''*\/'' ] a else a; res = if with_quotes then ''"${b}"'' else b; in res; sieve_quote_string = sieve_encode_string { allow_vars = false; for_debug_comment = false; with_quotes = true; }; sieve_quote_string_with_interp = sieve_encode_string { allow_vars = true; for_debug_comment = false; with_quotes = true; }; is_valid_long_ident = is_match "[a-z_][a-z0-9_]*"; is_number_ident = is_match "[0-9]*"; is_valid_ident = s: (is_valid_long_ident s) || (is_number_ident s); interp = ident: assert isString ident; assert is_valid_ident ident; "$" + "{${ident}}"; dest = "envelope_to"; dest_domain = "envelope_to_domain"; set_envelope = '' #set_envelope START if header :index 1 :matches "X-Envelope-To" "*" { set ${sieve_quote_string dest} "''${1}"; } if header :index 1 :matches "X-Envelope-To" "*@*" { set ${sieve_quote_string dest_domain} "''${2}"; } #set_envelope END ''; envelope_is = key: assert stringOrList key; ''string :is "${interp dest}" ${sieve_encode key}''; envelope_matches = key: assert stringOrList key; ''string :matches "${interp dest}" ${sieve_encode key}''; envelope_domain_is = key: ''string :is "${interp dest_domain}" ${sieve_quote_string key}''; sieve_encode_list = xs: assert isListWhere xs isString; "[ ${mapConcatSep ", " sieve_encode xs} ]"; sieve_encode = val: if isString val then sieve_quote_string val else if isList val then sieve_encode_list val else assert "dunno what to do with this"; null; sieve_debug_list = xs: "[ ${mapConcat (s: (sieve_debug s) + " ") xs}]"; sieve_debug_attrs = attrs: let toPairStr = name: val: "${sieve_debug name} = ${sieve_debug val}; "; pairStrs = mapAttrsToList toPairStr attrs; pairsStr = concatStrings pairStrs; in "{ ${pairsStr}}"; sieve_debug = val: if isString val then sieve_encode_string { allow_vars = true; for_debug_comment = true; with_quotes = true; } val else if (isInt val) || (isFloat val) then toString val else if (isBool val) then (if val then "true" else "false") else if isNull val then "null" else if isList val then sieve_debug_list val else if isAttrs val then sieve_debug_attrs val else assert "dunno what to do with this"; null; is_flagish = flag_name: let # escape_all = map lib.escapeRegex; # all from https://datatracker.ietf.org/doc/html/rfc9051#name-formal-syntax # resp-specials = escape_all [ "]" ]; # DQUOTE = ''"''; # quoted-specials = escape_all [ DQUOTE "\\" ]; # list-wildcards = escape_all [ "%" "*" ]; # CTL = something; # 0x00 thru 0x1F, and 0x7F # SP = escape_all [ " " ]; # atom-specials = (escape_all [ "(" ")" "{" ]) ++ [ SP CTL list-wildcards quoted-specials resp-specials ]; # " " 0x20 !allowed # "!" 0x21 ok # "\"" 0x22 !allowed # "#" 0x23 ok # "$" 0x24 ok # "%" 0x25 !allowed # "&" 0x26 ok # "'" 0x27 ok # "(" 0x28 !allowed # ")" 0x29 !allowed # "*" 0x2a !allowed # "+" 0x2b ok # ... # "Z" 0x5a ok # "[" 0x5b !allowed # "\\" 0x5c !allowed # "]" 0x5d ok # "^" 0x5e ok # ... # "z" 0x7a ok # "{" 0x7b !allowed # "|" 0x7c ok # "}" 0x7d ok # "~" 0x7e ok # DEL 0x7f !allowed # ATOM-CHAR = something; # "any CHAR except atom-specials" ATOM-CHAR = ''[]!#$&'+-Z^-z|}~]''; atom = "${ATOM-CHAR}+"; flag-keyword = ''\$MDNSent|\$Forwarded|\$Junk|\$NotJunk|\$Phishing|(${atom})''; flag-extension = ''\\(${atom})''; flag = ''\\Answered|\\Flagged|\\Deleted|\\Seen|\\Draft|(${flag-keyword})|(${flag-extension})''; in (isString flag_name) && ((builtins.match flag flag_name) != null); known_flags = rec { seen = ''\Seen''; read = seen; }; pure_flags_impl = flags: conditions: assert isListWhere flags isString; assert isListWhere conditions isString; assert (length flags) > 0; assert (length conditions) > 0; let argAttrs = { inherit flags conditions; }; firstFlag = head flags; combined_condition = if (length conditions) == 1 then head conditions else (allof conditions); in '' # pure_flags ${sieve_debug argAttrs}; removeflag ${sieve_quote_string firstFlag}; if ${combined_condition} { ${record_action "pure_flags ${concatStringsSep " " flags}"} ${concatStringsSep "\n" (map (flag: ''addflag ${sieve_quote_string flag};'') flags)} } # pure_flags end ''; pure_flags = flags: conditions: assert stringOrList flags; assert stringOrList conditions; pure_flags_impl (listify flags) (listify conditions); exists_impl = headers: assert isListWhere headers isString; if headers == [ ] then "/* exists START: called with empty array */ false /* exists END */" else "/* exists START */ exists ${sieve_encode_list headers} /* exists END */"; exists = headers: assert stringOrList headers; exists_impl (listify headers); header_generic = match_kind: header_s: match_es: assert stringOrList header_s; assert stringOrList match_es; ''/* header_generic START */ header ${match_kind} ${sieve_encode header_s} ${sieve_encode match_es} /* header_generic END */''; header_matches = header_generic ":matches"; header_is = header_generic ":is"; subject_generic = match_kind: match_es: header_generic match_kind "Subject" match_es; subject_matches = subject_generic ":matches"; subject_is = subject_generic ":is"; environment_generic = match_kind: environment_name_s: match_es: assert stringOrList environment_name_s; assert stringOrList match_es; "environment ${match_kind} ${sieve_encode environment_name_s} ${sieve_encode match_es}"; environment_matches = environment_generic ":matches"; environment_is = environment_generic ":is"; from_is = addr_list: assert stringOrList addr_list; ''/* from_is START */ address :is :all "From" ${sieve_encode addr_list} /* from_is END */''; from_matches = addr_list: assert stringOrList addr_list; ''/* from_is START */ address :matches :all "From" ${sieve_encode addr_list} /* from_is END */''; var_is = var_name: rhs: assert isString var_name; assert stringOrList rhs; ''string :is "''${${var_name}}" ${sieve_encode rhs}''; var_is_true = var_name: var_is var_name "1"; var_is_false = var_name: not (var_is_true var_name); has_flag = flag_name: assert isString flag_name; assert is_flagish flag_name; # no spaces allowed in flag names ''hasflag :is ${sieve_encode flag_name}''; set_with_interp = var_name: new_val: assert isString var_name; assert is_valid_ident var_name; assert isString new_val; "set ${sieve_encode var_name} ${sieve_quote_string_with_interp new_val};"; set = var_name: new_val: assert isString var_name; assert is_valid_ident var_name; assert isString new_val; "set ${sieve_encode var_name} ${sieve_encode new_val};"; set_bool_var = var_name: bool_val: assert isBool bool_val; set var_name (if bool_val then "1" else "0"); over_test_list = name: test_list: assert isListWhere test_list isString; '' ${name}( ${concatStringsSep ",\n" test_list} ) ''; anyof = over_test_list "anyof"; allof = over_test_list "allof"; not = test: "not ${test}"; record_action = action_desc: assert isString action_desc; ''addheader "X-Vacu-Action" ${sieve_encode action_desc};''; fileinto = folder: assert isString folder; '' ${record_action "fileinto ${folder}"} fileinto :create ${sieve_encode folder}; ''; ihave = extension_name_s: assert stringOrList extension_name_s; "ihave ${sieve_encode extension_name_s}"; # email_filters = map (e: '' # elsif ${envelope_is e} { # item of email_filters # ${record_action "email_filters fileinto ${mk_email_folder_name e}"} # fileinto :create ${sieve_quote_string (mk_email_folder_name e)}; # } # '') email_folders; # domain_filters = map (d: '' # elsif ${envelope_domain_is d} { # item of domain_filters # ${record_action "domain_filters fileinto ${mk_domain_folder_name d}"} # fileinto :create ${sieve_quote_string (mk_domain_folder_name d)}; # } # '') domain_folders; set_from = { condition, var, default ? "-", warn_if_unset ? false, }@args: '' # set_from ${sieve_debug args} if ${condition} { ${set_with_interp var (interp "1")} } else { ${lib.optionalString warn_if_unset ( maybe_debug "info: Could not set ${var} from condition ${condition}, setting to default(${default})" )} ${set var default} } # set_from END ''; set_var_from_environment = item: var: '' # set_var_from_environment '' + set_from { condition = ''environment :matches ${sieve_quote_string item} "*"''; inherit var; }; maybe_debug = msg: '' if ${ihave "vnd.dovecot.debug"} { debug_log ${sieve_quote_string_with_interp msg}; } ''; # trimmed down from https://pages.ebay.com/securitycenter/security_researchers_eligible_domains.html ebay_domains = vaculib.listOfLines { } '' ebay.com ebay.co.uk ebay.com.au ebay.de ebay.ca ebay.fr ebay.it ebay.es ebay.at ebay.ch ebay.com.hk ebay.com.sg ebay.com.my ebay.in ebay.ph ebay.ie ebay.pl ebay.be ebay.nl ebay.cn ebay.com.tw ebay.co.jp ebaythailand.co.th ''; sieve_text = '' require [ "fileinto", "mailbox", "imap4flags", "editheader", "environment", "variables", "date", "index", "ihave" ]; if ${ allof [ (ihave "imapsieve") (environment_matches "imap.user" "*") (environment_matches "location" "MS") (environment_matches "phase" "post") ] } { ${set_bool_var "in_imap" true} } else { ${set_bool_var "in_imap" false} } if ${var_is_true "in_imap"} { if ${ not (allof [ (environment_is "imap.cause" [ "APPEND" "COPY" "" ]) (environment_is "imap.mailbox" [ "MagicRefilter" "" ]) ]) } { ${maybe_debug "NOT doing anything cuz imap.cause and/or imap.mailbox isn't right"} stop; } } ${set_envelope} ${set_var_from_environment "location" "env_location"} ${set_var_from_environment "phase" "env_phase"} ${set_var_from_environment "imap.user" "env_imap_user"} ${set_var_from_environment "imap.email" "env_imap_email"} ${set_var_from_environment "imap.cause" "env_imap_cause"} ${set_var_from_environment "imap.mailbox" "env_imap_mailbox"} ${set_var_from_environment "imap.changedflags" "env_imap_changedflags"} ${set_from { condition = ''currentdate :matches "iso8601" "*"''; var = "datetime"; }} ${set_with_interp "sieved_message" ''at ''${datetime} by ${config.vacu.versionId} loc ''${env_location} phase ''${env_phase} user ''${env_imap_user} email ''${env_imap_email} cause ''${env_imap_cause} mailbox ''${env_imap_mailbox} changedflags ''${env_imap_changedflags} envelope ''${dest}''} ${maybe_debug ''X-Vacu-Sieved: ''${sieved_message}''} if ${ihave "envelope"} { if envelope :all :matches "to" "*@*" { ${set_with_interp "userfor" (interp "1")} } else { error "i dunno what to do, theres no envelope"; } } elsif ${var_is_true "in_imap"} { ${set_with_interp "userfor" (interp "env_imap_user")} } else { error "dont have envelope or imapsieve, dunno what to do"; } if ${var_is "userfor" "shelvacu"} { addheader "X-Vacu-Sieved" "''${sieved_message}"; removeflag "not-spamish"; removeflag "orders"; removeflag "banking"; removeflag "banking-statements"; removeflag "banking-transactions"; removeflag "A"; removeflag "B"; removeflag "B.subscriptions"; removeflag "C"; removeflag "D"; ${pure_flags [ "wells-fargo" "banking" ] (envelope_is "wf-primary@shelvacu.com")} ${pure_flags [ "wells-fargo-transactions" "banking-transactions" "B" ] [ (has_flag "wells-fargo") (subject_matches [ "You just got paid!" "Wells Fargo card purchase exceeded preset amount" "You made a payment" "You made a credit card purchase of *" "Your card wasn't present for a purchase" "Account update" "You've earned cash back from My Wells Fargo Deals" "Confirmation of your Wells Fargo Rewards redemption" "You sent money with Zelle(R)" ]) ] } ${pure_flags [ "wells-fargo-statements" "banking-statements" "C" ] [ (has_flag "wells-fargo") (subject_matches [ "Your statement for credit card account *" "Your statement for account *" ]) ] } ${pure_flags [ "wells-fargo-action-required" "A" ] [ # wf is actually careful about saying action required (has_flag "wells-fargo") (subject_matches "Action Required: *") ] } ${pure_flags [ "wells-fargo-misc" "A" ] [ (has_flag "wells-fargo") (not (has_flag "wells-fargo-transactions")) (not (has_flag "wells-fargo-statements")) (not (has_flag "wells-fargo-action-required")) ] } ${pure_flags [ "chase" "banking" ] (envelope_is "chase@shelvacu.com")} ${pure_flags [ "chase-transactions" "banking-transactions" "B" ] [ (has_flag "chase") (subject_matches [ "Your * payment is scheduled" "You made a * transaction with *" "Your * transaction with *" "Chase security alert: You signed in with a new device" ]) ] } ${pure_flags [ "chase-statements" "banking-statements" "C" ] [ (has_flag "chase") (subject_matches [ "Your credit card statement is available" ]) ] } ${pure_flags [ "chase-spam" "D" ] [ (has_flag "chase") (anyof [ (header_is "From" "Chase Credit Journey ") (subject_is [ "Review your recent activity" "Good news: You may qualify for a credit line increase!" "Your Chase card is available to use with Paze - Activate now!" ]) ]) ] } ${pure_flags [ "experian" ] (envelope_is "fbyjemby@shelvacu.com")} ${pure_flags [ "experian-spam" "D" ] [ (has_flag "experian") (subject_matches [ "*, your FICO* Score has been updated" "Your monthly account statement is here, *" ]) ] } ${pure_flags [ "paypal" "banking" ] [ # can't go purely on envelope, because paypal loves to give my email to every merchant I interact with (envelope_is "paypal@shelvacu.com") (from_matches [ "*@paypal.com" "*@*.paypal.com" ]) ] } ${pure_flags [ "paypal-transactions" "banking-transactions" "B" ] [ (has_flag "paypal") (subject_matches [ "Receipt for your payment to *" "*: $* USD" "*: $* CAD" "*: kr * SEK" "You authorized a payment to *" "You sent an automatic payment to *" "Review your new automatic payment setup for *" "You have a refund from *" ]) ] } ${pure_flags [ "paypal-statements" "banking-statements" "C" ] [ (has_flag "paypal") (subject_matches [ "*, your * account statement is available." ]) ] } ${pure_flags [ "usps-id" ] (envelope_is "usps-id@shelvacu.com")} ${pure_flags [ "usps-expected-delivery" "C" ] [ (has_flag "usps-id") (subject_matches "USPS* Expected Delivery *") ] } ${pure_flags [ "amazon-ignore" "C" ] [ (envelope_is "amznbsns@shelvacu.com") (subject_matches [ "Your Amazon.com order has shipped*" "Your Amazon.com order of * has shipped!" ]) ] } ${pure_flags [ "bandcamp-ignore" "C" ] [ (envelope_is "bandcamp@shelvacu.com") (subject_matches [ "* just announced a listening party on Bandcamp" "New items from *" "Starting in *" "New from *" ]) ] } ${pure_flags [ "bandcamp-not-ignore" "B.subscriptions" ] [ (envelope_is "bandcamp@shelvacu.com") ''not hasflag "bandcamp-ignore"'' ] } ${pure_flags [ "ika-ignore" "D" ] (envelope_is "ika@dis8.net")} ${pure_flags [ "ally-statement" "C" ] [ (envelope_is "ally@shelvacu.com") (subject_is "Your latest statement is ready to view.") ] } ${pure_flags "bloomberg" (envelope_is "bloomberg@shelvacu.com")} ${pure_flags [ "money-stuff" "not-spamish" ] [ (envelope_is "bloomberg@shelvacu.com") ''header :matches "From" "\"Matt Levine\" *"'' ] } ${pure_flags [ "money-stuff-podcast" "D" known_flags.read ] [ (has_flag "money-stuff") (subject_matches "Money Stuff: The Podcast:*") ] } ${pure_flags [ "money-stuff-not-podcast" "B.subscriptions" ] [ (has_flag "money-stuff") (not (has_flag "money-stuff-podcast")) ] } ${pure_flags [ "git" "not-spamish" "B" ] (exists [ "X-GitHub-Reason" "X-GitLab-Project" ])} ${pure_flags [ "git-uninsane" "git" "not-spamish" "B" ] (envelope_is "git-uninsane@shelvacu.com")} ${pure_flags [ "github" "git" "not-spamish" "B" ] (header_matches "List-Id" "*<*.github.com>")} ${pure_flags [ "mailing-list-by-envelope" "not-spamish" "B" ] ( envelope_matches "*-ml@shelvacu.com" )} ${pure_flags [ "discourse" "not-spamish" "B" ] (exists "X-Discourse-Post-Id")} ${pure_flags [ "agora" "not-spamish" ] (envelope_is "agora@shelvacu.com")} ${pure_flags [ "postgres-list" "not-spamish" ] ( header_matches "List-Id" "<*.lists.postgresql.org>" )} ${pure_flags [ "secureaccesswa" "not-spamish" "A" ] (from_is "help@secureaccess.wa.gov")} ${pure_flags [ "letsencrypt-mailing-list" "not-spamish" "B" ] ( envelope_is "lets-encrypt-mailing-list@shelvacu.com" )} ${pure_flags [ "jmp-news" "not-spamish" "B" ] (header_matches "List-Id" "*")} ${pure_flags [ "tf2wiki" "not-spamish" "B" ] [ (envelope_is "tf2wiki@shelvacu.com") (from_is "noreply@wiki.teamfortress.com") ] } ${pure_flags "gmail-fwd" (envelope_is "gmailfwd-fc2e10bec8b2@shelvacu.com")} ${pure_flags [ "ebay" "orders" ] (envelope_is "ebay@shelvacu.com")} ${pure_flags [ "ebay-delivered" "B" ] [ (has_flag "ebay") (subject_matches [ "*ORDER DELIVERED: *" ]) ] } ${pure_flags [ "ebay-message" "B" ] [ (has_flag "ebay") (from_matches (map (domain: "*@members.${domain}") ebay_domains)) ] } ${pure_flags [ "ebay-offer" "B" ] [ (has_flag "ebay") (subject_matches [ "You have an offer from the seller, *" "You saw it at *, but the seller is now offering *" ]) ] } ${pure_flags [ "ebay-order-update" "C" ] [ (has_flag "ebay") (subject_matches [ "Out for delivery: *" "*DELIVERY UPDATE: *" "*Order update: *" "EARLY DELIVERY UPDATE: *" "Important information regarding your Global Shipping Program transaction *" # ebay: "important information! your order is being shipped." why did you say this was ""important""??? "Your package is now with *" "*Order confirmed: *" "Your order is confirmed" "Your order is in!" "*An update on your order" ]) ] } ${pure_flags [ "ebay-bid-ongoing-notification" "C" ] [ (has_flag "ebay") (subject_matches [ "Michael, your bid for * is winning" "* just got a new bid." ]) ] } ${pure_flags [ "ebay-feedback" "D" ] [ (has_flag "ebay") (subject_matches "Please provide feedback for your eBay items") ] } ${pure_flags [ "royal-mail" "orders" ] (from_is "no-reply@royalmail.com")} ${pure_flags [ "royal-mail-delivered" "B" ] [ (has_flag "royal-mail") (subject_matches "Your Royal Mail parcel has been delivered") ] } ${pure_flags [ "royal-mail-on-the-way" "D" ] [ (has_flag "royal-mail") (subject_matches "Your Royal Mail parcel is on its way") ] } ${pure_flags [ "aliexpress" "orders" ] (from_is [ "transaction@notice.aliexpress.com" "aliexpress@notice.aliexpress.com" ])} ${pure_flags [ "aliexpress-delivered" "B" ] [ (has_flag "aliexpress") (from_is "transaction@notice.aliexpress.com") (subject_matches "Order * has been signed for") ] } ${pure_flags [ "aliexpress" "orders" "C" ] [ (has_flag "aliexpress") (not (has_flag "aliexpress-delivered")) ] } ${pure_flags [ "brandcrowd" "D" ] (envelope_is "brandcrowd@shelvacu.com")} ${pure_flags [ "cpapsupplies" "D" ] (envelope_is "cpapsupplies@shelvacu.com")} ${pure_flags [ "genshin" "D" ] (envelope_is "genshin@shelvacu.com")} ${pure_flags [ "jork" "B" ] (envelope_is "jork@shelvacu.com")} ${pure_flags [ "patreon" "not-spamish" ] (envelope_is "patreon@shelvacu.com")} ${pure_flags [ "patreon-post" "B.subscriptions" ] [ (has_flag "patreon") (header_is "X-Mailgun-Tag" "template_newsletterpostcontrol") ] } ${pure_flags [ "patreon-free-member-digest" "D" ] [ (has_flag "patreon") (header_is "X-Mailgun-Tag" "template_freememberdigest") ] } ${pure_flags [ "patreon-other" "B" ] [ (has_flag "patreon") (not (has_flag "patreon-post")) (not (has_flag "patreon-free-member-digest")) ] } ${pure_flags [ "rsb" "B" ] (from_is "support@rapidseedbox.com")} ${pure_flags [ "fresh-avocado-dis8" "D" ] (envelope_is "fresh.avocado@dis8.net")} ${pure_flags [ "discord" "A" ] (envelope_matches "discord@*")} ${pure_flags [ "za-sa" "D" ] (from_matches [ "*@*.sa.com" "*@*.za.com" ])} ${pure_flags [ "localdomain" "D" ] (from_matches [ "*@*.local" "*@*.localdomain" ])} ${pure_flags [ "helium" "D" ] (envelope_is "creepyface@dis8.net")} ${pure_flags [ "sharkmood" "C" ] (envelope_is "sharkmood@dis8.net")} ${pure_flags [ "im-not-district-158" "D" ] (envelope_is [ "khamar.anderson@dis8.net" "pbooth@dis8.net" "sgaylor@dis8.net" ])} ${pure_flags [ "next-level-burger" "D" ] (header_matches "From" "*Next Level Burger*")} ${pure_flags [ "lyft" "D" ] (envelope_is "lyft@shelvacu.com")} ${pure_flags [ "coursera" "D" ] (from_matches "*.*.coursera.org")} ${pure_flags [ "taskrabbit" "D" ] (envelope_is "taskrabbit@shelvacu.com")} ${pure_flags [ "subscribestar_code" "A" ] (allof [ (envelope_is "subscribestar@shelvacu.com") (subject_is "Your authentication code") ])} ${pure_flags "itch-io" (from_is "postmaster@itch.io")} ${pure_flags [ "itch-io-update" "B.subscriptions" ] [ (has_flag "itch-io") (subject_matches "[itch.io] * update *") ] } ${pure_flags [ "lowering-the-bar" "B.subscriptions" ] [ (envelope_is "ltb@shelvacu.com") ] } ${pure_flags [ "hotels-com" "D" ] (from_matches [ "hotels.com" "*.hotels.com" ])} ${pure_flags [ "spamish-by-headers" "C" ] [ (anyof [ (header_is "Precedence" "bulk") (exists "List-Unsubscribe") (exists "List-Unsubscribe-Post") ]) (not (has_flag "not-spamish")) ] } if hasflag "agora" { ${fileinto "M.agora"} } elsif hasflag "postgres-list" { ${fileinto "M.postgres"} } elsif hasflag "D" { ${fileinto "D"} } elsif hasflag "C" { ${fileinto "C"} } elsif hasflag "A" { ${fileinto "A"} } elsif hasflag "B.subscriptions" { ${fileinto "B.subscriptions"} } else { ${fileinto "B"} } } # disable any sieve scripts that might want to run after this one stop; ''; pigeonhole_pkg = pkgs.dovecot_pigeonhole; in { imports = [ # Allow running a sieve filter when a message gets moved to another folder in imap # see https://doc.dovecot.org/2.3/configuration_manual/sieve/plugins/imapsieve/ { services.dovecot2 = { sieve.plugins = [ "sieve_imapsieve" ]; mailPlugins.perProtocol.imap.enable = [ "imap_sieve" ]; }; } ]; options.vacu.checkSieve = lib.mkOption { readOnly = true; default = pkgs.writeScriptBin "check-liam-sieve" '' set -xev ${lib.escapeShellArgs [ (lib.getExe' pigeonhole_pkg "sieve-test") "-c" config.services.dovecot2.configFile "-C" # force compilation "-D" # enable sieve debugging "-f" "some-rando@example.com" "-a" "shelvacu@liam.dis8.net" config.services.dovecot2.sieve.scripts.before "/dev/null" ]} ''; defaultText = "check-liam-sieve package"; }; options.vacu.liam-sieve-script = lib.mkOption { readOnly = true; default = pkgs.writeText "mainsieve" sieve_text; defaultText = "mainsieve text package"; }; config = { vacu.packages = [ pigeonhole_pkg ]; services.dovecot2.sieve = { extensions = [ "fileinto" "mailbox" "editheader" "vnd.dovecot.debug" ]; scripts.before = config.vacu.liam-sieve-script; }; services.dovecot2.imapsieve.mailbox = [ { name = "*"; causes = [ "APPEND" "COPY" "FLAG" ]; before = config.vacu.liam-sieve-script; } ]; # services.dovecot2.mailboxes."magic-refilter".auto = "create"; }; }