Back to index

lightning-sunbird  0.9+nobinonly
Classes | Functions | Variables
sign.c File Reference
#include "signtool.h"
#include "zip.h"
#include "prmem.h"
#include "blapi.h"
#include "sechash.h"

Go to the source code of this file.

Classes

struct  SignArcInfo

Functions

static int create_pk7 (char *dir, char *keyName, int *keyType)
static int jar_find_key_type (CERTCertificate *cert)
static int manifesto (char *dirname, char *install_script, PRBool recurse)
static int manifesto_fn (char *relpath, char *basedir, char *reldir, char *filename, void *arg)
static int manifesto_xpi_fn (char *relpath, char *basedir, char *reldir, char *filename, void *arg)
static int sign_all_arc_fn (char *relpath, char *basedir, char *reldir, char *filename, void *arg)
static int add_meta (FILE *fp, char *name)
static int SignFile (FILE *outFile, FILE *inFile, CERTCertificate *cert)
static int generate_SF_file (char *manifile, char *who)
static int calculate_MD5_range (FILE *fp, long r1, long r2, JAR_Digest *dig)
static void SignOut (void *arg, const char *buf, unsigned long len)
int SignArchive (char *tree, char *keyName, char *zip_file, int javascript, char *meta_file, char *install_script, int _optimize, PRBool recurse)
int SignAllArc (char *jartree, char *keyName, int javascript, char *metafile, char *install_script, int optimize, PRBool recurse)

Variables

static char * metafile = NULL
static int optimize = 0
static FILEmf
static ZIPfilezipfile = NULL

Class Documentation

struct SignArcInfo

Definition at line 160 of file sign.c.

Collaboration diagram for SignArcInfo:
Class Members
char * install_script
int javascript
char * keyName
char * metafile
int optimize

Function Documentation

static int add_meta ( FILE fp,
char *  name 
) [static]

Definition at line 564 of file sign.c.

{
    FILE * met;
    char      buf [BUFSIZ];

    int       place;
    char      *pattern, *meta;

    int       num = 0;

    if ((met = fopen (metafile, "r")) != NULL) {
       while (fgets (buf, BUFSIZ, met)) {
           char      *s;

           for (s = buf; *s && *s != '\n' && *s != '\r'; s++)
              ;
           *s = 0;

           if (*buf == 0)
              continue;

           pattern = buf;

           /* skip to whitespace */
           for (s = buf; *s && *s != ' ' && *s != '\t'; s++)
              ;

           /* terminate pattern */
           if (*s == ' ' || *s == '\t') 
              *s++ = 0;

           /* eat through whitespace */
           while (*s == ' ' || *s == '\t') 
              s++;

           meta = s;

           /* this will eventually be regexp matching */

           place = 0;
           if (!PORT_Strcmp (pattern, name))
              place = 1;

           if (place) {
              num++;
              if (verbosity >= 0) {
                  PR_fprintf(outputFD, "[%s] %s\n", name, meta);
              }
              fprintf (fp, "%s\n", meta);
           }
       }
       fclose (met);
    } else {
       PR_fprintf(errorFD, "%s: can't open metafile: %s\n", PROGRAM_NAME,
            metafile);
       errorCount++;
       exit (ERRX);
    }

    return num;
}

Here is the call graph for this function:

Here is the caller graph for this function:

static int calculate_MD5_range ( FILE fp,
long  r1,
long  r2,
JAR_Digest dig 
) [static]

Definition at line 842 of file sign.c.

{
    int       num;
    int       range;
    unsigned char    *buf;

    MD5Context * md5 = 0;
    SHA1Context * sha1 = 0;

    unsigned int     sha1_length, md5_length;

    range = r2 - r1;

    /* position to the beginning of range */
    fseek (fp, r1, SEEK_SET);

    buf = (unsigned char *) PORT_ZAlloc (range);
    if (buf == NULL)
       out_of_memory();

    if ((num = fread (buf, 1, range, fp)) != range) {
       PR_fprintf(errorFD, "%s: expected %d bytes, got %d\n", PROGRAM_NAME,
                     range, num);
       errorCount++;
       exit (ERRX);
    }

    md5 = MD5_NewContext();
    sha1 = SHA1_NewContext();

    if (md5 == NULL || sha1 == NULL) {
       PR_fprintf(errorFD, "%s: can't generate digest context\n",
            PROGRAM_NAME);
       errorCount++;
       exit (ERRX);
    }

    MD5_Begin (md5);
    SHA1_Begin (sha1);

    MD5_Update (md5, buf, range);
    SHA1_Update (sha1, buf, range);

    MD5_End (md5, dig->md5, &md5_length, MD5_LENGTH);
    SHA1_End (sha1, dig->sha1, &sha1_length, SHA1_LENGTH);

    MD5_DestroyContext (md5, PR_TRUE);
    SHA1_DestroyContext (sha1, PR_TRUE);

    PORT_Free (buf);

    return 0;
}

