/*
 * Copyright (C) 2005 Alfresco, Inc.
 *
 * Licensed under the Mozilla Public License version 1.1 
 * with a permitted attribution clause. You may obtain a
 * copy of the License at
 *
 *   http://www.alfresco.org/legal/license.txt
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific
 * language governing permissions and limitations under the
 * License.
 */
package org.alfresco.filesys.smb.server;

import java.io.IOException;
import java.util.Enumeration;

import org.alfresco.filesys.server.core.ShareType;
import org.alfresco.filesys.server.core.SharedDevice;
import org.alfresco.filesys.server.core.SharedDeviceList;
import org.alfresco.filesys.smb.PacketType;
import org.alfresco.filesys.smb.SMBStatus;
import org.alfresco.filesys.smb.TransactBuffer;
import org.alfresco.filesys.util.DataBuffer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * IPC$ Transaction handler for \PIPE\LANMAN requests.
 */
class PipeLanmanHandler
{
    private static final Log logger = LogFactory.getLog("org.alfresco.smb.protocol");

    // Server capability flags

    public static final int WorkStation =   0x00000001;
    public static final int Server =        0x00000002;
    public static final int SQLServer =     0x00000004;
    public static final int DomainCtrl =    0x00000008;
    public static final int DomainBakCtrl = 0x00000010;
    public static final int TimeSource =    0x00000020;
    public static final int AFPServer =     0x00000040;
    public static final int NovellServer =  0x00000080;
    public static final int DomainMember =  0x00000100;
    public static final int PrintServer =   0x00000200;
    public static final int DialinServer =  0x00000400;
    public static final int UnixServer =    0x00000800;
    public static final int NTServer =      0x00001000;
    public static final int WfwServer =     0x00002000;
    public static final int MFPNServer =    0x00004000;
    public static final int NTNonDCServer = 0x00008000;
    public static final int PotentialBrowse = 0x00010000;
    public static final int BackupBrowser = 0x00020000;
    public static final int MasterBrowser = 0x00040000;
    public static final int DomainMaster =  0x00080000;
    public static final int OSFServer =     0x00100000;
    public static final int VMSServer =     0x00200000;
    public static final int Win95Plus =     0x00400000;
    public static final int DFSRoot =       0x00800000;
    public static final int NTCluster =     0x01000000;
    public static final int TerminalServer = 0x02000000;
    public static final int DCEServer =     0x10000000;
    public static final int AlternateXport = 0x20000000;
    public static final int LocalListOnly = 0x40000000;
    public static final int DomainEnum =    0x80000000;

    /**
     * Process a \PIPE\LANMAN transaction request.
     * 
     * @param tbuf Transaction setup, parameter and data buffers
     * @param sess SMB server session that received the transaction.
     * @param trans Packet to use for reply
     * @return true if the transaction has been handled, else false.
     * @exception java.io.IOException If an I/O error occurs
     * @exception SMBSrvException If an SMB protocol error occurs
     */
    public final static boolean processRequest(TransactBuffer tbuf, SMBSrvSession sess, SMBSrvPacket trans)
            throws IOException, SMBSrvException
    {

        // Create a transaction packet

        SMBSrvTransPacket tpkt = new SMBSrvTransPacket(trans.getBuffer());

        // Get the transaction command code, parameter descriptor and data descriptor strings from
        // the parameter block.

        DataBuffer paramBuf = tbuf.getParameterBuffer();

        int cmd = paramBuf.getShort();
        String prmDesc = paramBuf.getString(false);
        String dataDesc = paramBuf.getString(false);

        // Debug

        if (logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_IPC))
            logger.debug("\\PIPE\\LANMAN\\ transact request, cmd=" + cmd + ", prm=" + prmDesc + ", data=" + dataDesc);

        // Call the required transaction handler

        boolean processed = false;

