Using multiple "myhostname" in postfix

Does postfix allow to dynamically change "myhostname" ? I rtfmed a lot but did not found anything qualitative. My installation is a postfix multiple domain, multiple ip (tweaked in master.cf) and multiple dkim sig but the myhostname directive in master.cf isn't included.

That kind of stuff:

192.168.1.12:smtp inet n - n - - smtpd -o myhostname=mail.mail2.com -o smtpd_banner=mail.mail2.com -o smtp_helo_name=mail.mail2.com

Anyway, banner and helo are OK, but myhostname is still the one included in main.cf


Solution 1:

Postfix is highly configurable. so much so that rtfming seems only slightly more advantageous than perusing the source (only joking).

There are in fact numerous ways of dynamically overriding the static configuration. At a very minimum, some understanding of the Postfix workflow is required. this is in the overview.

It is also useful to understand:

  • different Postfix daemons reference different portions of the main.cf static configuration.
  • most built-in Postfix services are customisable;
  • ad-hoc custom services (milters etc) can be plugged into the workflow;
  • if all else fails, multiple Postfix instances can be deployed; each referring to separate main.cf and master.cf static configurations


Per the OP, wrt conditionally sending mail to some external recipients:

Methods of interest are those that are conditional upon either:

  • the smptd listening port
  • the sender domain name

Apparently specifically in this regard, and for reasons that will soon hopefully become clear, Wietse Venema made some *key changes in Postfix 2.7 to do with content filtering &so to summarise:

pre Postfix 2.7 :

where unspecified, the default nexthop destination is $myhostname (ie: localhost).


Postfix 2.7:

where unspecified, the default nexthop destination is the recipient domain.

transport conditional upon sender domain is supported out-of-the-box via the use of sender_dependent_default_transport_maps


This distinction is of issue because there are still folk stuck on pre 2.7 Postfix installs.. ie: for some Red Hat users relying upon the official rpms, then for example, users still on centOS 5x => Postfix 2.3 & for centOS 6x => Postfix 2.6 (but i might be wrong, dyor etc)


Postfix 2.7:

Things got easier! below are a couple of methods.

The first method involves setting up a couple of smtpd daemons listening on different non-standard ports and uses custom transports conditional upon which smtpd port we submit our outbound email to:

  • port 10026 => custom1_smtp
  • port 10027 => custom2_smtp

This is made possible by dynamically defining a custom filter service & overriding the content_filter directive in the smtpd service. the filter is a dummy. we're not really going to be writing & deploying our own custom filter service. instead we just hijack a postfix smtp service instance for the sole purpose of gaining access to and dynamically overriding the myhostname directive before the email is finally pushed out to the wild.

This can only happen in Postfix 2.7 (and maybe subsequent versions above) because the specified transports' default nexthop destination is defined to be the recipient domain. In versions below Postfix 2.7, it is defined as $myhostname and so the email loops back into Postfix, causing it to bork.


The second method is by the book and uses sender_dependent_default_transport_maps to conditionally route our outbound email to a couple more custom-defined transports; and depends upon the sender's domain name:

  • parrots.tld => custom3_smtp:
  • penguins.tld => custom4_smtp:

The sender_dependent_default_transport_maps act like sender_dependent_relayhost_maps but instead of routing outgoing mail from a specified sender domain to a specified destination domain, sender_dependent_default_transport_maps are used to internally route the outgoing mail from a specified sender (domain) to a specified (custom-defined) transport defined in master.cf.


We add the following directive to /etc/postfix/main.cf:

sender_dependent_default_transport_maps = hash:/etc/postfix/sender_transports


Then create the mapping file /etc/postfix/sender_transports:

@parrots.tld    custom3_smtp:
@penguins.tld   custom4_smtp:

&build the database using postmap:

postmap hash:/etc/postfix/sender_transports


Then we define all our custom transports in /etc/postfix/master.cf:

custom4_smtp      unix  -       -       n       -       -        smtp
             -o myhostname=mailer.external.penguins.tld
             -o smtp_bind_address=m.n.o.p
             -o smtp_helo_name=penguins.tld


custom3_smtp      unix  -       -       n       -       -        smtp
             -o myhostname=mailer.external.parrots.tld
             -o smtp_bind_address=i.j.k.l
             -o smtp_helo_name=parrots.tld


custom2_smtp      unix  -       -       n       -       -        smtp
             -o myhostname=mailer.external.cats.tld
             -o smtp_bind_address=e.f.g.h
             -o smtp_helo_name=cats.tld


custom1_smtp      unix  -       -       n       -       -        smtp
             -o myhostname=mailer.external.dogs.tld
             -o smtp_bind_address=a.b.c.d
             -o smtp_helo_name=dogs.tld



# our main internal entry for the dogs site; all outgoing dogs traffic is
# sent to this (non-standard) port and routed to our custom1_smtp transport
10026             inet  n      -        n       -       -        smtpd
             -o myhostname=mailer.internal.dogs.tld
             -o content_filter=custom1_smtp:  


# our main internal entry for the cats site; all outgoing cats traffic is
# sent to this (non-standard) port and routed to our custom2_smtp transport
10027             inet  n      -        n       -       -        smtpd
             -o myhostname=mailer.internal.cats.tld
             -o content_filter=custom2_smtp:  


# default smtpd entry;  outgoing traffic sent to this port (25) will get routed 
# subject to the conditions in our sender_dependent_default_transport_maps file
smtp              inet  n      -        n       -       -        smtpd
             -o myhostname=mailer.internal.tld


# hoorah.
#


before Postfix 2.7 :

