This file is indexed.

/usr/share/perl5/MSDW/SMTP/Server.pm is in libmsdw-smtp-perl 1.2-1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
#   This code is Copyright (C) 2001 Morgan Stanley Dean Witter, and
#   is distributed according to the terms of the GNU Public License
#   as found at <URL:http://www.fsf.org/copyleft/gpl.html>.
#
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
# Written by Bennett Todd <bet@rahul.net>

package MSDW::SMTP::Server;
use IO::Socket;
use IO::File;

=head1 NAME

  MSDW::SMTP::Server --- SMTP server for content-scanning proxy

=head1 SYNOPSIS

  use MSDW::SMTP::Server;

  my $server = MSDW::SMTP::Server->new(interface => $interface,
				       port => $port);
  while (1) {
    # prefork here
    $server->accept([options]);
    # per-connect fork here
    $server->ok("220 howdy");
    while (my $what = $server->chat) {
      if ($what =~ /^mail/i) {
	if (isgood($server->{from})) {
	  $server->ok([ ack msg ]);
	} else {
	  $server->fail([ fail msg ]);
	}
      } elsif ($what =~ /^rcpt/i) {
	if (isgood(@{$server}{qw(from to)})) {
	  $sever->ok([ ack msg ]);
	} else {
	  $server->fail([ fail msg ]);
	}
      } elsif ($what =~ /^data/i) {
	if (isgood(@{$server}{qw(from to)})) {
	  # NB to is now an array of all recipients
	  $self->ok("354 natter on.");
	} else {
	  $self->fail;
	}
      } elsif ($what eq '.') {
        if (isgood(@server->{from,to,data})) {
	  $server->ok;
	} else {
	  $server->fail;
	}
      } else {
        # deal with other msg types as you will
	die "can't happen";
      }
      # process $server->{from,to,data} here
      $server->ok; # or $server->fail;
    }
  }

=head1 DESCRIPTION

MSDW::SMTP::Server fills a gap in the available range of Perl SMTP
servers. The existing candidates are not suitable for a
high-performance, content-scanning robust SMTP proxy. They insist on
heavy-weight structuring and parsing of the body, and they
acknowledge receipt of the data before returning control to the
caller.

This server simply gathers the SMTP acquired information (envelope
sender and recipient, and data) into unparsed memory buffers (or a
file for the data), and returns control to the caller to explicitly
acknowlege each command or request. Since acknowlegement or failure
are driven explicitly from the caller, this module can be used to
create a robust SMTP content scanning proxy, transparent or not as
desired.

=head1 METHODS

=over 8

=item new(interface => $interface, port => $port);

The interface and port to listen on must be specified. The interface
must be a valid numeric IP address (0.0.0.0 to listen on all
interfaces, as usual); the port must be numeric. If this call
succeeds, it returns a server structure with an open
IO::Socket::INET in it, ready to listen on. If it fails it dies, so
if you want anything other than an exit with an explanatory error
message, wrap the constructor call in an eval block and pull the
error out of $@ as usual. This is also the case for all other
methods; they succeed or they die.

=item accept([debug => FD]);

accept takes optional args and returns nothing. If an error occurs
it dies, otherwise it returns when a client connects to this server.
This is factored out as a separate entry point to allow preforking
(e.g. Apache-style) or fork-per-client strategies to be implemented
on the common protocol core. If a filehandle is passed for debugging
it will receive a complete trace of the entire SMTP dialogue, data
and all. Note that nothing in this module sends anything to the
client, including the initial login banner; all such backtalk must
come from the calling program.

=item chat;

The chat method carries the SMTP dialogue up to the point where any
acknowlegement must be made. If chat returns true, then its return
value is the previous SMTP command. If the return value begins with
'mail' (case insensitive), then the attribute 'from' has been filled
in, and may be checked; if the return value begins with 'rcpt' then
both from and to have been been filled in with scalars, and should
be checked, then either 'ok' or 'fail' should be called to accept
or reject the given sender/recipient pair. If the return value is
'data', then the attributes from and to are populated; in this case,
the 'to' attribute is a reference to an anonymous array containing
all the recipients for this data. If the return value is '.', then
the 'data' attribute (which may be pre-populated in the "new" or
"accept" methods if desired) is a reference to a filehandle; if it's
created automatically by this module it will point to an unlinked
tmp file in /tmp. If chat returns false, the SMTP dialogue has been
completed and the socket closed; this server is ready to exit or to
accept again, as appropriate for the server style.

