davidmoten / subethasmtp

SubEtha SMTP is a Java library for receiving SMTP mail
Other
149 stars 40 forks source link

Is it possible to get the IP Address of the client #30

Closed lifeanddeath closed 4 years ago

lifeanddeath commented 4 years ago

Hi David,

Is it allowed or possible the retrieve the raw IP address of a client, be it in the form of x.x.x.x , using a helper class in this library? I couldn't find any class which would make this possible or? Thanks in advance

davidmoten commented 4 years ago

Yes you can, look at ConcurrentSessionsBySourceLimiterTest.java and ConcurrentSessionsBySourceLimiter.java in the project which is an implementation of a SessionHandler and you can see how it accesses the IP address of the client. A SessionHandler can be set in the builder.

lifeanddeath commented 4 years ago

Hi David, so the way to get the IP address of a client is when we call the

return session.getSocket().getInetAddress();

method from a Session object right?

To do this, you need a session but, it gets really complicated because you have to make tons of initializations, which I couldn't figure out. Could you please help me with it?

public void start() throws IOException {

        initServerSettings();
        MailHandlerFactory ersMailHandlerFactory = new MailHandlerFactory();
        SessionHandler handler = new ConcurrentSessionsBySourceLimiter(100);

        if (isSmtpAuthenticationEnabled) {
            SMTPServer server = SMTPServer.port(smtpPort).requireTLS(isStartTlsEnabled).enableTLS(isStartTlsEnabled)
                    .messageHandlerFactory(ersMailHandlerFactory)
                    .authenticationHandlerFactory(getAuthenticationHandler()).sessionHandler(handler).build();
            server.start();
                ServerSocket socket = new ServerSocket();
                ServerThread threat = new ServerThread(server, socket);
        Session session = new Session(server, threat, new Socket());
        } else {
            SMTPServer.port(smtpPort).messageHandlerFactory(ersMailHandlerFactory).requireTLS(isStartTlsEnabled)
                    .enableTLS(true).build().start();
        }

    }

It s like an endless chain

1) sessionHandler in build method requires a sessions handler 2) In session Handler, I can call the accept method which requires a session. 3) in order to create a session you need a smtp server as parameter.

When I run it like this I get the error of socket is not yet exception

davidmoten commented 4 years ago

You don't create the Session object. Please look at the Test class I mentioned for a fully worked example.

davidmoten commented 4 years ago

I'll make you an example.

davidmoten commented 4 years ago

Are you using a BasicMessageListener? If so it provides you with access to the remote socket address without much trouble:

SMTPServer server = SMTPServer //
                .port(PORT) //
                .messageHandler(
                        (context, from, to,
                                data) -> System.out.println("source=" 
                                              + ((InetSocketAddress) context
                                                     .getRemoteAddress())
                                                     .getAddress()
                                                     .getHostAddress()))
                .build();

output:

source=127.0.0.1

By the way I can see that MessageContext.getRemoteAddress() returns a SocketAddress and could return an InetAddress to make things easier. That would be a breaking change but I might include it in 6.x.

lifeanddeath commented 4 years ago

Hey David, thank you for your detailed explanation.

Actually, prior to reading your message, I'd already figured out something. The following also works.

if (isSmtpAuthenticationEnabled) {
    SMTPServer server = SMTPServer.port(smtpPort).requireTLS(isStartTlsEnabled).enableTLS(isStartTlsEnabled)
        .messageHandlerFactory(ersMailHandlerFactory)
        .authenticationHandlerFactory(getAuthenticationHandler())
        .sessionHandler(new SessionHandler() {      
            @Override
            public void onSessionEnd(Session session) {
                // TODO Auto-generated method stub  
            }

            @Override
            public SessionAcceptance accept(Session session) {
                String clientIP = session.getSocket().getInetAddress().toString();
                return SessionAcceptance.success();
                }
            }).build();
        server.start();
}

You can also use this alternative. However, pls bear in mind that, passing the clientIP to MessageHandler using this way is a bit tricky. I had to use a workaround. However, I'll try your solution as it looks more generic and clean approach.

Thank you!

davidmoten commented 4 years ago

@lifeanddeath yep nearly there, I was going to suggest something similar till I realized the the MessageContext passed to the BasicMessageListener is actually the Session object. If you were going to proceed as you were then you need to associate the message with the session which is where things get messy as the interface MessageContext does not present the sessionId. I've put in a TODO to add this as a default method in 5.x. If there's only one session then of course you're ok but if you have multiple concurrent sessions you can get into trouble. If your only unique id is the socket address then that's not ideal as two consecutive sessions could use the same socket address.

davidmoten commented 4 years ago

You should be sorted already but I've added MessageContext.getSessionId() in release 5.2.6 which is on Maven Central now.