#!/usr/bin/python
"""Fill in the first digits of the hash in a form created by gpgparticipants."""

__version__ = "1.0"

__author__ = "Stefan Huber"
__email__ = "shuber@sthu.org"
__copyright__ = "Copyright 2013, Stefan Huber"

__license__ = "MIT"

# also Copyright 2014 Peter Palfrader

# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.


import sys
import hashlib
import getopt

hexdigits = "0123456789abcdef"

def insertspaces(s):
    """Inserts a space after every 4-th character, and three spaces after every
    8-th character of string s."""

    def inpacks(s, n):
        while len(s) > 0:
            yield s[0:n]
            s = s[n:]

    out = "   ".join([ " ".join(inpacks(octp, 4)) for octp in inpacks(s, 8)])
    return out


def range_hex(length, fixed_prefix=''):
    """Give all hex-strings from 00...0 until ff...f of given length."""

    if length == 0:
        yield ""
    elif len(fixed_prefix) > 0:
      prefix = fixed_prefix[0]
      for postfix in range_hex(length-1, fixed_prefix[1:]):
        yield prefix + postfix
    elif length == 1:
        for c in hexdigits:
            yield c
    elif length > 1:
        for prefix in range_hex(length-1):
            for postfix in range_hex(1):
                yield prefix + postfix


def usage():
    """Print --help text"""

    print("""Usage:
  {0} <emptylist> <filledlist>
  {0} --help
  {0} -h

Takes a file produced by gpgparticipants as <emptylist> and trys to fill in
some digits into the SHA256 field such that the resulting list actually has
a SHA256 checksum that starts with those digits. Whenever a match is found a
file with the digits filled in is written to `<filledlist>.DIGITS`.

OPTIONS:

  --fastforward        If a match is found of given length and --fastforward
                       is given then the program immediately jumps to the next
                       length.
  --min-length NUM     Start search with given length.
  --max-length NUM     Stop search with given length.

""".format(sys.argv[0]))


if __name__ == "__main__":

    fastforward = False
    minlength = 1
    maxlength = 32
    prefix = ''

    optlist, args = getopt.getopt(sys.argv[1:], 'h', ['fastforward', 'min-length=', 'max-length=', 'prefix=', 'help'])
    for o, a in optlist:
        if o in ("-h", "--help"):
            usage()
            exit(0)
        elif o in ("--fastforward"):
            fastforward = True
        elif o in ("--min-length"):
            minlength = int(a)
        elif o in ("--max-length"):
            maxlength = int(a)+1
        elif o in ("--prefix"):
            prefix = a.lower()

    if len(args) < 2:
        print >>sys.stderr, "You need to give two filenames."
        exit(1)
    if not all(c in hexdigits for c in prefix):
        print >>sys.stderr, "Invalid prefix."
        exit(1)

    emptyfile = open(args[0]).read()

    idx = emptyfile.find("SHA256 Checksum:")
    idx = emptyfile.find("_", idx)

    for l in range(minlength, maxlength):
        print "Looking at length", l

        for h in range_hex(l, prefix):
            H = insertspaces(h.upper())
            filledfile = emptyfile[:idx] + H + emptyfile[idx+len(H):]
            actual = hashlib.sha256(filledfile).hexdigest()

            if actual[:len(h)] == h:
                print "Found: ", H
                open(args[1] + "." + h, "w").write(filledfile)

                if fastforward:
                    break