Here is the call graph for this function:

Here is the caller graph for this function:

static int create_pk7 ( char *  dir,
char *  keyName,
int keyType 
) [static]

Definition at line 249 of file sign.c.

{
    int       status = 0;
    char      *file_ext;

    CERTCertificate * cert;
    CERTCertDBHandle * db;

    FILE * in, *out;

    char      sf_file [FNSIZE];
    char      pk7_file [FNSIZE];

    /* open cert database */
    db = CERT_GetDefaultCertDB();

    if (db == NULL)
       return - 1;

    /* find cert */
    /*cert = CERT_FindCertByNicknameOrEmailAddr(db, keyName);*/
    cert = PK11_FindCertFromNickname(keyName, NULL /*wincx*/);

    if (cert == NULL) {
       SECU_PrintError ( PROGRAM_NAME,
           "the cert \"%s\" does not exist in the database", keyName);
       return -1;
    }


    /* determine the key type, which sets the extension for pkcs7 object */

    *keyType = jar_find_key_type (cert);
    file_ext = (*keyType == dsaKey) ? "dsa" : "rsa";

    sprintf (sf_file, "%s/META-INF/%s.sf", dir, base);
    sprintf (pk7_file, "%s/META-INF/%s.%s", dir, base, file_ext);

    if ((in = fopen (sf_file, "rb")) == NULL) {
       PR_fprintf(errorFD, "%s: Can't open %s for reading\n", PROGRAM_NAME,
            sf_file);
       errorCount++;
       exit (ERRX);
    }

    if ((out = fopen (pk7_file, "wb")) == NULL) {
       PR_fprintf(errorFD, "%s: Can't open %s for writing\n", PROGRAM_NAME,
            sf_file);
       errorCount++;
       exit (ERRX);
    }

    status = SignFile (out, in, cert);

    CERT_DestroyCertificate (cert);
    fclose (in);
    fclose (out);

    if (status) {
       PR_fprintf(errorFD, "%s: PROBLEM signing data (%s)\n",
           PROGRAM_NAME, SECU_ErrorString ((int16) PORT_GetError()));
       errorCount++;
       return - 1;
    }

    return 0;
}

Here is the call graph for this function:

Here is the caller graph for this function:

static int generate_SF_file ( char *  manifile,
char *  who 
) [static]

Definition at line 724 of file sign.c.

