mirror of
https://github.com/wakatime/sublime-wakatime.git
synced 2023-08-10 21:13:02 +03:00
Compare commits
58 Commits
Author | SHA1 | Date | |
---|---|---|---|
aba72b0f1e | |||
5b9d86a57d | |||
fa40874635 | |||
6d4a4cf9eb | |||
f628b8dd11 | |||
f932ee9fc6 | |||
2f14009279 | |||
453d96bf9c | |||
9de153f156 | |||
dcc782338d | |||
9d0dba988a | |||
e76f2e514e | |||
224f7cd82a | |||
3cce525a84 | |||
ce885501ad | |||
c9448a9a19 | |||
04f8c61ebc | |||
04a4630024 | |||
02138220fd | |||
d0b162bdd8 | |||
1b8895cd38 | |||
938bbb73d1 | |||
008fdc6b49 | |||
a788625dd0 | |||
bcbce681c3 | |||
35299db832 | |||
eb7814624c | |||
1c092b2fd8 | |||
507ef95f71 | |||
9777bc7788 | |||
20b78defa6 | |||
8cb1c557d9 | |||
20a1965f13 | |||
0b802a554e | |||
30186c9b2c | |||
311a0b5309 | |||
b7602d89fb | |||
305de46e32 | |||
c574234927 | |||
a69c50f470 | |||
f4b40089f3 | |||
08394357b7 | |||
205d4eb163 | |||
c4c27e4e9e | |||
9167eb2558 | |||
eaa3bb5180 | |||
7755971d11 | |||
7634be5446 | |||
5e17ad88f6 | |||
24d0f65116 | |||
a326046733 | |||
9bab00fd8b | |||
b4a13a48b9 | |||
21601f9688 | |||
4c3ec87341 | |||
b149d7fc87 | |||
52e6107c6e | |||
b340637331 |
119
HISTORY.rst
119
HISTORY.rst
@ -3,6 +3,125 @@ History
|
||||
-------
|
||||
|
||||
|
||||
7.0.22 (2017-06-08)
|
||||
++++++++++++++++++
|
||||
|
||||
- Upgrade wakatime-cli to v8.0.3.
|
||||
- Improve Matlab language detection.
|
||||
|
||||
|
||||
7.0.21 (2017-05-24)
|
||||
++++++++++++++++++
|
||||
|
||||
- Upgrade wakatime-cli to v8.0.2.
|
||||
- Only treat proxy string as NTLM proxy after unable to connect with HTTPS and
|
||||
SOCKS proxy.
|
||||
- Support running automated tests on Linux, OS X, and Windows.
|
||||
- Ability to disable SSL cert verification. wakatime/wakatime#90
|
||||
- Disable line count stats for files larger than 2MB to improve performance.
|
||||
- Print error saying Python needs upgrading when requests can't be imported.
|
||||
|
||||
|
||||
7.0.20 (2017-04-10)
|
||||
++++++++++++++++++
|
||||
|
||||
- Fix install instructions formatting.
|
||||
|
||||
|
||||
7.0.19 (2017-04-10)
|
||||
++++++++++++++++++
|
||||
|
||||
- Remove /var/www/ from default ignored folders.
|
||||
|
||||
|
||||
7.0.18 (2017-03-16)
|
||||
++++++++++++++++++
|
||||
|
||||
- Upgrade wakatime-cli to v8.0.0.
|
||||
- No longer creating ~/.wakatime.cfg file, since only using Sublime settings.
|
||||
|
||||
|
||||
7.0.17 (2017-03-01)
|
||||
++++++++++++++++++
|
||||
|
||||
- Upgrade wakatime-cli to v7.0.4.
|
||||
|
||||
|
||||
7.0.16 (2017-02-20)
|
||||
++++++++++++++++++
|
||||
|
||||
- Upgrade wakatime-cli to v7.0.2.
|
||||
|
||||
|
||||
7.0.15 (2017-02-13)
|
||||
++++++++++++++++++
|
||||
|
||||
- Upgrade wakatime-cli to v6.2.2.
|
||||
- Upgrade pygments library to v2.2.0 for improved language detection.
|
||||
|
||||
|
||||
7.0.14 (2017-02-08)
|
||||
++++++++++++++++++
|
||||
|
||||
- Upgrade wakatime-cli to v6.2.1.
|
||||
- Allow boolean or list of regex patterns for hidefilenames config setting.
|
||||
|
||||
|
||||
7.0.13 (2016-11-11)
|
||||
++++++++++++++++++
|
||||
|
||||
- Support old Sublime Text with Python 2.6.
|
||||
- Fix bug that prevented reading default api key from existing config file.
|
||||
|
||||
|
||||
7.0.12 (2016-10-24)
|
||||
++++++++++++++++++
|
||||
|
||||
- Upgrade wakatime-cli to v6.2.0.
|
||||
- Exit with status code 104 when api key is missing or invalid. Exit with
|
||||
status code 103 when config file missing or invalid.
|
||||
- New WAKATIME_HOME env variable for setting path to config and log files.
|
||||
- Improve debug warning message from unsupported dependency parsers.
|
||||
|
||||
|
||||
7.0.11 (2016-09-23)
|
||||
++++++++++++++++++
|
||||
|
||||
- Handle UnicodeDecodeError when when logging. Related to #68.
|
||||
|
||||
|
||||
7.0.10 (2016-09-22)
|
||||
++++++++++++++++++
|
||||
|
||||
- Handle UnicodeDecodeError when looking for python. Fixes #68.
|
||||
- Upgrade wakatime-cli to v6.0.9.
|
||||
|
||||
|
||||
7.0.9 (2016-09-02)
|
||||
++++++++++++++++++
|
||||
|
||||
- Upgrade wakatime-cli to v6.0.8.
|
||||
|
||||
|
||||
7.0.8 (2016-07-21)
|
||||
++++++++++++++++++
|
||||
|
||||
- Upgrade wakatime-cli to master version to fix debug logging encoding bug.
|
||||
|
||||
|
||||
7.0.7 (2016-07-06)
|
||||
++++++++++++++++++
|
||||
|
||||
- Upgrade wakatime-cli to v6.0.7.
|
||||
- Handle unknown exceptions from requests library by deleting cached session
|
||||
object because it could be from a previous conflicting version.
|
||||
- New hostname setting in config file to set machine hostname. Hostname
|
||||
argument takes priority over hostname from config file.
|
||||
- Prevent logging unrelated exception when logging tracebacks.
|
||||
- Use correct namespace for pygments.lexers.ClassNotFound exception so it is
|
||||
caught when dependency detection not available for a language.
|
||||
|
||||
|
||||
7.0.6 (2016-06-13)
|
||||
++++++++++++++++++
|
||||
|
||||
|
27
README.md
27
README.md
@ -9,19 +9,15 @@ Installation
|
||||
|
||||
1. Install [Package Control](https://packagecontrol.io/installation).
|
||||
|
||||
2. Using [Package Control](https://packagecontrol.io/docs/usage):
|
||||
2. In Sublime, press `ctrl+shift+p`(Windows, Linux) or `cmd+shift+p`(OS X).
|
||||
|
||||
a) Inside Sublime, press `ctrl+shift+p`(Windows, Linux) or `cmd+shift+p`(OS X).
|
||||
3. Type `install`, then press `enter` with `Package Control: Install Package` selected.
|
||||
|
||||
b) Type `install`, then press `enter` with `Package Control: Install Package` selected.
|
||||
4. Type `wakatime`, then press `enter` with the `WakaTime` plugin selected.
|
||||
|
||||
c) Type `wakatime`, then press `enter` with the `WakaTime` plugin selected.
|
||||
5. Enter your [api key](https://wakatime.com/settings#apikey), then press `enter`.
|
||||
|
||||
3. Enter your [api key](https://wakatime.com/settings#apikey), then press `enter`.
|
||||
|
||||
4. Use Sublime and your time will be tracked for you automatically.
|
||||
|
||||
5. Visit https://wakatime.com/dashboard to see your logged time.
|
||||
6. Use Sublime and your coding activity will be displayed on your [WakaTime dashboard](https://wakatime.com).
|
||||
|
||||
|
||||
Screen Shots
|
||||
@ -37,7 +33,7 @@ In Sublime Text 2, if you get a warning message:
|
||||
|
||||
A plugin (WakaTime) may be making Sublime Text unresponsive by taking too long (0.017332s) in its on_modified callback.
|
||||
|
||||
To fix this, go to `Preferences > Settings - User` then add the following setting:
|
||||
To fix this, go to `Preferences → Settings - User` then add the following setting:
|
||||
|
||||
`"detect_slow_plugins": false`
|
||||
|
||||
@ -51,6 +47,13 @@ First, turn on debug mode in your `WakaTime.sublime-settings` file.
|
||||
|
||||
Add the line: `"debug": true`
|
||||
|
||||
Then, open your Sublime Console with `View -> Show Console` to see the plugin executing the wakatime cli process when sending a heartbeat. Also, tail your `$HOME/.wakatime.log` file to debug wakatime cli problems.
|
||||
Then, open your Sublime Console with `View → Show Console` ( CTRL + \` ) to see the plugin executing the wakatime cli process when sending a heartbeat.
|
||||
Also, tail your `$HOME/.wakatime.log` file to debug wakatime cli problems.
|
||||
|
||||
For more general troubleshooting information, see [wakatime/wakatime#troubleshooting](https://github.com/wakatime/wakatime#troubleshooting).
|
||||
The [How to Debug Plugins][how to debug] guide shows how to check when coding activity was last received from your editor using the [User Agents API][user agents api].
|
||||
For more general troubleshooting info, see the [wakatime-cli Troubleshooting Section][wakatime-cli-help].
|
||||
|
||||
|
||||
[wakatime-cli-help]: https://github.com/wakatime/wakatime#troubleshooting
|
||||
[how to debug]: https://wakatime.com/faq#debug-plugins
|
||||
[user agents api]: https://wakatime.com/developers#user_agents
|
||||
|
83
WakaTime.py
83
WakaTime.py
@ -7,12 +7,13 @@ Website: https://wakatime.com/
|
||||
==========================================================="""
|
||||
|
||||
|
||||
__version__ = '7.0.6'
|
||||
__version__ = '7.0.22'
|
||||
|
||||
|
||||
import sublime
|
||||
import sublime_plugin
|
||||
|
||||
import contextlib
|
||||
import json
|
||||
import os
|
||||
import platform
|
||||
@ -20,11 +21,12 @@ import re
|
||||
import sys
|
||||
import time
|
||||
import threading
|
||||
import traceback
|
||||
import urllib
|
||||
import webbrowser
|
||||
from datetime import datetime
|
||||
from zipfile import ZipFile
|
||||
from subprocess import Popen, STDOUT, PIPE
|
||||
from zipfile import ZipFile
|
||||
try:
|
||||
import _winreg as winreg # py2
|
||||
except ImportError:
|
||||
@ -45,6 +47,8 @@ if is_py2:
|
||||
def u(text):
|
||||
if text is None:
|
||||
return None
|
||||
if isinstance(text, unicode):
|
||||
return text
|
||||
try:
|
||||
return text.decode('utf-8')
|
||||
except:
|
||||
@ -54,7 +58,13 @@ if is_py2:
|
||||
try:
|
||||
return unicode(text)
|
||||
except:
|
||||
return text
|
||||
try:
|
||||
return text.decode('utf-8', 'replace')
|
||||
except:
|
||||
try:
|
||||
return unicode(str(text))
|
||||
except:
|
||||
return unicode('')
|
||||
|
||||
elif is_py3:
|
||||
def u(text):
|
||||
@ -71,7 +81,7 @@ elif is_py3:
|
||||
try:
|
||||
return str(text)
|
||||
except:
|
||||
return text
|
||||
return text.decode('utf-8', 'replace')
|
||||
|
||||
else:
|
||||
raise Exception('Unsupported Python version: {0}.{1}.{2}'.format(
|
||||
@ -107,7 +117,7 @@ ERROR = 'ERROR'
|
||||
# add wakatime package to path
|
||||
sys.path.insert(0, os.path.join(PLUGIN_DIR, 'packages'))
|
||||
try:
|
||||
from wakatime.base import parseConfigFile
|
||||
from wakatime.main import parseConfigFile
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
@ -135,7 +145,10 @@ def log(lvl, message, *args, **kwargs):
|
||||
msg = message.format(*args)
|
||||
elif len(kwargs) > 0:
|
||||
msg = message.format(**kwargs)
|
||||
print('[WakaTime] [{lvl}] {msg}'.format(lvl=lvl, msg=msg))
|
||||
try:
|
||||
print('[WakaTime] [{lvl}] {msg}'.format(lvl=lvl, msg=msg))
|
||||
except UnicodeDecodeError:
|
||||
print(u('[WakaTime] [{lvl}] {msg}').format(lvl=lvl, msg=u(msg)))
|
||||
except RuntimeError:
|
||||
set_timeout(lambda: log(lvl, message, *args, **kwargs), 0)
|
||||
|
||||
@ -164,28 +177,11 @@ def update_status_bar(status):
|
||||
set_timeout(lambda: update_status_bar(status), 0)
|
||||
|
||||
|
||||
def create_config_file():
|
||||
"""Creates the .wakatime.cfg INI file in $HOME directory, if it does
|
||||
not already exist.
|
||||
"""
|
||||
configFile = os.path.join(os.path.expanduser('~'), '.wakatime.cfg')
|
||||
try:
|
||||
with open(configFile) as fh:
|
||||
pass
|
||||
except IOError:
|
||||
try:
|
||||
with open(configFile, 'w') as fh:
|
||||
fh.write("[settings]\n")
|
||||
fh.write("debug = false\n")
|
||||
fh.write("hidefilenames = false\n")
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
|
||||
def prompt_api_key():
|
||||
global SETTINGS
|
||||
|
||||
create_config_file()
|
||||
if SETTINGS.get('api_key'):
|
||||
return True
|
||||
|
||||
default_key = ''
|
||||
try:
|
||||
@ -196,20 +192,17 @@ def prompt_api_key():
|
||||
except:
|
||||
pass
|
||||
|
||||
if SETTINGS.get('api_key'):
|
||||
return True
|
||||
else:
|
||||
window = sublime.active_window()
|
||||
if window:
|
||||
def got_key(text):
|
||||
if text:
|
||||
SETTINGS.set('api_key', str(text))
|
||||
sublime.save_settings(SETTINGS_FILE)
|
||||
window = sublime.active_window()
|
||||
if window:
|
||||
window.show_input_panel('[WakaTime] Enter your wakatime.com api key:', default_key, got_key, None, None)
|
||||
return True
|
||||
else:
|
||||
log(ERROR, 'Could not prompt for api key because no window found.')
|
||||
return False
|
||||
window.show_input_panel('[WakaTime] Enter your wakatime.com api key:', default_key, got_key, None, None)
|
||||
return True
|
||||
else:
|
||||
log(ERROR, 'Could not prompt for api key because no window found.')
|
||||
return False
|
||||
|
||||
|
||||
def python_binary():
|
||||
@ -302,6 +295,12 @@ def find_python_from_registry(location, reg=None):
|
||||
reg=reg,
|
||||
key=location,
|
||||
))
|
||||
except:
|
||||
log(ERROR, 'Could not read registry value "{reg}\\{key}":\n{exc}'.format(
|
||||
reg=reg,
|
||||
key=location,
|
||||
exc=traceback.format_exc(),
|
||||
))
|
||||
|
||||
return val
|
||||
|
||||
@ -314,7 +313,7 @@ def find_python_in_folder(folder, headless=True):
|
||||
path = os.path.realpath(os.path.join(folder, 'python'))
|
||||
if headless:
|
||||
path = u(path) + u('w')
|
||||
log(DEBUG, u('Looking for Python at: {0}').format(path))
|
||||
log(DEBUG, u('Looking for Python at: {0}').format(u(path)))
|
||||
try:
|
||||
process = Popen([path, '--version'], stdout=PIPE, stderr=STDOUT)
|
||||
output, err = process.communicate()
|
||||
@ -463,6 +462,8 @@ class SendHeartbeatsThread(threading.Thread):
|
||||
self.debug = SETTINGS.get('debug')
|
||||
self.api_key = SETTINGS.get('api_key', '')
|
||||
self.ignore = SETTINGS.get('ignore', [])
|
||||
self.hidefilenames = SETTINGS.get('hidefilenames')
|
||||
self.proxy = SETTINGS.get('proxy')
|
||||
|
||||
self.heartbeat = heartbeat
|
||||
self.has_extra_heartbeats = False
|
||||
@ -518,9 +519,13 @@ class SendHeartbeatsThread(threading.Thread):
|
||||
if heartbeat.get('cursorpos') is not None:
|
||||
cmd.extend(['--cursorpos', heartbeat['cursorpos']])
|
||||
for pattern in self.ignore:
|
||||
cmd.extend(['--ignore', pattern])
|
||||
cmd.extend(['--exclude', pattern])
|
||||
if self.debug:
|
||||
cmd.append('--verbose')
|
||||
if self.hidefilenames:
|
||||
cmd.append('--hidefilenames')
|
||||
if self.proxy:
|
||||
cmd.extend(['--proxy', self.proxy])
|
||||
if self.has_extra_heartbeats:
|
||||
cmd.append('--extra-heartbeats')
|
||||
stdin = PIPE
|
||||
@ -572,7 +577,7 @@ class DownloadPython(threading.Thread):
|
||||
def run(self):
|
||||
log(INFO, 'Downloading embeddable Python...')
|
||||
|
||||
ver = '3.5.0'
|
||||
ver = '3.5.2'
|
||||
arch = 'amd64' if platform.architecture()[0] == '64bit' else 'win32'
|
||||
url = 'https://www.python.org/ftp/python/{ver}/python-{ver}-embed-{arch}.zip'.format(
|
||||
ver=ver,
|
||||
@ -589,7 +594,7 @@ class DownloadPython(threading.Thread):
|
||||
urllib.request.urlretrieve(url, zip_file)
|
||||
|
||||
log(INFO, 'Extracting Python...')
|
||||
with ZipFile(zip_file) as zf:
|
||||
with contextlib.closing(ZipFile(zip_file)) as zf:
|
||||
path = os.path.join(resources_folder(), 'python')
|
||||
zf.extractall(path)
|
||||
|
||||
|
@ -6,18 +6,24 @@
|
||||
// Your api key from https://wakatime.com/#apikey
|
||||
// Set this in your User specific WakaTime.sublime-settings file.
|
||||
"api_key": "",
|
||||
|
||||
// Ignore files; Files (including absolute paths) that match one of these
|
||||
// POSIX regular expressions will not be logged.
|
||||
"ignore": ["^/tmp/", "^/etc/", "^/var/", "COMMIT_EDITMSG$", "PULLREQ_EDITMSG$", "MERGE_MSG$", "TAG_EDITMSG$"],
|
||||
|
||||
|
||||
// Debug mode. Set to true for verbose logging. Defaults to false.
|
||||
"debug": false,
|
||||
|
||||
|
||||
// Proxy with format https://user:pass@host:port or socks5://user:pass@host:port or domain\\user:pass.
|
||||
"proxy": "",
|
||||
|
||||
// Ignore files; Files (including absolute paths) that match one of these
|
||||
// POSIX regular expressions will not be logged.
|
||||
"ignore": ["^/tmp/", "^/etc/", "^/var/(?!www/).*", "COMMIT_EDITMSG$", "PULLREQ_EDITMSG$", "MERGE_MSG$", "TAG_EDITMSG$"],
|
||||
|
||||
// Status bar message. Set to false to hide status bar message.
|
||||
// Defaults to true.
|
||||
"status_bar_message": true,
|
||||
|
||||
|
||||
// Status bar message format.
|
||||
"status_bar_message_fmt": "WakaTime {status} %I:%M %p"
|
||||
"status_bar_message_fmt": "WakaTime {status} %I:%M %p",
|
||||
|
||||
// Obfuscate file paths when sending to API. Your dashboard will no longer display coding activity per file.
|
||||
"hidefilenames": false
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
__title__ = 'wakatime'
|
||||
__description__ = 'Common interface to the WakaTime api.'
|
||||
__url__ = 'https://github.com/wakatime/wakatime'
|
||||
__version_info__ = ('6', '0', '5')
|
||||
__version_info__ = ('8', '0', '3')
|
||||
__version__ = '.'.join(__version_info__)
|
||||
__author__ = 'Alan Hamlett'
|
||||
__author_email__ = 'alan@wakatime.com'
|
||||
__license__ = 'BSD'
|
||||
__copyright__ = 'Copyright 2016 Alan Hamlett'
|
||||
__copyright__ = 'Copyright 2017 Alan Hamlett'
|
||||
|
240
packages/wakatime/arguments.py
Normal file
240
packages/wakatime/arguments.py
Normal file
@ -0,0 +1,240 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
wakatime.arguments
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Command-line arguments.
|
||||
|
||||
:copyright: (c) 2016 Alan Hamlett.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import traceback
|
||||
from .__about__ import __version__
|
||||
from .configs import parseConfigFile
|
||||
from .constants import AUTH_ERROR
|
||||
from .packages import argparse
|
||||
|
||||
|
||||
class FileAction(argparse.Action):
|
||||
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
try:
|
||||
if os.path.isfile(values):
|
||||
values = os.path.realpath(values)
|
||||
except: # pragma: nocover
|
||||
pass
|
||||
setattr(namespace, self.dest, values)
|
||||
|
||||
|
||||
def parseArguments():
|
||||
"""Parse command line arguments and configs from ~/.wakatime.cfg.
|
||||
Command line arguments take precedence over config file settings.
|
||||
Returns instances of ArgumentParser and SafeConfigParser.
|
||||
"""
|
||||
|
||||
# define supported command line arguments
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Common interface for the WakaTime api.')
|
||||
parser.add_argument('--entity', dest='entity', metavar='FILE',
|
||||
action=FileAction,
|
||||
help='absolute path to file for the heartbeat; can also be a '+
|
||||
'url, domain, or app when --entity-type is not file')
|
||||
parser.add_argument('--file', dest='file', action=FileAction,
|
||||
help=argparse.SUPPRESS)
|
||||
parser.add_argument('--key', dest='key',
|
||||
help='your wakatime api key; uses api_key from '+
|
||||
'~/.wakatime.cfg by default')
|
||||
parser.add_argument('--write', dest='is_write',
|
||||
action='store_true',
|
||||
help='when set, tells api this heartbeat was triggered from '+
|
||||
'writing to a file')
|
||||
parser.add_argument('--plugin', dest='plugin',
|
||||
help='optional text editor plugin name and version '+
|
||||
'for User-Agent header')
|
||||
parser.add_argument('--time', dest='timestamp', metavar='time',
|
||||
type=float,
|
||||
help='optional floating-point unix epoch timestamp; '+
|
||||
'uses current time by default')
|
||||
parser.add_argument('--lineno', dest='lineno',
|
||||
help='optional line number; current line being edited')
|
||||
parser.add_argument('--cursorpos', dest='cursorpos',
|
||||
help='optional cursor position in the current file')
|
||||
parser.add_argument('--entity-type', dest='entity_type',
|
||||
help='entity type for this heartbeat. can be one of "file", '+
|
||||
'"domain", or "app"; defaults to file.')
|
||||
parser.add_argument('--proxy', dest='proxy',
|
||||
help='optional proxy configuration. Supports HTTPS '+
|
||||
'and SOCKS proxies. For example: '+
|
||||
'https://user:pass@host:port or '+
|
||||
'socks5://user:pass@host:port or ' +
|
||||
'domain\\user:pass')
|
||||
parser.add_argument('--no-ssl-verify', dest='nosslverify',
|
||||
action='store_true',
|
||||
help='disables SSL certificate verification for HTTPS '+
|
||||
'requests. By default, SSL certificates are verified.')
|
||||
parser.add_argument('--project', dest='project',
|
||||
help='optional project name')
|
||||
parser.add_argument('--alternate-project', dest='alternate_project',
|
||||
help='optional alternate project name; auto-discovered project '+
|
||||
'takes priority')
|
||||
parser.add_argument('--alternate-language', dest='alternate_language',
|
||||
help=argparse.SUPPRESS)
|
||||
parser.add_argument('--language', dest='language',
|
||||
help='optional language name; if valid, takes priority over '+
|
||||
'auto-detected language')
|
||||
parser.add_argument('--hostname', dest='hostname', help='hostname of '+
|
||||
'current machine.')
|
||||
parser.add_argument('--disableoffline', dest='offline',
|
||||
action='store_false',
|
||||
help='disables offline time logging instead of queuing logged time')
|
||||
parser.add_argument('--hidefilenames', dest='hidefilenames',
|
||||
action='store_true',
|
||||
help='obfuscate file names; will not send file names to api')
|
||||
parser.add_argument('--exclude', dest='exclude', action='append',
|
||||
help='filename patterns to exclude from logging; POSIX regex '+
|
||||
'syntax; can be used more than once')
|
||||
parser.add_argument('--include', dest='include', action='append',
|
||||
help='filename patterns to log; when used in combination with '+
|
||||
'--exclude, files matching include will still be logged; '+
|
||||
'POSIX regex syntax; can be used more than once')
|
||||
parser.add_argument('--ignore', dest='ignore', action='append',
|
||||
help=argparse.SUPPRESS)
|
||||
parser.add_argument('--extra-heartbeats', dest='extra_heartbeats',
|
||||
action='store_true',
|
||||
help='reads extra heartbeats from STDIN as a JSON array until EOF')
|
||||
parser.add_argument('--logfile', dest='logfile',
|
||||
help='defaults to ~/.wakatime.log')
|
||||
parser.add_argument('--apiurl', dest='api_url',
|
||||
help='heartbeats api url; for debugging with a local server')
|
||||
parser.add_argument('--timeout', dest='timeout', type=int,
|
||||
help='number of seconds to wait when sending heartbeats to api; '+
|
||||
'defaults to 60 seconds')
|
||||
parser.add_argument('--config', dest='config',
|
||||
help='defaults to ~/.wakatime.cfg')
|
||||
parser.add_argument('--verbose', dest='verbose', action='store_true',
|
||||
help='turns on debug messages in log file')
|
||||
parser.add_argument('--version', action='version', version=__version__)
|
||||
|
||||
# parse command line arguments
|
||||
args = parser.parse_args()
|
||||
|
||||
# use current unix epoch timestamp by default
|
||||
if not args.timestamp:
|
||||
args.timestamp = time.time()
|
||||
|
||||
# parse ~/.wakatime.cfg file
|
||||
configs = parseConfigFile(args.config)
|
||||
|
||||
# update args from configs
|
||||
if not args.hostname:
|
||||
if configs.has_option('settings', 'hostname'):
|
||||
args.hostname = configs.get('settings', 'hostname')
|
||||
if not args.key:
|
||||
default_key = None
|
||||
if configs.has_option('settings', 'api_key'):
|
||||
default_key = configs.get('settings', 'api_key')
|
||||
elif configs.has_option('settings', 'apikey'):
|
||||
default_key = configs.get('settings', 'apikey')
|
||||
if default_key:
|
||||
args.key = default_key
|
||||
else:
|
||||
try:
|
||||
parser.error('Missing api key. Find your api key from wakatime.com/settings.')
|
||||
except SystemExit:
|
||||
raise SystemExit(AUTH_ERROR)
|
||||
|
||||
is_valid = not not re.match(r'^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$', args.key, re.I)
|
||||
if not is_valid:
|
||||
try:
|
||||
parser.error('Invalid api key. Find your api key from wakatime.com/settings.')
|
||||
except SystemExit:
|
||||
raise SystemExit(AUTH_ERROR)
|
||||
|
||||
if not args.entity:
|
||||
if args.file:
|
||||
args.entity = args.file
|
||||
else:
|
||||
parser.error('argument --entity is required')
|
||||
|
||||
if not args.language and args.alternate_language:
|
||||
args.language = args.alternate_language
|
||||
|
||||
if not args.exclude:
|
||||
args.exclude = []
|
||||
if configs.has_option('settings', 'ignore'):
|
||||
try:
|
||||
for pattern in configs.get('settings', 'ignore').split("\n"):
|
||||
if pattern.strip() != '':
|
||||
args.exclude.append(pattern)
|
||||
except TypeError: # pragma: nocover
|
||||
pass
|
||||
if configs.has_option('settings', 'exclude'):
|
||||
try:
|
||||
for pattern in configs.get('settings', 'exclude').split("\n"):
|
||||
if pattern.strip() != '':
|
||||
args.exclude.append(pattern)
|
||||
except TypeError: # pragma: nocover
|
||||
pass
|
||||
if not args.include:
|
||||
args.include = []
|
||||
if configs.has_option('settings', 'include'):
|
||||
try:
|
||||
for pattern in configs.get('settings', 'include').split("\n"):
|
||||
if pattern.strip() != '':
|
||||
args.include.append(pattern)
|
||||
except TypeError: # pragma: nocover
|
||||
pass
|
||||
if args.hidefilenames:
|
||||
args.hidefilenames = ['.*']
|
||||
else:
|
||||
args.hidefilenames = []
|
||||
if configs.has_option('settings', 'hidefilenames'):
|
||||
option = configs.get('settings', 'hidefilenames')
|
||||
if option.strip().lower() == 'true':
|
||||
args.hidefilenames = ['.*']
|
||||
elif option.strip().lower() != 'false':
|
||||
for pattern in option.split("\n"):
|
||||
if pattern.strip() != '':
|
||||
args.hidefilenames.append(pattern)
|
||||
if args.offline and configs.has_option('settings', 'offline'):
|
||||
args.offline = configs.getboolean('settings', 'offline')
|
||||
if not args.proxy and configs.has_option('settings', 'proxy'):
|
||||
args.proxy = configs.get('settings', 'proxy')
|
||||
if args.proxy:
|
||||
pattern = r'^((https?|socks5)://)?([^:@]+(:([^:@])+)?@)?[^:]+(:\d+)?$'
|
||||
if '\\' in args.proxy:
|
||||
pattern = r'^.*\\.+$'
|
||||
is_valid = not not re.match(pattern, args.proxy, re.I)
|
||||
if not is_valid:
|
||||
parser.error('Invalid proxy. Must be in format ' +
|
||||
'https://user:pass@host:port or ' +
|
||||
'socks5://user:pass@host:port or ' +
|
||||
'domain\\user:pass.')
|
||||
if configs.has_option('settings', 'no_ssl_verify'):
|
||||
args.nosslverify = configs.getboolean('settings', 'no_ssl_verify')
|
||||
if not args.verbose and configs.has_option('settings', 'verbose'):
|
||||
args.verbose = configs.getboolean('settings', 'verbose')
|
||||
if not args.verbose and configs.has_option('settings', 'debug'):
|
||||
args.verbose = configs.getboolean('settings', 'debug')
|
||||
if not args.logfile and configs.has_option('settings', 'logfile'):
|
||||
args.logfile = configs.get('settings', 'logfile')
|
||||
if not args.logfile and os.environ.get('WAKATIME_HOME'):
|
||||
home = os.environ.get('WAKATIME_HOME')
|
||||
args.logfile = os.path.join(os.path.expanduser(home), '.wakatime.log')
|
||||
if not args.api_url and configs.has_option('settings', 'api_url'):
|
||||
args.api_url = configs.get('settings', 'api_url')
|
||||
if not args.timeout and configs.has_option('settings', 'timeout'):
|
||||
try:
|
||||
args.timeout = int(configs.get('settings', 'timeout'))
|
||||
except ValueError:
|
||||
print(traceback.format_exc())
|
||||
|
||||
return args, configs
|
@ -31,7 +31,7 @@ if is_py2: # pragma: nocover
|
||||
try:
|
||||
return unicode(text)
|
||||
except:
|
||||
return text
|
||||
return text.decode('utf-8', 'replace')
|
||||
open = codecs.open
|
||||
basestring = basestring
|
||||
|
||||
@ -52,7 +52,7 @@ elif is_py3: # pragma: nocover
|
||||
try:
|
||||
return str(text)
|
||||
except:
|
||||
return text
|
||||
return text.decode('utf-8', 'replace')
|
||||
open = open
|
||||
basestring = (str, bytes)
|
||||
|
||||
|
64
packages/wakatime/configs.py
Normal file
64
packages/wakatime/configs.py
Normal file
@ -0,0 +1,64 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
wakatime.configs
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Config file parser.
|
||||
|
||||
:copyright: (c) 2016 Alan Hamlett.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import traceback
|
||||
|
||||
from .compat import open
|
||||
from .constants import CONFIG_FILE_PARSE_ERROR
|
||||
|
||||
|
||||
try:
|
||||
import configparser
|
||||
except ImportError:
|
||||
from .packages import configparser
|
||||
|
||||
|
||||
def getConfigFile():
|
||||
"""Returns the config file location.
|
||||
|
||||
If $WAKATIME_HOME env varialbe is defined, returns
|
||||
$WAKATIME_HOME/.wakatime.cfg, otherwise ~/.wakatime.cfg.
|
||||
"""
|
||||
|
||||
fileName = '.wakatime.cfg'
|
||||
|
||||
home = os.environ.get('WAKATIME_HOME')
|
||||
if home:
|
||||
return os.path.join(os.path.expanduser(home), fileName)
|
||||
|
||||
return os.path.join(os.path.expanduser('~'), fileName)
|
||||
|
||||
|
||||
def parseConfigFile(configFile=None):
|
||||
"""Returns a configparser.SafeConfigParser instance with configs
|
||||
read from the config file. Default location of the config file is
|
||||
at ~/.wakatime.cfg.
|
||||
"""
|
||||
|
||||
# get config file location from ENV
|
||||
if not configFile:
|
||||
configFile = getConfigFile()
|
||||
|
||||
configs = configparser.ConfigParser(delimiters=('='), strict=False)
|
||||
try:
|
||||
with open(configFile, 'r', encoding='utf-8') as fh:
|
||||
try:
|
||||
configs.read_file(fh)
|
||||
except configparser.Error:
|
||||
print(traceback.format_exc())
|
||||
raise SystemExit(CONFIG_FILE_PARSE_ERROR)
|
||||
except IOError:
|
||||
pass
|
||||
return configs
|
@ -9,10 +9,44 @@
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
|
||||
""" Success
|
||||
Exit code used when a heartbeat was sent successfully.
|
||||
"""
|
||||
SUCCESS = 0
|
||||
|
||||
""" Api Error
|
||||
Exit code used when the WakaTime API returned an error.
|
||||
"""
|
||||
API_ERROR = 102
|
||||
|
||||
""" Config File Parse Error
|
||||
Exit code used when the ~/.wakatime.cfg config file could not be parsed.
|
||||
"""
|
||||
CONFIG_FILE_PARSE_ERROR = 103
|
||||
|
||||
""" Auth Error
|
||||
Exit code used when our api key is invalid.
|
||||
"""
|
||||
AUTH_ERROR = 104
|
||||
|
||||
""" Unknown Error
|
||||
Exit code used when there was an unhandled exception.
|
||||
"""
|
||||
UNKNOWN_ERROR = 105
|
||||
|
||||
""" Malformed Heartbeat Error
|
||||
Exit code used when the JSON input from `--extra-heartbeats` is malformed.
|
||||
"""
|
||||
MALFORMED_HEARTBEAT_ERROR = 106
|
||||
|
||||
""" Connection Error
|
||||
Exit code used when there was proxy or other problem connecting to the WakaTime
|
||||
API servers.
|
||||
"""
|
||||
CONNECTION_ERROR = 107
|
||||
|
||||
""" Max file size supporting line number count stats.
|
||||
Files larger than this in bytes will not have a line count stat for performance.
|
||||
Default is 2MB.
|
||||
"""
|
||||
MAX_FILE_SIZE_SUPPORTED = 2000000
|
||||
|
@ -12,7 +12,6 @@
|
||||
import logging
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from ..compat import u, open, import_module
|
||||
from ..exceptions import NotYetImplemented
|
||||
@ -68,7 +67,7 @@ class TokenParser(object):
|
||||
pass
|
||||
try:
|
||||
with open(self.source_file, 'r', encoding=sys.getfilesystemencoding()) as fh:
|
||||
return self.lexer.get_tokens_unprocessed(fh.read(512000))
|
||||
return self.lexer.get_tokens_unprocessed(fh.read(512000)) # pragma: nocover
|
||||
except:
|
||||
pass
|
||||
return []
|
||||
@ -118,9 +117,9 @@ class DependencyParser(object):
|
||||
try:
|
||||
self.parser = getattr(module, class_name)
|
||||
except AttributeError:
|
||||
log.debug('Module {0} is missing class {1}'.format(module.__name__, class_name))
|
||||
log.debug('Parsing dependencies not supported for {0}.{1}'.format(module_name, class_name))
|
||||
except ImportError:
|
||||
log.debug(traceback.format_exc())
|
||||
log.debug('Parsing dependencies not supported for {0}.{1}'.format(module_name, class_name))
|
||||
|
||||
def parse(self):
|
||||
if self.parser:
|
||||
|
@ -20,6 +20,7 @@ class PythonParser(TokenParser):
|
||||
r'^os$',
|
||||
r'^sys$',
|
||||
r'^sys\.',
|
||||
r'^__future__$',
|
||||
]
|
||||
|
||||
def parse(self):
|
||||
@ -49,9 +50,7 @@ class PythonParser(TokenParser):
|
||||
self._process_import(token, content)
|
||||
|
||||
def _process_operator(self, token, content):
|
||||
if self.state is not None:
|
||||
if content == '.':
|
||||
self.nonpackage = True
|
||||
pass
|
||||
|
||||
def _process_punctuation(self, token, content):
|
||||
if content == '(':
|
||||
@ -74,8 +73,6 @@ class PythonParser(TokenParser):
|
||||
if self.state == 'from':
|
||||
self.append(content, truncate=True, truncate_to=1)
|
||||
self.state = 'from-2'
|
||||
elif self.state == 'from-2' and content != 'import':
|
||||
self.append(content, truncate=True, truncate_to=1)
|
||||
elif self.state == 'import':
|
||||
self.append(content, truncate=True, truncate_to=1)
|
||||
self.state = 'import-2'
|
||||
|
@ -114,16 +114,11 @@ class HtmlDjangoParser(TokenParser):
|
||||
if self.opening_tag:
|
||||
self.tags.insert(0, content.replace('<', '', 1).strip().lower())
|
||||
self.getting_attrs = True
|
||||
elif content.startswith('>'):
|
||||
self.opening_tag = False
|
||||
self.getting_attrs = False
|
||||
self.current_attr = None
|
||||
|
||||
def _process_attribute(self, token, content):
|
||||
if self.getting_attrs:
|
||||
self.current_attr = content.lower().strip('=')
|
||||
else:
|
||||
self.current_attr = None
|
||||
self.current_attr_value = None
|
||||
|
||||
def _process_string(self, token, content):
|
||||
@ -146,8 +141,6 @@ class HtmlDjangoParser(TokenParser):
|
||||
elif content.startswith('"') or content.startswith("'"):
|
||||
if self.current_attr_value is None:
|
||||
self.current_attr_value = content
|
||||
else:
|
||||
self.current_attr_value += content
|
||||
|
||||
|
||||
class VelocityHtmlParser(HtmlDjangoParser):
|
||||
|
18
packages/wakatime/language_priorities.py
Normal file
18
packages/wakatime/language_priorities.py
Normal file
@ -0,0 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
wakatime.language_priorities
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Overwrite pygments Lexer.priority attribute for specific languages.
|
||||
|
||||
:copyright: (c) 2017 Alan Hamlett.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
|
||||
LANGUAGES = {
|
||||
'f#': 0.01,
|
||||
'perl': 0.01,
|
||||
'perl6': 0.01,
|
||||
'typescript': 0.01,
|
||||
}
|
@ -1,80 +1,81 @@
|
||||
{
|
||||
"ActionScript": "ActionScript",
|
||||
"ApacheConf": "ApacheConf",
|
||||
"AppleScript": "AppleScript",
|
||||
"ASP": "ASP",
|
||||
"Assembly": "Assembly",
|
||||
"Awk": "Awk",
|
||||
"Bash": "Bash",
|
||||
"Basic": "Basic",
|
||||
"BrightScript": "BrightScript",
|
||||
"C": "C",
|
||||
"C#": "C#",
|
||||
"C++": "C++",
|
||||
"Clojure": "Clojure",
|
||||
"Cocoa": "Cocoa",
|
||||
"CoffeeScript": "CoffeeScript",
|
||||
"ColdFusion": "ColdFusion",
|
||||
"Common Lisp": "Common Lisp",
|
||||
"CSHTML": "CSHTML",
|
||||
"CSS": "CSS",
|
||||
"Dart": "Dart",
|
||||
"Delphi": "Delphi",
|
||||
"Elixir": "Elixir",
|
||||
"Elm": "Elm",
|
||||
"Emacs Lisp": "Emacs Lisp",
|
||||
"Erlang": "Erlang",
|
||||
"F#": "F#",
|
||||
"Fortran": "Fortran",
|
||||
"Go": "Go",
|
||||
"Gous": "Gosu",
|
||||
"Groovy": "Groovy",
|
||||
"Haml": "Haml",
|
||||
"HaXe": "HaXe",
|
||||
"Haskell": "Haskell",
|
||||
"HTML": "HTML",
|
||||
"INI": "INI",
|
||||
"Jade": "Jade",
|
||||
"Java": "Java",
|
||||
"JavaScript": "JavaScript",
|
||||
"JSON": "JSON",
|
||||
"JSX": "JSX",
|
||||
"Kotlin": "Kotlin",
|
||||
"LESS": "LESS",
|
||||
"Lua": "Lua",
|
||||
"Markdown": "Markdown",
|
||||
"Matlab": "Matlab",
|
||||
"Mustache": "Mustache",
|
||||
"OCaml": "OCaml",
|
||||
"Objective-C": "Objective-C",
|
||||
"Objective-C++": "Objective-C++",
|
||||
"Objective-J": "Objective-J",
|
||||
"Perl": "Perl",
|
||||
"PHP": "PHP",
|
||||
"PowerShell": "PowerShell",
|
||||
"Prolog": "Prolog",
|
||||
"Puppet": "Puppet",
|
||||
"Python": "Python",
|
||||
"R": "R",
|
||||
"reStructuredText": "reStructuredText",
|
||||
"Ruby": "Ruby",
|
||||
"Rust": "Rust",
|
||||
"Sass": "Sass",
|
||||
"Scala": "Scala",
|
||||
"Scheme": "Scheme",
|
||||
"SCSS": "SCSS",
|
||||
"Shell": "Shell",
|
||||
"Slim": "Slim",
|
||||
"Smalltalk": "Smalltalk",
|
||||
"SQL": "SQL",
|
||||
"Swift": "Swift",
|
||||
"Text": "Text",
|
||||
"Turing": "Turing",
|
||||
"Twig": "Twig",
|
||||
"TypeScript": "TypeScript",
|
||||
"VB.net": "VB.net",
|
||||
"VimL": "VimL",
|
||||
"XAML": "XAML",
|
||||
"XML": "XML",
|
||||
"YAML": "YAML"
|
||||
"actionscript": "ActionScript",
|
||||
"apacheconf": "ApacheConf",
|
||||
"applescript": "AppleScript",
|
||||
"asp": "ASP",
|
||||
"assembly": "Assembly",
|
||||
"awk": "Awk",
|
||||
"bash": "Bash",
|
||||
"basic": "Basic",
|
||||
"brightscript": "BrightScript",
|
||||
"c": "C",
|
||||
"c#": "C#",
|
||||
"c++": "C++",
|
||||
"clojure": "Clojure",
|
||||
"cocoa": "Cocoa",
|
||||
"coffeescript": "CoffeeScript",
|
||||
"coldfusion": "ColdFusion",
|
||||
"common lisp": "Common Lisp",
|
||||
"cshtml": "CSHTML",
|
||||
"css": "CSS",
|
||||
"dart": "Dart",
|
||||
"delphi": "Delphi",
|
||||
"elixir": "Elixir",
|
||||
"elm": "Elm",
|
||||
"emacs lisp": "Emacs Lisp",
|
||||
"erlang": "Erlang",
|
||||
"f#": "F#",
|
||||
"fortran": "Fortran",
|
||||
"go": "Go",
|
||||
"gous": "Gosu",
|
||||
"groovy": "Groovy",
|
||||
"haml": "Haml",
|
||||
"haskell": "Haskell",
|
||||
"haxe": "Haxe",
|
||||
"html": "HTML",
|
||||
"ini": "INI",
|
||||
"jade": "Jade",
|
||||
"java": "Java",
|
||||
"javascript": "JavaScript",
|
||||
"json": "JSON",
|
||||
"jsx": "JSX",
|
||||
"kotlin": "Kotlin",
|
||||
"less": "LESS",
|
||||
"lua": "Lua",
|
||||
"markdown": "Markdown",
|
||||
"matlab": "Matlab",
|
||||
"mustache": "Mustache",
|
||||
"objective-c": "Objective-C",
|
||||
"objective-c++": "Objective-C++",
|
||||
"objective-j": "Objective-J",
|
||||
"ocaml": "OCaml",
|
||||
"perl": "Perl",
|
||||
"php": "PHP",
|
||||
"powershell": "PowerShell",
|
||||
"prolog": "Prolog",
|
||||
"puppet": "Puppet",
|
||||
"python": "Python",
|
||||
"r": "R",
|
||||
"restructuredtext": "reStructuredText",
|
||||
"ruby": "Ruby",
|
||||
"rust": "Rust",
|
||||
"sass": "Sass",
|
||||
"scala": "Scala",
|
||||
"scheme": "Scheme",
|
||||
"scss": "SCSS",
|
||||
"shell": "Shell",
|
||||
"slim": "Slim",
|
||||
"smalltalk": "Smalltalk",
|
||||
"sql": "SQL",
|
||||
"swift": "Swift",
|
||||
"text": "Text",
|
||||
"turing": "Turing",
|
||||
"twig": "Twig",
|
||||
"typescript": "TypeScript",
|
||||
"typoscript": "TypoScript",
|
||||
"vb.net": "VB.net",
|
||||
"viml": "VimL",
|
||||
"xaml": "XAML",
|
||||
"xml": "XML",
|
||||
"yaml": "YAML"
|
||||
}
|
||||
|
@ -25,20 +25,6 @@ except (ImportError, SyntaxError): # pragma: nocover
|
||||
import json
|
||||
|
||||
|
||||
class CustomEncoder(json.JSONEncoder):
|
||||
|
||||
def default(self, obj):
|
||||
if isinstance(obj, bytes): # pragma: nocover
|
||||
obj = u(obj)
|
||||
return json.dumps(obj)
|
||||
try: # pragma: nocover
|
||||
encoded = super(CustomEncoder, self).default(obj)
|
||||
except UnicodeDecodeError: # pragma: nocover
|
||||
obj = u(obj)
|
||||
encoded = super(CustomEncoder, self).default(obj)
|
||||
return encoded
|
||||
|
||||
|
||||
class JsonFormatter(logging.Formatter):
|
||||
|
||||
def setup(self, timestamp, is_write, entity, version, plugin, verbose,
|
||||
@ -55,32 +41,25 @@ class JsonFormatter(logging.Formatter):
|
||||
data = OrderedDict([
|
||||
('now', self.formatTime(record, self.datefmt)),
|
||||
])
|
||||
data['version'] = self.version
|
||||
data['plugin'] = self.plugin
|
||||
data['version'] = u(self.version)
|
||||
if self.plugin:
|
||||
data['plugin'] = u(self.plugin)
|
||||
data['time'] = self.timestamp
|
||||
if self.verbose:
|
||||
data['caller'] = record.pathname
|
||||
data['caller'] = u(record.pathname)
|
||||
data['lineno'] = record.lineno
|
||||
data['is_write'] = self.is_write
|
||||
data['file'] = self.entity
|
||||
if not self.is_write:
|
||||
del data['is_write']
|
||||
if self.is_write:
|
||||
data['is_write'] = self.is_write
|
||||
data['file'] = u(self.entity)
|
||||
data['level'] = record.levelname
|
||||
data['message'] = record.getMessage() if self.warnings else record.msg
|
||||
if not self.plugin:
|
||||
del data['plugin']
|
||||
return CustomEncoder().encode(data)
|
||||
data['message'] = u(record.getMessage() if self.warnings else record.msg)
|
||||
return json.dumps(data)
|
||||
|
||||
|
||||
def traceback_formatter(*args, **kwargs):
|
||||
if 'level' in kwargs and (kwargs['level'].lower() == 'warn' or kwargs['level'].lower() == 'warning'):
|
||||
logging.getLogger('WakaTime').warning(traceback.format_exc())
|
||||
elif 'level' in kwargs and kwargs['level'].lower() == 'info':
|
||||
logging.getLogger('WakaTime').info(traceback.format_exc())
|
||||
elif 'level' in kwargs and kwargs['level'].lower() == 'debug':
|
||||
logging.getLogger('WakaTime').debug(traceback.format_exc())
|
||||
else:
|
||||
logging.getLogger('WakaTime').error(traceback.format_exc())
|
||||
def traceback(self, lvl=None):
|
||||
logger = logging.getLogger('WakaTime')
|
||||
if not lvl:
|
||||
lvl = logger.getEffectiveLevel()
|
||||
logger.log(lvl, traceback.format_exc())
|
||||
|
||||
|
||||
def set_log_level(logger, args):
|
||||
@ -113,7 +92,7 @@ def setup_logging(args, version):
|
||||
logger.addHandler(handler)
|
||||
|
||||
# add custom traceback logging method
|
||||
logger.traceback = traceback_formatter
|
||||
logger.traceback = formatter.traceback
|
||||
|
||||
warnings_formatter = JsonFormatter(datefmt='%Y/%m/%d %H:%M:%S %z')
|
||||
warnings_formatter.setup(
|
||||
|
@ -3,7 +3,7 @@
|
||||
wakatime.main
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
wakatime module entry point.
|
||||
Module entry point.
|
||||
|
||||
:copyright: (c) 2013 Alan Hamlett.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
@ -14,39 +14,44 @@ from __future__ import print_function
|
||||
import base64
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
import socket
|
||||
try:
|
||||
import ConfigParser as configparser
|
||||
except ImportError: # pragma: nocover
|
||||
import configparser
|
||||
|
||||
pwd = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, os.path.dirname(pwd))
|
||||
sys.path.insert(0, os.path.join(pwd, 'packages'))
|
||||
|
||||
from .__about__ import __version__
|
||||
from .compat import u, open, is_py3
|
||||
from .arguments import parseArguments
|
||||
from .compat import u, is_py3
|
||||
from .constants import (
|
||||
API_ERROR,
|
||||
AUTH_ERROR,
|
||||
CONFIG_FILE_PARSE_ERROR,
|
||||
SUCCESS,
|
||||
UNKNOWN_ERROR,
|
||||
MALFORMED_HEARTBEAT_ERROR,
|
||||
)
|
||||
from .logger import setup_logging
|
||||
|
||||
log = logging.getLogger('WakaTime')
|
||||
|
||||
try:
|
||||
from .packages import requests
|
||||
except ImportError:
|
||||
log.traceback(logging.ERROR)
|
||||
print(traceback.format_exc())
|
||||
log.error('Please upgrade Python to the latest version.')
|
||||
print('Please upgrade Python to the latest version.')
|
||||
sys.exit(UNKNOWN_ERROR)
|
||||
|
||||
from .offlinequeue import Queue
|
||||
from .packages import argparse
|
||||
from .packages import requests
|
||||
from .packages.requests.exceptions import RequestException
|
||||
from .project import get_project_info
|
||||
from .session_cache import SessionCache
|
||||
from .stats import get_file_stats
|
||||
from .utils import get_user_agent, should_exclude, format_file_path
|
||||
try:
|
||||
from .packages import simplejson as json # pragma: nocover
|
||||
except (ImportError, SyntaxError): # pragma: nocover
|
||||
@ -54,255 +59,11 @@ except (ImportError, SyntaxError): # pragma: nocover
|
||||
from .packages import tzlocal
|
||||
|
||||
|
||||
log = logging.getLogger('WakaTime')
|
||||
|
||||
|
||||
class FileAction(argparse.Action):
|
||||
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
try:
|
||||
if os.path.isfile(values):
|
||||
values = os.path.realpath(values)
|
||||
except: # pragma: nocover
|
||||
pass
|
||||
setattr(namespace, self.dest, values)
|
||||
|
||||
|
||||
def parseConfigFile(configFile=None):
|
||||
"""Returns a configparser.SafeConfigParser instance with configs
|
||||
read from the config file. Default location of the config file is
|
||||
at ~/.wakatime.cfg.
|
||||
"""
|
||||
|
||||
if not configFile:
|
||||
configFile = os.path.join(os.path.expanduser('~'), '.wakatime.cfg')
|
||||
|
||||
configs = configparser.SafeConfigParser()
|
||||
try:
|
||||
with open(configFile, 'r', encoding='utf-8') as fh:
|
||||
try:
|
||||
configs.readfp(fh)
|
||||
except configparser.Error:
|
||||
print(traceback.format_exc())
|
||||
return None
|
||||
except IOError:
|
||||
print(u('Error: Could not read from config file {0}').format(u(configFile)))
|
||||
return configs
|
||||
|
||||
|
||||
def parseArguments():
|
||||
"""Parse command line arguments and configs from ~/.wakatime.cfg.
|
||||
Command line arguments take precedence over config file settings.
|
||||
Returns instances of ArgumentParser and SafeConfigParser.
|
||||
"""
|
||||
|
||||
# define supported command line arguments
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Common interface for the WakaTime api.')
|
||||
parser.add_argument('--entity', dest='entity', metavar='FILE',
|
||||
action=FileAction,
|
||||
help='absolute path to file for the heartbeat; can also be a '+
|
||||
'url, domain, or app when --entity-type is not file')
|
||||
parser.add_argument('--file', dest='file', action=FileAction,
|
||||
help=argparse.SUPPRESS)
|
||||
parser.add_argument('--key', dest='key',
|
||||
help='your wakatime api key; uses api_key from '+
|
||||
'~/.wakatime.cfg by default')
|
||||
parser.add_argument('--write', dest='is_write',
|
||||
action='store_true',
|
||||
help='when set, tells api this heartbeat was triggered from '+
|
||||
'writing to a file')
|
||||
parser.add_argument('--plugin', dest='plugin',
|
||||
help='optional text editor plugin name and version '+
|
||||
'for User-Agent header')
|
||||
parser.add_argument('--time', dest='timestamp', metavar='time',
|
||||
type=float,
|
||||
help='optional floating-point unix epoch timestamp; '+
|
||||
'uses current time by default')
|
||||
parser.add_argument('--lineno', dest='lineno',
|
||||
help='optional line number; current line being edited')
|
||||
parser.add_argument('--cursorpos', dest='cursorpos',
|
||||
help='optional cursor position in the current file')
|
||||
parser.add_argument('--entity-type', dest='entity_type',
|
||||
help='entity type for this heartbeat. can be one of "file", '+
|
||||
'"domain", or "app"; defaults to file.')
|
||||
parser.add_argument('--proxy', dest='proxy',
|
||||
help='optional proxy configuration. Supports HTTPS '+
|
||||
'and SOCKS proxies. For example: '+
|
||||
'https://user:pass@host:port or '+
|
||||
'socks5://user:pass@host:port')
|
||||
parser.add_argument('--project', dest='project',
|
||||
help='optional project name')
|
||||
parser.add_argument('--alternate-project', dest='alternate_project',
|
||||
help='optional alternate project name; auto-discovered project '+
|
||||
'takes priority')
|
||||
parser.add_argument('--alternate-language', dest='alternate_language',
|
||||
help='optional alternate language name; auto-detected language'+
|
||||
'takes priority')
|
||||
parser.add_argument('--hostname', dest='hostname', help='hostname of '+
|
||||
'current machine.')
|
||||
parser.add_argument('--disableoffline', dest='offline',
|
||||
action='store_false',
|
||||
help='disables offline time logging instead of queuing logged time')
|
||||
parser.add_argument('--hidefilenames', dest='hidefilenames',
|
||||
action='store_true',
|
||||
help='obfuscate file names; will not send file names to api')
|
||||
parser.add_argument('--exclude', dest='exclude', action='append',
|
||||
help='filename patterns to exclude from logging; POSIX regex '+
|
||||
'syntax; can be used more than once')
|
||||
parser.add_argument('--include', dest='include', action='append',
|
||||
help='filename patterns to log; when used in combination with '+
|
||||
'--exclude, files matching include will still be logged; '+
|
||||
'POSIX regex syntax; can be used more than once')
|
||||
parser.add_argument('--ignore', dest='ignore', action='append',
|
||||
help=argparse.SUPPRESS)
|
||||
parser.add_argument('--extra-heartbeats', dest='extra_heartbeats',
|
||||
action='store_true',
|
||||
help='reads extra heartbeats from STDIN as a JSON array until EOF')
|
||||
parser.add_argument('--logfile', dest='logfile',
|
||||
help='defaults to ~/.wakatime.log')
|
||||
parser.add_argument('--apiurl', dest='api_url',
|
||||
help='heartbeats api url; for debugging with a local server')
|
||||
parser.add_argument('--timeout', dest='timeout', type=int,
|
||||
help='number of seconds to wait when sending heartbeats to api; '+
|
||||
'defaults to 60 seconds')
|
||||
parser.add_argument('--config', dest='config',
|
||||
help='defaults to ~/.wakatime.cfg')
|
||||
parser.add_argument('--verbose', dest='verbose', action='store_true',
|
||||
help='turns on debug messages in log file')
|
||||
parser.add_argument('--version', action='version', version=__version__)
|
||||
|
||||
# parse command line arguments
|
||||
args = parser.parse_args()
|
||||
|
||||
# use current unix epoch timestamp by default
|
||||
if not args.timestamp:
|
||||
args.timestamp = time.time()
|
||||
|
||||
# parse ~/.wakatime.cfg file
|
||||
configs = parseConfigFile(args.config)
|
||||
if configs is None:
|
||||
return args, configs
|
||||
|
||||
# update args from configs
|
||||
if not args.key:
|
||||
default_key = None
|
||||
if configs.has_option('settings', 'api_key'):
|
||||
default_key = configs.get('settings', 'api_key')
|
||||
elif configs.has_option('settings', 'apikey'):
|
||||
default_key = configs.get('settings', 'apikey')
|
||||
if default_key:
|
||||
args.key = default_key
|
||||
else:
|
||||
parser.error('Missing api key')
|
||||
if not args.entity:
|
||||
if args.file:
|
||||
args.entity = args.file
|
||||
else:
|
||||
parser.error('argument --entity is required')
|
||||
if not args.exclude:
|
||||
args.exclude = []
|
||||
if configs.has_option('settings', 'ignore'):
|
||||
try:
|
||||
for pattern in configs.get('settings', 'ignore').split("\n"):
|
||||
if pattern.strip() != '':
|
||||
args.exclude.append(pattern)
|
||||
except TypeError: # pragma: nocover
|
||||
pass
|
||||
if configs.has_option('settings', 'exclude'):
|
||||
try:
|
||||
for pattern in configs.get('settings', 'exclude').split("\n"):
|
||||
if pattern.strip() != '':
|
||||
args.exclude.append(pattern)
|
||||
except TypeError: # pragma: nocover
|
||||
pass
|
||||
if not args.include:
|
||||
args.include = []
|
||||
if configs.has_option('settings', 'include'):
|
||||
try:
|
||||
for pattern in configs.get('settings', 'include').split("\n"):
|
||||
if pattern.strip() != '':
|
||||
args.include.append(pattern)
|
||||
except TypeError: # pragma: nocover
|
||||
pass
|
||||
if args.offline and configs.has_option('settings', 'offline'):
|
||||
args.offline = configs.getboolean('settings', 'offline')
|
||||
if not args.hidefilenames and configs.has_option('settings', 'hidefilenames'):
|
||||
args.hidefilenames = configs.getboolean('settings', 'hidefilenames')
|
||||
if not args.proxy and configs.has_option('settings', 'proxy'):
|
||||
args.proxy = configs.get('settings', 'proxy')
|
||||
if not args.verbose and configs.has_option('settings', 'verbose'):
|
||||
args.verbose = configs.getboolean('settings', 'verbose')
|
||||
if not args.verbose and configs.has_option('settings', 'debug'):
|
||||
args.verbose = configs.getboolean('settings', 'debug')
|
||||
if not args.logfile and configs.has_option('settings', 'logfile'):
|
||||
args.logfile = configs.get('settings', 'logfile')
|
||||
if not args.api_url and configs.has_option('settings', 'api_url'):
|
||||
args.api_url = configs.get('settings', 'api_url')
|
||||
if not args.timeout and configs.has_option('settings', 'timeout'):
|
||||
try:
|
||||
args.timeout = int(configs.get('settings', 'timeout'))
|
||||
except ValueError:
|
||||
print(traceback.format_exc())
|
||||
|
||||
return args, configs
|
||||
|
||||
|
||||
def should_exclude(entity, include, exclude):
|
||||
if entity is not None and entity.strip() != '':
|
||||
try:
|
||||
for pattern in include:
|
||||
try:
|
||||
compiled = re.compile(pattern, re.IGNORECASE)
|
||||
if compiled.search(entity):
|
||||
return False
|
||||
except re.error as ex:
|
||||
log.warning(u('Regex error ({msg}) for include pattern: {pattern}').format(
|
||||
msg=u(ex),
|
||||
pattern=u(pattern),
|
||||
))
|
||||
except TypeError: # pragma: nocover
|
||||
pass
|
||||
try:
|
||||
for pattern in exclude:
|
||||
try:
|
||||
compiled = re.compile(pattern, re.IGNORECASE)
|
||||
if compiled.search(entity):
|
||||
return pattern
|
||||
except re.error as ex:
|
||||
log.warning(u('Regex error ({msg}) for exclude pattern: {pattern}').format(
|
||||
msg=u(ex),
|
||||
pattern=u(pattern),
|
||||
))
|
||||
except TypeError: # pragma: nocover
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def get_user_agent(plugin):
|
||||
ver = sys.version_info
|
||||
python_version = '%d.%d.%d.%s.%d' % (ver[0], ver[1], ver[2], ver[3], ver[4])
|
||||
user_agent = u('wakatime/{ver} ({platform}) Python{py_ver}').format(
|
||||
ver=u(__version__),
|
||||
platform=u(platform.platform()),
|
||||
py_ver=python_version,
|
||||
)
|
||||
if plugin:
|
||||
user_agent = u('{user_agent} {plugin}').format(
|
||||
user_agent=user_agent,
|
||||
plugin=u(plugin),
|
||||
)
|
||||
else:
|
||||
user_agent = u('{user_agent} Unknown/0').format(
|
||||
user_agent=user_agent,
|
||||
)
|
||||
return user_agent
|
||||
|
||||
|
||||
def send_heartbeat(project=None, branch=None, hostname=None, stats={}, key=None,
|
||||
entity=None, timestamp=None, is_write=None, plugin=None,
|
||||
offline=None, entity_type='file', hidefilenames=None,
|
||||
proxy=None, api_url=None, timeout=None, **kwargs):
|
||||
proxy=None, nosslverify=None, api_url=None, timeout=None,
|
||||
use_ntlm_proxy=False, **kwargs):
|
||||
"""Sends heartbeat as POST request to WakaTime api server.
|
||||
|
||||
Returns `SUCCESS` when heartbeat was sent, otherwise returns an
|
||||
@ -320,8 +81,18 @@ def send_heartbeat(project=None, branch=None, hostname=None, stats={}, key=None,
|
||||
'type': entity_type,
|
||||
}
|
||||
if hidefilenames and entity is not None and entity_type == 'file':
|
||||
extension = u(os.path.splitext(data['entity'])[1])
|
||||
data['entity'] = u('HIDDEN{0}').format(extension)
|
||||
for pattern in hidefilenames:
|
||||
try:
|
||||
compiled = re.compile(pattern, re.IGNORECASE)
|
||||
if compiled.search(entity):
|
||||
extension = u(os.path.splitext(data['entity'])[1])
|
||||
data['entity'] = u('HIDDEN{0}').format(extension)
|
||||
break
|
||||
except re.error as ex:
|
||||
log.warning(u('Regex error ({msg}) for include pattern: {pattern}').format(
|
||||
msg=u(ex),
|
||||
pattern=u(pattern),
|
||||
))
|
||||
if stats.get('lines'):
|
||||
data['lines'] = stats['lines']
|
||||
if stats.get('language'):
|
||||
@ -352,9 +123,6 @@ def send_heartbeat(project=None, branch=None, hostname=None, stats={}, key=None,
|
||||
}
|
||||
if hostname:
|
||||
headers['X-Machine-Name'] = u(hostname).encode('utf-8')
|
||||
proxies = {}
|
||||
if proxy:
|
||||
proxies['https'] = proxy
|
||||
|
||||
# add Olson timezone to request
|
||||
try:
|
||||
@ -367,24 +135,93 @@ def send_heartbeat(project=None, branch=None, hostname=None, stats={}, key=None,
|
||||
session_cache = SessionCache()
|
||||
session = session_cache.get()
|
||||
|
||||
# log time to api
|
||||
should_try_ntlm = False
|
||||
proxies = {}
|
||||
if proxy:
|
||||
if use_ntlm_proxy:
|
||||
from .packages.requests_ntlm import HttpNtlmAuth
|
||||
username = proxy.rsplit(':', 1)
|
||||
password = ''
|
||||
if len(username) == 2:
|
||||
password = username[1]
|
||||
username = username[0]
|
||||
session.auth = HttpNtlmAuth(username, password, session)
|
||||
else:
|
||||
should_try_ntlm = '\\' in proxy
|
||||
proxies['https'] = proxy
|
||||
|
||||
# send request to api
|
||||
response = None
|
||||
try:
|
||||
response = session.post(api_url, data=request_body, headers=headers,
|
||||
proxies=proxies, timeout=timeout)
|
||||
proxies=proxies, timeout=timeout,
|
||||
verify=not nosslverify)
|
||||
except RequestException:
|
||||
exception_data = {
|
||||
sys.exc_info()[0].__name__: u(sys.exc_info()[1]),
|
||||
}
|
||||
if log.isEnabledFor(logging.DEBUG):
|
||||
exception_data['traceback'] = traceback.format_exc()
|
||||
if offline:
|
||||
queue = Queue()
|
||||
queue.push(data, json.dumps(stats), plugin)
|
||||
if log.isEnabledFor(logging.DEBUG):
|
||||
log.warn(exception_data)
|
||||
if should_try_ntlm:
|
||||
return send_heartbeat(
|
||||
project=project,
|
||||
entity=entity,
|
||||
timestamp=timestamp,
|
||||
branch=branch,
|
||||
hostname=hostname,
|
||||
stats=stats,
|
||||
key=key,
|
||||
is_write=is_write,
|
||||
plugin=plugin,
|
||||
offline=offline,
|
||||
hidefilenames=hidefilenames,
|
||||
entity_type=entity_type,
|
||||
proxy=proxy,
|
||||
api_url=api_url,
|
||||
timeout=timeout,
|
||||
use_ntlm_proxy=True,
|
||||
)
|
||||
else:
|
||||
log.error(exception_data)
|
||||
exception_data = {
|
||||
sys.exc_info()[0].__name__: u(sys.exc_info()[1]),
|
||||
}
|
||||
if log.isEnabledFor(logging.DEBUG):
|
||||
exception_data['traceback'] = traceback.format_exc()
|
||||
if offline:
|
||||
queue = Queue()
|
||||
queue.push(data, json.dumps(stats), plugin)
|
||||
if log.isEnabledFor(logging.DEBUG):
|
||||
log.warn(exception_data)
|
||||
else:
|
||||
log.error(exception_data)
|
||||
|
||||
except: # delete cached session when requests raises unknown exception
|
||||
if should_try_ntlm:
|
||||
return send_heartbeat(
|
||||
project=project,
|
||||
entity=entity,
|
||||
timestamp=timestamp,
|
||||
branch=branch,
|
||||
hostname=hostname,
|
||||
stats=stats,
|
||||
key=key,
|
||||
is_write=is_write,
|
||||
plugin=plugin,
|
||||
offline=offline,
|
||||
hidefilenames=hidefilenames,
|
||||
entity_type=entity_type,
|
||||
proxy=proxy,
|
||||
api_url=api_url,
|
||||
timeout=timeout,
|
||||
use_ntlm_proxy=True,
|
||||
)
|
||||
else:
|
||||
exception_data = {
|
||||
sys.exc_info()[0].__name__: u(sys.exc_info()[1]),
|
||||
'traceback': traceback.format_exc(),
|
||||
}
|
||||
if offline:
|
||||
queue = Queue()
|
||||
queue.push(data, json.dumps(stats), plugin)
|
||||
log.warn(exception_data)
|
||||
session_cache.delete()
|
||||
return API_ERROR
|
||||
|
||||
else:
|
||||
code = response.status_code if response is not None else None
|
||||
content = response.text if response is not None else None
|
||||
@ -394,32 +231,52 @@ def send_heartbeat(project=None, branch=None, hostname=None, stats={}, key=None,
|
||||
})
|
||||
session_cache.save(session)
|
||||
return SUCCESS
|
||||
if offline:
|
||||
if code != 400:
|
||||
queue = Queue()
|
||||
queue.push(data, json.dumps(stats), plugin)
|
||||
if code == 401:
|
||||
if should_try_ntlm:
|
||||
return send_heartbeat(
|
||||
project=project,
|
||||
entity=entity,
|
||||
timestamp=timestamp,
|
||||
branch=branch,
|
||||
hostname=hostname,
|
||||
stats=stats,
|
||||
key=key,
|
||||
is_write=is_write,
|
||||
plugin=plugin,
|
||||
offline=offline,
|
||||
hidefilenames=hidefilenames,
|
||||
entity_type=entity_type,
|
||||
proxy=proxy,
|
||||
api_url=api_url,
|
||||
timeout=timeout,
|
||||
use_ntlm_proxy=True,
|
||||
)
|
||||
else:
|
||||
if offline:
|
||||
if code != 400:
|
||||
queue = Queue()
|
||||
queue.push(data, json.dumps(stats), plugin)
|
||||
if code == 401:
|
||||
log.error({
|
||||
'response_code': code,
|
||||
'response_content': content,
|
||||
})
|
||||
session_cache.delete()
|
||||
return AUTH_ERROR
|
||||
elif log.isEnabledFor(logging.DEBUG):
|
||||
log.warn({
|
||||
'response_code': code,
|
||||
'response_content': content,
|
||||
})
|
||||
else:
|
||||
log.error({
|
||||
'response_code': code,
|
||||
'response_content': content,
|
||||
})
|
||||
session_cache.delete()
|
||||
return AUTH_ERROR
|
||||
elif log.isEnabledFor(logging.DEBUG):
|
||||
log.warn({
|
||||
'response_code': code,
|
||||
'response_content': content,
|
||||
})
|
||||
else:
|
||||
log.error({
|
||||
'response_code': code,
|
||||
'response_content': content,
|
||||
})
|
||||
else:
|
||||
log.error({
|
||||
'response_code': code,
|
||||
'response_content': content,
|
||||
})
|
||||
session_cache.delete()
|
||||
return API_ERROR
|
||||
|
||||
@ -467,6 +324,9 @@ def process_heartbeat(args, configs, hostname, heartbeat):
|
||||
if heartbeat.get('entity_type') not in ['file', 'domain', 'app']:
|
||||
heartbeat['entity_type'] = 'file'
|
||||
|
||||
if heartbeat['entity_type'] == 'file':
|
||||
heartbeat['entity'] = format_file_path(heartbeat['entity'])
|
||||
|
||||
if heartbeat['entity_type'] != 'file' or os.path.isfile(heartbeat['entity']):
|
||||
|
||||
stats = get_file_stats(heartbeat['entity'],
|
||||
@ -474,7 +334,7 @@ def process_heartbeat(args, configs, hostname, heartbeat):
|
||||
lineno=heartbeat.get('lineno'),
|
||||
cursorpos=heartbeat.get('cursorpos'),
|
||||
plugin=args.plugin,
|
||||
alternate_language=heartbeat.get('alternate_language'))
|
||||
language=heartbeat.get('language'))
|
||||
|
||||
project = heartbeat.get('project') or heartbeat.get('alternate_project')
|
||||
branch = None
|
||||
@ -491,6 +351,7 @@ def process_heartbeat(args, configs, hostname, heartbeat):
|
||||
heartbeat['offline'] = args.offline
|
||||
heartbeat['hidefilenames'] = args.hidefilenames
|
||||
heartbeat['proxy'] = args.proxy
|
||||
heartbeat['nosslverify'] = args.nosslverify
|
||||
heartbeat['api_url'] = args.api_url
|
||||
|
||||
return send_heartbeat(**heartbeat)
|
||||
@ -505,8 +366,6 @@ def execute(argv=None):
|
||||
sys.argv = ['wakatime'] + argv
|
||||
|
||||
args, configs = parseArguments()
|
||||
if configs is None:
|
||||
return CONFIG_FILE_PARSE_ERROR
|
||||
|
||||
setup_logging(args, __version__)
|
||||
|
||||
@ -530,6 +389,6 @@ def execute(argv=None):
|
||||
return retval
|
||||
|
||||
except:
|
||||
log.traceback()
|
||||
log.traceback(logging.ERROR)
|
||||
print(traceback.format_exc())
|
||||
return UNKNOWN_ERROR
|
||||
|
@ -12,7 +12,6 @@
|
||||
|
||||
import logging
|
||||
import os
|
||||
import traceback
|
||||
from time import sleep
|
||||
|
||||
try:
|
||||
@ -35,7 +34,7 @@ class Queue(object):
|
||||
return self.db_file
|
||||
|
||||
def connect(self):
|
||||
conn = sqlite3.connect(self.get_db_file())
|
||||
conn = sqlite3.connect(self.get_db_file(), isolation_level=None)
|
||||
c = conn.cursor()
|
||||
c.execute('''CREATE TABLE IF NOT EXISTS {0} (
|
||||
entity text,
|
||||
@ -70,7 +69,7 @@ class Queue(object):
|
||||
conn.commit()
|
||||
conn.close()
|
||||
except sqlite3.Error:
|
||||
log.error(traceback.format_exc())
|
||||
log.traceback()
|
||||
|
||||
def pop(self):
|
||||
if not HAS_SQL: # pragma: nocover
|
||||
@ -81,7 +80,7 @@ class Queue(object):
|
||||
try:
|
||||
conn, c = self.connect()
|
||||
except sqlite3.Error:
|
||||
log.debug(traceback.format_exc())
|
||||
log.traceback(logging.DEBUG)
|
||||
return None
|
||||
loop = True
|
||||
while loop and tries > -1:
|
||||
@ -119,11 +118,11 @@ class Queue(object):
|
||||
}
|
||||
loop = False
|
||||
except sqlite3.Error: # pragma: nocover
|
||||
log.debug(traceback.format_exc())
|
||||
log.traceback(logging.DEBUG)
|
||||
sleep(wait)
|
||||
tries -= 1
|
||||
try:
|
||||
conn.close()
|
||||
except sqlite3.Error: # pragma: nocover
|
||||
log.debug(traceback.format_exc())
|
||||
log.traceback(logging.DEBUG)
|
||||
return heartbeat
|
||||
|
@ -1,14 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from ..compat import is_py2
|
||||
|
||||
if is_py2:
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'py2'))
|
||||
else:
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'py3'))
|
||||
|
||||
import tzlocal
|
||||
from pygments.lexers import get_lexer_by_name, guess_lexer_for_filename
|
||||
from pygments.modeline import get_filetype_from_buffer
|
||||
from pygments.util import ClassNotFound
|
||||
|
1390
packages/wakatime/packages/configparser/__init__.py
Normal file
1390
packages/wakatime/packages/configparser/__init__.py
Normal file
File diff suppressed because it is too large
Load Diff
171
packages/wakatime/packages/configparser/helpers.py
Normal file
171
packages/wakatime/packages/configparser/helpers.py
Normal file
@ -0,0 +1,171 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from collections import MutableMapping
|
||||
try:
|
||||
from collections import UserDict
|
||||
except ImportError:
|
||||
from UserDict import UserDict
|
||||
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
from ordereddict import OrderedDict
|
||||
|
||||
from io import open
|
||||
import sys
|
||||
try:
|
||||
from thread import get_ident
|
||||
except ImportError:
|
||||
try:
|
||||
from _thread import get_ident
|
||||
except ImportError:
|
||||
from _dummy_thread import get_ident
|
||||
|
||||
|
||||
PY2 = sys.version_info[0] == 2
|
||||
PY3 = sys.version_info[0] == 3
|
||||
|
||||
str = type('str')
|
||||
|
||||
|
||||
def from_none(exc):
|
||||
"""raise from_none(ValueError('a')) == raise ValueError('a') from None"""
|
||||
exc.__cause__ = None
|
||||
exc.__suppress_context__ = True
|
||||
return exc
|
||||
|
||||
|
||||
# from reprlib 3.2.1
|
||||
def recursive_repr(fillvalue='...'):
|
||||
'Decorator to make a repr function return fillvalue for a recursive call'
|
||||
|
||||
def decorating_function(user_function):
|
||||
repr_running = set()
|
||||
|
||||
def wrapper(self):
|
||||
key = id(self), get_ident()
|
||||
if key in repr_running:
|
||||
return fillvalue
|
||||
repr_running.add(key)
|
||||
try:
|
||||
result = user_function(self)
|
||||
finally:
|
||||
repr_running.discard(key)
|
||||
return result
|
||||
|
||||
# Can't use functools.wraps() here because of bootstrap issues
|
||||
wrapper.__module__ = getattr(user_function, '__module__')
|
||||
wrapper.__doc__ = getattr(user_function, '__doc__')
|
||||
wrapper.__name__ = getattr(user_function, '__name__')
|
||||
wrapper.__annotations__ = getattr(user_function, '__annotations__', {})
|
||||
return wrapper
|
||||
|
||||
return decorating_function
|
||||
|
||||
# from collections 3.2.1
|
||||
class _ChainMap(MutableMapping):
|
||||
''' A ChainMap groups multiple dicts (or other mappings) together
|
||||
to create a single, updateable view.
|
||||
|
||||
The underlying mappings are stored in a list. That list is public and can
|
||||
accessed or updated using the *maps* attribute. There is no other state.
|
||||
|
||||
Lookups search the underlying mappings successively until a key is found.
|
||||
In contrast, writes, updates, and deletions only operate on the first
|
||||
mapping.
|
||||
|
||||
'''
|
||||
|
||||
def __init__(self, *maps):
|
||||
'''Initialize a ChainMap by setting *maps* to the given mappings.
|
||||
If no mappings are provided, a single empty dictionary is used.
|
||||
|
||||
'''
|
||||
self.maps = list(maps) or [{}] # always at least one map
|
||||
|
||||
def __missing__(self, key):
|
||||
raise KeyError(key)
|
||||
|
||||
def __getitem__(self, key):
|
||||
for mapping in self.maps:
|
||||
try:
|
||||
return mapping[key] # can't use 'key in mapping' with defaultdict
|
||||
except KeyError:
|
||||
pass
|
||||
return self.__missing__(key) # support subclasses that define __missing__
|
||||
|
||||
def get(self, key, default=None):
|
||||
return self[key] if key in self else default
|
||||
|
||||
def __len__(self):
|
||||
return len(set().union(*self.maps)) # reuses stored hash values if possible
|
||||
|
||||
def __iter__(self):
|
||||
return iter(set().union(*self.maps))
|
||||
|
||||
def __contains__(self, key):
|
||||
return any(key in m for m in self.maps)
|
||||
|
||||
@recursive_repr()
|
||||
def __repr__(self):
|
||||
return '{0.__class__.__name__}({1})'.format(
|
||||
self, ', '.join(map(repr, self.maps)))
|
||||
|
||||
@classmethod
|
||||
def fromkeys(cls, iterable, *args):
|
||||
'Create a ChainMap with a single dict created from the iterable.'
|
||||
return cls(dict.fromkeys(iterable, *args))
|
||||
|
||||
def copy(self):
|
||||
'New ChainMap or subclass with a new copy of maps[0] and refs to maps[1:]'
|
||||
return self.__class__(self.maps[0].copy(), *self.maps[1:])
|
||||
|
||||
__copy__ = copy
|
||||
|
||||
def new_child(self): # like Django's Context.push()
|
||||
'New ChainMap with a new dict followed by all previous maps.'
|
||||
return self.__class__({}, *self.maps)
|
||||
|
||||
@property
|
||||
def parents(self): # like Django's Context.pop()
|
||||
'New ChainMap from maps[1:].'
|
||||
return self.__class__(*self.maps[1:])
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.maps[0][key] = value
|
||||
|
||||
def __delitem__(self, key):
|
||||
try:
|
||||
del self.maps[0][key]
|
||||
except KeyError:
|
||||
raise KeyError('Key not found in the first mapping: {!r}'.format(key))
|
||||
|
||||
def popitem(self):
|
||||
'Remove and return an item pair from maps[0]. Raise KeyError is maps[0] is empty.'
|
||||
try:
|
||||
return self.maps[0].popitem()
|
||||
except KeyError:
|
||||
raise KeyError('No keys found in the first mapping.')
|
||||
|
||||
def pop(self, key, *args):
|
||||
'Remove *key* from maps[0] and return its value. Raise KeyError if *key* not in maps[0].'
|
||||
try:
|
||||
return self.maps[0].pop(key, *args)
|
||||
except KeyError:
|
||||
raise KeyError('Key not found in the first mapping: {!r}'.format(key))
|
||||
|
||||
def clear(self):
|
||||
'Clear maps[0], leaving maps[1:] intact.'
|
||||
self.maps[0].clear()
|
||||
|
||||
|
||||
try:
|
||||
from collections import ChainMap
|
||||
except ImportError:
|
||||
ChainMap = _ChainMap
|
156
packages/wakatime/packages/ntlm_auth/U32.py
Normal file
156
packages/wakatime/packages/ntlm_auth/U32.py
Normal file
@ -0,0 +1,156 @@
|
||||
# This file is part of 'NTLM Authorization Proxy Server' http://sourceforge.net/projects/ntlmaps/
|
||||
# Copyright 2001 Dmitry A. Rozmanov <dima@xenon.spb.ru>
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation, either
|
||||
# version 3 of the License, or (at your option) any later version.
|
||||
|
||||
# This library 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
|
||||
# Lesser General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.
|
||||
|
||||
from __future__ import division
|
||||
import six
|
||||
|
||||
C = 0x1000000000
|
||||
|
||||
|
||||
def norm(n):
|
||||
return n & 0xFFFFFFFF
|
||||
|
||||
|
||||
class U32:
|
||||
v = 0
|
||||
|
||||
def __init__(self, value=0):
|
||||
if not isinstance(value, six.integer_types):
|
||||
value = six.byte2int(value)
|
||||
|
||||
self.v = C + norm(abs(int(value)))
|
||||
|
||||
def set(self, value=0):
|
||||
self.v = C + norm(abs(int(value)))
|
||||
|
||||
def __repr__(self):
|
||||
return hex(norm(self.v))
|
||||
|
||||
def __long__(self):
|
||||
return int(norm(self.v))
|
||||
|
||||
def __int__(self):
|
||||
return int(norm(self.v))
|
||||
|
||||
def __chr__(self):
|
||||
return chr(norm(self.v))
|
||||
|
||||
def __add__(self, b):
|
||||
r = U32()
|
||||
r.v = C + norm(self.v + b.v)
|
||||
return r
|
||||
|
||||
def __sub__(self, b):
|
||||
r = U32()
|
||||
if self.v < b.v:
|
||||
r.v = C + norm(0x100000000 - (b.v - self.v))
|
||||
else:
|
||||
r.v = C + norm(self.v - b.v)
|
||||
return r
|
||||
|
||||
def __mul__(self, b):
|
||||
r = U32()
|
||||
r.v = C + norm(self.v * b.v)
|
||||
return r
|
||||
|
||||
def __div__(self, b):
|
||||
r = U32()
|
||||
r.v = C + (norm(self.v) // norm(b.v))
|
||||
return r
|
||||
|
||||
def __truediv__(self, b):
|
||||
r = U32()
|
||||
r.v = C + (norm(self.v) / norm(b.v))
|
||||
return r
|
||||
|
||||
def __mod__(self, b):
|
||||
r = U32()
|
||||
r.v = C + (norm(self.v) % norm(b.v))
|
||||
return r
|
||||
|
||||
def __neg__(self):
|
||||
return U32(self.v)
|
||||
|
||||
def __pos__(self):
|
||||
return U32(self.v)
|
||||
|
||||
def __abs__(self):
|
||||
return U32(self.v)
|
||||
|
||||
def __invert__(self):
|
||||
r = U32()
|
||||
r.v = C + norm(~self.v)
|
||||
return r
|
||||
|
||||
def __lshift__(self, b):
|
||||
r = U32()
|
||||
r.v = C + norm(self.v << b)
|
||||
return r
|
||||
|
||||
def __rshift__(self, b):
|
||||
r = U32()
|
||||
r.v = C + (norm(self.v) >> b)
|
||||
return r
|
||||
|
||||
def __and__(self, b):
|
||||
r = U32()
|
||||
r.v = C + norm(self.v & b.v)
|
||||
return r
|
||||
|
||||
def __or__(self, b):
|
||||
r = U32()
|
||||
r.v = C + norm(self.v | b.v)
|
||||
return r
|
||||
|
||||
def __xor__(self, b):
|
||||
r = U32()
|
||||
r.v = C + norm(self.v ^ b.v)
|
||||
return r
|
||||
|
||||
def __not__(self):
|
||||
return U32(not norm(self.v))
|
||||
|
||||
def truth(self):
|
||||
return norm(self.v)
|
||||
|
||||
def __cmp__(self, b):
|
||||
if norm(self.v) > norm(b.v):
|
||||
return 1
|
||||
elif norm(self.v) < norm(b.v):
|
||||
return -1
|
||||
else:
|
||||
return 0
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.v < other.v
|
||||
|
||||
def __gt__(self, other):
|
||||
return self.v > other.v
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.v == other.v
|
||||
|
||||
def __le__(self, other):
|
||||
return self.v <= other.v
|
||||
|
||||
def __ge__(self, other):
|
||||
return self.v >= other.v
|
||||
|
||||
def __ne__(self, other):
|
||||
return self.v != other.v
|
||||
|
||||
def __nonzero__(self):
|
||||
return norm(self.v)
|
3
packages/wakatime/packages/ntlm_auth/__init__.py
Normal file
3
packages/wakatime/packages/ntlm_auth/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
from . import ntlm, session_security
|
||||
|
||||
__all__ = ('ntlm', 'session_security')
|
80
packages/wakatime/packages/ntlm_auth/compute_hash.py
Normal file
80
packages/wakatime/packages/ntlm_auth/compute_hash.py
Normal file
@ -0,0 +1,80 @@
|
||||
# This library is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation, either
|
||||
# version 3 of the License, or (at your option) any later version.
|
||||
|
||||
# This library 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
|
||||
# Lesser General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.
|
||||
|
||||
import binascii
|
||||
import hashlib
|
||||
import hmac
|
||||
import re
|
||||
from ntlm_auth import des
|
||||
|
||||
|
||||
def _lmowfv1(password):
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
3.3.1 NTLM v1 Authentication
|
||||
Same function as LMOWFv1 in document to create a one way hash of the password. Only
|
||||
used in NTLMv1 auth without session security
|
||||
|
||||
:param password: The password of the user we are trying to authenticate with
|
||||
:return res: A Lan Manager hash of the password supplied
|
||||
"""
|
||||
|
||||
# fix the password length to 14 bytes
|
||||
password = password.upper()
|
||||
lm_pw = password[0:14]
|
||||
|
||||
# do hash
|
||||
magic_str = b"KGS!@#$%" # page 56 in [MS-NLMP v28.0]
|
||||
|
||||
res = b''
|
||||
dobj = des.DES(lm_pw[0:7])
|
||||
res = res + dobj.encrypt(magic_str)
|
||||
|
||||
dobj = des.DES(lm_pw[7:14])
|
||||
res = res + dobj.encrypt(magic_str)
|
||||
|
||||
return res
|
||||
|
||||
def _ntowfv1(password):
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
3.3.1 NTLM v1 Authentication
|
||||
Same function as NTOWFv1 in document to create a one way hash of the password. Only
|
||||
used in NTLMv1 auth without session security
|
||||
|
||||
:param password: The password of the user we are trying to authenticate with
|
||||
:return digest: An NT hash of the password supplied
|
||||
"""
|
||||
|
||||
digest = hashlib.new('md4', password.encode('utf-16le')).digest()
|
||||
return digest
|
||||
|
||||
def _ntowfv2(user_name, password, domain_name):
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
3.3.2 NTLM v2 Authentication
|
||||
Same function as NTOWFv2 (and LMOWFv2) in document to create a one way hash of the password.
|
||||
This combines some extra security features over the v1 calculations used in NTLMv2 auth.
|
||||
|
||||
:param user_name: The user name of the user we are trying to authenticate with
|
||||
:param password: The password of the user we are trying to authenticate with
|
||||
:param domain_name: The domain name of the user account we are authenticated with
|
||||
:return digest: An NT hash of the parameters supplied
|
||||
"""
|
||||
digest = _ntowfv1(password)
|
||||
digest = hmac.new(digest, (user_name.upper() + domain_name).encode('utf-16le')).digest()
|
||||
|
||||
return digest
|
138
packages/wakatime/packages/ntlm_auth/compute_keys.py
Normal file
138
packages/wakatime/packages/ntlm_auth/compute_keys.py
Normal file
@ -0,0 +1,138 @@
|
||||
# This library is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation, either
|
||||
# version 3 of the License, or (at your option) any later version.
|
||||
|
||||
# This library 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
|
||||
# Lesser General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.
|
||||
|
||||
import binascii
|
||||
import hashlib
|
||||
import hmac
|
||||
from ntlm_auth import des
|
||||
from ntlm_auth.constants import NegotiateFlags
|
||||
|
||||
def _get_exchange_key_ntlm_v1(negotiate_flags, session_base_key, server_challenge, lm_challenge_response, lm_hash):
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
4.3.5.1 KXKEY
|
||||
Calculates the Key Exchange Key for NTLMv1 authentication. Used for signing and sealing messages
|
||||
|
||||
@param negotiate_flags:
|
||||
@param session_base_key: A session key calculated from the user password challenge
|
||||
@param server_challenge: A random 8-byte response generated by the server in the CHALLENGE_MESSAGE
|
||||
@param lm_challenge_response: The LmChallengeResponse value computed in ComputeResponse
|
||||
@param lm_hash: The LMOWF computed in Compute Response
|
||||
@return key_exchange_key: The Key Exchange Key (KXKEY) used to sign and seal messages and compute the ExportedSessionKey
|
||||
"""
|
||||
if negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
|
||||
key_exchange_key = hmac.new(session_base_key, server_challenge + lm_challenge_response[:8]).digest()
|
||||
elif negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_LM_KEY:
|
||||
des_handler = des.DES(lm_hash[:7])
|
||||
first_des = des_handler.encrypt(lm_challenge_response[:8])
|
||||
des_handler = des.DES(lm_hash[7:8] + binascii.unhexlify('bdbdbdbdbdbdbd'))
|
||||
second_des = des_handler.encrypt(lm_challenge_response[:8])
|
||||
|
||||
key_exchange_key = first_des + second_des
|
||||
elif negotiate_flags & NegotiateFlags.NTLMSSP_REQUEST_NON_NT_SESSION_KEY:
|
||||
key_exchange_key = lm_hash[:8] + b'\0' * 8
|
||||
else:
|
||||
key_exchange_key = session_base_key
|
||||
|
||||
return key_exchange_key
|
||||
|
||||
def _get_exchange_key_ntlm_v2(session_base_key):
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
4.3.5.1 KXKEY
|
||||
Calculates the Key Exchange Key for NTLMv2 authentication. Used for signing and sealing messages.
|
||||
According to docs, 'If NTLM v2 is used, KeyExchangeKey MUST be set to the given 128-bit SessionBaseKey
|
||||
|
||||
@param session_base_key: A session key calculated from the user password challenge
|
||||
@return key_exchange_key: The Key Exchange Key (KXKEY) used to sign and seal messages
|
||||
"""
|
||||
return session_base_key
|
||||
|
||||
def get_sign_key(exported_session_key, magic_constant):
|
||||
"""
|
||||
3.4.5.2 SIGNKEY
|
||||
|
||||
@param exported_session_key: A 128-bit session key used to derive signing and sealing keys
|
||||
@param magic_constant: A constant value set in the MS-NLMP documentation (constants.SignSealConstants)
|
||||
@return sign_key: Key used to sign messages
|
||||
"""
|
||||
|
||||
sign_key = hashlib.md5(exported_session_key + magic_constant).digest()
|
||||
|
||||
return sign_key
|
||||
|
||||
def get_seal_key(negotiate_flags, exported_session_key, magic_constant):
|
||||
"""
|
||||
3.4.5.3. SEALKEY
|
||||
Main method to use to calculate the seal_key used to seal (encrypt) messages. This will determine
|
||||
the correct method below to use based on the compatibility flags set and should be called instead
|
||||
of the others
|
||||
|
||||
@param exported_session_key: A 128-bit session key used to derive signing and sealing keys
|
||||
@param negotiate_flags: The negotiate_flags structure sent by the server
|
||||
@param magic_constant: A constant value set in the MS-NLMP documentation (constants.SignSealConstants)
|
||||
@return seal_key: Key used to seal messages
|
||||
"""
|
||||
|
||||
if negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
|
||||
seal_key = _get_seal_key_ntlm2(negotiate_flags, exported_session_key, magic_constant)
|
||||
elif negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_LM_KEY:
|
||||
seal_key = _get_seal_key_ntlm1(negotiate_flags, exported_session_key)
|
||||
else:
|
||||
seal_key = exported_session_key
|
||||
|
||||
return seal_key
|
||||
|
||||
def _get_seal_key_ntlm1(negotiate_flags, exported_session_key):
|
||||
"""
|
||||
3.4.5.3 SEALKEY
|
||||
Calculates the seal_key used to seal (encrypt) messages. This for authentication where
|
||||
NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY has not been negotiated. Will weaken the keys
|
||||
if NTLMSSP_NEGOTIATE_56 is not negotiated it will default to the 40-bit key
|
||||
|
||||
@param negotiate_flags: The negotiate_flags structure sent by the server
|
||||
@param exported_session_key: A 128-bit session key used to derive signing and sealing keys
|
||||
@return seal_key: Key used to seal messages
|
||||
"""
|
||||
if negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_56:
|
||||
seal_key = exported_session_key[:7] + binascii.unhexlify('a0')
|
||||
else:
|
||||
seal_key = exported_session_key[:5] + binascii.unhexlify('e538b0')
|
||||
|
||||
return seal_key
|
||||
|
||||
def _get_seal_key_ntlm2(negotiate_flags, exported_session_key, magic_constant):
|
||||
"""
|
||||
3.4.5.3 SEALKEY
|
||||
Calculates the seal_key used to seal (encrypt) messages. This for authentication where
|
||||
NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY has been negotiated. Will weaken the keys
|
||||
if NTLMSSP_NEGOTIATE_128 is not negotiated, will try NEGOTIATE_56 and then will default
|
||||
to the 40-bit key
|
||||
|
||||
@param negotiate_flags: The negotiate_flags structure sent by the server
|
||||
@param exported_session_key: A 128-bit session key used to derive signing and sealing keys
|
||||
@param magic_constant: A constant value set in the MS-NLMP documentation (constants.SignSealConstants)
|
||||
@return seal_key: Key used to seal messages
|
||||
"""
|
||||
if negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_128:
|
||||
seal_key = exported_session_key
|
||||
elif negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_56:
|
||||
seal_key = exported_session_key[:7]
|
||||
else:
|
||||
seal_key = exported_session_key[:5]
|
||||
|
||||
seal_key = hashlib.md5(seal_key + magic_constant).digest()
|
||||
|
||||
return seal_key
|
397
packages/wakatime/packages/ntlm_auth/compute_response.py
Normal file
397
packages/wakatime/packages/ntlm_auth/compute_response.py
Normal file
@ -0,0 +1,397 @@
|
||||
# This library is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation, either
|
||||
# version 3 of the License, or (at your option) any later version.
|
||||
|
||||
# This library 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
|
||||
# Lesser General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.
|
||||
|
||||
import base64
|
||||
import calendar
|
||||
import hashlib
|
||||
import hmac
|
||||
import os
|
||||
import struct
|
||||
import time
|
||||
import ntlm_auth.compute_hash as comphash
|
||||
import ntlm_auth.compute_keys as compkeys
|
||||
from ntlm_auth import des
|
||||
from ntlm_auth.constants import NegotiateFlags, AvFlags
|
||||
from ntlm_auth.gss_channel_bindings import GssChannelBindingsStruct
|
||||
from ntlm_auth.target_info import TargetInfo
|
||||
|
||||
class ComputeResponse():
|
||||
"""
|
||||
Constructor for the response computations. This class will compute the various
|
||||
nt and lm challenge responses.
|
||||
|
||||
:param user_name: The user name of the user we are trying to authenticate with
|
||||
:param password: The password of the user we are trying to authenticate with
|
||||
:param domain_name: The domain name of the user account we are authenticated with, default is None
|
||||
:param challenge_message: A ChallengeMessage object that was received from the server after the negotiate_message
|
||||
:param ntlm_compatibility: The Lan Manager Compatibility Level, used to determine what NTLM auth version to use, see Ntlm in ntlm.py for more details
|
||||
"""
|
||||
def __init__(self, user_name, password, domain_name, challenge_message, ntlm_compatibility):
|
||||
self._user_name = user_name
|
||||
self._password = password
|
||||
self._domain_name = domain_name
|
||||
self._challenge_message = challenge_message
|
||||
self._negotiate_flags = challenge_message.negotiate_flags
|
||||
self._server_challenge = challenge_message.server_challenge
|
||||
self._server_target_info = challenge_message.target_info
|
||||
self._ntlm_compatibility = ntlm_compatibility
|
||||
self._client_challenge = os.urandom(8)
|
||||
|
||||
def get_lm_challenge_response(self):
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
3.3.1 - NTLM v1 Authentication
|
||||
3.3.2 - NTLM v2 Authentication
|
||||
|
||||
This method returns the LmChallengeResponse key based on the ntlm_compatibility chosen
|
||||
and the target_info supplied by the CHALLENGE_MESSAGE. It is quite different from what
|
||||
is set in the document as it combines the NTLMv1, NTLM2 and NTLMv2 methods into one
|
||||
and calls separate methods based on the ntlm_compatibility flag chosen.
|
||||
|
||||
:return: response (LmChallengeResponse) - The LM response to the server challenge. Computed by the client
|
||||
"""
|
||||
if self._negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY and self._ntlm_compatibility < 3:
|
||||
response = ComputeResponse._get_LMv1_with_session_security_response(self._client_challenge)
|
||||
|
||||
elif 0 <= self._ntlm_compatibility <= 1:
|
||||
response = ComputeResponse._get_LMv1_response(self._password, self._server_challenge)
|
||||
elif self._ntlm_compatibility == 2:
|
||||
# Based on the compatibility level we don't want to use LM responses, ignore the session_base_key as it is returned in nt
|
||||
response, ignore_key = ComputeResponse._get_NTLMv1_response(self._password, self._server_challenge)
|
||||
else:
|
||||
"""
|
||||
[MS-NLMP] v28.0 page 45 - 2016-07-14
|
||||
|
||||
3.1.5.12 Client Received a CHALLENGE_MESSAGE from the Server
|
||||
If NTLMv2 authentication is used and the CHALLENGE_MESSAGE TargetInfo field has an MsvAvTimestamp present,
|
||||
the client SHOULD NOT send the LmChallengeResponse and SHOULD send Z(24) instead.
|
||||
"""
|
||||
|
||||
response = ComputeResponse._get_LMv2_response(self._user_name, self._password, self._domain_name,
|
||||
self._server_challenge,
|
||||
self._client_challenge)
|
||||
if self._server_target_info is not None:
|
||||
timestamp = self._server_target_info[TargetInfo.MSV_AV_TIMESTAMP]
|
||||
if timestamp is not None:
|
||||
response = b'\0' * 24
|
||||
|
||||
return response
|
||||
|
||||
def get_nt_challenge_response(self, lm_challenge_response, server_certificate_hash):
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
3.3.1 - NTLM v1 Authentication
|
||||
3.3.2 - NTLM v2 Authentication
|
||||
|
||||
This method returns the NtChallengeResponse key based on the ntlm_compatibility chosen
|
||||
and the target_info supplied by the CHALLENGE_MESSAGE. It is quite different from what
|
||||
is set in the document as it combines the NTLMv1, NTLM2 and NTLMv2 methods into one
|
||||
and calls separate methods based on the ntlm_compatibility value chosen.
|
||||
|
||||
:param lm_challenge_response: The LmChallengeResponse calculated beforeand, used to get the key_exchange_key value
|
||||
:param server_certificate_hash: The SHA256 hash of the server certificate (DER encoded) NTLM is authenticated to.
|
||||
Used in Channel Binding Tokens if present, default value is None. See
|
||||
AuthenticateMessage in messages.py for more details
|
||||
:return response: (NtChallengeResponse) - The NT response to the server challenge. Computed by the client
|
||||
:return session_base_key: (SessionBaseKey) - A session key calculated from the user password challenge
|
||||
:return target_info: (AV_PAIR) - The AV_PAIR structure used in the nt_challenge calculations
|
||||
"""
|
||||
if self._negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY and self._ntlm_compatibility < 3:
|
||||
# The compatibility level is less than 3 which means it doesn't support NTLMv2 but we want extended security so use NTLM2 which is different from NTLMv2
|
||||
# [MS-NLMP] - 3.3.1 NTLMv1 Authentication
|
||||
response, session_base_key = ComputeResponse._get_NTLM2_response(self._password, self._server_challenge, self._client_challenge)
|
||||
key_exchange_key = compkeys._get_exchange_key_ntlm_v1(self._negotiate_flags, session_base_key,
|
||||
self._server_challenge, lm_challenge_response,
|
||||
comphash._lmowfv1(self._password))
|
||||
target_info = None
|
||||
|
||||
elif 0 <= self._ntlm_compatibility < 3:
|
||||
response, session_base_key = ComputeResponse._get_NTLMv1_response(self._password, self._server_challenge)
|
||||
key_exchange_key = compkeys._get_exchange_key_ntlm_v1(self._negotiate_flags, session_base_key,
|
||||
self._server_challenge, lm_challenge_response,
|
||||
comphash._lmowfv1(self._password))
|
||||
target_info = None
|
||||
else:
|
||||
if self._server_target_info is None:
|
||||
target_info = TargetInfo()
|
||||
else:
|
||||
target_info = self._server_target_info
|
||||
|
||||
if target_info[TargetInfo.MSV_AV_TIMESTAMP] is None:
|
||||
timestamp = get_windows_timestamp()
|
||||
else:
|
||||
timestamp = target_info[TargetInfo.MSV_AV_TIMESTAMP][1]
|
||||
|
||||
# [MS-NLMP] If the CHALLENGE_MESSAGE TargetInfo field has an MsvAvTimestamp present, the client SHOULD provide a MIC
|
||||
target_info[TargetInfo.MSV_AV_FLAGS] = struct.pack("<L", AvFlags.MIC_PROVIDED)
|
||||
|
||||
if server_certificate_hash != None:
|
||||
channel_bindings_hash = ComputeResponse._get_channel_bindings_value(server_certificate_hash)
|
||||
target_info[TargetInfo.MSV_AV_CHANNEL_BINDINGS] = channel_bindings_hash
|
||||
|
||||
response, session_base_key = ComputeResponse._get_NTLMv2_response(self._user_name, self._password, self._domain_name,
|
||||
self._server_challenge, self._client_challenge, timestamp, target_info)
|
||||
|
||||
key_exchange_key = compkeys._get_exchange_key_ntlm_v2(session_base_key)
|
||||
|
||||
return response, key_exchange_key, target_info
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _get_LMv1_response(password, server_challenge):
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
2.2.2.3 LM_RESPONSE
|
||||
The LM_RESPONSE structure defines the NTLM v1 authentication LmChallengeResponse
|
||||
in the AUTHENTICATE_MESSAGE. This response is used only when NTLM v1
|
||||
authentication is configured.
|
||||
|
||||
:param password: The password of the user we are trying to authenticate with
|
||||
:param server_challenge: A random 8-byte response generated by the server in the CHALLENGE_MESSAGE
|
||||
:return response: LmChallengeResponse to the server challenge
|
||||
"""
|
||||
lm_hash = comphash._lmowfv1(password)
|
||||
response = ComputeResponse._calc_resp(lm_hash, server_challenge)
|
||||
|
||||
return response
|
||||
|
||||
@staticmethod
|
||||
def _get_LMv1_with_session_security_response(client_challenge):
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
2.2.2.3 LM_RESPONSE
|
||||
The LM_RESPONSE structure defines the NTLM v1 authentication LmChallengeResponse
|
||||
in the AUTHENTICATE_MESSAGE. This response is used only when NTLM v1
|
||||
authentication is configured and NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY is flages.
|
||||
|
||||
:param client_challenge: A random 8-byte response generated by the client for the AUTHENTICATE_MESSAGE
|
||||
:return response: LmChallengeResponse to the server challenge
|
||||
"""
|
||||
|
||||
response = client_challenge + b'\0' * 16
|
||||
|
||||
return response
|
||||
|
||||
@staticmethod
|
||||
def _get_LMv2_response(user_name, password, domain_name, server_challenge, client_challenge):
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
2.2.2.4 LMv2_RESPONSE
|
||||
The LMv2_RESPONSE structure defines the NTLM v2 authentication LmChallengeResponse
|
||||
in the AUTHENTICATE_MESSAGE. This response is used only when NTLM v2
|
||||
authentication is configured.
|
||||
|
||||
:param user_name: The user name of the user we are trying to authenticate with
|
||||
:param password: The password of the user we are trying to authenticate with
|
||||
:param domain_name: The domain name of the user account we are authenticated with
|
||||
:param server_challenge: A random 8-byte response generated by the server in the CHALLENGE_MESSAGE
|
||||
:param client_challenge: A random 8-byte response generated by the client for the AUTHENTICATE_MESSAGE
|
||||
:return response: LmChallengeResponse to the server challenge
|
||||
"""
|
||||
nt_hash = comphash._ntowfv2(user_name, password, domain_name)
|
||||
lm_hash = hmac.new(nt_hash, (server_challenge + client_challenge)).digest()
|
||||
response = lm_hash + client_challenge
|
||||
|
||||
return response
|
||||
|
||||
@staticmethod
|
||||
def _get_NTLMv1_response(password, server_challenge):
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
2.2.2.6 NTLM v1 Response: NTLM_RESPONSE
|
||||
The NTLM_RESPONSE strucutre defines the NTLM v1 authentication NtChallengeResponse
|
||||
in the AUTHENTICATE_MESSAGE. This response is only used when NTLM v1 authentication
|
||||
is configured.
|
||||
|
||||
:param password: The password of the user we are trying to authenticate with
|
||||
:param server_challenge: A random 8-byte response generated by the server in the CHALLENGE_MESSAGE
|
||||
:return response: NtChallengeResponse to the server_challenge
|
||||
:return session_base_key: A session key calculated from the user password challenge
|
||||
"""
|
||||
ntlm_hash = comphash._ntowfv1(password)
|
||||
response = ComputeResponse._calc_resp(ntlm_hash, server_challenge)
|
||||
|
||||
session_base_key = hashlib.new('md4', ntlm_hash).digest()
|
||||
|
||||
return response, session_base_key
|
||||
|
||||
@staticmethod
|
||||
def _get_NTLM2_response(password, server_challenge, client_challenge):
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
This name is really misleading as it isn't NTLM v2 authentication rather
|
||||
This authentication is only used when the ntlm_compatibility level is set
|
||||
to a value < 3 (No NTLMv2 auth) but the NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY
|
||||
flag is set in the negotiate flags section. The documentation for computing this
|
||||
value is on page 56 under section 3.3.1 NTLM v1 Authentication
|
||||
|
||||
:param password: The password of the user we are trying to authenticate with
|
||||
:param server_challenge: A random 8-byte response generated by the server in the CHALLENGE_MESSAGE
|
||||
:param client_challenge: A random 8-byte response generated by the client for the AUTHENTICATE_MESSAGE
|
||||
:return response: NtChallengeResponse to the server_challenge
|
||||
:return session_base_key: A session key calculated from the user password challenge
|
||||
"""
|
||||
ntlm_hash = comphash._ntowfv1(password)
|
||||
nt_session_hash = hashlib.md5(server_challenge + client_challenge).digest()[:8]
|
||||
response = ComputeResponse._calc_resp(ntlm_hash, nt_session_hash[0:8])
|
||||
|
||||
session_base_key = hashlib.new('md4', ntlm_hash).digest()
|
||||
|
||||
return response, session_base_key
|
||||
|
||||
@staticmethod
|
||||
def _get_NTLMv2_response(user_name, password, domain_name, server_challenge, client_challenge, timestamp, target_info):
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
2.2.2.8 NTLM V2 Response: NTLMv2_RESPONSE
|
||||
The NTLMv2_RESPONSE strucutre defines the NTLMv2 authentication NtChallengeResponse
|
||||
in the AUTHENTICATE_MESSAGE. This response is used only when NTLMv2 authentication
|
||||
is configured.
|
||||
|
||||
The guide on how this is computed is in 3.3.2 NTLM v2 Authentication.
|
||||
|
||||
:param user_name: The user name of the user we are trying to authenticate with
|
||||
:param password: The password of the user we are trying to authenticate with
|
||||
:param domain_name: The domain name of the user account we are authenticated with
|
||||
:param server_challenge: A random 8-byte response generated by the server in the CHALLENGE_MESSAGE
|
||||
:param client_challenge: A random 8-byte response generated by the client for the AUTHENTICATE_MESSAGE
|
||||
:param timestamp: An 8-byte timestamp in windows format, 100 nanoseconds since 1601-01-01
|
||||
:param target_info: The target_info structure from the CHALLENGE_MESSAGE with the CBT attached if required
|
||||
:return response: NtChallengeResponse to the server_challenge
|
||||
:return session_base_key: A session key calculated from the user password challenge
|
||||
"""
|
||||
|
||||
nt_hash = comphash._ntowfv2(user_name, password, domain_name)
|
||||
temp = ComputeResponse._get_NTLMv2_temp(timestamp, client_challenge, target_info)
|
||||
nt_proof_str = hmac.new(nt_hash, (server_challenge + temp)).digest()
|
||||
response = nt_proof_str + temp
|
||||
|
||||
session_base_key = hmac.new(nt_hash, nt_proof_str).digest()
|
||||
|
||||
return response, session_base_key
|
||||
|
||||
@staticmethod
|
||||
def _get_NTLMv2_temp(timestamp, client_challenge, target_info):
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
2.2.2.7 NTLMv2_CLIENT_CHALLENGE - variable length
|
||||
The NTLMv2_CLIENT_CHALLENGE structure defines the client challenge in
|
||||
the AUTHENTICATE_MESSAGE. This structure is used only when NTLM v2
|
||||
authentication is configured and is transported in the NTLMv2_RESPONSE
|
||||
structure.
|
||||
|
||||
The method to create this structure is defined in 3.3.2 NTLMv2 Authentication.
|
||||
In this method this variable is known as the temp value. The target_info variable
|
||||
corresponds to the ServerName variable used in that documentation. This is in
|
||||
reality a lot more than just the ServerName and contains the AV_PAIRS structure
|
||||
we need to transport with the message like Channel Binding tokens and others.
|
||||
By default this will be the target_info returned from the CHALLENGE_MESSAGE plus
|
||||
MSV_AV_CHANNEL_BINDINGS if specified otherwise it is a new target_info set with
|
||||
MSV_AV_TIMESTAMP to the current time.
|
||||
|
||||
:param timestamp: An 8-byte timestamp in windows format, 100 nanoseconds since 1601-01-01
|
||||
:param client_challenge: A random 8-byte response generated by the client for the AUTHENTICATE_MESSAGE
|
||||
:param target_info: The target_info structure from the CHALLENGE_MESSAGE with the CBT attached if required
|
||||
:return temp: The CLIENT_CHALLENGE structure that will be added to the NtChallengeResponse structure
|
||||
"""
|
||||
resp_type = b'\1'
|
||||
hi_resp_type = b'\1'
|
||||
reserved1 = b'\0' * 2
|
||||
reserved2 = b'\0' * 4
|
||||
reserved3 = b'\0' * 4
|
||||
reserved4 = b'\0' * 4 # This byte is not in the structure defined in 2.2.2.7 but is in the computation guide, works with it present
|
||||
|
||||
temp = resp_type + hi_resp_type + reserved1 + \
|
||||
reserved2 + \
|
||||
timestamp + \
|
||||
client_challenge + \
|
||||
reserved3 + \
|
||||
target_info.get_data() + reserved4
|
||||
|
||||
return temp
|
||||
|
||||
@staticmethod
|
||||
def _calc_resp(password_hash, server_challenge):
|
||||
"""
|
||||
Generate the LM response given a 16-byte password hash and the challenge
|
||||
from the CHALLENGE_MESSAGE
|
||||
|
||||
:param password_hash: A 16-byte password hash
|
||||
:param server_challenge: A random 8-byte response generated by the server in the CHALLENGE_MESSAGE
|
||||
:return res: A 24-byte buffer to contain the LM response upon return
|
||||
"""
|
||||
|
||||
# padding with zeros to make the hash 21 bytes long
|
||||
password_hash += b'\0' * (21 - len(password_hash))
|
||||
|
||||
res = b''
|
||||
dobj = des.DES(password_hash[0:7])
|
||||
res = res + dobj.encrypt(server_challenge[0:8])
|
||||
|
||||
dobj = des.DES(password_hash[7:14])
|
||||
res = res + dobj.encrypt(server_challenge[0:8])
|
||||
|
||||
dobj = des.DES(password_hash[14:21])
|
||||
res = res + dobj.encrypt(server_challenge[0:8])
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
def _get_channel_bindings_value(server_certificate_hash):
|
||||
"""
|
||||
https://msdn.microsoft.com/en-us/library/windows/desktop/dd919963%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396
|
||||
https://blogs.msdn.microsoft.com/openspecification/2013/03/26/ntlm-and-channel-binding-hash-aka-extended-protection-for-authentication/
|
||||
|
||||
Get's the MD5 hash of the gss_channel_bindings_struct to add to the AV_PAIR MSV_AV_CHANNEL_BINDINGS.
|
||||
This method takes in the SHA256 hash (Hash of the DER encoded certificate of the server we are connecting to)
|
||||
and add's it to the gss_channel_bindings_struct. It then gets the MD5 hash and converts this to a
|
||||
byte array in preparation of adding it to the AV_PAIR structure.
|
||||
|
||||
:param server_certificate_hash: The SHA256 hash of the server certificate (DER encoded) NTLM is authenticated to
|
||||
:return channel_bindings: An MD5 hash of the gss_channel_bindings_struct to add to the AV_PAIR MsvChannelBindings
|
||||
"""
|
||||
# Channel Binding Tokens support, used for NTLMv2
|
||||
# Decode the SHA256 certificate hash
|
||||
certificate_digest = base64.b16decode(server_certificate_hash)
|
||||
|
||||
# Initialise the GssChannelBindingsStruct and add the certificate_digest to the application_data field
|
||||
gss_channel_bindings = GssChannelBindingsStruct()
|
||||
gss_channel_bindings[gss_channel_bindings.APPLICATION_DATA] = 'tls-server-end-point:'.encode() + certificate_digest
|
||||
|
||||
# Get the gss_channel_bindings_struct and create an MD5 hash
|
||||
channel_bindings_struct_data = gss_channel_bindings.get_data()
|
||||
channel_bindings_hash = hashlib.md5(channel_bindings_struct_data).hexdigest()
|
||||
|
||||
try:
|
||||
cbt_value = bytearray.fromhex(channel_bindings_hash)
|
||||
except TypeError:
|
||||
# Work-around for Python 2.6 bug
|
||||
cbt_value = bytearray.fromhex(unicode(channel_bindings_hash))
|
||||
|
||||
channel_bindings = bytes(cbt_value)
|
||||
return channel_bindings
|
||||
|
||||
|
||||
def get_windows_timestamp():
|
||||
# Get Windows Date time, 100 nanoseconds since 1601-01-01 in a 64 bit structure
|
||||
timestamp = struct.pack('<q', (116444736000000000 + calendar.timegm(time.gmtime()) * 10000000))
|
||||
|
||||
return timestamp
|
92
packages/wakatime/packages/ntlm_auth/constants.py
Normal file
92
packages/wakatime/packages/ntlm_auth/constants.py
Normal file
@ -0,0 +1,92 @@
|
||||
# This library is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation, either
|
||||
# version 3 of the License, or (at your option) any later version.
|
||||
|
||||
# This library 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
|
||||
# Lesser General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.
|
||||
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
2.2 Message Syntax
|
||||
The signature field used in NTLM messages
|
||||
"""
|
||||
NTLM_SIGNATURE = b'NTLMSSP\0'
|
||||
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
2.2 Message Syntax
|
||||
The 3 message type options you can have in a message.
|
||||
"""
|
||||
class MessageTypes(object):
|
||||
NTLM_NEGOTIATE = 0x1
|
||||
NTLM_CHALLENGE = 0x2
|
||||
NTLM_AUTHENTICATE = 0x3
|
||||
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
2.2.2.1 AV_PAIR (MsvAvFlags)
|
||||
A 32-bit value indicated server or client configuration
|
||||
"""
|
||||
class AvFlags(object):
|
||||
AUTHENTICATION_CONSTRAINED = 0x1
|
||||
MIC_PROVIDED = 0x2
|
||||
UNTRUSTED_SPN_SOURCE = 0x4
|
||||
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
2.2.2.5 NEGOTIATE
|
||||
During NTLM authentication, each of the following flags is a possible value of the
|
||||
NegotiateFlags field of the NEGOTIATE_MESSAGE, CHALLENGE_MESSAGE and AUTHENTICATE_MESSAGE,
|
||||
unless otherwise noted. These flags define client or server NTLM capabilities
|
||||
supported by the sender.
|
||||
"""
|
||||
class NegotiateFlags(object):
|
||||
NTLMSSP_NEGOTIATE_56 = 0x80000000
|
||||
NTLMSSP_NEGOTIATE_KEY_EXCH = 0x40000000
|
||||
NTLMSSP_NEGOTIATE_128 = 0x20000000
|
||||
NTLMSSP_RESERVED_R1 = 0x10000000
|
||||
NTLMSSP_RESERVED_R2 = 0x08000000
|
||||
NTLMSSP_RESERVED_R3 = 0x04000000
|
||||
NTLMSSP_NEGOTIATE_VERSION = 0x02000000
|
||||
NTLMSSP_RESERVED_R4 = 0x01000000
|
||||
NTLMSSP_NEGOTIATE_TARGET_INFO = 0x00800000
|
||||
NTLMSSP_REQUEST_NON_NT_SESSION_KEY = 0x00400000
|
||||
NTLMSSP_RESERVED_R5 = 0x00200000
|
||||
NTLMSSP_NEGOTIATE_IDENTITY = 0x00100000
|
||||
NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY = 0x00080000
|
||||
NTLMSSP_RESERVED_R6 = 0x00040000
|
||||
NTLMSSP_TARGET_TYPE_SERVER = 0x00020000
|
||||
NTLMSSP_TARGET_TYPE_DOMAIN = 0x00010000
|
||||
NTLMSSP_NEGOTIATE_ALWAYS_SIGN = 0x00008000
|
||||
NTLMSSP_RESERVED_R7 = 0x00004000
|
||||
NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED = 0x00002000
|
||||
NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED = 0x00001000
|
||||
NTLMSSP_ANOYNMOUS = 0x00000800
|
||||
NTLMSSP_RESERVED_R8 = 0x00000400
|
||||
NTLMSSP_NEGOTIATE_NTLM = 0x00000200
|
||||
NTLMSSP_RESERVED_R9 = 0x00000100
|
||||
NTLMSSP_NEGOTIATE_LM_KEY = 0x00000080
|
||||
NTLMSSP_NEGOTIATE_DATAGRAM = 0x00000040
|
||||
NTLMSSP_NEGOTIATE_SEAL = 0x00000020
|
||||
NTLMSSP_NEGOTIATE_SIGN = 0x00000010
|
||||
NTLMSSP_RESERVED_R10 = 0x00000008
|
||||
NTLMSSP_REQUEST_TARGET = 0x00000004
|
||||
NTLMSSP_NEGOTIATE_OEM = 0x00000002
|
||||
NTLMSSP_NEGOTIATE_UNICODE = 0x00000001
|
||||
|
||||
class SignSealConstants(object):
|
||||
# Magic Contants used to get the signing and sealing key for Extended Session Security
|
||||
CLIENT_SIGNING = b"session key to client-to-server signing key magic constant\0"
|
||||
SERVER_SIGNING = b"session key to server-to-client signing key magic constant\0"
|
||||
CLIENT_SEALING = b"session key to client-to-server sealing key magic constant\0"
|
||||
SERVER_SEALING = b"session key to server-to-client sealing key magic constant\0"
|
88
packages/wakatime/packages/ntlm_auth/des.py
Normal file
88
packages/wakatime/packages/ntlm_auth/des.py
Normal file
@ -0,0 +1,88 @@
|
||||
# This file is part of 'NTLM Authorization Proxy Server' http://sourceforge.net/projects/ntlmaps/
|
||||
# Copyright 2001 Dmitry A. Rozmanov <dima@xenon.spb.ru>
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation, either
|
||||
# version 3 of the License, or (at your option) any later version.
|
||||
|
||||
# This library 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
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.
|
||||
import logging
|
||||
import six
|
||||
from ntlm_auth import des_c
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DES:
|
||||
des_c_obj = None
|
||||
|
||||
def __init__(self, key_str):
|
||||
k = str_to_key56(key_str)
|
||||
k = key56_to_key64(k)
|
||||
|
||||
key_str = b''
|
||||
for i in k:
|
||||
key_str += six.int2byte(i & 0xFF)
|
||||
|
||||
self.des_c_obj = des_c.DES(key_str)
|
||||
|
||||
def encrypt(self, plain_text):
|
||||
return self.des_c_obj.encrypt(plain_text)
|
||||
|
||||
def decrypt(self, crypted_text):
|
||||
return self.des_c_obj.decrypt(crypted_text)
|
||||
|
||||
|
||||
DESException = 'DESException'
|
||||
|
||||
|
||||
def str_to_key56(key_str):
|
||||
|
||||
if not type(key_str) == six.binary_type:
|
||||
# TODO rsanders high - figure out how to make this not necessary
|
||||
key_str = key_str.encode('ascii')
|
||||
|
||||
if len(key_str) < 7:
|
||||
key_str = key_str + b'\000\000\000\000\000\000\000'[:(7 - len(key_str))]
|
||||
key_56 = []
|
||||
for i in six.iterbytes(key_str[:7]):
|
||||
key_56.append(i)
|
||||
|
||||
return key_56
|
||||
|
||||
|
||||
def key56_to_key64(key_56):
|
||||
key = []
|
||||
for i in range(8):
|
||||
key.append(0)
|
||||
|
||||
key[0] = key_56[0]
|
||||
key[1] = ((key_56[0] << 7) & 0xFF) | (key_56[1] >> 1)
|
||||
key[2] = ((key_56[1] << 6) & 0xFF) | (key_56[2] >> 2)
|
||||
key[3] = ((key_56[2] << 5) & 0xFF) | (key_56[3] >> 3)
|
||||
key[4] = ((key_56[3] << 4) & 0xFF) | (key_56[4] >> 4)
|
||||
key[5] = ((key_56[4] << 3) & 0xFF) | (key_56[5] >> 5)
|
||||
key[6] = ((key_56[5] << 2) & 0xFF) | (key_56[6] >> 6)
|
||||
key[7] = (key_56[6] << 1) & 0xFF
|
||||
|
||||
key = set_key_odd_parity(key)
|
||||
|
||||
return key
|
||||
|
||||
|
||||
def set_key_odd_parity(key):
|
||||
for i in range(len(key)):
|
||||
for k in range(7):
|
||||
bit = 0
|
||||
t = key[i] >> k
|
||||
bit = (t ^ bit) & 0x1
|
||||
key[i] = (key[i] & 0xFE) | bit
|
||||
|
||||
return key
|
254
packages/wakatime/packages/ntlm_auth/des_c.py
Normal file
254
packages/wakatime/packages/ntlm_auth/des_c.py
Normal file
@ -0,0 +1,254 @@
|
||||
# This file is part of 'NTLM Authorization Proxy Server' http://sourceforge.net/projects/ntlmaps/
|
||||
# Copyright 2001 Dmitry A. Rozmanov <dima@xenon.spb.ru>
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation, either
|
||||
# version 3 of the License, or (at your option) any later version.
|
||||
|
||||
# This library 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
|
||||
# Lesser General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.
|
||||
import six
|
||||
|
||||
from ntlm_auth.U32 import U32
|
||||
from ntlm_auth.des_data import des_SPtrans, des_skb
|
||||
|
||||
def c2l(c):
|
||||
"char[4] to unsigned long"
|
||||
l = U32(c[0])
|
||||
l = l | (U32(c[1]) << 8)
|
||||
l = l | (U32(c[2]) << 16)
|
||||
l = l | (U32(c[3]) << 24)
|
||||
return l
|
||||
|
||||
|
||||
def l2c(l):
|
||||
"unsigned long to char[4]"
|
||||
c = []
|
||||
c.append(int(l & U32(0xFF)))
|
||||
c.append(int((l >> 8) & U32(0xFF)))
|
||||
c.append(int((l >> 16) & U32(0xFF)))
|
||||
c.append(int((l >> 24) & U32(0xFF)))
|
||||
return c
|
||||
|
||||
|
||||
def D_ENCRYPT(tup, u, t, s):
|
||||
L, R, S = tup
|
||||
# print 'LRS1', L, R, S, u, t, '-->',
|
||||
u = (R ^ s[S])
|
||||
t = R ^ s[S + 1]
|
||||
t = ((t >> 4) + (t << 28))
|
||||
L = L ^ (des_SPtrans[1][int((t) & U32(0x3f))] |
|
||||
des_SPtrans[3][int((t >> 8) & U32(0x3f))] |
|
||||
des_SPtrans[5][int((t >> 16) & U32(0x3f))] |
|
||||
des_SPtrans[7][int((t >> 24) & U32(0x3f))] |
|
||||
des_SPtrans[0][int((u) & U32(0x3f))] |
|
||||
des_SPtrans[2][int((u >> 8) & U32(0x3f))] |
|
||||
des_SPtrans[4][int((u >> 16) & U32(0x3f))] |
|
||||
des_SPtrans[6][int((u >> 24) & U32(0x3f))])
|
||||
# print 'LRS:', L, R, S, u, t
|
||||
return (L, R, S), u, t, s
|
||||
|
||||
|
||||
def PERM_OP(tup, n, m):
|
||||
"tup - (a, b, t)"
|
||||
a, b, t = tup
|
||||
t = ((a >> n) ^ b) & m
|
||||
b = b ^ t
|
||||
a = a ^ (t << n)
|
||||
return (a, b, t)
|
||||
|
||||
|
||||
def HPERM_OP(tup, n, m):
|
||||
"tup - (a, t)"
|
||||
a, t = tup
|
||||
t = ((a << (16 - n)) ^ a) & m
|
||||
a = a ^ t ^ (t >> (16 - n))
|
||||
return a, t
|
||||
|
||||
|
||||
shifts2 = [0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0]
|
||||
|
||||
|
||||
class DES:
|
||||
KeySched = None # des_key_schedule
|
||||
|
||||
def __init__(self, key_str):
|
||||
self.KeySched = des_set_key(key_str)
|
||||
|
||||
def decrypt(self, str):
|
||||
# block - UChar[]
|
||||
block = []
|
||||
|
||||
for i in six.iterbytes(str):
|
||||
block.append(i)
|
||||
|
||||
# print block
|
||||
block = des_ecb_encrypt(block, self.KeySched, 0)
|
||||
|
||||
res = b''
|
||||
for i in block:
|
||||
res = res + six.int2byte(i)
|
||||
|
||||
return res
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
# block - UChar[]
|
||||
|
||||
block = []
|
||||
for i in plaintext:
|
||||
block.append(i)
|
||||
|
||||
block = des_ecb_encrypt(block, self.KeySched, 1)
|
||||
|
||||
res = b''
|
||||
|
||||
for i in block:
|
||||
res += six.int2byte(i)
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def des_encript(input, ks, encrypt):
|
||||
# input - U32[]
|
||||
# output - U32[]
|
||||
# ks - des_key_shedule - U32[2][16]
|
||||
# encrypt - int
|
||||
# l, r, t, u - U32
|
||||
# i - int
|
||||
# s - U32[]
|
||||
|
||||
l = input[0]
|
||||
r = input[1]
|
||||
t = U32(0)
|
||||
u = U32(0)
|
||||
|
||||
r, l, t = PERM_OP((r, l, t), 4, U32(0x0f0f0f0f))
|
||||
l, r, t = PERM_OP((l, r, t), 16, U32(0x0000ffff))
|
||||
r, l, t = PERM_OP((r, l, t), 2, U32(0x33333333))
|
||||
l, r, t = PERM_OP((l, r, t), 8, U32(0x00ff00ff))
|
||||
r, l, t = PERM_OP((r, l, t), 1, U32(0x55555555))
|
||||
|
||||
t = (r << 1) | (r >> 31)
|
||||
r = (l << 1) | (l >> 31)
|
||||
l = t
|
||||
|
||||
s = ks # ???????????????
|
||||
# print l, r
|
||||
if encrypt:
|
||||
for i in range(0, 32, 4):
|
||||
rtup, u, t, s = D_ENCRYPT((l, r, i + 0), u, t, s)
|
||||
l = rtup[0]
|
||||
r = rtup[1]
|
||||
rtup, u, t, s = D_ENCRYPT((r, l, i + 2), u, t, s)
|
||||
r = rtup[0]
|
||||
l = rtup[1]
|
||||
else:
|
||||
for i in range(30, 0, -4):
|
||||
rtup, u, t, s = D_ENCRYPT((l, r, i - 0), u, t, s)
|
||||
l = rtup[0]
|
||||
r = rtup[1]
|
||||
rtup, u, t, s = D_ENCRYPT((r, l, i - 2), u, t, s)
|
||||
r = rtup[0]
|
||||
l = rtup[1]
|
||||
# print l, r
|
||||
l = (l >> 1) | (l << 31)
|
||||
r = (r >> 1) | (r << 31)
|
||||
|
||||
r, l, t = PERM_OP((r, l, t), 1, U32(0x55555555))
|
||||
l, r, t = PERM_OP((l, r, t), 8, U32(0x00ff00ff))
|
||||
r, l, t = PERM_OP((r, l, t), 2, U32(0x33333333))
|
||||
l, r, t = PERM_OP((l, r, t), 16, U32(0x0000ffff))
|
||||
r, l, t = PERM_OP((r, l, t), 4, U32(0x0f0f0f0f))
|
||||
|
||||
output = [l]
|
||||
output.append(r)
|
||||
l, r, t, u = U32(0), U32(0), U32(0), U32(0)
|
||||
return output
|
||||
|
||||
|
||||
def des_ecb_encrypt(input, ks, encrypt):
|
||||
# input - des_cblock - UChar[8]
|
||||
# output - des_cblock - UChar[8]
|
||||
# ks - des_key_shedule - U32[2][16]
|
||||
# encrypt - int
|
||||
|
||||
# print input
|
||||
l0 = c2l(input[0:4])
|
||||
l1 = c2l(input[4:8])
|
||||
ll = [l0]
|
||||
ll.append(l1)
|
||||
# print ll
|
||||
ll = des_encript(ll, ks, encrypt)
|
||||
# print ll
|
||||
l0 = ll[0]
|
||||
l1 = ll[1]
|
||||
output = l2c(l0)
|
||||
output = output + l2c(l1)
|
||||
# print output
|
||||
l0, l1, ll[0], ll[1] = U32(0), U32(0), U32(0), U32(0)
|
||||
return output
|
||||
|
||||
|
||||
def des_set_key(key):
|
||||
# key - des_cblock - UChar[8]
|
||||
# schedule - des_key_schedule
|
||||
|
||||
# register unsigned long c,d,t,s;
|
||||
# register unsigned char *in;
|
||||
# register unsigned long *k;
|
||||
# register int i;
|
||||
|
||||
# k = schedule
|
||||
# in = key
|
||||
|
||||
k = []
|
||||
c = c2l(key[0:4])
|
||||
d = c2l(key[4:8])
|
||||
t = U32(0)
|
||||
|
||||
d, c, t = PERM_OP((d, c, t), 4, U32(0x0f0f0f0f))
|
||||
c, t = HPERM_OP((c, t), -2, U32(0xcccc0000))
|
||||
d, t = HPERM_OP((d, t), -2, U32(0xcccc0000))
|
||||
d, c, t = PERM_OP((d, c, t), 1, U32(0x55555555))
|
||||
c, d, t = PERM_OP((c, d, t), 8, U32(0x00ff00ff))
|
||||
d, c, t = PERM_OP((d, c, t), 1, U32(0x55555555))
|
||||
|
||||
d = (((d & U32(0x000000ff)) << 16) | (d & U32(0x0000ff00)) | ((d & U32(0x00ff0000)) >> 16) | (
|
||||
(c & U32(0xf0000000)) >> 4))
|
||||
c = c & U32(0x0fffffff)
|
||||
|
||||
for i in range(16):
|
||||
if (shifts2[i]):
|
||||
c = ((c >> 2) | (c << 26))
|
||||
d = ((d >> 2) | (d << 26))
|
||||
else:
|
||||
c = ((c >> 1) | (c << 27))
|
||||
d = ((d >> 1) | (d << 27))
|
||||
c = c & U32(0x0fffffff)
|
||||
d = d & U32(0x0fffffff)
|
||||
|
||||
s = des_skb[0][int((c) & U32(0x3f))] | \
|
||||
des_skb[1][int(((c >> 6) & U32(0x03)) | ((c >> 7) & U32(0x3c)))] | \
|
||||
des_skb[2][int(((c >> 13) & U32(0x0f)) | ((c >> 14) & U32(0x30)))] | \
|
||||
des_skb[3][int(((c >> 20) & U32(0x01)) | ((c >> 21) & U32(0x06)) | ((c >> 22) & U32(0x38)))]
|
||||
|
||||
t = des_skb[4][int((d) & U32(0x3f))] | \
|
||||
des_skb[5][int(((d >> 7) & U32(0x03)) | ((d >> 8) & U32(0x3c)))] | \
|
||||
des_skb[6][int((d >> 15) & U32(0x3f))] | \
|
||||
des_skb[7][int(((d >> 21) & U32(0x0f)) | ((d >> 22) & U32(0x30)))]
|
||||
# print s, t
|
||||
|
||||
k.append(((t << 16) | (s & U32(0x0000ffff))) & U32(0xffffffff))
|
||||
s = ((s >> 16) | (t & U32(0xffff0000)))
|
||||
s = (s << 4) | (s >> 28)
|
||||
k.append(s & U32(0xffffffff))
|
||||
|
||||
schedule = k
|
||||
|
||||
return schedule
|
348
packages/wakatime/packages/ntlm_auth/des_data.py
Normal file
348
packages/wakatime/packages/ntlm_auth/des_data.py
Normal file
@ -0,0 +1,348 @@
|
||||
# This file is part of 'NTLM Authorization Proxy Server' http://sourceforge.net/projects/ntlmaps/
|
||||
# Copyright 2001 Dmitry A. Rozmanov <dima@xenon.spb.ru>
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation, either
|
||||
# version 3 of the License, or (at your option) any later version.
|
||||
|
||||
# This library 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
|
||||
# Lesser General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.
|
||||
|
||||
from ntlm_auth.U32 import U32
|
||||
|
||||
# static unsigned long des_SPtrans[8][64]={
|
||||
|
||||
des_SPtrans = \
|
||||
[
|
||||
# nibble 0
|
||||
[
|
||||
U32(0x00820200), U32(0x00020000), U32(0x80800000), U32(0x80820200),
|
||||
U32(0x00800000), U32(0x80020200), U32(0x80020000), U32(0x80800000),
|
||||
U32(0x80020200), U32(0x00820200), U32(0x00820000), U32(0x80000200),
|
||||
U32(0x80800200), U32(0x00800000), U32(0x00000000), U32(0x80020000),
|
||||
U32(0x00020000), U32(0x80000000), U32(0x00800200), U32(0x00020200),
|
||||
U32(0x80820200), U32(0x00820000), U32(0x80000200), U32(0x00800200),
|
||||
U32(0x80000000), U32(0x00000200), U32(0x00020200), U32(0x80820000),
|
||||
U32(0x00000200), U32(0x80800200), U32(0x80820000), U32(0x00000000),
|
||||
U32(0x00000000), U32(0x80820200), U32(0x00800200), U32(0x80020000),
|
||||
U32(0x00820200), U32(0x00020000), U32(0x80000200), U32(0x00800200),
|
||||
U32(0x80820000), U32(0x00000200), U32(0x00020200), U32(0x80800000),
|
||||
U32(0x80020200), U32(0x80000000), U32(0x80800000), U32(0x00820000),
|
||||
U32(0x80820200), U32(0x00020200), U32(0x00820000), U32(0x80800200),
|
||||
U32(0x00800000), U32(0x80000200), U32(0x80020000), U32(0x00000000),
|
||||
U32(0x00020000), U32(0x00800000), U32(0x80800200), U32(0x00820200),
|
||||
U32(0x80000000), U32(0x80820000), U32(0x00000200), U32(0x80020200),
|
||||
],
|
||||
|
||||
# nibble 1
|
||||
[
|
||||
U32(0x10042004), U32(0x00000000), U32(0x00042000), U32(0x10040000),
|
||||
U32(0x10000004), U32(0x00002004), U32(0x10002000), U32(0x00042000),
|
||||
U32(0x00002000), U32(0x10040004), U32(0x00000004), U32(0x10002000),
|
||||
U32(0x00040004), U32(0x10042000), U32(0x10040000), U32(0x00000004),
|
||||
U32(0x00040000), U32(0x10002004), U32(0x10040004), U32(0x00002000),
|
||||
U32(0x00042004), U32(0x10000000), U32(0x00000000), U32(0x00040004),
|
||||
U32(0x10002004), U32(0x00042004), U32(0x10042000), U32(0x10000004),
|
||||
U32(0x10000000), U32(0x00040000), U32(0x00002004), U32(0x10042004),
|
||||
U32(0x00040004), U32(0x10042000), U32(0x10002000), U32(0x00042004),
|
||||
U32(0x10042004), U32(0x00040004), U32(0x10000004), U32(0x00000000),
|
||||
U32(0x10000000), U32(0x00002004), U32(0x00040000), U32(0x10040004),
|
||||
U32(0x00002000), U32(0x10000000), U32(0x00042004), U32(0x10002004),
|
||||
U32(0x10042000), U32(0x00002000), U32(0x00000000), U32(0x10000004),
|
||||
U32(0x00000004), U32(0x10042004), U32(0x00042000), U32(0x10040000),
|
||||
U32(0x10040004), U32(0x00040000), U32(0x00002004), U32(0x10002000),
|
||||
U32(0x10002004), U32(0x00000004), U32(0x10040000), U32(0x00042000),
|
||||
],
|
||||
|
||||
# nibble 2
|
||||
[
|
||||
U32(0x41000000), U32(0x01010040), U32(0x00000040), U32(0x41000040),
|
||||
U32(0x40010000), U32(0x01000000), U32(0x41000040), U32(0x00010040),
|
||||
U32(0x01000040), U32(0x00010000), U32(0x01010000), U32(0x40000000),
|
||||
U32(0x41010040), U32(0x40000040), U32(0x40000000), U32(0x41010000),
|
||||
U32(0x00000000), U32(0x40010000), U32(0x01010040), U32(0x00000040),
|
||||
U32(0x40000040), U32(0x41010040), U32(0x00010000), U32(0x41000000),
|
||||
U32(0x41010000), U32(0x01000040), U32(0x40010040), U32(0x01010000),
|
||||
U32(0x00010040), U32(0x00000000), U32(0x01000000), U32(0x40010040),
|
||||
U32(0x01010040), U32(0x00000040), U32(0x40000000), U32(0x00010000),
|
||||
U32(0x40000040), U32(0x40010000), U32(0x01010000), U32(0x41000040),
|
||||
U32(0x00000000), U32(0x01010040), U32(0x00010040), U32(0x41010000),
|
||||
U32(0x40010000), U32(0x01000000), U32(0x41010040), U32(0x40000000),
|
||||
U32(0x40010040), U32(0x41000000), U32(0x01000000), U32(0x41010040),
|
||||
U32(0x00010000), U32(0x01000040), U32(0x41000040), U32(0x00010040),
|
||||
U32(0x01000040), U32(0x00000000), U32(0x41010000), U32(0x40000040),
|
||||
U32(0x41000000), U32(0x40010040), U32(0x00000040), U32(0x01010000),
|
||||
],
|
||||
|
||||
# nibble 3
|
||||
[
|
||||
U32(0x00100402), U32(0x04000400), U32(0x00000002), U32(0x04100402),
|
||||
U32(0x00000000), U32(0x04100000), U32(0x04000402), U32(0x00100002),
|
||||
U32(0x04100400), U32(0x04000002), U32(0x04000000), U32(0x00000402),
|
||||
U32(0x04000002), U32(0x00100402), U32(0x00100000), U32(0x04000000),
|
||||
U32(0x04100002), U32(0x00100400), U32(0x00000400), U32(0x00000002),
|
||||
U32(0x00100400), U32(0x04000402), U32(0x04100000), U32(0x00000400),
|
||||
U32(0x00000402), U32(0x00000000), U32(0x00100002), U32(0x04100400),
|
||||
U32(0x04000400), U32(0x04100002), U32(0x04100402), U32(0x00100000),
|
||||
U32(0x04100002), U32(0x00000402), U32(0x00100000), U32(0x04000002),
|
||||
U32(0x00100400), U32(0x04000400), U32(0x00000002), U32(0x04100000),
|
||||
U32(0x04000402), U32(0x00000000), U32(0x00000400), U32(0x00100002),
|
||||
U32(0x00000000), U32(0x04100002), U32(0x04100400), U32(0x00000400),
|
||||
U32(0x04000000), U32(0x04100402), U32(0x00100402), U32(0x00100000),
|
||||
U32(0x04100402), U32(0x00000002), U32(0x04000400), U32(0x00100402),
|
||||
U32(0x00100002), U32(0x00100400), U32(0x04100000), U32(0x04000402),
|
||||
U32(0x00000402), U32(0x04000000), U32(0x04000002), U32(0x04100400),
|
||||
],
|
||||
|
||||
# nibble 4
|
||||
[
|
||||
U32(0x02000000), U32(0x00004000), U32(0x00000100), U32(0x02004108),
|
||||
U32(0x02004008), U32(0x02000100), U32(0x00004108), U32(0x02004000),
|
||||
U32(0x00004000), U32(0x00000008), U32(0x02000008), U32(0x00004100),
|
||||
U32(0x02000108), U32(0x02004008), U32(0x02004100), U32(0x00000000),
|
||||
U32(0x00004100), U32(0x02000000), U32(0x00004008), U32(0x00000108),
|
||||
U32(0x02000100), U32(0x00004108), U32(0x00000000), U32(0x02000008),
|
||||
U32(0x00000008), U32(0x02000108), U32(0x02004108), U32(0x00004008),
|
||||
U32(0x02004000), U32(0x00000100), U32(0x00000108), U32(0x02004100),
|
||||
U32(0x02004100), U32(0x02000108), U32(0x00004008), U32(0x02004000),
|
||||
U32(0x00004000), U32(0x00000008), U32(0x02000008), U32(0x02000100),
|
||||
U32(0x02000000), U32(0x00004100), U32(0x02004108), U32(0x00000000),
|
||||
U32(0x00004108), U32(0x02000000), U32(0x00000100), U32(0x00004008),
|
||||
U32(0x02000108), U32(0x00000100), U32(0x00000000), U32(0x02004108),
|
||||
U32(0x02004008), U32(0x02004100), U32(0x00000108), U32(0x00004000),
|
||||
U32(0x00004100), U32(0x02004008), U32(0x02000100), U32(0x00000108),
|
||||
U32(0x00000008), U32(0x00004108), U32(0x02004000), U32(0x02000008),
|
||||
],
|
||||
|
||||
# nibble 5
|
||||
[
|
||||
U32(0x20000010), U32(0x00080010), U32(0x00000000), U32(0x20080800),
|
||||
U32(0x00080010), U32(0x00000800), U32(0x20000810), U32(0x00080000),
|
||||
U32(0x00000810), U32(0x20080810), U32(0x00080800), U32(0x20000000),
|
||||
U32(0x20000800), U32(0x20000010), U32(0x20080000), U32(0x00080810),
|
||||
U32(0x00080000), U32(0x20000810), U32(0x20080010), U32(0x00000000),
|
||||
U32(0x00000800), U32(0x00000010), U32(0x20080800), U32(0x20080010),
|
||||
U32(0x20080810), U32(0x20080000), U32(0x20000000), U32(0x00000810),
|
||||
U32(0x00000010), U32(0x00080800), U32(0x00080810), U32(0x20000800),
|
||||
U32(0x00000810), U32(0x20000000), U32(0x20000800), U32(0x00080810),
|
||||
U32(0x20080800), U32(0x00080010), U32(0x00000000), U32(0x20000800),
|
||||
U32(0x20000000), U32(0x00000800), U32(0x20080010), U32(0x00080000),
|
||||
U32(0x00080010), U32(0x20080810), U32(0x00080800), U32(0x00000010),
|
||||
U32(0x20080810), U32(0x00080800), U32(0x00080000), U32(0x20000810),
|
||||
U32(0x20000010), U32(0x20080000), U32(0x00080810), U32(0x00000000),
|
||||
U32(0x00000800), U32(0x20000010), U32(0x20000810), U32(0x20080800),
|
||||
U32(0x20080000), U32(0x00000810), U32(0x00000010), U32(0x20080010),
|
||||
],
|
||||
|
||||
# nibble 6
|
||||
[
|
||||
U32(0x00001000), U32(0x00000080), U32(0x00400080), U32(0x00400001),
|
||||
U32(0x00401081), U32(0x00001001), U32(0x00001080), U32(0x00000000),
|
||||
U32(0x00400000), U32(0x00400081), U32(0x00000081), U32(0x00401000),
|
||||
U32(0x00000001), U32(0x00401080), U32(0x00401000), U32(0x00000081),
|
||||
U32(0x00400081), U32(0x00001000), U32(0x00001001), U32(0x00401081),
|
||||
U32(0x00000000), U32(0x00400080), U32(0x00400001), U32(0x00001080),
|
||||
U32(0x00401001), U32(0x00001081), U32(0x00401080), U32(0x00000001),
|
||||
U32(0x00001081), U32(0x00401001), U32(0x00000080), U32(0x00400000),
|
||||
U32(0x00001081), U32(0x00401000), U32(0x00401001), U32(0x00000081),
|
||||
U32(0x00001000), U32(0x00000080), U32(0x00400000), U32(0x00401001),
|
||||
U32(0x00400081), U32(0x00001081), U32(0x00001080), U32(0x00000000),
|
||||
U32(0x00000080), U32(0x00400001), U32(0x00000001), U32(0x00400080),
|
||||
U32(0x00000000), U32(0x00400081), U32(0x00400080), U32(0x00001080),
|
||||
U32(0x00000081), U32(0x00001000), U32(0x00401081), U32(0x00400000),
|
||||
U32(0x00401080), U32(0x00000001), U32(0x00001001), U32(0x00401081),
|
||||
U32(0x00400001), U32(0x00401080), U32(0x00401000), U32(0x00001001),
|
||||
],
|
||||
|
||||
# nibble 7
|
||||
[
|
||||
U32(0x08200020), U32(0x08208000), U32(0x00008020), U32(0x00000000),
|
||||
U32(0x08008000), U32(0x00200020), U32(0x08200000), U32(0x08208020),
|
||||
U32(0x00000020), U32(0x08000000), U32(0x00208000), U32(0x00008020),
|
||||
U32(0x00208020), U32(0x08008020), U32(0x08000020), U32(0x08200000),
|
||||
U32(0x00008000), U32(0x00208020), U32(0x00200020), U32(0x08008000),
|
||||
U32(0x08208020), U32(0x08000020), U32(0x00000000), U32(0x00208000),
|
||||
U32(0x08000000), U32(0x00200000), U32(0x08008020), U32(0x08200020),
|
||||
U32(0x00200000), U32(0x00008000), U32(0x08208000), U32(0x00000020),
|
||||
U32(0x00200000), U32(0x00008000), U32(0x08000020), U32(0x08208020),
|
||||
U32(0x00008020), U32(0x08000000), U32(0x00000000), U32(0x00208000),
|
||||
U32(0x08200020), U32(0x08008020), U32(0x08008000), U32(0x00200020),
|
||||
U32(0x08208000), U32(0x00000020), U32(0x00200020), U32(0x08008000),
|
||||
U32(0x08208020), U32(0x00200000), U32(0x08200000), U32(0x08000020),
|
||||
U32(0x00208000), U32(0x00008020), U32(0x08008020), U32(0x08200000),
|
||||
U32(0x00000020), U32(0x08208000), U32(0x00208020), U32(0x00000000),
|
||||
U32(0x08000000), U32(0x08200020), U32(0x00008000), U32(0x00208020),
|
||||
],
|
||||
]
|
||||
|
||||
# static unsigned long des_skb[8][64]={
|
||||
|
||||
des_skb = \
|
||||
[
|
||||
# for C bits (numbered as per FIPS 46) 1 2 3 4 5 6
|
||||
[
|
||||
U32(0x00000000), U32(0x00000010), U32(0x20000000), U32(0x20000010),
|
||||
U32(0x00010000), U32(0x00010010), U32(0x20010000), U32(0x20010010),
|
||||
U32(0x00000800), U32(0x00000810), U32(0x20000800), U32(0x20000810),
|
||||
U32(0x00010800), U32(0x00010810), U32(0x20010800), U32(0x20010810),
|
||||
U32(0x00000020), U32(0x00000030), U32(0x20000020), U32(0x20000030),
|
||||
U32(0x00010020), U32(0x00010030), U32(0x20010020), U32(0x20010030),
|
||||
U32(0x00000820), U32(0x00000830), U32(0x20000820), U32(0x20000830),
|
||||
U32(0x00010820), U32(0x00010830), U32(0x20010820), U32(0x20010830),
|
||||
U32(0x00080000), U32(0x00080010), U32(0x20080000), U32(0x20080010),
|
||||
U32(0x00090000), U32(0x00090010), U32(0x20090000), U32(0x20090010),
|
||||
U32(0x00080800), U32(0x00080810), U32(0x20080800), U32(0x20080810),
|
||||
U32(0x00090800), U32(0x00090810), U32(0x20090800), U32(0x20090810),
|
||||
U32(0x00080020), U32(0x00080030), U32(0x20080020), U32(0x20080030),
|
||||
U32(0x00090020), U32(0x00090030), U32(0x20090020), U32(0x20090030),
|
||||
U32(0x00080820), U32(0x00080830), U32(0x20080820), U32(0x20080830),
|
||||
U32(0x00090820), U32(0x00090830), U32(0x20090820), U32(0x20090830),
|
||||
],
|
||||
|
||||
# for C bits (numbered as per FIPS 46) 7 8 10 11 12 13
|
||||
[
|
||||
U32(0x00000000), U32(0x02000000), U32(0x00002000), U32(0x02002000),
|
||||
U32(0x00200000), U32(0x02200000), U32(0x00202000), U32(0x02202000),
|
||||
U32(0x00000004), U32(0x02000004), U32(0x00002004), U32(0x02002004),
|
||||
U32(0x00200004), U32(0x02200004), U32(0x00202004), U32(0x02202004),
|
||||
U32(0x00000400), U32(0x02000400), U32(0x00002400), U32(0x02002400),
|
||||
U32(0x00200400), U32(0x02200400), U32(0x00202400), U32(0x02202400),
|
||||
U32(0x00000404), U32(0x02000404), U32(0x00002404), U32(0x02002404),
|
||||
U32(0x00200404), U32(0x02200404), U32(0x00202404), U32(0x02202404),
|
||||
U32(0x10000000), U32(0x12000000), U32(0x10002000), U32(0x12002000),
|
||||
U32(0x10200000), U32(0x12200000), U32(0x10202000), U32(0x12202000),
|
||||
U32(0x10000004), U32(0x12000004), U32(0x10002004), U32(0x12002004),
|
||||
U32(0x10200004), U32(0x12200004), U32(0x10202004), U32(0x12202004),
|
||||
U32(0x10000400), U32(0x12000400), U32(0x10002400), U32(0x12002400),
|
||||
U32(0x10200400), U32(0x12200400), U32(0x10202400), U32(0x12202400),
|
||||
U32(0x10000404), U32(0x12000404), U32(0x10002404), U32(0x12002404),
|
||||
U32(0x10200404), U32(0x12200404), U32(0x10202404), U32(0x12202404),
|
||||
],
|
||||
|
||||
# for C bits (numbered as per FIPS 46) 14 15 16 17 19 20
|
||||
[
|
||||
U32(0x00000000), U32(0x00000001), U32(0x00040000), U32(0x00040001),
|
||||
U32(0x01000000), U32(0x01000001), U32(0x01040000), U32(0x01040001),
|
||||
U32(0x00000002), U32(0x00000003), U32(0x00040002), U32(0x00040003),
|
||||
U32(0x01000002), U32(0x01000003), U32(0x01040002), U32(0x01040003),
|
||||
U32(0x00000200), U32(0x00000201), U32(0x00040200), U32(0x00040201),
|
||||
U32(0x01000200), U32(0x01000201), U32(0x01040200), U32(0x01040201),
|
||||
U32(0x00000202), U32(0x00000203), U32(0x00040202), U32(0x00040203),
|
||||
U32(0x01000202), U32(0x01000203), U32(0x01040202), U32(0x01040203),
|
||||
U32(0x08000000), U32(0x08000001), U32(0x08040000), U32(0x08040001),
|
||||
U32(0x09000000), U32(0x09000001), U32(0x09040000), U32(0x09040001),
|
||||
U32(0x08000002), U32(0x08000003), U32(0x08040002), U32(0x08040003),
|
||||
U32(0x09000002), U32(0x09000003), U32(0x09040002), U32(0x09040003),
|
||||
U32(0x08000200), U32(0x08000201), U32(0x08040200), U32(0x08040201),
|
||||
U32(0x09000200), U32(0x09000201), U32(0x09040200), U32(0x09040201),
|
||||
U32(0x08000202), U32(0x08000203), U32(0x08040202), U32(0x08040203),
|
||||
U32(0x09000202), U32(0x09000203), U32(0x09040202), U32(0x09040203),
|
||||
],
|
||||
|
||||
# for C bits (numbered as per FIPS 46) 21 23 24 26 27 28
|
||||
[
|
||||
U32(0x00000000), U32(0x00100000), U32(0x00000100), U32(0x00100100),
|
||||
U32(0x00000008), U32(0x00100008), U32(0x00000108), U32(0x00100108),
|
||||
U32(0x00001000), U32(0x00101000), U32(0x00001100), U32(0x00101100),
|
||||
U32(0x00001008), U32(0x00101008), U32(0x00001108), U32(0x00101108),
|
||||
U32(0x04000000), U32(0x04100000), U32(0x04000100), U32(0x04100100),
|
||||
U32(0x04000008), U32(0x04100008), U32(0x04000108), U32(0x04100108),
|
||||
U32(0x04001000), U32(0x04101000), U32(0x04001100), U32(0x04101100),
|
||||
U32(0x04001008), U32(0x04101008), U32(0x04001108), U32(0x04101108),
|
||||
U32(0x00020000), U32(0x00120000), U32(0x00020100), U32(0x00120100),
|
||||
U32(0x00020008), U32(0x00120008), U32(0x00020108), U32(0x00120108),
|
||||
U32(0x00021000), U32(0x00121000), U32(0x00021100), U32(0x00121100),
|
||||
U32(0x00021008), U32(0x00121008), U32(0x00021108), U32(0x00121108),
|
||||
U32(0x04020000), U32(0x04120000), U32(0x04020100), U32(0x04120100),
|
||||
U32(0x04020008), U32(0x04120008), U32(0x04020108), U32(0x04120108),
|
||||
U32(0x04021000), U32(0x04121000), U32(0x04021100), U32(0x04121100),
|
||||
U32(0x04021008), U32(0x04121008), U32(0x04021108), U32(0x04121108),
|
||||
],
|
||||
|
||||
# for D bits (numbered as per FIPS 46) 1 2 3 4 5 6
|
||||
[
|
||||
U32(0x00000000), U32(0x10000000), U32(0x00010000), U32(0x10010000),
|
||||
U32(0x00000004), U32(0x10000004), U32(0x00010004), U32(0x10010004),
|
||||
U32(0x20000000), U32(0x30000000), U32(0x20010000), U32(0x30010000),
|
||||
U32(0x20000004), U32(0x30000004), U32(0x20010004), U32(0x30010004),
|
||||
U32(0x00100000), U32(0x10100000), U32(0x00110000), U32(0x10110000),
|
||||
U32(0x00100004), U32(0x10100004), U32(0x00110004), U32(0x10110004),
|
||||
U32(0x20100000), U32(0x30100000), U32(0x20110000), U32(0x30110000),
|
||||
U32(0x20100004), U32(0x30100004), U32(0x20110004), U32(0x30110004),
|
||||
U32(0x00001000), U32(0x10001000), U32(0x00011000), U32(0x10011000),
|
||||
U32(0x00001004), U32(0x10001004), U32(0x00011004), U32(0x10011004),
|
||||
U32(0x20001000), U32(0x30001000), U32(0x20011000), U32(0x30011000),
|
||||
U32(0x20001004), U32(0x30001004), U32(0x20011004), U32(0x30011004),
|
||||
U32(0x00101000), U32(0x10101000), U32(0x00111000), U32(0x10111000),
|
||||
U32(0x00101004), U32(0x10101004), U32(0x00111004), U32(0x10111004),
|
||||
U32(0x20101000), U32(0x30101000), U32(0x20111000), U32(0x30111000),
|
||||
U32(0x20101004), U32(0x30101004), U32(0x20111004), U32(0x30111004),
|
||||
],
|
||||
|
||||
# for D bits (numbered as per FIPS 46) 8 9 11 12 13 14
|
||||
[
|
||||
U32(0x00000000), U32(0x08000000), U32(0x00000008), U32(0x08000008),
|
||||
U32(0x00000400), U32(0x08000400), U32(0x00000408), U32(0x08000408),
|
||||
U32(0x00020000), U32(0x08020000), U32(0x00020008), U32(0x08020008),
|
||||
U32(0x00020400), U32(0x08020400), U32(0x00020408), U32(0x08020408),
|
||||
U32(0x00000001), U32(0x08000001), U32(0x00000009), U32(0x08000009),
|
||||
U32(0x00000401), U32(0x08000401), U32(0x00000409), U32(0x08000409),
|
||||
U32(0x00020001), U32(0x08020001), U32(0x00020009), U32(0x08020009),
|
||||
U32(0x00020401), U32(0x08020401), U32(0x00020409), U32(0x08020409),
|
||||
U32(0x02000000), U32(0x0A000000), U32(0x02000008), U32(0x0A000008),
|
||||
U32(0x02000400), U32(0x0A000400), U32(0x02000408), U32(0x0A000408),
|
||||
U32(0x02020000), U32(0x0A020000), U32(0x02020008), U32(0x0A020008),
|
||||
U32(0x02020400), U32(0x0A020400), U32(0x02020408), U32(0x0A020408),
|
||||
U32(0x02000001), U32(0x0A000001), U32(0x02000009), U32(0x0A000009),
|
||||
U32(0x02000401), U32(0x0A000401), U32(0x02000409), U32(0x0A000409),
|
||||
U32(0x02020001), U32(0x0A020001), U32(0x02020009), U32(0x0A020009),
|
||||
U32(0x02020401), U32(0x0A020401), U32(0x02020409), U32(0x0A020409),
|
||||
],
|
||||
|
||||
# for D bits (numbered as per FIPS 46) 16 17 18 19 20 21
|
||||
[
|
||||
U32(0x00000000), U32(0x00000100), U32(0x00080000), U32(0x00080100),
|
||||
U32(0x01000000), U32(0x01000100), U32(0x01080000), U32(0x01080100),
|
||||
U32(0x00000010), U32(0x00000110), U32(0x00080010), U32(0x00080110),
|
||||
U32(0x01000010), U32(0x01000110), U32(0x01080010), U32(0x01080110),
|
||||
U32(0x00200000), U32(0x00200100), U32(0x00280000), U32(0x00280100),
|
||||
U32(0x01200000), U32(0x01200100), U32(0x01280000), U32(0x01280100),
|
||||
U32(0x00200010), U32(0x00200110), U32(0x00280010), U32(0x00280110),
|
||||
U32(0x01200010), U32(0x01200110), U32(0x01280010), U32(0x01280110),
|
||||
U32(0x00000200), U32(0x00000300), U32(0x00080200), U32(0x00080300),
|
||||
U32(0x01000200), U32(0x01000300), U32(0x01080200), U32(0x01080300),
|
||||
U32(0x00000210), U32(0x00000310), U32(0x00080210), U32(0x00080310),
|
||||
U32(0x01000210), U32(0x01000310), U32(0x01080210), U32(0x01080310),
|
||||
U32(0x00200200), U32(0x00200300), U32(0x00280200), U32(0x00280300),
|
||||
U32(0x01200200), U32(0x01200300), U32(0x01280200), U32(0x01280300),
|
||||
U32(0x00200210), U32(0x00200310), U32(0x00280210), U32(0x00280310),
|
||||
U32(0x01200210), U32(0x01200310), U32(0x01280210), U32(0x01280310),
|
||||
],
|
||||
|
||||
# for D bits (numbered as per FIPS 46) 22 23 24 25 27 28
|
||||
[
|
||||
U32(0x00000000), U32(0x04000000), U32(0x00040000), U32(0x04040000),
|
||||
U32(0x00000002), U32(0x04000002), U32(0x00040002), U32(0x04040002),
|
||||
U32(0x00002000), U32(0x04002000), U32(0x00042000), U32(0x04042000),
|
||||
U32(0x00002002), U32(0x04002002), U32(0x00042002), U32(0x04042002),
|
||||
U32(0x00000020), U32(0x04000020), U32(0x00040020), U32(0x04040020),
|
||||
U32(0x00000022), U32(0x04000022), U32(0x00040022), U32(0x04040022),
|
||||
U32(0x00002020), U32(0x04002020), U32(0x00042020), U32(0x04042020),
|
||||
U32(0x00002022), U32(0x04002022), U32(0x00042022), U32(0x04042022),
|
||||
U32(0x00000800), U32(0x04000800), U32(0x00040800), U32(0x04040800),
|
||||
U32(0x00000802), U32(0x04000802), U32(0x00040802), U32(0x04040802),
|
||||
U32(0x00002800), U32(0x04002800), U32(0x00042800), U32(0x04042800),
|
||||
U32(0x00002802), U32(0x04002802), U32(0x00042802), U32(0x04042802),
|
||||
U32(0x00000820), U32(0x04000820), U32(0x00040820), U32(0x04040820),
|
||||
U32(0x00000822), U32(0x04000822), U32(0x00040822), U32(0x04040822),
|
||||
U32(0x00002820), U32(0x04002820), U32(0x00042820), U32(0x04042820),
|
||||
U32(0x00002822), U32(0x04002822), U32(0x00042822), U32(0x04042822),
|
||||
]
|
||||
|
||||
]
|
67
packages/wakatime/packages/ntlm_auth/gss_channel_bindings.py
Normal file
67
packages/wakatime/packages/ntlm_auth/gss_channel_bindings.py
Normal file
@ -0,0 +1,67 @@
|
||||
# This library is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation, either
|
||||
# version 3 of the License, or (at your option) any later version.
|
||||
|
||||
# This library 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
|
||||
# Lesser General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.
|
||||
|
||||
import struct
|
||||
|
||||
"""
|
||||
This is not the easiest structure to understand, ultimately this is a set structure
|
||||
as defined by Microsoft. Channel Binding Tokens set the SHA256 hash of the server
|
||||
certificate to the application_data field and then ultimately creates the MD5 hash
|
||||
to include in the NTLM auth from there. This class is just designed to create the
|
||||
bindings structure which is then used by compute_response.py to do the rest of the
|
||||
work.
|
||||
|
||||
For more infor on how this works and how it is derived, this is a great link;
|
||||
https://blogs.msdn.microsoft.com/openspecification/2013/03/26/ntlm-and-channel-binding-hash-aka-extended-protection-for-authentication/
|
||||
"""
|
||||
class GssChannelBindingsStruct(object):
|
||||
INITIATOR_ADDTYPE = 'initiator_addtype'
|
||||
INITIATOR_ADDRESS_LENGTH = 'initiator_address_length'
|
||||
ACCEPTOR_ADDRTYPE = 'acceptor_addrtype'
|
||||
ACCEPTOR_ADDRESS_LENGTH = 'acceptor_address_length'
|
||||
APPLICATION_DATA_LENGTH = 'application_data_length'
|
||||
INITIATOR_ADDRESS = 'initiator_address'
|
||||
ACCEPTOR_ADDRESS = 'acceptor_address'
|
||||
APPLICATION_DATA = 'application_data'
|
||||
|
||||
def __init__(self):
|
||||
self.fields = {}
|
||||
self.fields[self.INITIATOR_ADDTYPE] = 0
|
||||
self.fields[self.INITIATOR_ADDRESS_LENGTH] = 0
|
||||
self.fields[self.ACCEPTOR_ADDRTYPE] = 0
|
||||
self.fields[self.ACCEPTOR_ADDRESS_LENGTH] = 0
|
||||
self.fields[self.APPLICATION_DATA_LENGTH] = 0
|
||||
self.fields[self.INITIATOR_ADDRESS] = b''
|
||||
self.fields[self.ACCEPTOR_ADDRESS] = b''
|
||||
self.fields[self.APPLICATION_DATA] = b''
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.fields[key] = value
|
||||
|
||||
def get_data(self):
|
||||
# Set the lengths of each len field in case they have changed
|
||||
self.fields[self.INITIATOR_ADDRESS_LENGTH] = len(self.fields[self.INITIATOR_ADDRESS])
|
||||
self.fields[self.ACCEPTOR_ADDRESS_LENGTH] = len(self.fields[self.ACCEPTOR_ADDRESS])
|
||||
self.fields[self.APPLICATION_DATA_LENGTH] = len(self.fields[self.APPLICATION_DATA])
|
||||
|
||||
# Add all the values together to create the gss_channel_bindings_struct
|
||||
data = struct.pack('<L', self.fields[self.INITIATOR_ADDTYPE]) + \
|
||||
struct.pack('<L', self.fields[self.INITIATOR_ADDRESS_LENGTH]) + \
|
||||
self.fields[self.INITIATOR_ADDRESS] + \
|
||||
struct.pack('<L', self.fields[self.ACCEPTOR_ADDRTYPE]) + \
|
||||
struct.pack('<L', self.fields[self.ACCEPTOR_ADDRESS_LENGTH]) + \
|
||||
self.fields[self.ACCEPTOR_ADDRESS] + \
|
||||
struct.pack('<L', self.fields[self.APPLICATION_DATA_LENGTH]) + \
|
||||
self.fields[self.APPLICATION_DATA]
|
||||
|
||||
return data
|
359
packages/wakatime/packages/ntlm_auth/messages.py
Normal file
359
packages/wakatime/packages/ntlm_auth/messages.py
Normal file
@ -0,0 +1,359 @@
|
||||
# This library is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation, either
|
||||
# version 3 of the License, or (at your option) any later version.
|
||||
|
||||
# This library 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
|
||||
# Lesser General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.
|
||||
|
||||
import hmac
|
||||
import os
|
||||
import struct
|
||||
from ntlm_auth.compute_response import ComputeResponse
|
||||
from ntlm_auth.constants import NegotiateFlags, MessageTypes, NTLM_SIGNATURE, AvFlags
|
||||
from ntlm_auth.rc4 import ARC4
|
||||
from ntlm_auth.target_info import TargetInfo
|
||||
|
||||
class NegotiateMessage(object):
|
||||
EXPECTED_BODY_LENGTH = 40
|
||||
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
2.2.1.1 NEGOTIATE_MESSAGE
|
||||
The NEGOTIATE_MESSAGE defines an NTLM Negotiate message that is sent from the client to
|
||||
the server. This message allows the client to specify its supported NTLM options to
|
||||
the server.
|
||||
|
||||
:param negotiate_flags: A NEGOTIATE structure that contains a set of bit flags. These flags are the options the client supports
|
||||
:param domain_name: The domain name of the user to authenticate with, default is None
|
||||
:param workstation: The worksation of the client machine, default is None
|
||||
|
||||
Attributes:
|
||||
signature: An 8-byte character array that MUST contain the ASCII string 'NTLMSSP\0'
|
||||
message_type: A 32-bit unsigned integer that indicates the message type. This field must be set to 0x00000001
|
||||
negotiate_flags: A NEGOTIATE structure that contains a set of bit flags. These flags are the options the client supports
|
||||
version: Contains the windows version info of the client. It is used only debugging purposes and are only set when NTLMSSP_NEGOTIATE_VERSION flag is set
|
||||
domain_name: A byte-array that contains the name of the client authentication domain that MUST Be encoded in the negotiated character set
|
||||
workstation: A byte-array that contains the name of the client machine that MUST Be encoded in the negotiated character set
|
||||
"""
|
||||
def __init__(self, negotiate_flags, domain_name, workstation):
|
||||
self.signature = NTLM_SIGNATURE
|
||||
self.message_type = struct.pack('<L', MessageTypes.NTLM_NEGOTIATE)
|
||||
|
||||
# Check if the domain_name value is set, if it is, make sure the negotiate_flag is also set
|
||||
if domain_name is None:
|
||||
self.domain_name = ''
|
||||
else:
|
||||
self.domain_name = domain_name
|
||||
negotiate_flags |= NegotiateFlags.NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED
|
||||
|
||||
# Check if the workstation value is set, if it is, make sure the negotiate_flag is also set
|
||||
if workstation is None:
|
||||
self.workstation = ''
|
||||
else:
|
||||
self.workstation = workstation
|
||||
negotiate_flags |= NegotiateFlags.NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED
|
||||
|
||||
# Set the encoding flag to use OEM, remove UNICODE if set as it isn't support in this message
|
||||
negotiate_flags -= NegotiateFlags.NTLMSSP_NEGOTIATE_UNICODE
|
||||
negotiate_flags |= NegotiateFlags.NTLMSSP_NEGOTIATE_OEM
|
||||
self.domain_name = self.domain_name.encode('ascii')
|
||||
self.workstation = self.workstation.encode('ascii')
|
||||
|
||||
self.version = get_version(negotiate_flags)
|
||||
|
||||
self.negotiate_flags = struct.pack('<I', negotiate_flags)
|
||||
|
||||
def get_data(self):
|
||||
payload_offset = self.EXPECTED_BODY_LENGTH
|
||||
|
||||
# DomainNameFields - 8 bytes
|
||||
domain_name_len = struct.pack('<H', len(self.domain_name))
|
||||
domain_name_max_len = struct.pack('<H', len(self.domain_name))
|
||||
domain_name_buffer_offset = struct.pack('<I', payload_offset)
|
||||
payload_offset += len(self.domain_name)
|
||||
|
||||
# WorkstationFields - 8 bytes
|
||||
workstation_len = struct.pack('<H', len(self.workstation))
|
||||
workstation_max_len = struct.pack('<H', len(self.workstation))
|
||||
workstation_buffer_offset = struct.pack('<I', payload_offset)
|
||||
payload_offset += len(self.workstation)
|
||||
|
||||
# Payload - variable length
|
||||
payload = self.domain_name
|
||||
payload += self.workstation
|
||||
|
||||
# Bring the header values together into 1 message
|
||||
msg1 = self.signature
|
||||
msg1 += self.message_type
|
||||
msg1 += self.negotiate_flags
|
||||
msg1 += domain_name_len
|
||||
msg1 += domain_name_max_len
|
||||
msg1 += domain_name_buffer_offset
|
||||
msg1 += workstation_len
|
||||
msg1 += workstation_max_len
|
||||
msg1 += workstation_buffer_offset
|
||||
msg1 += self.version
|
||||
|
||||
assert self.EXPECTED_BODY_LENGTH == len(msg1), "BODY_LENGTH: %d != msg1: %d" % (self.EXPECTED_BODY_LENGTH, len(msg1))
|
||||
|
||||
# Adding the payload data to the message
|
||||
msg1 += payload
|
||||
return msg1
|
||||
|
||||
class ChallengeMessage(object):
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
2.2.1.2 CHALLENGE_MESSAGE
|
||||
The CHALLENGE_MESSAGE defines an NTLM challenge message that is sent from the server to
|
||||
the client. The CHALLENGE_MESSAGE is used by the server to challenge the client to prove
|
||||
its identity, For connection-oriented requests, the CHALLENGE_MESSAGE generated by the
|
||||
server is in response to the NEGOTIATE_MESSAGE from the client.
|
||||
|
||||
:param msg2: The CHALLENGE_MESSAGE received from the server after sending our NEGOTIATE_MESSAGE. This has
|
||||
been decoded from a base64 string
|
||||
|
||||
Attributes
|
||||
signature: An 8-byte character array that MUST contain the ASCII string 'NTLMSSP\0'
|
||||
message_type: A 32-bit unsigned integer that indicates the message type. This field must be set to 0x00000002
|
||||
negotiate_flags: A NEGOTIATE strucutre that contains a set of bit flags. The server sets flags to indicate options it supports
|
||||
server_challenge: A 64-bit value that contains the NTLM challenge. The challenge is a 64-bit nonce. Used in the AuthenticateMessage message
|
||||
reserved: An 8-byte array whose elements MUST be zero when sent and MUST be ignored on receipt
|
||||
version: When NTLMSSP_NEGOTIATE_VERSION flag is set in negotiate_flags field which contains the windows version info. Used only for debugging purposes
|
||||
target_name: When NTLMSSP_REQUEST_TARGET is set is a byte array that contains the name of the server authentication realm. In a domain environment this is the domain name not server name
|
||||
target_info: When NTLMSSP_NEGOTIATE_TARGET_INFO is set is a byte array that contains a sequence of AV_PAIR structures (target_info.py)
|
||||
"""
|
||||
def __init__(self, msg2):
|
||||
self.data = msg2
|
||||
# Setting the object values from the raw_challenge_message
|
||||
self.signature = msg2[0:8]
|
||||
self.message_type = struct.unpack("<I", msg2[8:12])[0]
|
||||
self.negotiate_flags = struct.unpack("<I", msg2[20:24])[0]
|
||||
self.server_challenge = msg2[24:32]
|
||||
self.reserved = msg2[32:40]
|
||||
|
||||
if self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_VERSION and self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
|
||||
size = len(msg2)
|
||||
self.version = struct.unpack("<q", msg2[48:56])[0]
|
||||
else:
|
||||
self.version = None
|
||||
|
||||
if self.negotiate_flags & NegotiateFlags.NTLMSSP_REQUEST_TARGET:
|
||||
target_name_len = struct.unpack("<H", msg2[12:14])[0]
|
||||
target_name_max_len = struct.unpack("<H", msg2[14:16])[0]
|
||||
target_name_buffer_offset = struct.unpack("<I", msg2[16:20])[0]
|
||||
self.target_name = msg2[target_name_buffer_offset:target_name_buffer_offset + target_name_len]
|
||||
else:
|
||||
self.target_name = None
|
||||
|
||||
if self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_TARGET_INFO:
|
||||
target_info_len = struct.unpack("<H", msg2[40:42])[0]
|
||||
target_info_max_len = struct.unpack("<H", msg2[42:44])[0]
|
||||
target_info_buffer_offset = struct.unpack("<I", msg2[44:48])[0]
|
||||
|
||||
target_info_raw = msg2[target_info_buffer_offset:target_info_buffer_offset + target_info_len]
|
||||
self.target_info = TargetInfo(target_info_raw)
|
||||
else:
|
||||
self.target_info = None
|
||||
|
||||
# Verify initial integrity of the message, it matches what should be there
|
||||
assert self.signature == NTLM_SIGNATURE
|
||||
assert self.message_type == MessageTypes.NTLM_CHALLENGE
|
||||
|
||||
def get_data(self):
|
||||
return self.data
|
||||
|
||||
class AuthenticateMessage(object):
|
||||
EXPECTED_BODY_LENGTH = 72
|
||||
EXPECTED_BODY_LENGTH_WITH_MIC = 88
|
||||
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
2.2.1.3 AUTHENTICATE_MESSAGE
|
||||
The AUTHENTICATE_MESSAGE defines an NTLM authenticate message that is sent from the
|
||||
client to the server after the CHALLENGE_MESSAGE is processed by the client.
|
||||
|
||||
:param user_name: The user name of the user we are trying to authenticate with
|
||||
:param password: The password of the user we are trying to authenticate with
|
||||
:param domain_name: The domain name of the user account we are authenticated with, default is None
|
||||
:param workstation: The workstation we are using to authenticate with, default is None
|
||||
:param challenge_message: A ChallengeMessage object that was received from the server after the negotiate_message
|
||||
:param ntlm_compatibility: The Lan Manager Compatibility Level, used to determine what NTLM auth version to use, see Ntlm in ntlm.py for more details
|
||||
:param server_certificate_hash: The SHA256 hash string of the server certificate (DER encoded) NTLM is authenticating to. This is used to add
|
||||
to the gss_channel_bindings_struct for Channel Binding Tokens support. If none is passed through then ntlm-auth
|
||||
will not use Channel Binding Tokens when authenticating with the server which could cause issues if it is set to
|
||||
only authenticate when these are present. This is only used for NTLMv2 authentication.
|
||||
|
||||
Message Attributes (Attributes not used to compute the message structure):
|
||||
signature: An 8-byte character array that MUST contain the ASCII string 'NTLMSSP\0'
|
||||
message_type: A 32-bit unsigned integer that indicates the message type. This field must be set to 0x00000003
|
||||
negotiate_flags: A NEGOTIATE strucutre that contains a set of bit flags. These flags are the choices the client has made from the CHALLENGE_MESSAGE options
|
||||
version: Contains the windows version info of the client. It is used only debugging purposes and are only set when NTLMSSP_NEGOTIATE_VERSION flag is set
|
||||
mic: The message integrity for the NEGOTIATE_MESSAGE, CHALLENGE_MESSAGE and AUTHENTICATE_MESSAGE
|
||||
lm_challenge_response: An LM_RESPONSE of LMv2_RESPONSE structure that contains the computed LM response to the challenge
|
||||
nt_challenge_response: An NTLM_RESPONSE or NTLMv2_RESPONSE structure that contains the computed NT response to the challenge
|
||||
domain_name: The domain or computer name hosting the user account, MUST be encoded in the negotiated character set
|
||||
user_name: The name of the user to be authenticated, MUST be encoded in the negotiated character set
|
||||
workstation: The name of the computer to which the user is logged on, MUST Be encoded in the negotiated character set
|
||||
encrypted_random_session_key: The client's encrypted random session key
|
||||
|
||||
Non-Message Attributes (Attributes not used to compute the message structure):
|
||||
exported_session_key: A randomly generated session key based on other keys, used to derive the SIGNKEY and SEALKEY
|
||||
target_info: The AV_PAIR structure used in the nt response calculation
|
||||
"""
|
||||
def __init__(self, user_name, password, domain_name, workstation, challenge_message, ntlm_compatibility, server_certificate_hash):
|
||||
self.signature = NTLM_SIGNATURE
|
||||
self.message_type = struct.pack('<L', MessageTypes.NTLM_AUTHENTICATE)
|
||||
self.negotiate_flags = challenge_message.negotiate_flags
|
||||
self.version = get_version(self.negotiate_flags)
|
||||
self.mic = None
|
||||
|
||||
if domain_name is None:
|
||||
self.domain_name = ''
|
||||
else:
|
||||
self.domain_name = domain_name
|
||||
|
||||
if workstation is None:
|
||||
self.workstation = ''
|
||||
else:
|
||||
self.workstation = workstation
|
||||
|
||||
if self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_UNICODE:
|
||||
self.negotiate_flags -= NegotiateFlags.NTLMSSP_NEGOTIATE_OEM
|
||||
encoding_value = 'utf-16-le'
|
||||
else:
|
||||
encoding_value = 'ascii'
|
||||
|
||||
self.domain_name = self.domain_name.encode(encoding_value)
|
||||
self.user_name = user_name.encode(encoding_value)
|
||||
self.workstation = self.workstation.encode(encoding_value)
|
||||
|
||||
compute_response = ComputeResponse(user_name, password, domain_name, challenge_message,
|
||||
ntlm_compatibility)
|
||||
|
||||
self.lm_challenge_response = compute_response.get_lm_challenge_response()
|
||||
self.nt_challenge_response, key_exchange_key, target_info = compute_response.get_nt_challenge_response(
|
||||
self.lm_challenge_response, server_certificate_hash)
|
||||
self.target_info = target_info
|
||||
|
||||
if self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_KEY_EXCH:
|
||||
self.exported_session_key = get_random_export_session_key()
|
||||
|
||||
rc4_handle = ARC4(key_exchange_key)
|
||||
self.encrypted_random_session_key = rc4_handle.update(self.exported_session_key)
|
||||
else:
|
||||
self.exported_session_key = key_exchange_key
|
||||
self.encrypted_random_session_key = b''
|
||||
|
||||
self.negotiate_flags = struct.pack('<I', self.negotiate_flags)
|
||||
|
||||
def get_data(self):
|
||||
if self.mic is None:
|
||||
mic = b''
|
||||
expected_body_length = self.EXPECTED_BODY_LENGTH
|
||||
else:
|
||||
mic = self.mic
|
||||
expected_body_length = self.EXPECTED_BODY_LENGTH_WITH_MIC
|
||||
|
||||
payload_offset = expected_body_length
|
||||
|
||||
# DomainNameFields - 8 bytes
|
||||
domain_name_len = struct.pack('<H', len(self.domain_name))
|
||||
domain_name_max_len = struct.pack('<H', len(self.domain_name))
|
||||
domain_name_buffer_offset = struct.pack('<I', payload_offset)
|
||||
payload_offset += len(self.domain_name)
|
||||
|
||||
# UserNameFields - 8 bytes
|
||||
user_name_len = struct.pack('<H', len(self.user_name))
|
||||
user_name_max_len = struct.pack('<H', len(self.user_name))
|
||||
user_name_buffer_offset = struct.pack('<I', payload_offset)
|
||||
payload_offset += len(self.user_name)
|
||||
|
||||
# WorkstatonFields - 8 bytes
|
||||
workstation_len = struct.pack('<H', len(self.workstation))
|
||||
workstation_max_len = struct.pack('<H', len(self.workstation))
|
||||
workstation_buffer_offset = struct.pack('<I', payload_offset)
|
||||
payload_offset += len(self.workstation)
|
||||
|
||||
# LmChallengeResponseFields - 8 bytes
|
||||
lm_challenge_response_len = struct.pack('<H', len(self.lm_challenge_response))
|
||||
lm_challenge_response_max_len = struct.pack('<H', len(self.lm_challenge_response))
|
||||
lm_challenge_response_buffer_offset = struct.pack('<I', payload_offset)
|
||||
payload_offset += len(self.lm_challenge_response)
|
||||
|
||||
# NtChallengeResponseFields - 8 bytes
|
||||
nt_challenge_response_len = struct.pack('<H', len(self.nt_challenge_response))
|
||||
nt_challenge_response_max_len = struct.pack('<H', len(self.nt_challenge_response))
|
||||
nt_challenge_response_buffer_offset = struct.pack('<I', payload_offset)
|
||||
payload_offset += len(self.nt_challenge_response)
|
||||
|
||||
# EncryptedRandomSessionKeyFields - 8 bytes
|
||||
encrypted_random_session_key_len = struct.pack('<H', len(self.encrypted_random_session_key))
|
||||
encrypted_random_session_key_max_len = struct.pack('<H', len(self.encrypted_random_session_key))
|
||||
encrypted_random_session_key_buffer_offset = struct.pack('<I', payload_offset)
|
||||
payload_offset += len(self.encrypted_random_session_key)
|
||||
|
||||
# Payload - variable length
|
||||
payload = self.domain_name
|
||||
payload += self.user_name
|
||||
payload += self.workstation
|
||||
payload += self.lm_challenge_response
|
||||
payload += self.nt_challenge_response
|
||||
payload += self.encrypted_random_session_key
|
||||
|
||||
msg3 = self.signature
|
||||
msg3 += self.message_type
|
||||
msg3 += lm_challenge_response_len + lm_challenge_response_max_len + lm_challenge_response_buffer_offset
|
||||
msg3 += nt_challenge_response_len + nt_challenge_response_max_len + nt_challenge_response_buffer_offset
|
||||
msg3 += domain_name_len + domain_name_max_len + domain_name_buffer_offset
|
||||
msg3 += user_name_len + user_name_max_len + user_name_buffer_offset
|
||||
msg3 += workstation_len + workstation_max_len + workstation_buffer_offset
|
||||
msg3 += encrypted_random_session_key_len + encrypted_random_session_key_max_len + encrypted_random_session_key_buffer_offset
|
||||
msg3 += self.negotiate_flags
|
||||
msg3 += self.version
|
||||
msg3 += mic
|
||||
|
||||
# Adding the payload data to the message
|
||||
msg3 += payload
|
||||
|
||||
return msg3
|
||||
|
||||
def add_mic(self, negotiate_message, challenge_message):
|
||||
if self.target_info is not None:
|
||||
av_flags = self.target_info[TargetInfo.MSV_AV_FLAGS]
|
||||
|
||||
if av_flags is not None and av_flags[1] == struct.pack("<L", AvFlags.MIC_PROVIDED):
|
||||
self.mic = struct.pack("<IIII", 0, 0, 0, 0)
|
||||
negotiate_data = negotiate_message.get_data()
|
||||
challenge_data = challenge_message.get_data()
|
||||
authenticate_data = self.get_data()
|
||||
|
||||
mic = hmac.new(self.exported_session_key,
|
||||
(negotiate_data + challenge_data + authenticate_data)).digest()
|
||||
self.mic = mic
|
||||
|
||||
def get_version(negotiate_flags):
|
||||
# Check the negotiate_flag version is set, if it is make sure the version info is added to the data
|
||||
if negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_VERSION:
|
||||
# TODO: Get the major and minor version of Windows instead of using default values
|
||||
product_major_version = struct.pack('<B', 6)
|
||||
product_minor_version = struct.pack('<B', 1)
|
||||
product_build = struct.pack('<H', 7601)
|
||||
version_reserved = b'\0' * 3
|
||||
ntlm_revision_current = struct.pack('<B', 15)
|
||||
version = product_major_version + product_minor_version + product_build + version_reserved + ntlm_revision_current
|
||||
else:
|
||||
version = b'\0' * 8
|
||||
|
||||
return version
|
||||
|
||||
def get_random_export_session_key():
|
||||
return os.urandom(16)
|
146
packages/wakatime/packages/ntlm_auth/ntlm.py
Normal file
146
packages/wakatime/packages/ntlm_auth/ntlm.py
Normal file
@ -0,0 +1,146 @@
|
||||
# This library is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation, either
|
||||
# version 3 of the License, or (at your option) any later version.
|
||||
|
||||
# This library 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
|
||||
# Lesser General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.
|
||||
|
||||
import base64
|
||||
import socket
|
||||
import struct
|
||||
from ntlm_auth.constants import NegotiateFlags
|
||||
from ntlm_auth.messages import NegotiateMessage, ChallengeMessage, AuthenticateMessage
|
||||
from ntlm_auth.session_security import SessionSecurity
|
||||
|
||||
|
||||
"""
|
||||
utility functions for Microsoft NTLM authentication
|
||||
|
||||
References:
|
||||
[MS-NLMP]: NT LAN Manager (NTLM) Authentication Protocol Specification
|
||||
http://download.microsoft.com/download/a/e/6/ae6e4142-aa58-45c6-8dcf-a657e5900cd3/%5BMS-NLMP%5D.pdf
|
||||
|
||||
[MS-NTHT]: NTLM Over HTTP Protocol Specification
|
||||
http://download.microsoft.com/download/a/e/6/ae6e4142-aa58-45c6-8dcf-a657e5900cd3/%5BMS-NTHT%5D.pdf
|
||||
|
||||
Cntlm Authentication Proxy
|
||||
http://cntlm.awk.cz/
|
||||
|
||||
NTLM Authorization Proxy Server
|
||||
http://sourceforge.net/projects/ntlmaps/
|
||||
|
||||
Optimized Attack for NTLM2 Session Response
|
||||
http://www.blackhat.com/presentations/bh-asia-04/bh-jp-04-pdfs/bh-jp-04-seki.pdf
|
||||
"""
|
||||
|
||||
class Ntlm(object):
|
||||
"""
|
||||
Initialises the NTLM context to use when sending and receiving messages to and from the server. You should be
|
||||
using this object as it supports NTLMv2 authenticate and it easier to use than before. It also brings in the
|
||||
ability to use signing and sealing with session_security and generate a MIC structure.
|
||||
|
||||
:param ntlm_compatibility: The Lan Manager Compatibility Level to use withe the auth message - Default 3
|
||||
This is set by an Administrator in the registry key
|
||||
'HKLM\SYSTEM\CurrentControlSet\Control\Lsa\LmCompatibilityLevel'
|
||||
The values correspond to the following;
|
||||
0 : LM and NTLMv1
|
||||
1 : LM, NTLMv1 and NTLMv1 with Extended Session Security
|
||||
2 : NTLMv1 and NTLMv1 with Extended Session Security
|
||||
3-5 : NTLMv2 Only
|
||||
Note: Values 3 to 5 are no different as the client supports the same types
|
||||
|
||||
Attributes:
|
||||
negotiate_flags: A NEGOTIATE structure that contains a set of bit flags. These flags are the options the client supports and are sent in the negotiate_message
|
||||
ntlm_compatibility: The Lan Manager Compatibility Level, same as the input if supplied
|
||||
negotiate_message: A NegotiateMessage object that is sent to the server
|
||||
challenge_message: A ChallengeMessage object that has been created from the server response
|
||||
authenticate_message: An AuthenticateMessage object that is sent to the server based on the ChallengeMessage
|
||||
session_security: A SessionSecurity structure that can be used to sign and seal messages sent after the authentication challenge
|
||||
"""
|
||||
def __init__(self, ntlm_compatibility=3):
|
||||
self.ntlm_compatibility = ntlm_compatibility
|
||||
|
||||
# Setting up our flags so the challenge message returns the target info block if supported
|
||||
self.negotiate_flags = NegotiateFlags.NTLMSSP_NEGOTIATE_TARGET_INFO | \
|
||||
NegotiateFlags.NTLMSSP_NEGOTIATE_128 | \
|
||||
NegotiateFlags.NTLMSSP_NEGOTIATE_56 | \
|
||||
NegotiateFlags.NTLMSSP_NEGOTIATE_UNICODE | \
|
||||
NegotiateFlags.NTLMSSP_NEGOTIATE_VERSION | \
|
||||
NegotiateFlags.NTLMSSP_NEGOTIATE_KEY_EXCH | \
|
||||
NegotiateFlags.NTLMSSP_NEGOTIATE_ALWAYS_SIGN | \
|
||||
NegotiateFlags.NTLMSSP_NEGOTIATE_SIGN | \
|
||||
NegotiateFlags.NTLMSSP_NEGOTIATE_SEAL
|
||||
|
||||
# Setting the message types based on the ntlm_compatibility level
|
||||
self._set_ntlm_compatibility_flags(self.ntlm_compatibility)
|
||||
|
||||
self.negotiate_message = None
|
||||
self.challenge_message = None
|
||||
self.authenticate_message = None
|
||||
self.session_security = None
|
||||
|
||||
|
||||
def create_negotiate_message(self, domain_name=None, workstation=None):
|
||||
"""
|
||||
Create an NTLM NEGOTIATE_MESSAGE
|
||||
|
||||
:param domain_name: The domain name of the user account we are authenticating with, default is None
|
||||
:param worksation: The workstation we are using to authenticate with, default is None
|
||||
:return: A base64 encoded string of the NEGOTIATE_MESSAGE
|
||||
"""
|
||||
self.negotiate_message = NegotiateMessage(self.negotiate_flags, domain_name, workstation)
|
||||
|
||||
return base64.b64encode(self.negotiate_message.get_data())
|
||||
|
||||
def parse_challenge_message(self, msg2):
|
||||
"""
|
||||
Parse the NTLM CHALLENGE_MESSAGE from the server and add it to the Ntlm context fields
|
||||
|
||||
:param msg2: A base64 encoded string of the CHALLENGE_MESSAGE
|
||||
"""
|
||||
msg2 = base64.b64decode(msg2)
|
||||
self.challenge_message = ChallengeMessage(msg2)
|
||||
|
||||
def create_authenticate_message(self, user_name, password, domain_name=None, workstation=None, server_certificate_hash=None):
|
||||
"""
|
||||
Create an NTLM AUTHENTICATE_MESSAGE based on the Ntlm context and the previous messages sent and received
|
||||
|
||||
:param user_name: The user name of the user we are trying to authenticate with
|
||||
:param password: The password of the user we are trying to authenticate with
|
||||
:param domain_name: The domain name of the user account we are authenticated with, default is None
|
||||
:param workstation: The workstation we are using to authenticate with, default is None
|
||||
:param server_certificate_hash: The SHA256 hash string of the server certificate (DER encoded) NTLM is authenticating to. Used for Channel
|
||||
Binding Tokens. If nothing is supplied then the CBT hash will not be sent. See messages.py AuthenticateMessage
|
||||
for more details
|
||||
:return: A base64 encoded string of the AUTHENTICATE_MESSAGE
|
||||
"""
|
||||
self.authenticate_message = AuthenticateMessage(user_name, password, domain_name, workstation,
|
||||
self.challenge_message, self.ntlm_compatibility,
|
||||
server_certificate_hash)
|
||||
self.authenticate_message.add_mic(self.negotiate_message, self.challenge_message)
|
||||
|
||||
# Setups up the session_security context used to sign and seal messages if wanted
|
||||
if self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_SEAL or self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_SIGN:
|
||||
self.session_security = SessionSecurity(struct.unpack("<I", self.authenticate_message.negotiate_flags)[0],
|
||||
self.authenticate_message.exported_session_key)
|
||||
|
||||
return base64.b64encode(self.authenticate_message.get_data())
|
||||
|
||||
def _set_ntlm_compatibility_flags(self, ntlm_compatibility):
|
||||
if (ntlm_compatibility >= 0) and (ntlm_compatibility <= 5):
|
||||
if ntlm_compatibility == 0:
|
||||
self.negotiate_flags |= NegotiateFlags.NTLMSSP_NEGOTIATE_NTLM | \
|
||||
NegotiateFlags.NTLMSSP_NEGOTIATE_LM_KEY
|
||||
elif ntlm_compatibility == 1:
|
||||
self.negotiate_flags |= NegotiateFlags.NTLMSSP_NEGOTIATE_NTLM | \
|
||||
NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY
|
||||
else:
|
||||
self.negotiate_flags |= NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY
|
||||
else:
|
||||
raise Exception("Unknown ntlm_compatibility level - expecting value between 0 and 5")
|
51
packages/wakatime/packages/ntlm_auth/rc4.py
Normal file
51
packages/wakatime/packages/ntlm_auth/rc4.py
Normal file
@ -0,0 +1,51 @@
|
||||
# This library is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation, either
|
||||
# version 3 of the License, or (at your option) any later version.
|
||||
|
||||
# This library 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
|
||||
# Lesser General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.
|
||||
|
||||
class ARC4(object):
|
||||
state = None
|
||||
i = 0
|
||||
j = 0
|
||||
|
||||
def __init__(self, key):
|
||||
# Split up the key into a list
|
||||
if isinstance(key, str):
|
||||
key = [ord(c) for c in key]
|
||||
else:
|
||||
key = [c for c in key]
|
||||
|
||||
#Key-scheduling algorithm (KSA)
|
||||
self.state = [n for n in range(256)]
|
||||
j = 0
|
||||
for i in range(256):
|
||||
j = (j + self.state[i] + key[i % len(key)]) % 256
|
||||
self.state[i], self.state[j] = self.state[j], self.state[i]
|
||||
|
||||
def update(self, value):
|
||||
chars = []
|
||||
random_gen = self._random_generator()
|
||||
for char in value:
|
||||
if isinstance(value, str):
|
||||
byte = ord(char)
|
||||
else:
|
||||
byte = char
|
||||
updated_byte = byte ^ next(random_gen)
|
||||
chars.append(updated_byte)
|
||||
return bytes(bytearray(chars))
|
||||
|
||||
def _random_generator(self):
|
||||
#Pseudo-Random Generation Algorithm (PRGA)
|
||||
while True:
|
||||
self.i = (self.i + 1) % 256
|
||||
self.j = (self.j + self.state[self.i]) % 256
|
||||
self.state[self.i], self.state[self.j] = self.state[self.j], self.state[self.i]
|
||||
yield self.state[(self.state[self.i] + self.state[self.j]) % 256]
|
250
packages/wakatime/packages/ntlm_auth/session_security.py
Normal file
250
packages/wakatime/packages/ntlm_auth/session_security.py
Normal file
@ -0,0 +1,250 @@
|
||||
# This library is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation, either
|
||||
# version 3 of the License, or (at your option) any later version.
|
||||
|
||||
# This library 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
|
||||
# Lesser General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.
|
||||
|
||||
import binascii
|
||||
import hmac
|
||||
import struct
|
||||
import ntlm_auth.compute_keys as compkeys
|
||||
from ntlm_auth.constants import NegotiateFlags, SignSealConstants
|
||||
from ntlm_auth.rc4 import ARC4
|
||||
|
||||
|
||||
class _NtlmMessageSignature1(object):
|
||||
EXPECTED_BODY_LENGTH = 16
|
||||
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
2.2.2.9.1 NTLMSSP_MESSAGE_SIGNATURE
|
||||
This version of the NTLMSSP_MESSAGE_SIGNATURE structure MUST be used when the
|
||||
NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY flag is not negotiated.
|
||||
|
||||
:param random_pad: A 4-byte array that contains the random pad for the emssage
|
||||
:param checksum: A 4-byte array that contains the checksum for the message
|
||||
:param seq_num: A 32-bit unsigned integer that contains the NTLM sequence number for this application message
|
||||
"""
|
||||
def __init__(self, random_pad, checksum, seq_num):
|
||||
self.version = struct.pack("<I", 1)
|
||||
self.random_pad = random_pad
|
||||
self.checksum = checksum
|
||||
self.seq_num = seq_num
|
||||
|
||||
def get_data(self):
|
||||
signature = self.version
|
||||
signature += self.random_pad
|
||||
signature += self.checksum
|
||||
signature += self.seq_num
|
||||
|
||||
assert self.EXPECTED_BODY_LENGTH == len(signature), "BODY_LENGTH: %d != signature: %d" % (
|
||||
self.EXPECTED_BODY_LENGTH, len(signature))
|
||||
|
||||
return signature
|
||||
|
||||
class _NtlmMessageSignature2(object):
|
||||
EXPECTED_BODY_LENGTH = 16
|
||||
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
2.2.2.9.2 NTLMSSP_MESSAGE_SIGNATURE for Extended Session Security
|
||||
This version of the NTLMSSP_MESSAGE_SIGNATURE structure MUST be used when the
|
||||
NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY flag is negotiated
|
||||
|
||||
:param checksum: An 8-byte array that contains the checksum for the message
|
||||
:param seq_num: A 32-bit unsigned integer that contains the NTLM sequence number for this application message
|
||||
"""
|
||||
|
||||
def __init__(self, checksum, seq_num):
|
||||
self.version = struct.pack("<I", 1)
|
||||
self.checksum = checksum
|
||||
self.seq_num = seq_num
|
||||
|
||||
def get_data(self):
|
||||
signature = self.version
|
||||
signature += self.checksum
|
||||
signature += self.seq_num
|
||||
|
||||
assert self.EXPECTED_BODY_LENGTH == len(signature), "BODY_LENGTH: %d != signature: %d" % (
|
||||
self.EXPECTED_BODY_LENGTH, len(signature))
|
||||
|
||||
return signature
|
||||
|
||||
class SessionSecurity(object):
|
||||
"""
|
||||
Initialises a security session context that can be used by libraries that call ntlm-auth to sign and seal
|
||||
messages send to the server as well as verify and unseal messages that have been received from the server.
|
||||
This is similar to the GSS_Wrap functions specified in the MS-NLMP document which does the same task.
|
||||
|
||||
:param negotiate_flags: The negotiate flag structure that has been negotiated with the server
|
||||
:param exported_session_key: A 128-bit session key used to derive signing and sealing keys
|
||||
:param source: The source of the message, only used in test scenarios when testing out a server sealing and unsealing
|
||||
"""
|
||||
def __init__(self, negotiate_flags, exported_session_key, source="client"):
|
||||
self.negotiate_flags = negotiate_flags
|
||||
self.outgoing_seq_num = 0
|
||||
self.incoming_seq_num = 0
|
||||
|
||||
client_sealing_key = compkeys.get_seal_key(self.negotiate_flags, exported_session_key, SignSealConstants.CLIENT_SEALING)
|
||||
server_sealing_key = compkeys.get_seal_key(self.negotiate_flags, exported_session_key, SignSealConstants.SERVER_SEALING)
|
||||
|
||||
if source == "client":
|
||||
self.outgoing_signing_key = compkeys.get_sign_key(exported_session_key, SignSealConstants.CLIENT_SIGNING)
|
||||
self.incoming_signing_key = compkeys.get_sign_key(exported_session_key, SignSealConstants.SERVER_SIGNING)
|
||||
self.outgoing_handle = ARC4(client_sealing_key)
|
||||
self.incoming_handle = ARC4(server_sealing_key)
|
||||
elif source == "server":
|
||||
self.outgoing_signing_key = compkeys.get_sign_key(exported_session_key, SignSealConstants.SERVER_SIGNING)
|
||||
self.incoming_signing_key = compkeys.get_sign_key(exported_session_key, SignSealConstants.CLIENT_SIGNING)
|
||||
self.outgoing_handle = ARC4(server_sealing_key)
|
||||
self.incoming_handle = ARC4(client_sealing_key)
|
||||
else:
|
||||
raise Exception("Invalid source parameter %s, must be client or server" % source)
|
||||
|
||||
def wrap(self, message):
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
3.4.6 GSS_WrapEx()
|
||||
Emulates the GSS_Wrap() implementation to sign and seal messages if the correct flags
|
||||
are set.
|
||||
|
||||
@param message: The message data that will be wrapped
|
||||
@return message: The message that has been sealed if flags are set
|
||||
@return signature: The signature of the message, None if flags are not set
|
||||
"""
|
||||
if self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_SEAL:
|
||||
encrypted_message = self._seal_message(message)
|
||||
signature = self._get_signature(message)
|
||||
message = encrypted_message
|
||||
|
||||
elif self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_SIGN:
|
||||
signature = self._get_signature(message)
|
||||
else:
|
||||
signature = None
|
||||
|
||||
return message, signature
|
||||
|
||||
def unwrap(self, message, signature):
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
3.4.7 GSS_UnwrapEx()
|
||||
Emulates the GSS_Unwrap() implementation to unseal messages and verify the signature
|
||||
sent matches what has been computed locally. Will throw an Exception if the signature
|
||||
doesn't match
|
||||
|
||||
@param message: The message data received from the server
|
||||
@param signature: The signature of the message
|
||||
@return message: The message that has been unsealed if flags are set
|
||||
"""
|
||||
if self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_SEAL:
|
||||
message = self._unseal_message(message)
|
||||
self._verify_signature(message, signature)
|
||||
|
||||
elif self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_SIGN:
|
||||
self._verify_signature(message, signature)
|
||||
|
||||
return message
|
||||
|
||||
def _seal_message(self, message):
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
3.4.3 Message Confidentiality
|
||||
Will generate an encrypted message using RC4 based on the ClientSealingKey
|
||||
|
||||
@param message: The message to be sealed (encrypted)
|
||||
@return encrypted_message: The encrypted message
|
||||
"""
|
||||
encrypted_message = self.outgoing_handle.update(message)
|
||||
return encrypted_message
|
||||
|
||||
def _unseal_message(self, message):
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
3.4.3 Message Confidentiality
|
||||
Will generate a dencrypted message using RC4 based on the ServerSealingKey
|
||||
|
||||
@param message: The message to be unsealed (dencrypted)
|
||||
@return decrypted_message: The decrypted message
|
||||
"""
|
||||
decrypted_message = self.incoming_handle.update(message)
|
||||
return decrypted_message
|
||||
|
||||
def _get_signature(self, message):
|
||||
"""
|
||||
[MS-NLMP] v28.0 2016-07-14
|
||||
|
||||
3.4.4 Message Signature Functions
|
||||
Will create the signature based on the message to send to the server. Depending on the negotiate_flags
|
||||
set this could either be an NTLMv1 signature or NTLMv2 with Extended Session Security signature.
|
||||
|
||||
@param message: The message data that will be signed
|
||||
@return signature: Either _NtlmMessageSignature1 or _NtlmMessageSignature2 depending on the flags set
|
||||
"""
|
||||
signature = calc_signature(message, self.negotiate_flags, self.outgoing_signing_key, self.outgoing_seq_num, self.outgoing_handle)
|
||||
self.outgoing_seq_num += 1
|
||||
|
||||
return signature.get_data()
|
||||
|
||||
def _verify_signature(self, message, signature):
|
||||
"""
|
||||
Will verify that the signature received from the server matches up with the expected signature
|
||||
computed locally. Will throw an exception if they do not match
|
||||
|
||||
@param message: The message data that is received from the server
|
||||
@param signature: The signature of the message received from the server
|
||||
"""
|
||||
if self.negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
|
||||
actual_checksum = signature[4:12]
|
||||
actual_seq_num = struct.unpack("<I", signature[12:16])[0]
|
||||
else:
|
||||
actual_checksum = signature[8:12]
|
||||
actual_seq_num = struct.unpack("<I", signature[12:16])[0]
|
||||
|
||||
expected_signature = calc_signature(message, self.negotiate_flags, self.incoming_signing_key, self.incoming_seq_num, self.incoming_handle)
|
||||
expected_checksum = expected_signature.checksum
|
||||
expected_seq_num = struct.unpack("<I", expected_signature.seq_num)[0]
|
||||
|
||||
if actual_checksum != expected_checksum:
|
||||
raise Exception("The signature checksum does not match, message has been altered")
|
||||
|
||||
if actual_seq_num != expected_seq_num:
|
||||
raise Exception("The signature sequence number does not match up, message not received in the correct sequence")
|
||||
|
||||
self.incoming_seq_num += 1
|
||||
|
||||
|
||||
def calc_signature(message, negotiate_flags, signing_key, seq_num, handle):
|
||||
seq_num = struct.pack("<I", seq_num)
|
||||
if negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
|
||||
checksum_hmac = hmac.new(signing_key, seq_num + message)
|
||||
if negotiate_flags & NegotiateFlags.NTLMSSP_NEGOTIATE_KEY_EXCH:
|
||||
checksum = handle.update(checksum_hmac.digest()[:8])
|
||||
else:
|
||||
checksum = checksum_hmac.digest()[:8]
|
||||
|
||||
signature = _NtlmMessageSignature2(checksum, seq_num)
|
||||
|
||||
else:
|
||||
message_crc = binascii.crc32(message) % (1 << 32)
|
||||
checksum = struct.pack("<I", message_crc)
|
||||
random_pad = handle.update(struct.pack("<I", 0))
|
||||
checksum = handle.update(checksum)
|
||||
seq_num = handle.update(seq_num)
|
||||
random_pad = struct.pack("<I", 0)
|
||||
|
||||
signature = _NtlmMessageSignature1(random_pad, checksum, seq_num)
|
||||
|
||||
return signature
|
68
packages/wakatime/packages/ntlm_auth/target_info.py
Normal file
68
packages/wakatime/packages/ntlm_auth/target_info.py
Normal file
@ -0,0 +1,68 @@
|
||||
"""
|
||||
Original Author: Ian Clegg
|
||||
Project: ntlmlib
|
||||
URL: https://github.com/ianclegg/ntlmlib
|
||||
License: Apache 2.0 License
|
||||
Notes: Most of this code has been copied from the messages.py in the ntlmlib repo.
|
||||
Some minor changes such as the name of the AV Pairs and extra comments have been added.
|
||||
"""
|
||||
|
||||
import struct
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
from ordereddict import OrderedDict
|
||||
|
||||
class TargetInfo(object):
|
||||
MSV_AV_EOL = 0x00
|
||||
MSV_AV_NB_COMPUTER_NAME = 0x01
|
||||
MSV_AV_NB_DOMAIN_NAME = 0x02
|
||||
MSV_AV_DNS_COMPUTER_NAME = 0x03
|
||||
MSV_AV_DNS_DOMAIN_NAME = 0x04
|
||||
MSV_AV_DNS_TREE_NAME = 0x05
|
||||
MSV_AV_FLAGS = 0x06
|
||||
MSV_AV_TIMESTAMP = 0x07
|
||||
MSV_AV_SINGLE_HOST = 0x08
|
||||
MSV_AV_TARGET_NAME = 0x09
|
||||
MSV_AV_CHANNEL_BINDINGS = 0x0a
|
||||
|
||||
def __init__(self, data=None):
|
||||
self.fields = OrderedDict()
|
||||
if data is not None:
|
||||
self.from_string(data)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.fields[key] = (len(value), value)
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key in self.fields:
|
||||
return self.fields[key]
|
||||
return None
|
||||
|
||||
def __delitem__(self, key):
|
||||
del self.fields[key]
|
||||
|
||||
def from_string(self, data):
|
||||
attribute_type = 0xff
|
||||
while attribute_type is not TargetInfo.MSV_AV_EOL:
|
||||
# Parse the Attribute Value pair from the structure
|
||||
attribute_type = struct.unpack('<H', data[:struct.calcsize('<H')])[0]
|
||||
data = data[struct.calcsize('<H'):]
|
||||
length = struct.unpack('<H', data[:struct.calcsize('<H')])[0]
|
||||
data = data[struct.calcsize('<H'):]
|
||||
# Add a new field to the object for the parse attribute value
|
||||
self.fields[attribute_type] = (length, data[:length])
|
||||
data = data[length:]
|
||||
|
||||
def get_data(self):
|
||||
if TargetInfo.MSV_AV_EOL in self.fields:
|
||||
del self.fields[TargetInfo.MSV_AV_EOL]
|
||||
|
||||
data = b''
|
||||
for i in self.fields.keys():
|
||||
data += struct.pack('<HH', i, self[i][0])
|
||||
data += self[i][1]
|
||||
|
||||
# end with a NTLMSSP_AV_EOL
|
||||
data += struct.pack('<HH', TargetInfo.MSV_AV_EOL, 0)
|
||||
return data
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user