The return value from chat is also remembered inside the server
structure in the "state" attribute.

=item ok([message]);

Approves of the data given to date, either the recipient or the
data, in the context of the sender [and, for data, recipients]
already given and available as attributes. If a message is given, it
will be sent instead of the internal default.

=item fail([message]);

Rejects the current info; if processing from, rejects the sender; if
processing 'to', rejects the current recipient; if processing data,
rejects the entire message. If a message is specified it means the
exact same thing as "ok" --- simply send that message to the sender.

=back

=cut

sub new {
    my ($this, @opts) = @_;
    my $class = ref($this) || $this;
    my $self = bless { @opts }, $class;
    $self->{sock} = IO::Socket::INET->new(
	LocalAddr => $self->{interface},
	LocalPort => $self->{port},
	Proto => 'tcp',
	Type => SOCK_STREAM,
	Listen => 65536,
	Reuse => 1,
    );
    die "$0: socket bind failure: $!\n" unless defined $self->{sock};
    $self->{state} = 'just bound',
    return $self;
}

sub accept {
    my ($self, @opts) = @_;
    %$self = (%$self, @opts);
    ($self->{"s"}, $self->{peeraddr}) = $self->{sock}->accept or
	die "$0: accept failure: $!\n";
    $self->{state} = ' accepted';
}


sub chat {
    my ($self) = @_;
    local(*_);
    if ($self->{state} !~ /^data/i) {
	return 0 unless defined($_ = $self->getline);
	s/[\r\n]*$//;
	$self->{state} = $_;
	if (s/^helo\s+//i) {
	    s/\s*$//;s/\s+/ /g;
	    $self->{helo} = $_;
	} elsif (s/^rset\s*//i) {
	    delete $self->{to};
	    delete $self->{data};
	    delete $self->{recipients};
	} elsif (s/^mail\s+from:\s*//i) {
	    delete $self->{to};
	    delete $self->{data};
	    delete $self->{recipients};
	    s/\s*$//;
	    $self->{from} = $_;
	} elsif (s/^rcpt\s+to:\s*//i) {
	    s/\s*$//; s/\s+/ /g;
	    $self->{to} = $_;
	    push @{$self->{recipients}}, $_;
	} elsif (/^data/i) {
	    $self->{to} = $self->{recipients};
	}
    } else {
	if (defined($self->{data})) {
	    $self->{data}->seek(0, 0);
	    $self->{data}->truncate(0);
	} else {
	    $self->{data} = IO::File->new_tmpfile;
	}
	while (defined($_ = $self->getline)) {
	    if ($_ eq ".\r\n") {
		$self->{data}->seek(0,0);
		return $self->{state} = '.';
	    }
	    s/^\.\./\./;
	    $self->{data}->print($_) or die "$0: write error saving data\n";
	}
	return(0);
    }
    return $self->{state};
}

sub getline {
    my ($self) = @_;
    local ($/) = "\r\n";
    return $self->{"s"}->getline unless defined $self->{debug};
    my $tmp = $self->{"s"}->getline;
    $self->{debug}->print($tmp) if ($tmp);
    return $tmp;
}

sub print {
    my ($self, @msg) = @_;
    $self->{debug}->print(@msg) if defined $self->{debug};
    $self->{"s"}->print(@msg);
}

sub ok {
    my ($self, @msg) = @_;
    @msg = ("250 ok.") unless @msg;
    $self->print("@msg\r\n") or
	die "$0: write error acknowledging $self->{state}: $!\n";
}

sub fail {
    my ($self, @msg) = @_;
    @msg = ("550 no.") unless @msg;
    $self->print("@msg\r\n") or
	die "$0: write error acknowledging $self->{state}: $!\n";
}

1;