{
    FILE * sf;
    FILE * mf;
    long      r1, r2, r3;
    char      whofile [FNSIZE];
    char      *buf, *name = NULL;
    JAR_Digest dig;
    int       line = 0;

    strcpy (whofile, who);

    if ((mf = fopen (manifile, "rb")) == NULL) {
       perror (manifile);
       exit (ERRX);
    }

    if ((sf = fopen (whofile, "wb")) == NULL) {
       perror (who);
       exit (ERRX);
    }

    buf = (char *) PORT_ZAlloc (BUFSIZ);

    if (buf)
       name = (char *) PORT_ZAlloc (BUFSIZ);

    if (buf == NULL || name == NULL)
       out_of_memory();

    fprintf (sf, "Signature-Version: 1.0\n");
    fprintf (sf, "Created-By: %s\n", CREATOR);
    fprintf (sf, "Comments: %s\n", BREAKAGE);

    if (fgets (buf, BUFSIZ, mf) == NULL) {
       PR_fprintf(errorFD, "%s: empty manifest file!\n", PROGRAM_NAME);
       errorCount++;
       exit (ERRX);
    }

    if (strncmp (buf, "Manifest-Version:", 17)) {
       PR_fprintf(errorFD, "%s: not a manifest file!\n", PROGRAM_NAME);
       errorCount++;
       exit (ERRX);
    }

    fseek (mf, 0L, SEEK_SET);

    /* Process blocks of headers, and calculate their hashen */

    while (1) {
       /* Beginning range */
       r1 = ftell (mf);

       if (fgets (name, BUFSIZ, mf) == NULL)
           break;

       line++;

       if (r1 != 0 && strncmp (name, "Name:", 5)) {
           PR_fprintf(errorFD, 
           "warning: unexpected input in manifest file \"%s\" at line %d:\n",
                manifile, line);
           PR_fprintf(errorFD, "%s\n", name);
           warningCount++;
       }

       r2 = r1;
       while (fgets (buf, BUFSIZ, mf)) {
           if (*buf == 0 || *buf == '\n' || *buf == '\r')
              break;

           line++;

           /* Ending range for hashing */
           r2 = ftell (mf);
       }

       r3 = ftell (mf);

       if (r1) {
           fprintf (sf, "\n");
           fprintf (sf, "%s", name);
       }

       calculate_MD5_range (mf, r1, r2, &dig);

       if (optimize == 0) {
           fprintf (sf, "Digest-Algorithms: MD5 SHA1\n");
           fprintf (sf, "MD5-Digest: %s\n", 
               BTOA_DataToAscii (dig.md5, MD5_LENGTH));
       }

       fprintf (sf, "SHA1-Digest: %s\n", 
           BTOA_DataToAscii (dig.sha1, SHA1_LENGTH));

       /* restore normalcy after changing offset position */
       fseek (mf, r3, SEEK_SET);
    }

    PORT_Free (buf);
    PORT_Free (name);

    fclose (sf);
    fclose (mf);

    return 0;
}

Here is the call graph for this function:

Here is the caller graph for this function:

static int jar_find_key_type ( CERTCertificate *  cert) [static]

Definition at line 326 of file sign.c.

{
    PK11SlotInfo * slot = NULL;
    SECKEYPrivateKey * privk = NULL;
    KeyType keyType;

    /* determine its type */
    PK11_FindObjectForCert (cert, /*wincx*/ NULL, &slot);

    if (slot == NULL) {
       PR_fprintf(errorFD, "warning - can't find slot for this cert\n");
       warningCount++;
       return 0;
    }

    privk = PK11_FindPrivateKeyFromCert (slot, cert, /*wincx*/ NULL);
    PK11_FreeSlot (slot);

    if (privk == NULL) {
       PR_fprintf(errorFD, "warning - can't find private key for this cert\n");
       warningCount++;
       return 0;
    }

    keyType = privk->keyType;
    SECKEY_DestroyPrivateKey (privk);
    return keyType;
}

Here is the call graph for this function:

Here is the caller graph for this function:

static int manifesto ( char *  dirname,
char *  install_script,
PRBool  recurse 
) [static]

Definition at line 364 of file sign.c.

{
    char      metadir [FNSIZE], sfname [FNSIZE];

    /* Create the META-INF directory to hold signing info */

    if (PR_Access (dirname, PR_ACCESS_READ_OK)) {
       PR_fprintf(errorFD, "%s: unable to read your directory: %s\n",
            PROGRAM_NAME, dirname);
       errorCount++;
       perror (dirname);
       exit (ERRX);
    }

    if (PR_Access (dirname, PR_ACCESS_WRITE_OK)) {
       PR_fprintf(errorFD, "%s: unable to write to your directory: %s\n",
                            PROGRAM_NAME, dirname);
       errorCount++;
       perror(dirname);
       exit(ERRX);
    }

    sprintf (metadir, "%s/META-INF", dirname);

    strcpy (sfname, metadir);

    PR_MkDir (metadir, 0777);

    strcat (metadir, "/");
    strcat (metadir, MANIFEST);

    if ((mf = fopen (metadir, "wb")) == NULL) {
       perror (MANIFEST);
       PR_fprintf(errorFD, "%s: Probably, the directory you are trying to"
                         " sign has\n", PROGRAM_NAME);
       PR_fprintf(errorFD, "%s: permissions problems or may not exist.\n",
                     PROGRAM_NAME);
       errorCount++;
       exit (ERRX);
    }

    if (verbosity >= 0) {
       PR_fprintf(outputFD, "Generating %s file..\n", metadir);
    }

    fprintf(mf, "Manifest-Version: 1.0\n");
    fprintf (mf, "Created-By: %s\n", CREATOR);
    fprintf (mf, "Comments: %s\n", BREAKAGE);

    if (scriptdir) {
       fprintf (mf, "Comments: --\n");
       fprintf (mf, "Comments: --\n");
       fprintf (mf, "Comments: -- This archive signs Javascripts which may not necessarily\n");
       fprintf (mf, "Comments: -- be included in the physical jar file.\n");
       fprintf (mf, "Comments: --\n");
       fprintf (mf, "Comments: --\n");
    }

    if (install_script)
       fprintf (mf, "Install-Script: %s\n", install_script);

    if (metafile)
       add_meta (mf, "+");

    /* Loop through all files & subdirectories */
    foreach (dirname, "", manifesto_fn, recurse, PR_FALSE /*include dirs */,
                     (void * )NULL);

    fclose (mf);

    strcat (sfname, "/");
    strcat (sfname, base);
    strcat (sfname, ".sf");

    if (verbosity >= 0) {
       PR_fprintf(outputFD, "Generating %s.sf file..\n", base);
    }
    generate_SF_file (metadir, sfname);

    return 0;
}

