#*
#*  GRUB  --  GRand Unified Bootloader
#*  Copyright (C) 2009  Free Software Foundation, Inc.
#*
#*  GRUB is free software: you can redistribute it and/or modify
#*  it under the terms of the GNU General Public License as published by
#*  the Free Software Foundation, either version 3 of the License, or
#*  (at your option) any later version.
#*
#*  GRUB 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 General Public License for more details.
#*
#*  You should have received a copy of the GNU General Public License
#*  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
#*

import re
import sys
import os
import datetime

if len (sys.argv) < 3:
    print ("Usage: %s SOURCE DESTINATION" % sys.argv[0])
    exit (0)
indir = sys.argv[1]
outdir = sys.argv[2]

basedir = os.path.join (outdir, "lib/libgcrypt-grub")
try:
    os.makedirs (basedir)
except:
    print ("WARNING: %s already exists" % basedir)
cipher_dir_in = os.path.join (indir, "cipher")
cipher_dir_out = os.path.join (basedir, "cipher")
try:
    os.makedirs (cipher_dir_out)
except:
    print ("WARNING: %s already exists" % cipher_dir_out)

cipher_files = os.listdir (cipher_dir_in)
conf = open (os.path.join (outdir, "conf", "gcry.rmk"), "w")
conf.write ("# -*- makefile -*-\n\n")
chlog = ""

# Strictly speaking CRC32/CRC24 work on bytes so this value should be 1
# But libgcrypt uses 64. Let's keep the value for compatibility. Since
# noone uses CRC24/CRC32 for HMAC this is no problem
mdblocksizes = {"_gcry_digest_spec_crc32" : 64,
                "_gcry_digest_spec_crc32_rfc1510" : 64,
                "_gcry_digest_spec_crc24_rfc2440" : 64,
                "_gcry_digest_spec_md4" : 64,
                "_gcry_digest_spec_md5" : 64,
                "_gcry_digest_spec_rmd160" : 64,
                "_gcry_digest_spec_sha1" : 64,
                "_gcry_digest_spec_sha224" : 64,
                "_gcry_digest_spec_sha256" : 64,
                "_gcry_digest_spec_sha384" : 128,
                "_gcry_digest_spec_sha512" : 128,
                "_gcry_digest_spec_tiger" : 64,
                "_gcry_digest_spec_whirlpool" : 64}

cryptolist = open (os.path.join (cipher_dir_out, "crypto.lst"), "w")
conf.write ("MAINTAINER_CLEANFILES += $(srcdir)/conf/gcry.rmk $(srcdir)/lib/libgcrypt-grub/cipher/ChangeLog $(srcdir)/lib/libgcrypt-grub/cipher/cipher.h $(srcdir)/lib/libgcrypt-grub/cipher/crypto.lst $(srcdir)/lib/libgcrypt-grub/cipher/g10lib.h $(srcdir)/lib/libgcrypt-grub/cipher/memory.h $(srcdir)/lib/libgcrypt-grub/cipher/types.h\n");

# rijndael is the only cipher using aliases. So no need for mangling, just
# hardcode it
cryptolist.write ("RIJNDAEL: gcry_rijndael\n");
cryptolist.write ("RIJNDAEL192: gcry_rijndael\n");
cryptolist.write ("RIJNDAEL256: gcry_rijndael\n");
cryptolist.write ("AES128: gcry_rijndael\n");
cryptolist.write ("AES-128: gcry_rijndael\n");
cryptolist.write ("AES-192: gcry_rijndael\n");
cryptolist.write ("AES-256: gcry_rijndael\n");

