/**
File: PeerSink.cs
Content: Client and Server Channel Sinks for Peer 2 Peer Web Services
Author: Ashish Banerjee
Created: 20-Aug-2001
Updated: 16-sept-2001 more debug tracing added

Copyright:(c) Ashish Banerjee, 2001

THIS CODE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE. 

Feel free to change or use this code for non commercial use,
but please acknowledge and retain the above copyright and disclaimer.

**/

using System;
using System.Collections;
using System.IO;
using System.Reflection;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Metadata;
using System.Diagnostics;

using System.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;

using Peer.Crypt;

namespace Peer.Channel
{

public class PeerClientSinkProvider :  IClientChannelSinkProvider
{
    public PeerClientSinkProvider()
    {
    }
    public PeerClientSinkProvider(IDictionary d, ICollection c)
    {
       string dbg = (string) d["verbose"];
       if(dbg != null && dbg.Length > 0)
           logger.verbose = int.Parse(dbg);
       Console.WriteLine("verbose = {0}",logger.verbose);

       logger.tag = "PeerClient";
       
       string prvKeyFile = (string) d["PrivateKeyFile"];
       if(prvKeyFile == null)
          throw new SecurityException("[PrivateKeyFile] attribute undefined");
       
       string keyStoreFile = (string) d["KeyStoreFile"];
       if(keyStoreFile == null)
          throw new SecurityException("[KeyStoreFile] attribute undefined");

       try {
          prvKey = new RSACryptoServiceProvider();
          string xstr = FileUtil.LoadString(prvKeyFile);
          prvKey.FromXmlString(xstr);
       }
       catch(Exception ex)
       {
          logger.Display(ex);
          throw new SecurityException("Error Loading Private Key");
       }
       try {
          keyStore = new HashKeyStore();
          keyStore.Load(keyStoreFile);
       }
       catch(Exception ex)
       {
          logger.Display(ex);
          throw new SecurityException("Error Loading Private Key");
       }
       
       logger.PrintStackPeer(("ctor of "+logger.tag+"@PeerClientSinkProvider"));
    }
    public IKeyStore KeyStore
    {
      get { return keyStore;}
    }
    public IClientChannelSink CreateSink(IChannelSender channel, String url, 
                                         Object remoteChannelData)
    {

        logger.WriteLine("***PeerClientSinkProvider.CreateSink() called");
        logger.WriteLine("   url: {0}",url);
        if(channel != null)
           logger.WriteLine("   Type of IChannelSender: {0}",channel.GetType());
        if(remoteChannelData != null)
           logger.WriteLine("   Type of remoteChannelData: {0}",remoteChannelData.GetType());
        
        IClientChannelSink nextSink = null;
        if (next != null)
        {
            logger.WriteLine(" calling next.CreateSink()");

            nextSink = next.CreateSink(channel, url, remoteChannelData);
            if (nextSink == null)
            {
               logger.WriteLine(" nextSink is null");
               return null;
            }
            else
              logger.WriteLine("After next.CreateSink() type is[{0}]",nextSink);
        }
        RSACryptoServiceProvider targetRsa = null;
        try 
        { 
           targetRsa = (RSACryptoServiceProvider)keyStore.GetPublicKey(url);
        }
        catch(Exception ex)
        {
           logger.Display(ex);
           throw new SecurityException("Error getting public key ["+url+"]",ex);
        }
        if(targetRsa == null)
        {
           throw new SecurityException(("No Public Key for ["+url+"]"));
        }
          
        return new PeerClientSink(nextSink, prvKey, targetRsa, logger);
    }


    public IClientChannelSinkProvider Next
    {
        get 
        {
          logger.WriteLine(" Property get Next of PeerClientSinkProvider"); 
          logger.PrintStackPeer("get Next@IClientChannelSinkProvider");
        
           return next; 
        }
        set 
        { 
          next = value; 
          logger.WriteLine(" Property set Next of PeerClientFormatterSinkProvider called with [{0}]",value); 
          logger.PrintStackPeer("set Next@IClientChannelSinkProvider");
        }
    }