Here is the call graph for this function:

Here is the caller graph for this function:

static int manifesto_fn ( char *  relpath,
char *  basedir,
char *  reldir,
char *  filename,
void arg 
) [static]

Definition at line 487 of file sign.c.

{
    int       use_js;

    JAR_Digest dig;
    char      fullname [FNSIZE];

    if (verbosity >= 0) {
       PR_fprintf(outputFD, "--> %s\n", relpath);
    }

    /* extension matching */
    if (extensionsGiven) {
       char   *ext = PL_strrchr(relpath, '.');
       if (!ext) 
           return 0;
       if (!PL_HashTableLookup(extensions, ext)) 
           return 0;
    }

    sprintf (fullname, "%s/%s", basedir, relpath);

    fprintf (mf, "\n");

    use_js = 0;

    if (scriptdir && !PORT_Strcmp (scriptdir, reldir))
       use_js++;

    /* sign non-.js files inside .arc directories using the javascript magic */

    if ( (PL_strcaserstr(filename, ".js") != filename + strlen(filename) - 3)
         && (PL_strcaserstr(reldir, ".arc") == reldir + strlen(filename) - 4))
       use_js++;

    if (use_js) {
       fprintf (mf, "Name: %s\n", filename);
       fprintf (mf, "Magic: javascript\n");

       if (optimize == 0)
           fprintf (mf, "javascript.id: %s\n", filename);

       if (metafile)
           add_meta (mf, filename);
    } else {
       fprintf (mf, "Name: %s\n", relpath);
       if (metafile)
           add_meta (mf, relpath);
    }

    JAR_digest_file (fullname, &dig);


    if (optimize == 0) {
       fprintf (mf, "Digest-Algorithms: MD5 SHA1\n");
       fprintf (mf, "MD5-Digest: %s\n", BTOA_DataToAscii (dig.md5,
            MD5_LENGTH));
    }

    fprintf (mf, "SHA1-Digest: %s\n", BTOA_DataToAscii (dig.sha1, SHA1_LENGTH));

    if (!use_js) {
       JzipAdd(fullname, relpath, zipfile, compression_level);
    }

    return 0;
}

Here is the call graph for this function:

Here is the caller graph for this function:

static int manifesto_xpi_fn ( char *  relpath,
char *  basedir,
char *  reldir,
char *  filename,
void arg 
) [static]

Definition at line 456 of file sign.c.

{
    char      fullname [FNSIZE];

    if (verbosity >= 0) {
       PR_fprintf(outputFD, "--> %s\n", relpath);
    }

    /* extension matching */
    if (extensionsGiven) {
       char   *ext = PL_strrchr(relpath, '.');
       if (!ext) 
           return 0;
       if (!PL_HashTableLookup(extensions, ext)) 
           return 0;
    }
    sprintf (fullname, "%s/%s", basedir, relpath);
    JzipAdd(fullname, relpath, zipfile, compression_level);

    return 0;
}

Here is the call graph for this function:

Here is the caller graph for this function:

static int sign_all_arc_fn ( char *  relpath,
char *  basedir,
char *  reldir,
char *  filename,
void arg 
) [static]

Definition at line 193 of file sign.c.