for cipher_file in cipher_files:
    infile = os.path.join (cipher_dir_in, cipher_file)
    outfile = os.path.join (cipher_dir_out, cipher_file)
    if cipher_file == "ChangeLog":
        continue
    chlognew = "	* %s" % cipher_file
    if re.match ("(Manifest|Makefile\.am|ac\.c|cipher\.c|hash-common\.c|hmac-tests\.c|md\.c|pubkey\.c)$", cipher_file):
        chlog = "%s%s: Removed\n" % (chlog, chlognew)
        continue
    # Autogenerated files. Not even worth mentionning in ChangeLog
    if re.match ("Makefile\.in$", cipher_file):
        continue
    nch = False
    if re.match (".*\.[ch]$", cipher_file):
        conf.write ("MAINTAINER_CLEANFILES += $(srcdir)/lib/libgcrypt-grub/cipher/" + cipher_file + "\n");
        isc = re.match (".*\.c$", cipher_file)
        f = open (infile, "r")
        fw = open (outfile, "w")
        fw.write ("/* This file was automatically imported with \n")
        fw.write ("   import_gcry.py. Please don't modify it */\n");
        ciphernames = []
        mdnames = []
        hold = False
        skip = False
        skip2 = False
        ismd = False
        iscryptostart = False
        iscomma = False
        isglue = False
        skip_statement = False
        if isc:
            modname = cipher_file [0:len(cipher_file) - 2]
            if re.match (".*-glue$", modname):
                modname = modname.replace ("-glue", "")
                isglue = True
            modname = "gcry_%s" % modname
        for line in f:
            if skip_statement:
                if not re.search (";", line) is None:
                    skip_statement = False
                continue
            if skip:
                if line[0] == "}":
                    skip = False
                continue
            if skip2:
                if not re.search (" *};", line) is None:
                    skip2 = False
                continue
            if iscryptostart:
                s = re.search (" *\"([A-Z0-9_a-z]*)\"", line)
                if not s is None:
                    sg = s.groups()[0]
                    cryptolist.write (("%s: %s\n") % (sg, modname))
                    iscryptostart = False
            if ismd:
                if not re.search (" *};", line) is None:
                    if not mdblocksizes.has_key (mdname):
                        print ("ERROR: Unknown digest blocksize: %s\n" % mdname)
                        exit (1)
                    if not iscomma:
                        fw.write ("    ,\n")
                    fw.write ("    .blocksize = %s\n" % mdblocksizes [mdname])
                    ismd = False
                iscomma = not re.search (",$", line) is None
            # Used only for selftests.
            m = re.match ("(static byte|static unsigned char) (weak_keys_chksum)\[[0-9]*\] =", line)
            if not m is None:
                skip = True
                fname = m.groups ()[1]
                chmsg = "(%s): Removed." % fname
                if nch:
                    chlognew = "%s\n	%s" % (chlognew, chmsg)
                else:
                    chlognew = "%s %s" % (chlognew, chmsg)
                    nch = True
                continue
            if hold:
                hold = False
                # We're optimising for size.
                if not re.match ("(run_selftests|selftest|_gcry_aes_c.._..c|_gcry_[a-z0-9]*_hash_buffer|tripledes_set2keys|do_tripledes_set_extra_info)", line) is None:
                    skip = True
                    fname = re.match ("[a-zA-Z0-9_]*", line).group ()
                    chmsg = "(%s): Removed." % fname
                    if nch:
                        chlognew = "%s\n	%s" % (chlognew, chmsg)
                    else:
                        chlognew = "%s %s" % (chlognew, chmsg)
                        nch = True
                    continue
                else:
                    fw.write (holdline)
            m = re.match ("#include <.*>", line)
            if not m is None:
                chmsg = "Removed including of %s" % \
                m.group () [len ("#include <"):len (m.group ()) - 1]
                if nch:
                    chlognew = "%s\n	%s" % (chlognew, chmsg)
                else:
                    chlognew = "%s: %s" % (chlognew, chmsg)
                    nch = True
                continue
            m = re.match ("gcry_cipher_spec_t", line)
            if isc and not m is None:
                assert (not iscryptostart)
                ciphername = line [len ("gcry_cipher_spec_t"):].strip ()
                ciphername = re.match("[a-zA-Z0-9_]*",ciphername).group ()
                ciphernames.append (ciphername)
                iscryptostart = True
            m = re.match ("gcry_md_spec_t", line)
            if isc and not m is None:
                assert (not ismd)
                assert (not iscryptostart)
                mdname = line [len ("gcry_md_spec_t"):].strip ()
                mdname = re.match("[a-zA-Z0-9_]*",mdname).group ()
                mdnames.append (mdname)
                ismd = True
                iscryptostart = True
            m = re.match ("static const char \*selftest.*;$", line)
            if not m is None:
                fname = line[len ("static const char \*"):]
                fname = re.match ("[a-zA-Z0-9_]*", fname).group ()
                chmsg = "(%s): Removed declaration." % fname
                if nch:
                    chlognew = "%s\n	%s" % (chlognew, chmsg)
                else:
                    chlognew = "%s %s" % (chlognew, chmsg)
                    nch = True
                continue
            m = re.match ("(static const char( |)\*|static gpg_err_code_t|void|static int|static gcry_err_code_t)$", line)
            if not m is None:
                hold = True
                holdline = line
                continue
            m = re.match ("static int tripledes_set2keys \(.*\);", line)
            if not m is None:
                continue
            m = re.match ("static int tripledes_set2keys \(", line)
            if not m is None:
                skip_statement = True
                continue
            m = re.match ("cipher_extra_spec_t", line)
            if isc and not m is None:
                skip2 = True
                fname = line[len ("cipher_extra_spec_t "):]
                fname = re.match ("[a-zA-Z0-9_]*", fname).group ()
                chmsg = "(%s): Removed." % fname
                if nch:
                    chlognew = "%s\n	%s" % (chlognew, chmsg)
                else:
                    chlognew = "%s %s" % (chlognew, chmsg)
                    nch = True
                continue
            m = re.match ("md_extra_spec_t", line)
            if isc and not m is None:
                skip2 = True
                fname = line[len ("md_extra_spec_t "):]
                fname = re.match ("[a-zA-Z0-9_]*", fname).group ()
                chmsg = "(%s): Removed." % fname
                if nch:
                    chlognew = "%s\n	%s" % (chlognew, chmsg)
                else:
                    chlognew = "%s %s" % (chlognew, chmsg)
                    nch = True
                continue
            fw.write (line)
        if len (ciphernames) > 0 or len (mdnames) > 0:
            if isglue:
                modfiles = "lib/libgcrypt-grub/cipher/%s lib/libgcrypt-grub/cipher/%s" \
                    % (cipher_file, cipher_file.replace ("-glue.c", ".c"))
            else:
                modfiles = "lib/libgcrypt-grub/cipher/%s" % cipher_file	
            chmsg = "(GRUB_MOD_INIT(%s)): New function\n" % modname
            if nch:
                chlognew = "%s\n	%s" % (chlognew, chmsg)
            else:
                chlognew = "%s%s" % (chlognew, chmsg)
                nch = True
            fw.write ("\n\nGRUB_MOD_INIT(%s)\n" % modname)
            fw.write ("{\n")
            for ciphername in ciphernames:
                chmsg = "Register cipher %s" % ciphername
                chlognew = "%s\n	%s" % (chlognew, chmsg)
                fw.write ("  grub_cipher_register (&%s);\n" % ciphername)
            for mdname in mdnames:
                chmsg = "Register digest %s" % mdname
                chlognew = "%s\n	%s" % (chlognew, chmsg)
                fw.write ("  grub_md_register (&%s);\n" % mdname)
            fw.write ("}")
            chmsg = "(GRUB_MOD_FINI(%s)): New function\n" % modname
            chlognew = "%s\n	%s" % (chlognew, chmsg)
            fw.write ("\n\nGRUB_MOD_FINI(%s)\n" % modname)
            fw.write ("{\n")
            for ciphername in ciphernames:
                chmsg = "Unregister cipher %s" % ciphername
                chlognew = "%s\n	%s" % (chlognew, chmsg)
                fw.write ("  grub_cipher_unregister (&%s);\n" % ciphername)
            for mdname in mdnames:
                chmsg = "Unregister MD %s" % mdname
                chlognew = "%s\n	%s" % (chlognew, chmsg)
                fw.write ("  grub_md_unregister (&%s);\n" % mdname)
            fw.write ("}\n")
            for mdname in mdnames:
                fw.write ("GRUB_EXPORT(%s);\n" % mdname)
            conf.write ("pkglib_MODULES += %s.mod\n" % modname)
            conf.write ("%s_mod_SOURCES = %s\n" %\
                            (modname, modfiles))
            conf.write ("%s_mod_CFLAGS = $(COMMON_CFLAGS) -Wno-error -I$(srcdir)/lib/libgcrypt_wrap\n" % modname)
            conf.write ("%s_mod_LDFLAGS = $(COMMON_LDFLAGS)\n\n" % modname)
        elif isc and cipher_file != "camellia.c":
            print ("WARNING: C file isn't a module: %s" % cipher_file)
        f.close ()
        fw.close ()
        if nch:
            chlog = "%s%s\n" % (chlog, chlognew)
        continue
    chlog = "%s%sSkipped unknown file\n" % (chlog, chlognew)
    print ("WARNING: unknown file %s" % cipher_file)