    private IClientChannelSinkProvider next = null;
    private Logger logger = new Logger();
    private RSACryptoServiceProvider prvKey = null;
    private HashKeyStore keyStore = null; 
}


internal class PeerClientSink : IMessageSink, IClientChannelSink, IChannelSinkBase
{
   private IClientChannelSink nextSink;
   protected Logger logger;
   protected RSACryptoServiceProvider prvKey;
   protected RSACryptoServiceProvider targetKey;

   public PeerClientSink( IClientChannelSink nextSink,
                             RSACryptoServiceProvider prvKey,
                               RSACryptoServiceProvider targetKey,
                                                      Logger logger) 
   {
      this.logger = logger;
      this.prvKey = prvKey;
      this.targetKey = targetKey;

      logger.WriteLine("ctor {0} tag[{1}]",this,logger.tag); 
      
      this.nextSink = nextSink;

      logger.WriteLine("The next IClientChannelSink --> {0}",nextSink);
   }
   
   public IMessageCtrl AsyncProcessMessage(IMessage msg,IMessageSink replySink)
   {
      logger.WriteLine("***PeerClientFormatterSink.AsyncProcessMessage() called");
      return NextSink.AsyncProcessMessage(msg,replySink); 
   }
   
   public void AsyncProcessRequest(
                    IClientChannelSinkStack sinkStack,
                        IMessage msg,
                            ITransportHeaders headers,
                                Stream stream)
   {
      logger.WriteLine("***{0}@AsyncProcessRequest() called",logger.tag);
      
      sinkStack.Push(this, null);
      nextSink.AsyncProcessRequest(sinkStack, msg, headers, stream);
   }

   public void AsyncProcessResponse(
        IClientResponseChannelSinkStack sinkStack, object state,
                ITransportHeaders headers,Stream stream )
   {
      logger.WriteLine("***{0}@AsyncProcessResponse() called",logger.tag);
      nextSink.AsyncProcessResponse(sinkStack,state,headers,stream);   
   }
   public Stream GetRequestStream( IMessage msg,ITransportHeaders headers )
   {
      logger.PrintStackPeer(("GetRequestStream@"+logger.tag)); 
      Stream ret = null;
      
      if(nextSink != null)
        ret = nextSink.GetRequestStream(msg,headers); 

      logger.WriteLine("return Stream@{0} type -> {1}", logger.tag,ret);
      
      return ret;
   }
   public void ProcessMessage(IMessage msg,ITransportHeaders requestHeaders,
                               Stream requestStream,
                               out ITransportHeaders responseHeaders,
                               out Stream responseStream )
   {
      logger.PrintStackPeer(("ProcessMessage@"+logger.tag)); 
      logger.PrintRequest(requestHeaders, ref requestStream);
      logger.PrintMessage(msg);
      
      Crypton crypt = new Crypton(prvKey);
      Stream sigEncStream = null;
      
      try 
      {
         sigEncStream = crypt.SignEncrypt(requestStream, targetKey);
      }
      catch(Exception ex)
      {
         logger.Display(ex);
         throw new SecurityException("Error Signing & Encrypting ",ex);
      }

      logger.WriteLine("After SignEncrypt @"+logger.tag);
      logger.PrintStream(sigEncStream);
      sigEncStream.Position =0;
      //requestHeaders["Content-Length"] = sigEncStream.Length;

      //********* next sink *********
      nextSink.ProcessMessage(msg,requestHeaders, sigEncStream ,
                            out responseHeaders, out responseStream);
      
      MemoryStream resStream = SafeCopy( ref responseStream);

      logger.WriteLine("After calling nextSink<{0}>.ProcessMessage @{1}",nextSink, logger.tag);
      Stream t = resStream;
      logger.PrintResponse(responseHeaders, ref t);
      
      
      
      RSACryptoServiceProvider retTargKey=null;
      
      MemoryStream decryptOut = null;
      try 
      {
         decryptOut = crypt.DecryptVerify(resStream, out retTargKey);
      }
      catch(Exception ex)
      {
         logger.Display(ex);
         throw new SecurityException("Error Decrypting ",ex);
      }
      //TODO: To stop PLAY BACK attack, enforce a time stamp in encrypt 
      //       packet, or make the secret key to be encrypted & sent back.
      
      if((decryptOut==null) || !RSAComparer.Equals(targetKey,retTargKey))
      {
         throw new SecurityException("Decryption Error OR Under Intercept Attack");
      }
      
      logger.WriteLine("Post Decrypt ");
      logger.PrintStream(decryptOut);

      responseStream = decryptOut; 
      TransportHeaders resHdr = new TransportHeaders();
      foreach (DictionaryEntry header in responseHeaders)
      {
           if(((string)header.Key) != "Content-Length")
               resHdr[header.Key] = header.Value;
      }
      responseHeaders = resHdr;
   }
   public IMessage SyncProcessMessage(IMessage msg)
   {
      logger.PrintStackPeer(("SyncProcessMessage@"+logger.tag)); 
      logger.WriteLine("*** Input Message ***");
      logger.PrintMessage(msg);
     
      logger.WriteLine("BEFORE calling nextSink<{0}>.SyncProcessMessage @{1}",nextSink, logger.tag);

      IMessage rmsg = NextSink.SyncProcessMessage(msg);

      logger.WriteLine("AFTER calling nextSink<{0}>.SyncProcessMessage @{1}",nextSink, logger.tag);
      logger.WriteLine("*** Output Message ***");
      logger.PrintMessage(msg);
      
      return msg;
   }
   public IDictionary Properties 
   {
      get 
      { 
        logger.PrintStackPeer((logger.tag+"@Properties"));
        return prop; 
      }
   }
   