{
    char      *zipfile = NULL;
    char      *arc = NULL, *archive = NULL;
    int       retval = 0;
    SignArcInfo * infop = (SignArcInfo * )arg;

    /* Make sure there is one and only one ".arc" in the relative path, 
     * and that it is at the end of the path (don't sign .arcs within .arcs) */
    if ( (PL_strcaserstr(relpath, ".arc") == relpath + strlen(relpath) -
        4) && 
        (PL_strcasestr(relpath, ".arc") == relpath + strlen(relpath) - 4) ) {

       if (!infop) {
           PR_fprintf(errorFD, "%s: Internal failure\n", PROGRAM_NAME);
           errorCount++;
           retval = -1;
           goto finish;
       }
       archive = PR_smprintf("%s/%s", basedir, relpath);

       zipfile = PL_strdup(archive);
       arc = PORT_Strrchr (zipfile, '.');

       if (arc == NULL) {
           PR_fprintf(errorFD, "%s: Internal failure\n", PROGRAM_NAME);
           errorCount++;
           retval = -1;
           goto finish;
       }

       PL_strcpy (arc, ".jar");

       if (verbosity >= 0) {
           PR_fprintf(outputFD, "\nsigning: %s\n", zipfile);
       }
       retval = SignArchive(archive, infop->keyName, zipfile,
           infop->javascript, infop->metafile, infop->install_script,
                            infop->optimize, PR_TRUE /* recurse */);
    }
finish:
    if (archive) 
       PR_Free(archive);
    if (zipfile) 
       PR_Free(zipfile);

    return retval;
}

Here is the call graph for this function:

Here is the caller graph for this function:

int SignAllArc ( char *  jartree,
char *  keyName,
int  javascript,
char *  metafile,
char *  install_script,
int  optimize,
PRBool  recurse 
)

Definition at line 176 of file sign.c.

{
    SignArcInfo info;

    info.keyName = keyName;
    info.javascript = javascript;
    info.metafile = metafile;
    info.install_script = install_script;
    info.optimize = optimize;

    return foreach(jartree, "", sign_all_arc_fn, recurse,
        PR_TRUE /*include dirs*/, (void * )&info);
}

Here is the caller graph for this function:

int SignArchive ( char *  tree,
char *  keyName,
char *  zip_file,
int  javascript,
char *  meta_file,
char *  install_script,
int  _optimize,
PRBool  recurse 
)

Definition at line 72 of file sign.c.

{
    int       status;
    char      tempfn [FNSIZE], fullfn [FNSIZE];
    int       keyType = rsaKey;

    metafile = meta_file;
    optimize = _optimize;

    /* To create XPI compatible Archive manifesto() must be run before 
     * the zipfile is opened. This is so the signed files are not added
     * the archive before the crucial rsa/dsa file*/
    if (xpi_arc) {
       manifesto (tree, install_script, recurse);
    }

    if (zip_file) {
       zipfile = JzipOpen(zip_file, NULL /*no comment*/);
    }

    /*Sign and add files to the archive normally with manifesto()*/
    if (!xpi_arc) {
       manifesto (tree, install_script, recurse);
    }

    if (keyName) {
       status = create_pk7 (tree, keyName, &keyType);
       if (status < 0) {
           PR_fprintf(errorFD, "the tree \"%s\" was NOT SUCCESSFULLY SIGNED\n",
                tree);
           errorCount++;
           exit (ERRX);
       }
    }

    /* Add the rsa/dsa file as the first file in the archive. This is crucial
     * for a XPInstall compatible archive */
    if (xpi_arc) {
       if (verbosity >= 0) {
           PR_fprintf(outputFD, "%s \n", XPI_TEXT);
       }

       /* rsa/dsa to zip */
       sprintf (tempfn, "META-INF/%s.%s", base, (keyType == dsaKey ?
           "dsa" : "rsa"));
       sprintf (fullfn, "%s/%s", tree, tempfn);
       JzipAdd(fullfn, tempfn, zipfile, compression_level);

       /* Loop through all files & subdirectories, add to archive */
       foreach (tree, "", manifesto_xpi_fn, recurse, PR_FALSE /*include dirs */,
                     (void * )NULL);
    }
    /* mf to zip */
    strcpy (tempfn, "META-INF/manifest.mf");
    sprintf (fullfn, "%s/%s", tree, tempfn);
    JzipAdd(fullfn, tempfn, zipfile, compression_level);

    /* sf to zip */
    sprintf (tempfn, "META-INF/%s.sf", base);
    sprintf (fullfn, "%s/%s", tree, tempfn);
    JzipAdd(fullfn, tempfn, zipfile, compression_level);

    /* Add the rsa/dsa file to the zip archive normally */
    if (!xpi_arc) {
       /* rsa/dsa to zip */
       sprintf (tempfn, "META-INF/%s.%s", base, (keyType == dsaKey ?
           "dsa" : "rsa"));
       sprintf (fullfn, "%s/%s", tree, tempfn);
       JzipAdd(fullfn, tempfn, zipfile, compression_level);
    }

    JzipClose(zipfile);

    if (verbosity >= 0) {
       if (javascript) {
           PR_fprintf(outputFD, "jarfile \"%s\" signed successfully\n",
                                          zip_file);
       } else {
           PR_fprintf(outputFD, "tree \"%s\" signed successfully\n",
                tree);
       }
    }

    return 0;
}

