docs: def authenticate(self, username, password, priv_lvl=TAC_PLUS_PRIV_LVL_MIN,
                       authen_type=TAC_PLUS_AUTHEN_TYPE_ASCII,
                       chap_ppp_id=None, chap_challenge=None,
                       rem_addr=TAC_PLUS_VIRTUAL_REM_ADDR, port=TAC_PLUS_VIRTUAL_PORT):
          """
          Authenticate to a TACACS+ server with a username and password.
  
          :param username:
          :param password:
          :param priv_lvl:
          :param authen_type:    TAC_PLUS_AUTHEN_TYPE_ASCII,
                                 TAC_PLUS_AUTHEN_TYPE_PAP,
                                 TAC_PLUS_AUTHEN_TYPE_CHAP
          :param chap_ppp_id:    PPP ID when authen_type == 'chap'
          :param chap_challenge: challenge value when authen_type == 'chap'
          :param rem_addr:       AAA request source, default to TAC_PLUS_VIRTUAL_REM_ADDR
          :param port:           AAA port, default to TAC_PLUS_VIRTUAL_PORT
          :return:               TACACSAuthenticationReply
          :raises:               socket.timeout, socket.error
          """
          start_data = six.b('')
          if authen_type in (TAC_PLUS_AUTHEN_TYPE_PAP,
                             TAC_PLUS_AUTHEN_TYPE_CHAP):
              self.version_min = TAC_PLUS_MINOR_VER_ONE
  
              if authen_type == TAC_PLUS_AUTHEN_TYPE_PAP:
                  start_data = six.b(password)
  
              if authen_type == TAC_PLUS_AUTHEN_TYPE_CHAP:
                  if not isinstance(chap_ppp_id, six.string_types):
                      raise ValueError('chap_ppp_id must be a string')
                  if len(chap_ppp_id) != 1:
                      raise ValueError('chap_ppp_id must be a 1-byte string')
                  if not isinstance(chap_challenge, six.string_types):
                      raise ValueError('chap_challenge must be a string')
                  if len(chap_challenge) > 255:
                      raise ValueError('chap_challenge may not be more 255 bytes')
                  start_data = (
                      six.b(chap_ppp_id) +
                      six.b(chap_challenge) +
                      md5(six.b(
                          chap_ppp_id + password + chap_challenge
                      )).digest()
                  )
          with self.closing():
              packet = self.send(
                  TACACSAuthenticationStart(username, authen_type, priv_lvl,
                                            start_data, rem_addr=rem_addr, port=port),
                  TAC_PLUS_AUTHEN
              )
              reply = TACACSAuthenticationReply.unpacked(packet.body)
              logger.debug('\n'.join([
                  reply.__class__.__name__,
                  'recv header <%s>' % packet.header,
                  'recv body <%s>' % reply
              ]))
              if authen_type == TAC_PLUS_AUTHEN_TYPE_ASCII and reply.getpass:
                  packet = self.send(TACACSAuthenticationContinue(password),
                                     TAC_PLUS_AUTHEN,
                                     packet.seq_no + 1)
                  reply = TACACSAuthenticationReply.unpacked(packet.body)
                  logger.debug('\n'.join([
                      reply.__class__.__name__,
                      'recv header <%s>' % packet.header,
                      'recv body <%s>' % reply
                  ]))
                  if reply.flags == TAC_PLUS_CONTINUE_FLAG_ABORT:
                      reply.status = TAC_PLUS_AUTHEN_STATUS_FAIL
  
          return reply