   private Hashtable prop = new Hashtable();

   public IClientChannelSink NextChannelSink 
   {
     get { return nextSink;}
   }
   public IMessageSink NextSink 
   {
      get
      {
        
        IMessageSink ret = null;
        for(IClientChannelSink nxt = nextSink; nxt != null; nxt = nxt.NextChannelSink)
        {
           if(nxt is  IMessageSink)
           {
             ret = (IMessageSink) nxt;
             break;
           }
        }
           
        return ret;
      }
   }
   protected MemoryStream SafeCopy(ref Stream responseStream)
   {
       MemoryStream resStream = new MemoryStream();
       StreamWriter sw = new   StreamWriter(resStream);
       StreamReader sr = new StreamReader(responseStream);
       String line;
       while ((line = sr.ReadLine()) != null)
       {
           sw.WriteLine(line);
       }
       sw.Flush();
       resStream.Position = 0;
       
       return resStream;
    }
}
public class PeerServerChannelSinkProvider : IServerChannelSinkProvider
{
    private IServerChannelSinkProvider next = null;
    private Logger logger = new Logger();
    private RSACryptoServiceProvider prvKey = null;

    public PeerServerChannelSinkProvider()
    {
    }

    public PeerServerChannelSinkProvider(IDictionary d, ICollection providerData)
    {
       string dbg = (string) d["verbose"];
       if(dbg != null && dbg.Length > 0)
           logger.verbose = int.Parse(dbg);
       Console.WriteLine("verbose = {0}",logger.verbose);

       logger.tag = "PeerServer";

       string prvKeyFile = (string) d["PrivateKeyFile"];
       if(prvKeyFile == null)
          throw new SecurityException("[PrivateKeyFile] attribute undefined");
           
       try {
          prvKey = new RSACryptoServiceProvider();
          string xstr = FileUtil.LoadString(prvKeyFile);
          prvKey.FromXmlString(xstr);
       }
       catch(Exception ex)
       {
          logger.Display(ex);
          throw new SecurityException("Error Loading Private Key");
       }
       
       logger.PrintStackPeer(("ctor of "+logger.tag+"@PeerServerChannelSinkProvider"));

    }