        switch (cmd)
        {

        // Share

        case PacketType.RAPShareEnum:
            processed = procNetShareEnum(sess, tbuf, prmDesc, dataDesc, tpkt);
            break;

        // Get share information

        case PacketType.RAPShareGetInfo:
            processed = procNetShareGetInfo(sess, tbuf, prmDesc, dataDesc, tpkt);
            break;

        // Workstation information

        case PacketType.RAPWkstaGetInfo:
            processed = procNetWkstaGetInfo(sess, tbuf, prmDesc, dataDesc, tpkt);
            break;

        // Server information

        case PacketType.RAPServerGetInfo:
            processed = procNetServerGetInfo(sess, tbuf, prmDesc, dataDesc, tpkt);
            break;

        // Print queue information

        case PacketType.NetPrintQGetInfo:
            processed = procNetPrintQGetInfo(sess, tbuf, prmDesc, dataDesc, tpkt);
            break;

        // No handler

        default:

            // Debug

            if (logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_IPC))
                logger.debug("No handler for \\PIPE\\LANMAN\\ request, cmd=" + cmd + ", prm=" + prmDesc + ", data="
                        + dataDesc);
            break;
        }

        // Return the transaction processed status

        return processed;
    }

    /**
     * Process a NetServerGetInfo transaction request.
     * 
     * @param sess Server session that received the request.
     * @param tbuf Transaction buffer
     * @param prmDesc Parameter descriptor string.
     * @param dataDesc Data descriptor string.
     * @param tpkt Transaction reply packet
     * @return true if the transaction has been processed, else false.
     */
    protected final static boolean procNetServerGetInfo(SMBSrvSession sess, TransactBuffer tbuf, String prmDesc,
            String dataDesc, SMBSrvTransPacket tpkt) throws IOException, SMBSrvException
    {

        // Validate the parameter string

        if (prmDesc.compareTo("WrLh") != 0)
            throw new SMBSrvException(SMBStatus.SRVInternalServerError, SMBStatus.ErrSrv);

        // Unpack the server get information specific parameters

        DataBuffer paramBuf = tbuf.getParameterBuffer();

        int infoLevel = paramBuf.getShort();
        int bufSize = paramBuf.getShort();

        // Debug

        if (logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_IPC))
            logger.debug("NetServerGetInfo infoLevel=" + infoLevel);

        // Check if the information level requested and data descriptor string match

        if (infoLevel == 1 && dataDesc.compareTo("B16BBDz") == 0)
        {

            // Create the transaction reply data buffer

            TransactBuffer replyBuf = new TransactBuffer(tbuf.isType(), 0, 6, 1024);

            // Pack the parameter block

            paramBuf = replyBuf.getParameterBuffer();

            paramBuf.putShort(0); // status code
            paramBuf.putShort(0); // converter for strings
            paramBuf.putShort(1); // number of entries

            // Pack the data block, calculate the size of the fixed data block

            DataBuffer dataBuf = replyBuf.getDataBuffer();
            int strPos = SMBSrvTransPacket.CalculateDataItemSize("B16BBDz");

            // Pack the server name pointer and string

            dataBuf.putStringPointer(strPos);
            strPos = dataBuf.putFixedStringAt(sess.getServerName(), 16, strPos);

            // Pack the major/minor version

            dataBuf.putByte(1);
            dataBuf.putByte(0);

            // Pack the server capability flags

            dataBuf.putInt(sess.getSMBServer().getServerType());

            // Pack the server comment string

            String srvComment = sess.getSMBServer().getComment();
            if (srvComment == null)
                srvComment = "";

            dataBuf.putStringPointer(strPos);
            strPos = dataBuf.putStringAt(srvComment, strPos, false, true);

            // Set the data block length

            dataBuf.setLength(strPos);

            // Send the transaction response

            tpkt.doTransactionResponse(sess, replyBuf);
        }
        else
            throw new SMBSrvException(SMBStatus.SRVInternalServerError, SMBStatus.ErrSrv);

        // We processed the request

        return true;
    }

    /**
     * Process a NetShareEnum transaction request.
     * 
     * @param sess Server session that received the request.
     * @param tbuf Transaction buffer
     * @param prmDesc Parameter descriptor string.
     * @param dataDesc Data descriptor string.
     * @param tpkt Transaction reply packet
     * @return true if the transaction has been processed, else false.
     */
    protected final static boolean procNetShareEnum(SMBSrvSession sess, TransactBuffer tbuf, String prmDesc,
            String dataDesc, SMBSrvTransPacket tpkt) throws IOException, SMBSrvException
    {

        // Validate the parameter string

        if (prmDesc.compareTo("WrLeh") != 0)
            throw new SMBSrvException(SMBStatus.SRVInternalServerError, SMBStatus.ErrSrv);

        // Unpack the server get information specific parameters

        DataBuffer paramBuf = tbuf.getParameterBuffer();

        int infoLevel = paramBuf.getShort();
        int bufSize = paramBuf.getShort();

        // Debug

        if (logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_IPC))
            logger.debug("NetShareEnum infoLevel=" + infoLevel);

        // Check if the information level requested and data descriptor string match

        if (infoLevel == 1 && dataDesc.compareTo("B13BWz") == 0)
        {

            // Get the share list from the server

            SharedDeviceList shrList = sess.getSMBServer().getShareList(null, sess);
            int shrCount = 0;
            int strPos = 0;

            if (shrList != null)
            {

                // Calculate the fixed data length

                shrCount = shrList.numberOfShares();
                strPos = SMBSrvTransPacket.CalculateDataItemSize("B13BWz") * shrCount;
            }

            // Create the transaction reply data buffer

            TransactBuffer replyBuf = new TransactBuffer(tbuf.isType(), 0, 6, bufSize);

            // Pack the parameter block

            paramBuf = replyBuf.getParameterBuffer();

            paramBuf.putShort(0); // status code
            paramBuf.putShort(0); // converter for strings
            paramBuf.putShort(shrCount); // number of entries
            paramBuf.putShort(shrCount); // total number of entries

            // Pack the data block

            DataBuffer dataBuf = replyBuf.getDataBuffer();
            Enumeration<SharedDevice> enm = shrList.enumerateShares();

            while (enm.hasMoreElements())
            {

                // Get the current share

                SharedDevice shrDev = enm.nextElement();

                // Pack the share name, share type and comment pointer

                dataBuf.putFixedString(shrDev.getName(), 13);
                dataBuf.putByte(0);
                dataBuf.putShort(ShareType.asShareInfoType(shrDev.getType()));
                dataBuf.putStringPointer(strPos);

                if (shrDev.getComment() != null)
                    strPos = dataBuf.putStringAt(shrDev.getComment(), strPos, false, true);
                else
                    strPos = dataBuf.putStringAt("", strPos, false, true);
            }

            // Set the data block length

            dataBuf.setLength(strPos);

            // Send the transaction response

            tpkt.doTransactionResponse(sess, replyBuf);
        }
        else
            throw new SMBSrvException(SMBStatus.SRVInternalServerError, SMBStatus.ErrSrv);

        // We processed the request

        return true;
    }

    /**
     * Process a NetShareGetInfo transaction request.
     * 
     * @param sess Server session that received the request.
     * @param tbuf Transaction buffer
     * @param prmDesc Parameter descriptor string.
     * @param dataDesc Data descriptor string.
     * @param tpkt Transaction reply packet
     * @return true if the transaction has been processed, else false.
     */
    protected final static boolean procNetShareGetInfo(SMBSrvSession sess, TransactBuffer tbuf, String prmDesc,
            String dataDesc, SMBSrvTransPacket tpkt) throws IOException, SMBSrvException
    {

        // Validate the parameter string

        if (prmDesc.compareTo("zWrLh") != 0)
            throw new SMBSrvException(SMBStatus.SRVInternalServerError, SMBStatus.ErrSrv);

        // Unpack the share get information specific parameters

        DataBuffer paramBuf = tbuf.getParameterBuffer();

        String shareName = paramBuf.getString(32, false);
        int infoLevel = paramBuf.getShort();
        int bufSize = paramBuf.getShort();

        // Debug

        if (logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_IPC))
            logger.debug("NetShareGetInfo - " + shareName + ", infoLevel=" + infoLevel);

        // Check if the information level requested and data descriptor string match

        if (infoLevel == 1 && dataDesc.compareTo("B13BWz") == 0)
        {

            // Find the required share information

            SharedDevice share = null;

            try
            {

                // Get the shared device details

                share = sess.getSMBServer().findShare(null, shareName, ShareType.UNKNOWN, sess, false);
            }
            catch (Exception ex)
            {
            }

            if (share == null)
            {
                sess.sendErrorResponseSMB(SMBStatus.SRVInternalServerError, SMBStatus.ErrSrv);
                return true;
            }

            // Create the transaction reply data buffer

            TransactBuffer replyBuf = new TransactBuffer(tbuf.isType(), 0, 6, 1024);

            // Pack the parameter block

            paramBuf = replyBuf.getParameterBuffer();

            paramBuf.putShort(0); // status code
            paramBuf.putShort(0); // converter for strings
            paramBuf.putShort(1); // number of entries

            // Pack the data block, calculate the size of the fixed data block

            DataBuffer dataBuf = replyBuf.getDataBuffer();
            int strPos = SMBSrvTransPacket.CalculateDataItemSize("B13BWz");

            // Pack the share name

            dataBuf.putStringPointer(strPos);
            strPos = dataBuf.putFixedStringAt(share.getName(), 13, strPos);

            // Pack unknown byte, alignment ?

            dataBuf.putByte(0);

            // Pack the share type flags

            dataBuf.putShort(share.getType());

            // Pack the share comment

            dataBuf.putStringPointer(strPos);

            if (share.getComment() != null)
                strPos = dataBuf.putStringAt(share.getComment(), strPos, false, true);
            else
                strPos = dataBuf.putStringAt("", strPos, false, true);

            // Set the data block length

            dataBuf.setLength(strPos);

            // Send the transaction response

            tpkt.doTransactionResponse(sess, replyBuf);
        }
        else
        {

            // Debug

            if (logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_IPC))
                logger.debug("NetShareGetInfo - UNSUPPORTED " + shareName + ", infoLevel=" + infoLevel + ", dataDesc="
                        + dataDesc);

            // Server error

            throw new SMBSrvException(SMBStatus.SRVInternalServerError, SMBStatus.ErrSrv);
        }

        // We processed the request

        return true;
    }

    /**
     * Process a NetWkstaGetInfo transaction request.
     * 
     * @param sess Server session that received the request.
     * @param tbuf Transaction buffer
     * @param prmDesc Parameter descriptor string.
     * @param dataDesc Data descriptor string.
     * @param tpkt Transaction reply packet
     * @return true if the transaction has been processed, else false.
     */
    protected final static boolean procNetWkstaGetInfo(SMBSrvSession sess, TransactBuffer tbuf, String prmDesc,
            String dataDesc, SMBSrvTransPacket tpkt) throws IOException, SMBSrvException
    {

        // Validate the parameter string

        if (prmDesc.compareTo("WrLh") != 0)
            throw new SMBSrvException(SMBStatus.SRVInternalServerError, SMBStatus.ErrSrv);

        // Unpack the share get information specific parameters

        DataBuffer paramBuf = tbuf.getParameterBuffer();

        int infoLevel = paramBuf.getShort();
        int bufSize = paramBuf.getShort();

        // Debug

        if (logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_IPC))
            logger.debug("NetWkstaGetInfo infoLevel=" + infoLevel);

        // Check if the information level requested and data descriptor string match

        if ((infoLevel == 1 && dataDesc.compareTo("zzzBBzzz") == 0)
                || (infoLevel == 10 && dataDesc.compareTo("zzzBBzz") == 0))
        {

            // Create the transaction reply data buffer

            TransactBuffer replyBuf = new TransactBuffer(tbuf.isType(), 0, 6, 1024);

            // Pack the data block, calculate the size of the fixed data block

            DataBuffer dataBuf = replyBuf.getDataBuffer();
            int strPos = SMBSrvTransPacket.CalculateDataItemSize(dataDesc);

            // Pack the server name

            dataBuf.putStringPointer(strPos);
            strPos = dataBuf.putStringAt(sess.getServerName(), strPos, false, true);

            // Pack the user name

            dataBuf.putStringPointer(strPos);
            strPos = dataBuf.putStringAt("", strPos, false, true);

            // Pack the domain name

            dataBuf.putStringPointer(strPos);

            String domain = sess.getServer().getConfiguration().getDomainName();
            if (domain == null)
                domain = "";
            strPos = dataBuf.putStringAt(domain, strPos, false, true);

            // Pack the major/minor version number

            dataBuf.putByte(4);
            dataBuf.putByte(2);

            // Pack the logon domain

            dataBuf.putStringPointer(strPos);
            strPos = dataBuf.putStringAt("", strPos, false, true);

            // Check if the other domains should be packed

            if (infoLevel == 1 && dataDesc.compareTo("zzzBBzzz") == 0)
            {

                // Pack the other domains

                dataBuf.putStringPointer(strPos);
                strPos = dataBuf.putStringAt("", strPos, false, true);
            }

            // Set the data block length

            dataBuf.setLength(strPos);

            // Pack the parameter block

            paramBuf = replyBuf.getParameterBuffer();

            paramBuf.putShort(0); // status code
            paramBuf.putShort(0); // converter for strings
            paramBuf.putShort(dataBuf.getLength());
            paramBuf.putShort(0); // number of entries

            // Send the transaction response

            tpkt.doTransactionResponse(sess, replyBuf);
        }
        else
        {

            // Debug

            if (logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_IPC))
                logger.debug("NetWkstaGetInfo UNSUPPORTED infoLevel=" + infoLevel + ", dataDesc=" + dataDesc);

            // Unsupported request

            throw new SMBSrvException(SMBStatus.SRVInternalServerError, SMBStatus.ErrSrv);
        }

        // We processed the request

        return true;
    }

    /**
     * Process a NetPrintQGetInfo transaction request.
     * 
     * @param sess Server session that received the request.
     * @param tbuf Transaction buffer
     * @param prmDesc Parameter descriptor string.
     * @param dataDesc Data descriptor string.
     * @param tpkt Transaction reply packet
     * @return true if the transaction has been processed, else false.
     */
    protected final static boolean procNetPrintQGetInfo(SMBSrvSession sess, TransactBuffer tbuf, String prmDesc,
            String dataDesc, SMBSrvTransPacket tpkt) throws IOException, SMBSrvException
    {

        // Validate the parameter string

        if (prmDesc.compareTo("zWrLh") != 0)
            throw new SMBSrvException(SMBStatus.SRVInternalServerError, SMBStatus.ErrSrv);

        // Unpack the share get information specific parameters

        DataBuffer paramBuf = tbuf.getParameterBuffer();

        String shareName = paramBuf.getString(32, false);
        int infoLevel = paramBuf.getShort();
        int bufSize = paramBuf.getShort();

        // Debug

        if (logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_IPC))
            logger.debug("NetPrintQGetInfo - " + shareName + ", infoLevel=" + infoLevel);

        // We did not process the request

        return false;
    }
}