cryptolist.close ()
chlog = "%s	* crypto.lst: New file.\n" % chlog

outfile = os.path.join (cipher_dir_out, "types.h")
fw=open (outfile, "w")
fw.write ("#include <grub/types.h>\n")
fw.write ("#include <cipher_wrap.h>\n")
chlog = "%s	* types.h: New file.\n" % chlog
fw.close ()

outfile = os.path.join (cipher_dir_out, "memory.h")
fw=open (outfile, "w")
fw.write ("#include <cipher_wrap.h>\n")
chlog = "%s	* memory.h: New file.\n" % chlog
fw.close ()


outfile = os.path.join (cipher_dir_out, "cipher.h")
fw=open (outfile, "w")
fw.write ("#include <grub/crypto.h>\n")
fw.write ("#include <cipher_wrap.h>\n")
chlog = "%s	* cipher.h: Likewise.\n" % chlog
fw.close ()

outfile = os.path.join (cipher_dir_out, "g10lib.h")
fw=open (outfile, "w")
fw.write ("#include <cipher_wrap.h>\n")
chlog = "%s	* g10lib.h: Likewise.\n" % chlog
fw.close ()

infile = os.path.join (cipher_dir_in, "ChangeLog")
outfile = os.path.join (cipher_dir_out, "ChangeLog")


f=open (infile, "r")
fw=open (outfile, "w")
dt = datetime.date.today ()
fw.write ("%04d-%02d-%02d  Automatic import tool\n" % \
          (dt.year,dt.month, dt.day))
fw.write ("\n")
fw.write ("	Imported ciphers to GRUB\n")
fw.write ("\n")
fw.write (chlog)
fw.write ("\n")
for line in f:
    fw.write (line)
f.close ()
fw.close ()