    public void GetChannelData(IChannelDataStore channelData)
    {
        logger.WriteLine("{0}@GetChannelData({1})",logger.tag,channelData); 
        if(channelData != null)
        {
           string [] uriz = channelData.ChannelUris;
           if(uriz != null)
              for(int i=0; i < uriz.Length; i++)
                 logger.WriteLine("uri[{0}] = {1}",i,uriz[i]);
        }
        logger.PrintStackPeer((logger.tag+"@GetChannelData"));
    }

    public IServerChannelSink CreateSink(IChannelReceiver channel)
    {
        logger.WriteLine("{0}@CreateSink({1})",logger.tag,channel); 
        logger.PrintStackPeer((logger.tag+"@CreateSink"));

        IServerChannelSink nextSink = null;
        if (next != null)
        {
            logger.WriteLine("calling {0}@CreateSink({1})",next,channel); 
            nextSink = next.CreateSink(channel);
        }
        return new PeerServerChannelSink(nextSink, prvKey, logger);
    }

    public IServerChannelSinkProvider Next
    {
        get 
        { 
           logger.PrintStackPeer((logger.tag+"@Next being called"));
           return next; 
        }
        set 
        { 
           next = value; 
        }
    }
} //END: class PeerServerChannelSinkProvider


internal class PeerServerChannelSink :  IServerChannelSink
{                                          
    private IServerChannelSink nextSink = null;
    private Logger logger;
    private RSACryptoServiceProvider prvKey = null;

    public PeerServerChannelSink(IServerChannelSink nextSink, 
                                        RSACryptoServiceProvider prvKey,  
                                            Logger logger) : base()
    {
        this.nextSink = nextSink;
        this.logger   = logger;
        this.prvKey = prvKey;

        logger.WriteLine("ctor {0}@PeerServerChannelSink({1})",logger.tag,nextSink);
    } 


    public ServerProcessing ProcessMessage(IServerChannelSinkStack sinkStack, IMessage requestMsg,
                                           ITransportHeaders requestHeaders, Stream requestStream,
                                           out IMessage msg, out ITransportHeaders responseHeaders,
                                           out Stream responseStream)
    {
        
        MemoryStream reqCopyStream = (MemoryStream)logger.CopyStream(requestStream);
        
        logger.WriteLine("Type of request stream {0}", requestStream.GetType());
        logger.PrintStackPeer((logger.tag+"@ProcessMessage"));
        //logger.PrintRequest(requestHeaders, ref requestStream);
    
        byte[] bx = reqCopyStream.ToArray();
        logger.WriteLine(" --ProcessMessage-- {0}",bx.Length);
        Stream t =  (Stream) reqCopyStream;
        logger.PrintRequest(requestHeaders, ref t);

        RSACryptoServiceProvider clntPubKey = null;

      Crypton crypt = new Crypton(prvKey);
      MemoryStream decryptOut = null;
      
      FileUtil.SaveBytes("AAA.xml", bx);
      logger.WriteLine("AAA.xml saved {0}", bx.Length);
      
      reqCopyStream.Position =0;
      try 
      {
         logger.WriteLine("typeof request stream 1: {0}", requestStream.GetType());
         decryptOut = crypt.DecryptVerify(reqCopyStream, out clntPubKey);
      }
      catch(Exception ex)
      {
         logger.WriteLine("ERROR: Decrypt Verify Failed");
         logger.Display(ex);
         throw new SecurityException("Error Decrypting ",ex);
      }
      if((decryptOut==null) || (clntPubKey == null))
      {
         logger.WriteLine("ERROR: {0}@ProcessMessage --> decryptOut or clntPubKey is null", logger.tag);
         throw new SecurityException("Decryption got null key or stream");
      }
      
      decryptOut.Position =0;
      logger.WriteLine("After Decrypt and verify -- dumping stream--");
      logger.PrintStream(decryptOut);
      
      decryptOut.Position =0;
      TransportHeaders reqHdr = new TransportHeaders();
      foreach (DictionaryEntry header in requestHeaders)
      {
           if(((string)header.Key) != "Content-Length")
               reqHdr[header.Key] = header.Value;
      }
      logger.WriteLine("Printing Request hdr {0}", decryptOut.Length);
      //logger.PrintHeaders(requestHeaders);
      logger.PrintHeaders(reqHdr);
      logger.WriteLine("---- end Printing Request hdr----");

      sinkStack.Push(this, clntPubKey);

      logger.WriteLine("before calling {0}@ProcessMessage",nextSink);
      ServerProcessing processing =
            nextSink.ProcessMessage(sinkStack, requestMsg, reqHdr, decryptOut, out msg,
                                     out responseHeaders, out responseStream);

        switch (processing)
        {

        case ServerProcessing.Complete:
        {
            sinkStack.Pop(this);
            logger.WriteLine("Got ServerProcessing.Complete {0}@ProcessMessage",logger.tag);
            logger.PrintResponse(responseHeaders, ref responseStream);
            logger.PrintMessage(msg);
          Stream sigEncStream = null;
          try 
          {
             sigEncStream = crypt.SignEncrypt(responseStream, clntPubKey);
          }
          catch(Exception ex)
          {
             logger.Display(ex);
             throw new SecurityException("Error Signing & Encrypting ",ex);
          }
          if(sigEncStream == null)
             throw new SecurityException("Sign Encrypt stream is null");

          logger.WriteLine("After SignEncrypt @"+logger.tag);
          logger.PrintStream(sigEncStream);

          responseStream = sigEncStream;
            break;
        }

        case ServerProcessing.OneWay:
        {
            logger.WriteLine("Got ServerProcessing.OneWay {0}@ProcessMessage",logger.tag);
            sinkStack.Pop(this);
            break;
        }

        case ServerProcessing.Async:
        {
            logger.WriteLine("Got ServerProcessing.Async {0}@ProcessMessage",logger.tag);
            sinkStack.Store(this, clntPubKey);
            break;
        }

        } //END: switch (processing)

        return processing;
    } 