The way commonly suggested by the Postfix pixies is to set up multiple Postfix instances.

But here's where an understanding of the Postfix workflow comes in handy.. because by devising a custom-defined Postfix service chain, it is also achievable with just a single Postfix instance. nothing in main.cf needs to be changed. static configuration is dynamically overridable from within the custom service chains we define in master.cf.

Each service reads it's own subset of all the directives residing in the main.cf config. so as we progress along our custom service chain, we can override any settings as may be permitted from within each respective Postfix service in the chain.

(Additionally, note that we could even write our own custom replacement for any of the default Postfix daemons. which is pretty cool).


In /etc/postfix/master.cf:

# each Postfix service reads it's own subset of directives from main.cf config.
# many settings can be dynamically overridden as we move thro the service chain..
# here we override the $myhostname setting depending upon which port we send our 
# outgoing smtp email to.

# custom2  cats service chain 
custom2_cleanup   unix  n       -       -       -       0        cleanup             -o queue_service_name=custom2_qmgr
custom2_qmgr      fifo  n       -       n       300     1        qmgr                -o rewrite_service_name=custom2_rewrite
custom2_rewrite   unix  -       -       n       -       -        trivial-rewrite     -o default_transport=custom2_smtp:
custom2_smtp      unix  -       -       n       -       -        smtp
             -o myhostname=mailer.external.cats.tld
             -o smtp_bind_address=e.f.g.h
             -o smtp_helo_name=cats.tld


# custom1 dogs service chain 
custom1_cleanup   unix  n       -       -       -       0        cleanup             -o queue_service_name=custom1_qmgr
custom1_qmgr      fifo  n       -       n       300     1        qmgr                -o rewrite_service_name=custom1_rewrite
custom1_rewrite   unix  -       -       n       -       -        trivial-rewrite     -o default_transport=custom1_smtp:
custom1_smtp      unix  -       -       n       -       -        smtp
             -o myhostname=mailer.external.dogs.tld
             -o smtp_bind_address=a.b.c.d
             -o smtp_helo_name=dogs.tld


# our main internal entry for the dogs site; all outgoing dogs traffic is
# sent to this (non-standard) port and routed to our custom1 service chain
10026             inet  n      -        n       -       -        smtpd
             -o myhostname=mailer.internal.dogs.tld
             -o cleanup_service_name=custom1_cleanup
           # NB: attempting to override the content_filter directive 
           # *will not work* in postfix < 2.7 since an unspecified nexthop 
           # destination defaults to localhost and causes the email to loop
           # -o content_filter=custom1_smtp:  


# our main internal entry for the cats site; all outgoing cats traffic is
# sent to this (non-standard) port and routed to our custom2 service chain
10027             inet  n      -        n       -       -        smtpd
             -o myhostname=mailer.internal.cats.tld
             -o cleanup_service_name=custom2_cleanup

# yeehar! acu ;)
#


NOTE1: is a caveat:
while directives such as cleanup_service_nameand rewrite_service_name are documented in the configuration parameters, they do not appear to be documented as configurable options parameters for any of the Postfix daemons?!

So at least this seems to be an undocumented approach and hence may well be frowned upon or even possibly deemed as evil in official circles. ~i wouldn't know as i was never able to form a cogent enough question to ever brave the [email protected] official mailing lists!


NOTE2:
there are some folk out there attempting to do this kind of thing in other imaginative ways such as setting up a header_checks FILTER in order overcome the default localhost nexthop destination by dynamically cooking-up a complete transport:recipient-domain-destination:

In /etc/postfix/master.cf:

custom2_smtp      unix  -       -       n       -       -        smtp
             -o myhostname=mailer.external.cats.tld
             -o smtp_bind_address=e.f.g.h
             -o smtp_helo_name=cats.tld

custom2_cleanup   unix  n       -       -       -       0        cleanup 
             -o header_checks=regexp:/etc/postfix/custom2_header_checks

#conditional processing contingent upon entry via some non-standard port
10027             inet  n      -        n       -       -        smtpd
             -o myhostname=mailer.internal.cats.tld
             -o cleanup_service_name=custom2_cleanup


Then in /etc/postfix/custom2_header_checks, pull the recipient domain out of the header-fields with a regex and provide it to the filter directive thus :

/^To:.*@(.*)$/      FILTER    custom2_smtp:$1

While this may seem like a good idea, & partially does work, it is nevertheless certainly a horrid hack; is potentially insecure & at least fails if/when the outgoing email contains multiple recipients. duh!


NOTE3:
if none of the above makes any sense whatsoever, then you probably need to do a bit more rtfming ;)

But seriously, i know there's quite a lot of stuff here.. for various reasons beyond my control, the business is stuck on Postfix 2.3 and i have needed to spend more than 5mins sorting their requirements out; to the extent that my hourly rate has diminished to that of a costa rican pineapple picker (only i do not benefit from their costa' living). so i have tried to lay this all out while it's fresh in my mind and in terms that do not just parrot the official documentation; hopefully so this may help the next poor soul who happens down this particular path ;)

Solution 2:

After a lot of testing, i must conclude that you cannot do this. It IS possible to dynamically change myhostname, it just doesn't affect the Received header.

With a test having banner use $myhostname, i can see that it changes when i use

-o myhostname=test.test.test

but the received header does not.

I tried using, in main.cf

mydomain=test.test.test
myhostname=$mydomain

This does affect both the Received header and banner. But trying to override the $mydomain

-o mydomain=test.test.test

only affects the banner again.

Thus it seems that the variable used in the Received header is filled from $myhostname before overriding $myhostname from the command line arguments.