/* Server.java
 *
 * Copyright (C) 2006-2025 wolfSSL Inc.
 *
 * This file is part of wolfSSL.
 *
 * wolfSSL is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * wolfSSL 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
 */

import java.io.*;
import java.net.*;
import java.nio.*;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CharacterCodingException;

import com.wolfssl.WolfSSL;
import com.wolfssl.WolfSSLSession;
import com.wolfssl.WolfSSLContext;
import com.wolfssl.WolfSSLCertificate;
import com.wolfssl.WolfSSLException;
import com.wolfssl.WolfSSLJNIException;
import com.wolfssl.WolfSSLIOSendCallback;
import com.wolfssl.WolfSSLIORecvCallback;

/* suppress SSLv3 deprecation warnings, meant for end user not examples */
@SuppressWarnings("deprecation")
public class Server {

    public static Charset charset = Charset.forName("UTF-8");
    public static CharsetEncoder encoder = charset.newEncoder();

    public static void main(String[] args) {
        new Server().run(args);
    }

    public void run(String[] args) {

        Socket clientSocket           = null;
        ServerSocket serverSocket     = null;
        DatagramSocket d_serverSocket = null;
        DataOutputStream outstream    = null;
        DataInputStream  instream     = null;

        int ret = 0, insz, err;
        String msg  = "I hear you fa shizzle, from Java!";
        byte[] input = new byte[80];
        long method = 0;

        /* config info */
        boolean useIOCallbacks = false;      /* test I/O callbacks */
        String cipherList = null;            /* default cipher suite list */
        int sslVersion = 3;                  /* default to TLS 1.2 */
        int verifyPeer = 1;                  /* verify peer by default */
        int doDTLS = 0;                      /* don't use DTLS by default */
        int useOcsp = 0;                     /* don't use OCSP by default */
        String ocspUrl = null;               /* OCSP override URL */
        int useAtomic = 0;                   /* atomic record lyr processing */
        int pkCallbacks = 0;                 /* public key callbacks */
        int logCallback = 0;                 /* test logging callback */
        int crlDirMonitor = 0;               /* enable CRL monitor */
        int usePsk = 0;                      /* use pre shared keys */
        int needDH = 0;                      /* toggle for loading DH params */
        int sendPskIdentityHint = 1;         /* toggle sending PSK ident hint */

        /* cert info */
        String serverCert = "../certs/server-cert.pem";
        String serverKey  = "../certs/server-key.pem";
        String caCert     = "../certs/client-cert.pem";
        String crlPemDir  = "../certs/crl";
        String dhParam    = "../certs/dh2048.pem";

        /* server info */
        int port    =  11111;

        try {

            /* load JNI library */
            WolfSSL.loadLibrary();

            /* pull in command line options from user */
            for (int i = 0; i < args.length; i++)
            {
                String arg = args[i];

                if (arg.equals("-?")) {
                    printUsage();

                } else if (arg.equals("-p")) {
                    if (args.length < i+2)
                        printUsage();
                    port = Integer.parseInt(args[++i]);

                } else if (arg.equals("-v")) {
                    if (args.length < i+2)
                        printUsage();
                    sslVersion = Integer.parseInt(args[++i]);
                    if (sslVersion < 0 || sslVersion > 4) {
                        printUsage();
                    }

                } else if (arg.equals("-l")) {
                    if (args.length < i+2)
                        printUsage();
                    cipherList = args[++i];

                } else if (arg.equals("-c")) {
                    if (args.length < i+2)
                        printUsage();
                    serverCert = args[++i];

                } else if (arg.equals("-k")) {
                    if (args.length < i+2)
                        printUsage();
                    serverKey = args[++i];

                } else if (arg.equals("-A")) {
                    if (args.length < i+2)
                        printUsage();
                    caCert = args[++i];

                } else if (arg.equals("-d")) {
                    verifyPeer = 0;

                } else if (arg.equals("-u")) {
                    doDTLS = 1;

                } else if (arg.equals("-s")) {
                    if (WolfSSL.isEnabledPSK() == 0) {
                        System.out.println("PSK support not enabled in " +
                                           "wolfSSL");
                        System.exit(1);
                    }
                    usePsk = 1;

                } else if (arg.equals("-iocb")) {
                    useIOCallbacks = true;

                } else if (arg.equals("-logtest")) {
                    logCallback = 1;

                } else if (arg.equals("-o")) {
                    if (WolfSSL.isEnabledOCSP() == 0) {
                        System.out.println("OCSP support not enabled in " +
                                           "wolfSSL");
                        System.exit(1);
                    }
                    useOcsp = 1;

                } else if (arg.equals("-O")) {
                    if (WolfSSL.isEnabledOCSP() == 0) {
                        System.out.println("OCSP support not enabled in " +
                                           "wolfSSL");
                        System.exit(1);
                    }
                    if (args.length < i+2)
                        printUsage();
                    useOcsp = 1;
                    ocspUrl = args[i++];

                } else if (arg.equals("-U")) {
                    if (WolfSSL.isEnabledAtomicUser() == 0) {
                        System.out.println("Atomic User support not enabled " +
                                           "in wolfSSL");
                        System.exit(1);
                    }
                    useAtomic = 1;

                } else if (arg.equals("-P")) {
                    if (WolfSSL.isEnabledPKCallbacks() == 0) {
                        System.out.println("Public Key callback support not " +
                                           "enabled in wolfSSL");
                        System.exit(1);
                    }
                    pkCallbacks = 1;

                } else if (arg.equals("-m")) {
                    if (WolfSSL.isEnabledCRLMonitor() == 0) {
                        System.out.println("CRL monitor support not enabled " +
                                           "in wolfSSL");
                        System.exit(1);
                    }
                    crlDirMonitor = 1;

                } else if (arg.equals("-I")) {
                    if (WolfSSL.isEnabledPSK() == 0) {
                        System.out.println("PSK support not enabled in " +
                                           "wolfSSL");
                        System.exit(1);
                    }
                    sendPskIdentityHint = 0;

                } else {
                    printUsage();
                }
            }

            /* sort out DTLS versus TLS versions */
            if (doDTLS == 1) {
                if (sslVersion == 4) {
                    sslVersion = -3;
                }
                else if (sslVersion == 3) {
                    sslVersion = -2;
                }
                else {
                    sslVersion = -1;
                }
            }

            /* init library */
            WolfSSL sslLib = new WolfSSL();

            /* set logging callback */
            if (logCallback == 1) {
                MyLoggingCallback lc = new MyLoggingCallback();
                sslLib.setLoggingCb(lc);
            }

            /* set SSL version method */
            switch (sslVersion) {
                case 0:
                    method = WolfSSL.SSLv3_ServerMethod();
                    break;
                case 1:
                    method = WolfSSL.TLSv1_ServerMethod();
                    break;
                case 2:
                    method = WolfSSL.TLSv1_1_ServerMethod();
                    break;
                case 3:
                    method = WolfSSL.TLSv1_2_ServerMethod();
                    break;
                case 4:
                    method = WolfSSL.TLSv1_3_ServerMethod();
                    break;
                case -1:
                    method = WolfSSL.DTLSv1_ServerMethod();
                    break;
                case -2:
                    method = WolfSSL.DTLSv1_2_ServerMethod();
                    break;
                case -3:
                    method = WolfSSL.DTLSv1_3_ServerMethod();
                    break;
                default:
                    System.err.println("Bad SSL version");
                    System.exit(1);
            }

            /* create context */
            WolfSSLContext sslCtx = new WolfSSLContext(method);

            if (usePsk == 1) {

                MyPskServerCallback pskServerCb = new MyPskServerCallback();
                sslCtx.setPskServerCb(pskServerCb);
                if (sendPskIdentityHint == 1) {
                    ret = sslCtx.usePskIdentityHint("cyassl server");
                    if (ret != WolfSSL.SSL_SUCCESS) {
                        System.out.println("Error setting PSK Identity Hint");
                        System.exit(1);
                    }
                }

            } else {

                /* load certificate/key files */
                ret = sslCtx.useCertificateChainFile(serverCert);
                if (ret != WolfSSL.SSL_SUCCESS) {
                    System.out.println("failed to load server certificate!");
                    System.exit(1);
                }

                ret = sslCtx.usePrivateKeyFile(serverKey,
                        WolfSSL.SSL_FILETYPE_PEM);
                if (ret != WolfSSL.SSL_SUCCESS) {
                    System.out.println("failed to load server private key!");
                    System.exit(1);
                }

                /* set verify callback */
                if (verifyPeer == 0) {
                    sslCtx.setVerify(WolfSSL.SSL_VERIFY_NONE, null);
                } else {
                    ret = sslCtx.loadVerifyLocations(caCert, null);
                    if (ret != WolfSSL.SSL_SUCCESS) {
                        System.out.println("failed to load CA certificates!");
                        System.exit(1);
                    }

                    VerifyCallback vc = new VerifyCallback();
                    sslCtx.setVerify(WolfSSL.SSL_VERIFY_PEER, vc);
                }
            }

            /* set cipher list */
            if (cipherList == null) {
                if (usePsk == 1)
                    ret = sslCtx.setCipherList("DHE-PSK-AES128-GCM-SHA256");
                    needDH = 1;
            } else {
                ret = sslCtx.setCipherList(cipherList);
            }

            if (ret != WolfSSL.SSL_SUCCESS) {
                System.out.println("failed to set cipher list, ret = " + ret);
                System.exit(1);
            }

            /* set OCSP options, override URL */
            if (useOcsp == 1) {

                long ocspOptions = WolfSSL.WOLFSSL_OCSP_NO_NONCE;

                if (ocspUrl != null) {
                    ocspOptions = ocspOptions |
                                  WolfSSL.WOLFSSL_OCSP_URL_OVERRIDE;
                }

                if (ocspUrl != null) {
                    ret = sslCtx.setOCSPOverrideUrl(ocspUrl);

                    if (ret != WolfSSL.SSL_SUCCESS) {
                        System.out.println("failed to set OCSP overrideUrl");
                        System.exit(1);
                    }
                }

                ret = sslCtx.enableOCSP(ocspOptions);
                if (ret != WolfSSL.SSL_SUCCESS) {
                    System.out.println("failed to enable OCSP, ret = "
                            + ret);
                    System.exit(1);
                }
            }

            /* register I/O callbacks, I/O ctx setup is later */
            if (useIOCallbacks || (doDTLS == 1)) {
                MyRecvCallback rcb = new MyRecvCallback();
                MySendCallback scb = new MySendCallback();
                sslCtx.setIORecv(rcb);
                sslCtx.setIOSend(scb);
                System.out.println("Registered I/O callbacks");

                /* register DTLS cookie generation callback */
                if (doDTLS == 1) {
                    MyGenCookieCallback gccb = new MyGenCookieCallback();
                    sslCtx.setGenCookie(gccb);
                    System.out.println("Registered DTLS cookie callback");
                }
            }

            /* register atomic record layer callbacks, ctx setup later */
            if (useAtomic == 1) {
                MyMacEncryptCallback mecb = new MyMacEncryptCallback();
                MyDecryptVerifyCallback dvcb =
                    new MyDecryptVerifyCallback();
                sslCtx.setMacEncryptCb(mecb);
                sslCtx.setDecryptVerifyCb(dvcb);

                if (WolfSSL.encryptThenMacEnabled()) {
                    MyVerifyDecryptCallback vdc =
                        new MyVerifyDecryptCallback();
                    sslCtx.setVerifyDecryptCb(vdc);
                }
            }

            /* register public key callbacks, ctx setup later */
            if (pkCallbacks == 1) {

                /* ECC */
                MyEccSignCallback eccSign = new MyEccSignCallback();
                MyEccVerifyCallback eccVerify = new MyEccVerifyCallback();
                MyEccSharedSecretCallback eccSharedSecret =
                    new MyEccSharedSecretCallback();
                sslCtx.setEccSignCb(eccSign);
                sslCtx.setEccVerifyCb(eccVerify);
                sslCtx.setEccSharedSecretCb(eccSharedSecret);

                /* RSA */
                MyRsaSignCallback rsaSign = new MyRsaSignCallback();
                MyRsaVerifyCallback rsaVerify = new MyRsaVerifyCallback();
                MyRsaEncCallback rsaEnc = new MyRsaEncCallback();
                MyRsaDecCallback rsaDec = new MyRsaDecCallback();
                sslCtx.setRsaSignCb(rsaSign);
                sslCtx.setRsaVerifyCb(rsaVerify);
                sslCtx.setRsaEncCb(rsaEnc);
                sslCtx.setRsaDecCb(rsaDec);
            }

            /* create server socket, later if DTLS */
            if (doDTLS == 0) {
                serverSocket = new ServerSocket(port);
            }

            InetAddress hostAddress = InetAddress.getLocalHost();
            System.out.println("Started server at " + hostAddress +
                    ", port " + port);

            /* wait for new client connections, then process */
            while (true) {

                System.out.println("\nwaiting for client connection...");
                if (doDTLS == 1) {
                    byte[] buf = new byte[1500];
                    d_serverSocket = new DatagramSocket(port);
                    DatagramPacket dp = new DatagramPacket(buf, buf.length);
                    d_serverSocket.setSoTimeout(0);
                    d_serverSocket.receive(dp);
                    d_serverSocket.connect(dp.getAddress(), dp.getPort());
                    System.out.println("client connection received from " +
                            dp.getAddress() + " at port " + dp.getPort() +
                            "\n");
                } else {
                    clientSocket = serverSocket.accept();

                    /* get input and output streams */
                    outstream =
                        new DataOutputStream(clientSocket.getOutputStream());
                    instream =
                        new DataInputStream(clientSocket.getInputStream());
                    System.out.println("client connection received from " +
                            clientSocket.getInetAddress().getHostAddress() +
                            " at port " + clientSocket.getLocalPort() + "\n");
                }

                /* create SSL object */
                WolfSSLSession ssl = new WolfSSLSession(sslCtx);

                if (usePsk == 0 || cipherList != null || needDH == 1) {
                    ret = ssl.setTmpDHFile(dhParam, WolfSSL.SSL_FILETYPE_PEM);
                    if (ret != WolfSSL.SSL_SUCCESS) {
                        System.out.println("failed to set DH file, ret = " +
                                ret);
                        System.exit(1);
                    }
                }

                /* enable/load CRL functionality */
                if (WolfSSL.isEnabledCRL() == 1) {
                    ret = ssl.enableCRL(0);
                    if (ret != WolfSSL.SSL_SUCCESS) {
                        System.out.println("failed to enable CRL, ret = "
                                + ret);
                        System.exit(1);
                    }
                    if (crlDirMonitor == 1) {
                        ret = ssl.loadCRL(crlPemDir, WolfSSL.SSL_FILETYPE_PEM,
                                (WolfSSL.WOLFSSL_CRL_MONITOR |
                                WolfSSL.WOLFSSL_CRL_START_MON));
                        if (ret == WolfSSL.MONITOR_RUNNING_E) {
                            System.out.println("CRL monitor already running, " +
                                    "continuing");
                        } else if (ret != WolfSSL.SSL_SUCCESS) {
                            System.out.println("failed to start CRL monitor, ret = "
                                    + ret);
                            System.exit(1);
                        }
                    } else {
                        ret = ssl.loadCRL(crlPemDir, WolfSSL.SSL_FILETYPE_PEM, 0);
                        if (ret != WolfSSL.SSL_SUCCESS) {
                            System.out.println("failed to load CRL, ret = " + ret);
                            System.exit(1);
                        }
                    }

                    MyMissingCRLCallback crlCb = new MyMissingCRLCallback();
                    ret = ssl.setCRLCb(crlCb);
                    if (ret != WolfSSL.SSL_SUCCESS) {
                        System.out.println("failed to set CRL callback, ret = "
                                + ret);
                        System.exit(1);
                    }
                }

                /* ALPN select callback */
                MyALPNSelectCallback alpnSelectCb = new MyALPNSelectCallback();
                ret = ssl.setAlpnSelectCb(alpnSelectCb, null);
                if (ret != WolfSSL.SSL_SUCCESS) {
                    System.out.println("failed to set ALPN select callback, " +
                        "ret = " + ret);
                    System.exit(1);
                }

                if (useIOCallbacks || (doDTLS == 1)) {
                    /* register I/O callback user context */
                    MyIOCtx ioctx = new MyIOCtx(outstream, instream,
                            d_serverSocket, hostAddress, port);
                    ssl.setIOReadCtx(ioctx);
                    ssl.setIOWriteCtx(ioctx);
                    System.out.println("Registered I/O callback user ctx");

                    /* register DTLS cookie generation callback */
                    if (doDTLS == 1) {
                        MyGenCookieCtx gctx = new MyGenCookieCtx(
                                hostAddress, port);
                        ssl.setGenCookieCtx(gctx);
                        System.out.println("Registered DTLS cookie " +
                                           "callback ctx");
                    }

                } else {
                    /* if not using DTLS or I/O callbacks, pass Socket
                     * fd to wolfSSL */
                     ret = ssl.setFd(clientSocket);

                    if (ret != WolfSSL.SSL_SUCCESS) {
                        System.out.println("Failed to set file descriptor");
                        return;
                    }
                }

                if (useAtomic == 1) {
                    /* register atomic record layer callback user contexts */
                    MyAtomicEncCtx encCtx = new MyAtomicEncCtx();
                    MyAtomicDecCtx decCtx = new MyAtomicDecCtx();
                    ssl.setMacEncryptCtx(encCtx);
                    ssl.setDecryptVerifyCtx(decCtx);

                    if (WolfSSL.encryptThenMacEnabled()) {
                        MyAtomicDecCtx vdCtx = new MyAtomicDecCtx();
                        ssl.setVerifyDecryptCtx(vdCtx);
                    }
                }

                if (pkCallbacks == 1) {
                    /* register public key callback user contexts */

                    /* ECC */
                    MyEccSignCtx eccSignCtx = new MyEccSignCtx();
                    MyEccVerifyCtx eccVerifyCtx = new MyEccVerifyCtx();
                    MyEccSharedSecretCtx eccSharedSecretCtx =
                        new MyEccSharedSecretCtx();
                    ssl.setEccSignCtx(eccSignCtx);
                    ssl.setEccVerifyCtx(eccVerifyCtx);
                    ssl.setEccSharedSecretCtx(eccSharedSecretCtx);

                    /* RSA */
                    MyRsaSignCtx rsaSignCtx = new MyRsaSignCtx();
                    MyRsaVerifyCtx rsaVerifyCtx = new MyRsaVerifyCtx();
                    MyRsaEncCtx rsaEncCtx = new MyRsaEncCtx();
                    MyRsaDecCtx rsaDecCtx = new MyRsaDecCtx();
                    ssl.setRsaSignCtx(rsaSignCtx);
                    ssl.setRsaVerifyCtx(rsaVerifyCtx);
                    ssl.setRsaEncCtx(rsaEncCtx);
                    ssl.setRsaDecCtx(rsaDecCtx);
                }

                do {
                    ret = ssl.accept();
                    err = ssl.getError(ret);
                } while (ret != WolfSSL.SSL_SUCCESS &&
                         (err == WolfSSL.SSL_ERROR_WANT_READ ||
                          err == WolfSSL.SSL_ERROR_WANT_WRITE));

                if (ret != WolfSSL.SSL_SUCCESS) {
                    err = ssl.getError(ret);
                    String errString = sslLib.getErrorString(err);
                    System.out.println("wolfSSL_accept failed. err = " + err +
                            ", " + errString);
                    System.exit(1);
                }

                /* show peer info */
                showPeer(ssl);

                /* read client response, and echo */
                do {
                    insz = ssl.read(input, input.length);
                    err = ssl.getError(0);
                } while (insz < 0 &&
                         (err == WolfSSL.SSL_ERROR_WANT_READ ||
                          err == WolfSSL.SSL_ERROR_WANT_WRITE));

                if (insz > 0) {
                    String cliMsg = new String(input, 0, insz);
                    System.out.println("client says: " + cliMsg);
                } else {
                    System.out.println("read failed");
                }

                do {
                    ret = ssl.write(msg.getBytes(), msg.length());
                    err = ssl.getError(0);
                } while (ret < 0 &&
                         (err == WolfSSL.SSL_ERROR_WANT_READ ||
                          err == WolfSSL.SSL_ERROR_WANT_WRITE));

                if (ret != msg.length()) {
                    System.out.println("ssl.write() failed");
                    System.exit(1);
                }

                ssl.shutdownSSL();
                ssl.freeSSL();

                if (doDTLS == 1) {
                    d_serverSocket.disconnect();
                    d_serverSocket.close();
                }
            }
        } catch (UnsatisfiedLinkError ule) {
            ule.printStackTrace();
        } catch (WolfSSLException wex) {
            wex.printStackTrace();
        } catch (WolfSSLJNIException jex) {
            jex.printStackTrace();
        } catch (CharacterCodingException cce) {
            cce.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (serverSocket != null) {
                    serverSocket.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    } /* end run() */

    void showPeer(WolfSSLSession ssl) {

        String altname;
        long peerCrtPtr = 0;

        try {

            peerCrtPtr = ssl.getPeerCertificate();

            if (peerCrtPtr != 0) {

                System.out.println("issuer : " +
                        ssl.getPeerX509Issuer(peerCrtPtr));
                System.out.println("subject : " +
                        ssl.getPeerX509Subject(peerCrtPtr));

                while( (altname = ssl.getPeerX509AltName(peerCrtPtr)) != null)
                    System.out.println("altname = " + altname);

            } else {
                System.out.println("peer has no cert!\n");
            }

            System.out.println("SSL version is " + ssl.getVersion());
            System.out.println("SSL cipher suite is " + ssl.cipherGetName());

        } catch (WolfSSLJNIException e) {
            e.printStackTrace();

        } finally {
            if (WolfSSL.getLibVersionHex() >= 0x05003000) {
                if (peerCrtPtr != 0) {
                    WolfSSLCertificate.freeX509(peerCrtPtr);
                }
            }
        }
    }

    void printUsage() {

        System.out.println("Java example server usage:");
        System.out.println("-?\t\tHelp, print this usage");
        System.out.println("-p <num>\tPort to connect to, default 11111");
        System.out.println("-v <num>\tSSL version [0-3], SSLv3(0) - " +
                "TLS1.3(4)), default 3");
        System.out.println("-l <str>\tCipher list");
        System.out.println("-c <file>\tCertificate file,\t\tdefault " +
                "../certs/client-cert.pem");
        System.out.println("-k <file>\tKey file,\t\t\tdefault " +
                "../certs/client-key.pem");
        System.out.println("-A <file>\tCertificate Authority file,\tdefault " +
                "../certs/client-cert.pem");
        System.out.println("-d\t\tDisable peer checks");
        if (WolfSSL.isEnabledPSK() == 1)
            System.out.println("-s\t\tUse pre shared keys");
        if (WolfSSL.isEnabledDTLS() == 1)
            System.out.println("-u\t\tUse UDP DTLS, add -v 2 for DTLSv1 (default)" +
                ", -v 3 for DTLSv1.2, -v 4 for DTLSv1.3");
        System.out.println("-iocb\t\tEnable test I/O callbacks");
        System.out.println("-logtest\tEnable test logging callback");
        if (WolfSSL.isEnabledOCSP() == 1) {
            System.out.println("-o\t\tPerform OCSP lookup on peer certificate");
            System.out.println("-O <url>\tPerform OCSP lookup using <url> " +
                    "as responder");
        }
        if (WolfSSL.isEnabledAtomicUser() == 1)
            System.out.println("-U\t\tAtomic User Record Layer Callbacks");
        if (WolfSSL.isEnabledPKCallbacks() == 1)
            System.out.println("-P\t\tPublic Key Callbacks");
        if (WolfSSL.isEnabledCRLMonitor() == 1)
            System.out.println("-m\t\tEnable CRL directory monitor");
        System.exit(1);
    }

} /* end Server */