    public void AsyncProcessResponse(IServerResponseChannelSinkStack sinkStack, Object state,
                                    IMessage msg, ITransportHeaders headers, Stream stream)
    {
         logger.PrintStackPeer((logger.tag+"@AsyncProcessResponse"));
         logger.PrintMessage(msg);
         logger.PrintResponse(headers, ref stream);
         
         sinkStack.AsyncProcessResponse(msg, headers, stream);
    } 


    public Stream GetResponseStream(IServerResponseChannelSinkStack sinkStack, Object state,
                                    IMessage msg, ITransportHeaders headers)
    {
        logger.PrintStackPeer((logger.tag+"@GetResponseStream"));
        logger.PrintMessage(msg);

        return null; //the saop formatter creates a mem chunk stream on null
    } 


    public IServerChannelSink NextChannelSink
    {
        get 
        { 
          logger.PrintStackPeer((logger.tag+"@NextChannelSink"));
          logger.WriteLine("nextSink --> {0}",nextSink);
          return nextSink; 
        }
    }

    public IDictionary Properties {get { return prop; }}
    private Hashtable prop = new Hashtable();
} //END: class PeerServerChannelSink

internal class Logger
{
    public Logger()
    {
      verbose = 0;
    }
    public Logger(int verbose)
    {
      this.verbose = verbose;
    }
    
    public void WriteLine(string fmt, params object[] args)
    {
       if(verbose >= 1)
       {
          output.WriteLine(fmt,args);
          output.Flush();
       }
    }
    public void WriteLine(string msg)
    { 
       Display(msg,1);
    }
    public void Display(Exception ex)
    {
       if(verbose >= 6)
         output.WriteLine(ex.StackTrace);

       output.WriteLine(ex.Message);
       output.Flush();
    }
    public  void Display(string msg, int level)
    {
       if(verbose >= level)
       {
         output.WriteLine(msg);
         output.Flush();
       }
    }
    public  void PrintRequest(ITransportHeaders requestHeaders, 
                                                  ref Stream requestStream)
    {
        if(verbose < 2)
          return;
        output.WriteLine("----------Request Headers-----------");
        PrintHeaders(requestHeaders);

        // print request message
        String contentType = requestHeaders["Content-Type"] as String;
        if ((contentType != null) && contentType.StartsWith("text"))
        {
            output.WriteLine("----------Request Message-----------");
            PrintStream( requestStream);
            output.WriteLine("------End of Request Message--------");
        }   
        output.Flush();
    } // PrintRequest


