This is the initial commit of the netconf server code.  It consists
of the following:
1) The server is built using Twisted Conch
2) It adapted an existing opensource netconf server (https://github.com/choppsv1/netconf)
   to handle some low-level protocols.  The adaptation is mostly around
   using Twisted Conch instead of Python Threads
3) A microservice to interface with Voltha on the SB and Netconf client on
   the NB
4) A set of credentials for the server and clients.  At this time these
   credentials are local and in files.  Additional work is required to
   secure these files
5) A rough-in to handle the rpc requests from Netconf clients
6) Code for initial handshaking is in place (hello)

Change-Id: I1ca0505d0ac35ff06066b107019ae87ae30e38f8
diff --git a/netconf/nc_server.py b/netconf/nc_server.py
new file mode 100644
index 0000000..99cff85
--- /dev/null
+++ b/netconf/nc_server.py
@@ -0,0 +1,204 @@
+#
+# Copyright 2016 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+#
+
+import structlog
+import sys
+from twisted.conch import avatar
+from twisted.cred import portal
+from twisted.conch.checkers import SSHPublicKeyChecker, InMemorySSHKeyDB
+from twisted.conch.ssh import factory, userauth, connection, keys, session
+from twisted.conch.ssh.transport import SSHServerTransport
+
+from twisted.cred.checkers import FilePasswordDB
+from twisted.internet import reactor
+from twisted.internet.defer import Deferred, inlineCallbacks
+# from twisted.python import log as logp
+from zope.interface import implementer
+from nc_protocol_handler import NetconfProtocolHandler
+
+from nc_connection import NetconfConnection
+
+# logp.startLogging(sys.stderr)
+
+log = structlog.get_logger()
+
+# Secure credentials directories
+# TODO:  In a production environment these locations require better
+# protection.  For now the user_passwords file is just a plain text file.
+KEYS_DIRECTORY = 'security/keys'
+CERTS_DIRECTORY = 'security/certificates'
+CLIENT_CRED_DIRECTORY = 'security/client_credentials'
+
+
+# @implementer(conchinterfaces.ISession)
+class NetconfAvatar(avatar.ConchUser):
+    def __init__(self, username, nc_server, grpc_stub):
+        avatar.ConchUser.__init__(self)
+        self.username = username
+        self.nc_server = nc_server
+        self.grpc_stub = grpc_stub
+        self.channelLookup.update({'session': session.SSHSession})
+        self.subsystemLookup.update(
+            {b"netconf": NetconfConnection})
+
+    def get_grpc_stub(self):
+        return self.grpc_stub
+
+    def get_nc_server(self):
+        return self.nc_server
+
+    def logout(self):
+        log.info('netconf-avatar-logout', username=self.username)
+
+
+@implementer(portal.IRealm)
+class NetconfRealm(object):
+    def __init__(self, nc_server, grpc_stub):
+        self.grpc_stub = grpc_stub
+        self.nc_server = nc_server
+
+    def requestAvatar(self, avatarId, mind, *interfaces):
+        user = NetconfAvatar(avatarId, self.nc_server, self.grpc_stub)
+        return interfaces[0], user, user.logout
+
+
+class NCServer(factory.SSHFactory):
+    #
+    services = {
+        'ssh-userauth': userauth.SSHUserAuthServer,
+        'ssh-connection': connection.SSHConnection
+    }
+
+    def __init__(self,
+                 netconf_port,
+                 server_private_key_file,
+                 server_public_key_file,
+                 client_public_keys_file,
+                 client_passwords_file,
+                 grpc_stub):
+
+        self.netconf_port = netconf_port
+        self.server_private_key_file = server_private_key_file
+        self.server_public_key_file = server_public_key_file
+        self.client_public_keys_file = client_public_keys_file
+        self.client_passwords_file = client_passwords_file
+        self.grpc_stub = grpc_stub
+        self.connector = None
+        self.nc_client_map = {}
+        self.running = False
+        self.exiting = False
+
+    def start(self):
+        log.debug('starting')
+        if self.running:
+            return
+        self.running = True
+        reactor.callLater(0, self.start_ssh_server)
+        log.info('started')
+        return self
+
+    def stop(self):
+        log.debug('stopping')
+        self.exiting = True
+        self.connector.disconnect()
+        self.d_stopped.callback(None)
+        log.info('stopped')
+
+    def client_disconnected(self, result, handler, reason):
+        assert isinstance(handler, NetconfProtocolHandler)
+
+        log.info('client-disconnected', reason=reason)
+
+        # For now just nullify the handler
+        handler.close()
+
+    def client_connected(self, client_conn):
+        assert isinstance(client_conn, NetconfConnection)
+        log.info('client-connected')
+        handler = NetconfProtocolHandler(self, client_conn,
+                                         self.grpc_stub)
+        client_conn.proto_handler = handler
+        reactor.callLater(0, handler.start)
+
+    def setup_secure_access(self):
+        try:
+            from twisted.cred import portal
+            portal = portal.Portal(NetconfRealm(self, self.grpc_stub))
+
+            # setup userid-password access
+            password_file = '{}/{}'.format(CLIENT_CRED_DIRECTORY,
+                                           self.client_passwords_file)
+            portal.registerChecker(FilePasswordDB(password_file))
+
+            # setup access when client uses keys
+            keys_file = '{}/{}'.format(CLIENT_CRED_DIRECTORY,
+                                       self.client_public_keys_file)
+            with open(keys_file) as f:
+                users = [line.rstrip('\n') for line in f]
+            users_dict = {}
+            for user in users:
+                users_dict[user.split(':')[0]] = [
+                    keys.Key.fromFile('{}/{}'.format(CLIENT_CRED_DIRECTORY,
+                                                     user.split(':')[1]))]
+            sshDB = SSHPublicKeyChecker(InMemorySSHKeyDB(users_dict))
+            portal.registerChecker(sshDB)
+            return portal
+        except Exception as e:
+            log.error('setup-secure-access-fail', exception=repr(e))
+
+    @inlineCallbacks
+    def start_ssh_server(self):
+        try:
+            log.debug('starting', port=self.netconf_port)
+            self.portal = self.setup_secure_access()
+            self.connector = reactor.listenTCP(self.netconf_port, self)
+            log.debug('started', port=self.netconf_port)
+            self.d_stopped = Deferred()
+            self.d_stopped.callback(self.stop)
+            yield self.d_stopped
+        except Exception as e:
+            log.error('netconf-server-not-started', port=self.netconf_port,
+                      exception=repr(e))
+
+    # Methods from SSHFactory
+    #
+
+    def protocol(self):
+        return SSHServerTransport()
+
+    def getPublicKeys(self):
+        key_file_name = '{}/{}'.format(KEYS_DIRECTORY,
+                                       self.server_public_key_file)
+        try:
+            publicKeys = {
+                'ssh-rsa': keys.Key.fromFile(key_file_name)
+            }
+            return publicKeys
+        except Exception as e:
+            log.error('cannot-retrieve-server-public-key',
+                      filename=key_file_name, exception=repr(e))
+
+    def getPrivateKeys(self):
+        key_file_name = '{}/{}'.format(KEYS_DIRECTORY,
+                                       self.server_private_key_file)
+        try:
+            privateKeys = {
+                'ssh-rsa': keys.Key.fromFile(key_file_name)
+            }
+            return privateKeys
+        except Exception as e:
+            log.error('cannot-retrieve-server-private-key',
+                      filename=key_file_name, exception=repr(e))