9615443db65274c12082b5ecb9754f40593eaffd
[exim.git] / src / src / exim_checkaccess.src
1 #! /bin/sh
2
3 # Copyright (c) The Exim Maintainers 2023
4 # Copyright (c) University of Cambridge, 1995 - 2007
5 # See the file NOTICE for conditions of use and distribution.
6 # SPDX-License-Identifier: GPL-2.0-or-later
7
8 # Except when they appear in comments, the following placeholders in this
9 # source are replaced when it is turned into a runnable script:
10 #
11 # CONFIGURE_FILE_USE_NODE
12 # CONFIGURE_FILE
13 # BIN_DIRECTORY
14 # PERL_COMMAND
15
16 # PROCESSED_FLAG
17
18 # A shell+perl wrapper script to run an automated -bh test to check out
19 # ACLs for incoming addresses.
20
21 # Save the shell arguments because we are going to need the shell variables
22 # while sorting out the configuration file.
23
24 args="$@"
25
26 # See if this installation is using the esoteric "USE_NODE" feature of Exim,
27 # in which it uses the host's name as a suffix for the configuration file name.
28
29 if [ "CONFIGURE_FILE_USE_NODE" = "yes" ]; then
30   hostsuffix=.`uname -n`
31 fi
32
33 # Now find the configuration file name. This has got complicated because
34 # CONFIGURE_FILE may now be a list of files. The one that is used is the first
35 # one that exists. Mimic the code in readconf.c by testing first for the
36 # suffixed file in each case.
37
38 set `awk -F: '{ for (i = 1; i <= NF; i++) print $i }' <<End
39 CONFIGURE_FILE
40 End
41 `
42 while [ "$config" = "" -a $# -gt 0 ] ; do
43   if [ -f "$1$hostsuffix" ] ; then
44     config="$1$hostsuffix"
45   elif [ -f "$1" ] ; then
46     config="$1"
47   fi
48   shift
49 done
50
51 # Search for an exim_path setting in the configure file; otherwise use the bin
52 # directory. BEWARE: a tab character is needed in the command below. It has had
53 # a nasty tendency to get lost in the past. Use a variable to hold a space and
54 # a tab to keep the tab in one place.
55
56 exim_path=`perl -ne 'chop;if (/^\s*exim_path\s*=\s*(.*)/){print "$1\n";last;}' $config`
57 if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim; fi
58
59
60 #########################################################################
61
62
63 # Now run the perl script, passing in the Exim path and the arguments given
64 # to the overall script.
65
66 PERL_COMMAND - $exim_path $args <<'End'
67
68 BEGIN { pop @INC if $INC[-1] eq '.' };
69 use FileHandle;
70 use File::Basename;
71 use IPC::Open2;
72
73 if ($ARGV[0] eq '--version' || $ARGV[0] eq '-v') {
74     print basename($0) . ": $0\n",
75           "build: EXIM_RELEASE_VERSIONEXIM_VARIANT_VERSION\n",
76           "perl(runtime): $]\n";
77     exit 0;
78 }
79
80 if (scalar(@ARGV) < 3)
81   {
82   print "Usage: exim_checkaccess <IP address> <email address> [exim options]\n";
83   exit(1);
84   }
85
86 $exim_path = $ARGV[0];          # Set up by the calling shell script
87 $host      = $ARGV[1];          # Mandatory original first argument
88 $recipient = $ARGV[2];          # Mandatory original second argument
89
90 $c4 = qr/2 (?:[0-4]\d | 5[0-5]) | 1\d\d | \d{1,2}/x;  # IPv4 component
91 $a4 = qr/^$c4\.$c4\.$c4\.$c4$/;                       # IPv4 address
92
93 $c6 = qr/[0-9a-f]{1,4}/i;                             # IPv6 component
94
95 # Split the various formats of IPv6 addresses into several cases. I don't
96 # think I can graft regex that matches all of them without using alternatives.
97
98 # 1. Starts with :: followed by up to 7 components
99
100 $a6_0 = qr/^::(?:$c6:){0,6}$c6$/x;
101
102 # 2. 8 non-empty components
103
104 $a6_1 = qr/^(?:$c6:){7}$c6$/x;
105
106 # 3. This is the cunning one. Up to 7 components, one (and only one) of which
107 # can be empty. We use 0 to cause a failure when we've already matched
108 # an empty component and may be hitting other. This has to fail, because we
109 # know we've just failed to match a component. We also do a final check to
110 # ensure that there has been an empty component.
111
112 $a6_2 = qr/^(?: (?: $c6 | (?(1)0 | () ) ) : ){1,7}$c6 $ (?(1)|.)/x;
113
114 if ($host !~ /$a4 | $a6_0 | $a6_1 | $a6_2/x)
115   {
116   print "** Invalid IP address \"$host\"\n";
117   print "Usage: exim_checkaccess <IP address> <email address> [exim options]\n";
118   exit(1);
119   }
120
121 # Build any remaining original arguments into a string for passing over
122 # as Exim options.
123
124 $opt = "";
125 for ($i = 3; $i < scalar(@ARGV); $i++) { $opt .= "$ARGV[$i] "; }
126
127 # If the string contains "-f xxxx", extract that as the sender. Otherwise
128 # the sender is <>.
129
130 $sender    = "";
131 if ($opt =~ /(?:^|\s)-f\s+(\S+|"[^"]*")/)
132   {
133   $sender = $1;
134   $opt = $` . $';
135   }
136
137 # Run a -bh test in Exim, passing the test data
138
139 $pid = open2(*IN, *OUT, "$exim_path -bh $host $opt 2>/dev/null");
140 print OUT "HELO [$host]\r\n";
141 print OUT "MAIL FROM:<$sender>\r\n";
142 print OUT "RCPT TO:<$recipient>\r\n";
143 print OUT "QUIT\r\n";
144 close OUT;
145
146 # Read the output, ignoring anything but the SMTP response to the RCPT
147 # command.
148
149 $count = 0;
150 $reply = "";
151
152 while (<IN>)
153   {
154   next if !/^\d\d\d/;
155   $reply .= $_;
156   next if /^\d\d\d\-/;
157
158   if (++$count != 4)
159     {
160     $reply = "";
161     next;
162     }
163
164   # We have the response we want. Interpret it.
165
166   if ($reply =~ /^2\d\d/)
167     {
168     print "Accepted\n";
169     }
170   else
171     {
172     print "Rejected:\n";
173     $reply =~ s/\n(.)/\n  $1/g;
174     print "  $reply";
175     }
176   last;
177   }
178
179 # Reap the child process
180
181 waitpid $pid, 0;
182
183 End