3

By default, exim allows any user to send out an email with any "From:" address they wish, which can lead to users spoofing each other. How do I restrict which addresses each system user can send from? For example the user james should be able to send from james@localhost, but also me@my-domain.com and james@other-domain.com.

I want all system users to be able to send email from their SYSTEM_USER@HOSTNAME, but if they attempt to send from another address I want to look up that address in a text file to see if it is allowed.

I currently have a list, but it is for what addresses a system user will receive from. But it could still work. Given the example me@my-domain.com the data is stored in the file /etc/exim4/virtual/my-domain.com where the content looks like:

me : james@localhost

If we could reuse the existing files that would be great. But any suggestions would be helpful. Thank you in advance.

1 Answers1

0

Exim4 already does some standard checks on the envelope sender, From: and Sender: of the locally submitted messages (cf. trusted users). Since you want to extend this validation you should:

  • For messages submitted through sendmail (i.e. exim4 -bm), add to the main configuration section:

    # allow untrusted users to set an envelope sender
    untrusted_set_sender = *
    # don't delete the Sender: header
    local_sender_retain = true
    # don't add Sender: header
    local_from_check = false
    
  • For messages submitted through SMTP you need to add the control = submission/sender_retain modifier. E.g. Debian has in the MAIL acl a rule:

    accept
      authenticated = *
      control = submission/sender_retain
      control = dkim_disable_verify
    

To configure your check you need to add a couple of ACL rules at different stages of mail processing. Since these rules need to be applied to different ways of message submission you can create a new named ACL (I assume that all your /etc/exim4/virtual/<domain> files return username@localhost as value):

# Requires system user id in $acl_arg1
acl_check_sender:

  # Users with default domain
  accept
    condition = ${if eq{$sender_address}{$acl_arg1@$qualify_domain}}
    condition = ${if eq{${address:$h_from:}}{$acl_arg1@$qualify_domain}}
    condition = ${if match_address{${address:$h_sender:}}{:$acl_arg1@$qualify_domain}}

  deny
    ! condition = ${if and{\
        {exists{/etc/exim4/virtual/${domain:${address:$h_from:}}}}\
        {eq {${acl_arg1}@localhost}\
          {${lookup {${local_part:${address:$h_from:}}}\
            lsearch{/etc/exim4/virtual/${domain:${address:$h_from:}}}\
          }}\
        }\
      }}
    message = Spoofed From: header.

  # Most messages don't have a Sender: header, but if it is not empty, check it.
  deny
    ! condition = ${if or{\
        {eq {$h_sender:}{}}\
        {and{\
          {exists{/etc/exim4/virtual/${domain:${address:$h_sender:}}}}\
          {eq {${acl_arg1}@localhost}\
              {${lookup {${local_part:${address:$h_sender:}}}\
                lsearch{/etc/exim4/virtual/${domain:${address:$h_sender:}}}\
              }}\ 
          }\
        }}\
      }}
    message = Spoofed Sender: header.

  deny
    ! condition = ${if and{\
        {exists{/etc/exim4/virtual/$sender_address_domain}}\
        {eq {${acl_arg1}@localhost}\
          {${lookup {$sender_address_local_part}\
            lsearch{/etc/exim4/virtual/$sender_address_domain}\
          }}\
        }\
      }}
    message = Spoofed envelope sender.

  accept

For the syntax of string expansion (quite full of braces), check chapter 11 of Exim's documentation. You can use exim4 -bem <message> <expansion_string> to test them (the envelope sender is passed in the message like the mbox format).

You can apply it to different situations:

  • For messages submitted through exim4 -bm, add to the acl_not_smtp ACL the following rule:

    deny
      ! acl = acl_check_sender ${sender_ident}
    
  • For messages submitted by authenticated users through SMTP add to the acl_smtp_data ACL something like:

    deny
      authenticated = *
      ! acl = acl_check_sender ${authenticated_id}
    
  • Some programs submit messages using a non-authenticated SMTP connection to localhost (or exim -bs). For them you can add to the acl_smtp_data ACL:

    deny
      hosts = : localhost
      ! acl = acl_check_sender ${sender_ident}
    

    and install some identd on the server.

  • 1
    Thanks so much! That got me on the right track. You switched your arguments round though in the statements, but I was able to sort that. Also, if no file existed for a domain it would temporarily fail rather than reject, but I fixed that too. I'll post my solution separately soon. – James Swift Apr 07 '20 at 02:02
  • Things I can't fix: If you set untrusted_set_sender = *, then for some obscure reason exim no longer populates $authenticated_id, it's just bank. So unless you can tell me another way to find the local username for a non-smtp email then it is impossible to filter locally sent mail. For the moment I don't use acl_check_sender for non-smtp mail and have just disabled being able to set custom From: addresses for local mail (if you want to do that, use smtp). – James Swift Apr 07 '20 at 02:06
  • And last, but not least. The setup works fine, except if blocks all mail unless they have a file in the the /virtual/ folder. My intention was to use the virtual folder only for aliases in addition to a user's @localhost account. I've been trying to think how to write a condition to not match if the mail is from localhost, but I don't know the variable to use to get the local system name. Any ideas? – James Swift Apr 07 '20 at 02:09
  • Thanks, I edited the messed up rules and added a rule for the default domain (which is in the variable $qualify_domain). – Piotr P. Karwasz Apr 07 '20 at 05:40
  • You should probably post your answer as an answer instead of modifying the question. – Piotr P. Karwasz Apr 07 '20 at 07:17
  • Thanks, I was hoping you would so I could mark your answer correct. I see you've added the file exists check as a separate condition. That was my first attempt, but Exim ANDs conditions, don't we need this lack of file existance to be ORed with the other rule? I haven't actually tried your code yet. Can you confirm for me if you want the conditions in your new deny case to be ORed or ANDed? Thank you – James Swift Apr 07 '20 at 10:04
  • I just tried out your new rule, it works perfectly! Thank you! So there's just the question of how to find $authorized_id for trusted local users (currently it is blank). – James Swift Apr 07 '20 at 10:33
  • Apparently for trusted users the $authenticated_id variable is unset, but $sender_ident isn't. I corrected the example in the answer. – Piotr P. Karwasz Apr 07 '20 at 16:08
  • When I enable the not_smtp rule, I suddenly become unable to send any mail from the mail command (whether changing the from or not). Looking in the log, for each attempt there is an entry which lists the from F=example@example.com, but then the next line has F=<> with the message Error while reading message with no usable sender address. Any ideas? – James Swift Apr 09 '20 at 12:39
  • Try adding log_message to the deny rules to debug it. – Piotr P. Karwasz Apr 10 '20 at 19:51