Here is the caller graph for this function:

static int SignFile ( FILE outFile,
FILE inFile,
CERTCertificate *  cert 
) [static]

Definition at line 632 of file sign.c.

{
    int       nb;
    char      ibuf[4096], digestdata[32];
    const SECHashObject *hashObj;
    void      *hashcx;
    unsigned int     len;

    SECItem digest;
    SEC_PKCS7ContentInfo * cinfo;
    SECStatus rv;

    if (outFile == NULL || inFile == NULL || cert == NULL)
       return - 1;

    /* XXX probably want to extend interface to allow other hash algorithms */
    hashObj = HASH_GetHashObject(HASH_AlgSHA1);

    hashcx = (*hashObj->create)();
    if (hashcx == NULL)
       return - 1;

    (*hashObj->begin)(hashcx);

    for (; ; ) {
       if (feof(inFile)) 
           break;
       nb = fread(ibuf, 1, sizeof(ibuf), inFile);
       if (nb == 0) {
           if (ferror(inFile)) {
              PORT_SetError(SEC_ERROR_IO);
              (*hashObj->destroy)(hashcx, PR_TRUE);
              return - 1;
           }
           /* eof */
           break;
       }
       (*hashObj->update)(hashcx, (unsigned char *) ibuf, nb);
    }

    (*hashObj->end)(hashcx, (unsigned char *) digestdata, &len, 32);
    (*hashObj->destroy)(hashcx, PR_TRUE);

    digest.data = (unsigned char *) digestdata;
    digest.len = len;

    cinfo = SEC_PKCS7CreateSignedData 
        (cert, certUsageObjectSigner, NULL, 
        SEC_OID_SHA1, &digest, NULL, NULL);

    if (cinfo == NULL)
       return - 1;

    rv = SEC_PKCS7IncludeCertChain (cinfo, NULL);
    if (rv != SECSuccess) {
       SEC_PKCS7DestroyContentInfo (cinfo);
       return - 1;
    }

    if (no_time == 0) {
       rv = SEC_PKCS7AddSigningTime (cinfo);
       if (rv != SECSuccess) {
           /* don't check error */
       }
    }

    if (password) {
       rv = SEC_PKCS7Encode(cinfo, SignOut, outFile, NULL, 
           (SECKEYGetPasswordKey) password_hardcode, NULL);
    } else {
       rv = SEC_PKCS7Encode(cinfo, SignOut, outFile, NULL, NULL,
                            NULL);
    }


    SEC_PKCS7DestroyContentInfo (cinfo);

    if (rv != SECSuccess)
       return - 1;

    return 0;
}

Here is the call graph for this function:

Here is the caller graph for this function:

static void SignOut ( void arg,
const char *  buf,
unsigned long  len 
) [static]

Definition at line 897 of file sign.c.

{
    fwrite (buf, len, 1, (FILE * ) arg);
}

Here is the call graph for this function:

Here is the caller graph for this function:


Variable Documentation

char* metafile = NULL [static]

Definition at line 59 of file sign.c.

FILE* mf [static]

Definition at line 61 of file sign.c.

int optimize = 0 [static]

Definition at line 60 of file sign.c.

ZIPfile* zipfile = NULL [static]

Definition at line 62 of file sign.c.