    public void PrintResponse(ITransportHeaders responseHeaders, 
                                                  ref Stream responseStream)
    {            
        if(verbose < 2)
          return;
        output.WriteLine("----------Response Headers----------");
        PrintHeaders(responseHeaders);

        // print response message
        String contentType = responseHeaders["Content-Type"] as String;
        if ((contentType != null) && contentType.StartsWith("text"))
        {
            output.WriteLine("----------Response Message----------");
            PrintStream(responseStream); 
            output.WriteLine("------End of Response Message-------");
        }
        output.Flush();
    } // PrintResponse


    public  void PrintHeaders(ITransportHeaders headers)
    {
        foreach (DictionaryEntry header in headers)
        {
            output.WriteLine(header.Key + ": " + header.Value);   
        }
    } // PrintHeaders


    public void PrintStream(Stream stream)
    {
        if(verbose < 2)
          return;
        output.WriteLine("---------- Stream START ---------");
        output.WriteLine("Type {0}",stream.GetType());
        
        //  since it is not seekable stream,  make a copy,
        if (!stream.CanSeek)
            stream = CopyStream(stream);

       
        long startPosition = stream.Position;

        StreamReader sr = new StreamReader(stream);
        String line;
        while ((line = sr.ReadLine()) != null)
        {
            output.WriteLine(line);
        }
        output.WriteLine("---------- Stream END ---------");
        stream.Position = startPosition;
        output.Flush();
    } // PrintStream

    public void PrintMessage(IMessage msg)
    {
        if(verbose < 3)
          return;

        IDictionary d = msg.Properties;
        IDictionaryEnumerator e = (IDictionaryEnumerator) d.GetEnumerator();
        output.WriteLine("----------- IMessage START ---------");
        output.WriteLine("Type {0}",msg.GetType());
        while (e.MoveNext())
        {
            Object key = e.Key;
            String keyName = key.ToString();
            Object value = e.Value;

            output.WriteLine("\t{0} : {1}", keyName, e.Value);
            if (keyName == "__Args")
            {
                Object[] args = (Object[])value;
                for (int a = 0; a < args.Length; a++)
                    output.WriteLine("\t\targ: {0} value: {1}", a, args[a]);
            }

            if ((keyName == "__MethodSignature") && (null != value))
            {
                Object[] args = (Object[])value;
                for (int a = 0; a < args.Length; a++)
                    output.WriteLine("\t\targ: {0} value: {1}", a, args[a]);
            }

        }

        output.WriteLine("URI {0}", d["__Uri"]);
        output.WriteLine("----------- IMessage END ---------");
        output.Flush();
    }
    public Stream CopyStream(Stream stream)
    {
        Stream streamCopy = new MemoryStream();

        const int bufferSize = 1024;
        byte[] buffer = new byte[bufferSize];

        int readCount;
        do
        {
            readCount = stream.Read(buffer, 0, bufferSize);
            if (readCount > 0)
                streamCopy.Write(buffer, 0, readCount);
        } while (readCount > 0);

        // close original stream
        stream.Close();

        streamCopy.Position = 0;
        return streamCopy;
    } // CopyStream  
    public void PrintStackPeer(string msgTag)
    {
        if(verbose < 5)
          return;
      StackTrace trace = new StackTrace(); 
      output.WriteLine("---- START StackTrace {0} ----", msgTag);
      output.WriteLine(trace);
      output.WriteLine("---- END StackTrace ----");
      output.Flush();
    }
    public TextWriter output = Console.Out;
    public int verbose = 0;
    public string tag = "";
}

} //END: Namespace