/* * The contents of this file are subject to the Mozilla Public * License Version 1.1 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. * * The Original Code is the Netscape security libraries. * * The Initial Developer of the Original Code is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1994-2000 Netscape Communications Corporation. All * Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the * terms of the GNU General Public License Version 2 or later (the * "GPL"), in which case the provisions of the GPL are applicable * instead of those above. If you wish to allow use of your * version of this file only under the terms of the GPL and not to * allow others to use your version of this file under the MPL, * indicate your decision by deleting the provisions above and * replace them with the notice and other provisions required by * the GPL. If you do not delete the provisions above, a recipient * may use your version of this file under either the MPL or the * GPL. */ #include "signtool.h" #include "cdbhdl.h" #include "prio.h" #include "prmem.h" static int is_dir (char *filename); static char *certDBNameCallback(void *arg, int dbVersion); /*********************************************************************** * * O p e n C e r t D B */ CERTCertDBHandle * OpenCertDB(PRBool readOnly) { CERTCertDBHandle *db; SECStatus rv; /* Allocate a handle to fill with CERT_OpenCertDB below */ db = (CERTCertDBHandle *) PORT_ZAlloc (sizeof(CERTCertDBHandle)); if (db == NULL) { SECU_PrintError(progName, "unable to get database handle"); return NULL; } rv = CERT_OpenCertDB (db, readOnly, certDBNameCallback, NULL); if (rv) { SECU_PrintError(progName, "could not open certificate database"); if (db) PORT_Free (db); return NULL; } else { CERT_SetDefaultCertDB(db); } return db; } /*********************************************************** * Nasty hackish function definitions */ long *mozilla_event_queue = 0; #ifndef XP_WIN char *XP_GetString (int i) { return SECU_ErrorStringRaw ((int16) i); } #endif void FE_SetPasswordEnabled() { } void /*MWContext*/ *FE_GetInitContext (void) { return 0; } void /*MWContext*/ *XP_FindSomeContext() { /* No windows context in command tools */ return NULL; } void ET_moz_CallFunction() { } /* * R e m o v e A l l A r c * * Remove .arc directories that are lingering * from a previous run of signtool. * */ int RemoveAllArc(char *tree) { PRDir *dir; PRDirEntry *entry; char *archive=NULL; int retval = 0; dir = PR_OpenDir (tree); if (!dir) return -1; for (entry = PR_ReadDir (dir,0); entry; entry = PR_ReadDir (dir,0)) { if(entry->name[0] == '.') { continue; } if(archive) PR_Free(archive); archive = PR_smprintf("%s/%s", tree, entry->name); if (PL_strcaserstr (entry->name, ".arc") == (entry->name + strlen(entry->name) - 4) ) { if(verbosity >= 0) { PR_fprintf(outputFD, "removing: %s\n", archive); } if(rm_dash_r(archive)) { PR_fprintf(errorFD, "Error removing %s\n", archive); errorCount++; retval = -1; goto finish; } } else if(is_dir(archive)) { if(RemoveAllArc(archive)) { retval = -1; goto finish; } } } finish: PR_CloseDir (dir); if(archive) PR_Free(archive); return retval; } /* * r m _ d a s h _ r * * Remove a file, or a directory recursively. * */ int rm_dash_r (char *path) { PRDir *dir; PRDirEntry *entry; PRFileInfo fileinfo; char filename[FNSIZE]; if(PR_GetFileInfo(path, &fileinfo) != PR_SUCCESS) { /*fprintf(stderr, "Error: Unable to access %s\n", filename);*/ return -1; } if(fileinfo.type == PR_FILE_DIRECTORY) { dir = PR_OpenDir(path); if(!dir) { PR_fprintf(errorFD, "Error: Unable to open directory %s.\n", path); errorCount++; return -1; } /* Recursively delete all entries in the directory */ while((entry = PR_ReadDir(dir, PR_SKIP_BOTH)) != NULL) { sprintf(filename, "%s/%s", path, entry->name); if(rm_dash_r(filename)) return -1; } if(PR_CloseDir(dir) != PR_SUCCESS) { PR_fprintf(errorFD, "Error: Could not close %s.\n", path); errorCount++; return -1; } /* Delete the directory itself */ if(PR_RmDir(path) != PR_SUCCESS) { PR_fprintf(errorFD, "Error: Unable to delete %s\n", path); errorCount++; return -1; } } else { if(PR_Delete(path) != PR_SUCCESS) { PR_fprintf(errorFD, "Error: Unable to delete %s\n", path); errorCount++; return -1; } } return 0; } /* * u s a g e * * Print some useful help information * */ void usage (void) { PR_fprintf(outputFD, "\n"); PR_fprintf(outputFD, "%s %s - a signing tool for jar files\n", LONG_PROGRAM_NAME, VERSION); PR_fprintf(outputFD, "\n"); PR_fprintf(outputFD, "Usage: %s [options] directory-tree \n\n", PROGRAM_NAME); PR_fprintf(outputFD, " -b\"basename\"\t\tbasename of .sf, .rsa files for signing\n"); PR_fprintf(outputFD, " -c#\t\t\t\tCompression level, 0-9, 0=none\n"); PR_fprintf(outputFD, " -d\"certificate directory\"\tcontains cert*.db and key*.db\n"); PR_fprintf(outputFD, " -e\".ext\"\t\t\tsign only files with this extension\n"); PR_fprintf(outputFD, " -f\"filename\"\t\t\tread commands from file\n"); PR_fprintf(outputFD, " -G\"nickname\"\t\tcreate object-signing cert with this nickname\n"); PR_fprintf(outputFD, " -i\"installer script\"\tassign installer javascript\n"); PR_fprintf(outputFD, " -j\"javascript directory\"\tsign javascript files in this subtree\n"); PR_fprintf(outputFD, " -J\t\t\t\tdirectory contains HTML files. Javascript will\n" "\t\t\t\tbe extracted and signed.\n"); PR_fprintf(outputFD, " -k\"cert nickname\"\t\tsign with this certificate\n"); PR_fprintf(outputFD, " --leavearc\t\t\tdo not delete .arc directories created\n" "\t\t\t\tby -J option\n"); PR_fprintf(outputFD, " -m\"metafile\"\t\tinclude custom meta-information\n"); PR_fprintf(outputFD, " --norecurse\t\t\tdo not operate on subdirectories\n"); PR_fprintf(outputFD, " -o\t\t\t\toptimize - omit optional headers\n"); PR_fprintf(outputFD, " --outfile \"filename\"\tredirect output to file\n"); PR_fprintf(outputFD, " -p\"password\"\t\tfor password on command line (insecure)\n"); PR_fprintf(outputFD, " -s keysize\t\t\tkeysize in bits of generated cert\n"); PR_fprintf(outputFD, " -t token\t\t\tname of token on which to generate cert\n"); PR_fprintf(outputFD, " --verbosity #\t\tSet amount of debugging information to generate.\n" "\t\t\t\tLower number means less output, 0 is default.\n"); PR_fprintf(outputFD, " -x\"name\"\t\t\tdirectory or filename to exclude\n"); PR_fprintf(outputFD, " -z\t\t\t\tomit signing time from signature\n"); PR_fprintf(outputFD, " -Z\"jarfile\"\t\t\tcreate JAR file with the given name.\n" "\t\t\t\t(Default compression level is 6.)\n"); PR_fprintf(outputFD, "\n"); PR_fprintf(outputFD, "%s -l\n", PROGRAM_NAME); PR_fprintf(outputFD, " lists the signing certificates in your database\n"); PR_fprintf(outputFD, "\n"); PR_fprintf(outputFD, "%s -L\n", PROGRAM_NAME); PR_fprintf(outputFD, " lists all certificates in your database, marks object-signing certificates\n"); PR_fprintf(outputFD, "\n"); PR_fprintf(outputFD, "%s -M\n", PROGRAM_NAME); PR_fprintf(outputFD, " lists the PKCS #11 modules available to %s\n", PROGRAM_NAME); PR_fprintf(outputFD, "\n"); PR_fprintf(outputFD, "%s -v file.jar\n", PROGRAM_NAME); PR_fprintf(outputFD, " show the contents of the specified jar file\n"); PR_fprintf(outputFD, "\n"); PR_fprintf(outputFD, "%s -w file.jar\n", PROGRAM_NAME); PR_fprintf(outputFD, " if valid, tries to tell you who signed the jar file\n"); PR_fprintf(outputFD, "\n"); PR_fprintf(outputFD, "For more details, visit\n"); PR_fprintf(outputFD, " http://developer.netscape.com/library/documentation/signedobj/signtool/\n"); exit (0); } /* * p r i n t _ e r r o r * * For the undocumented -E function. If an older version * of communicator gives you a numeric error, we can see what * really happened without doing hex math. * */ void print_error (int err) { PR_fprintf(errorFD, "Error %d: %s\n", err, JAR_get_error (err)); errorCount++; give_help (err); } /* * o u t _ o f _ m e m o r y * * Out of memory, exit Signtool. * */ void out_of_memory (void) { PR_fprintf(errorFD, "%s: out of memory\n", PROGRAM_NAME); errorCount++; exit (ERRX); } /* * V e r i f y C e r t D i r * * Validate that the specified directory * contains a certificate database * */ void VerifyCertDir(char *dir, char *keyName) { char fn [FNSIZE]; sprintf (fn, "%s/cert7.db", dir); if (PR_Access (fn, PR_ACCESS_EXISTS)) { PR_fprintf(errorFD, "%s: No certificate database in \"%s\"\n", PROGRAM_NAME, dir); PR_fprintf(errorFD, "%s: Check the -d arguments that you gave\n", PROGRAM_NAME); errorCount++; exit (ERRX); } if(verbosity >= 0) { PR_fprintf(outputFD, "using certificate directory: %s\n", dir); } if (keyName == NULL) return; /* if the user gave the -k key argument, verify that a key database already exists */ sprintf (fn, "%s/key3.db", dir); if (PR_Access (fn, PR_ACCESS_EXISTS)) { PR_fprintf(errorFD, "%s: No private key database in \"%s\"\n", PROGRAM_NAME, dir); PR_fprintf(errorFD, "%s: Check the -d arguments that you gave\n", PROGRAM_NAME); errorCount++; exit (ERRX); } } /* * f o r e a c h * * A recursive function to loop through all names in * the specified directory, as well as all subdirectories. * * FIX: Need to see if all platforms allow multiple * opendir's to be called. * */ int foreach(char *dirname, char *prefix, int (*fn)(char *relpath, char *basedir, char *reldir, char *filename, void* arg), PRBool recurse, PRBool includeDirs, void *arg) { char newdir [FNSIZE]; int retval = 0; PRDir *dir; PRDirEntry *entry; strcpy (newdir, dirname); if (*prefix) { strcat (newdir, "/"); strcat (newdir, prefix); } dir = PR_OpenDir (newdir); if (!dir) return -1; for (entry = PR_ReadDir (dir,0); entry; entry = PR_ReadDir (dir,0)) { if (*entry->name == '.' || *entry->name == '#') continue; /* can't sign self */ if (!strcmp (entry->name, "META-INF")) continue; /* -x option */ if (PL_HashTableLookup(excludeDirs, entry->name)) continue; strcpy (newdir, dirname); if (*dirname) strcat (newdir, "/"); if (*prefix) { strcat (newdir, prefix); strcat (newdir, "/"); } strcat (newdir, entry->name); if(!is_dir(newdir) || includeDirs) { char newpath [FNSIZE]; strcpy (newpath, prefix); if (*newpath) strcat (newpath, "/"); strcat (newpath, entry->name); if( (*fn) (newpath, dirname, prefix, (char *) entry->name, arg)) { retval = -1; break; } } if (is_dir (newdir)) { if(recurse) { char newprefix [FNSIZE]; strcpy (newprefix, prefix); if (*newprefix) { strcat (newprefix, "/"); } strcat (newprefix, entry->name); if(foreach (dirname, newprefix, fn, recurse, includeDirs,arg)) { retval = -1; break; } } } } PR_CloseDir (dir); return retval; } /* * i s _ d i r * * Return 1 if file is a directory. * Wonder if this runs on a mac, trust not. * */ static int is_dir (char *filename) { PRFileInfo finfo; if( PR_GetFileInfo(filename, &finfo) != PR_SUCCESS ) { printf("Unable to get information about %s\n", filename); return 0; } return ( finfo.type == PR_FILE_DIRECTORY ); } /* * p a s s w o r d _ h a r d c o d e * * A function to use the password passed in the -p(password) argument * of the command line. This is only to be used for build & testing purposes, * as it's extraordinarily insecure. * * After use once, null it out otherwise PKCS11 calls us forever. * */ SECItem * password_hardcode(void *arg, SECKEYKeyDBHandle *handle) { SECItem *pw = NULL; if (password) { pw = SECITEM_AllocItem(NULL, NULL, PL_strlen(password)); pw->data = PL_strdup(password); password = NULL; } return pw; } char * pk11_password_hardcode(PK11SlotInfo *slot, PRBool retry, void *arg) { char *pw; pw = password ? PORT_Strdup (password) : NULL; password = NULL; return pw; } /************************************************************************ * * c e r t D B N a m e C a l l b a c k */ static char * certDBNameCallback(void *arg, int dbVersion) { char *fnarg; char *dir; char *filename; dir = SECU_ConfigDirectory (NULL); switch ( dbVersion ) { case 7: fnarg = "7"; break; case 6: fnarg = "6"; break; case 5: fnarg = "5"; break; case 4: default: fnarg = ""; break; } filename = PR_smprintf("%s/cert%s.db", dir, fnarg); return(filename); } /*************************************************************** * * s e c E r r o r S t r i n g * * Returns an error string corresponding to the given error code. * Doesn't cover all errors; returns a default for many. * Returned string is only valid until the next call of this function. */ const char* secErrorString(long code) { static char errstring[80]; /* dynamically constructed error string */ char *c; /* the returned string */ switch(code) { case SEC_ERROR_IO: c = "io error"; break; case SEC_ERROR_LIBRARY_FAILURE: c = "security library failure"; break; case SEC_ERROR_BAD_DATA: c = "bad data"; break; case SEC_ERROR_OUTPUT_LEN: c = "output length"; break; case SEC_ERROR_INPUT_LEN: c = "input length"; break; case SEC_ERROR_INVALID_ARGS: c = "invalid args"; break; case SEC_ERROR_EXPIRED_CERTIFICATE: c = "expired certificate"; break; case SEC_ERROR_REVOKED_CERTIFICATE: c = "revoked certificate"; break; case SEC_ERROR_INADEQUATE_KEY_USAGE: c = "inadequate key usage"; break; case SEC_ERROR_INADEQUATE_CERT_TYPE: c = "inadequate certificate type"; break; case SEC_ERROR_UNTRUSTED_CERT: c = "untrusted cert"; break; case SEC_ERROR_NO_KRL: c = "no key revocation list"; break; case SEC_ERROR_KRL_BAD_SIGNATURE: c = "key revocation list: bad signature"; break; case SEC_ERROR_KRL_EXPIRED: c = "key revocation list expired"; break; case SEC_ERROR_REVOKED_KEY: c = "revoked key"; break; case SEC_ERROR_CRL_BAD_SIGNATURE: c = "certificate revocation list: bad signature"; break; case SEC_ERROR_CRL_EXPIRED: c = "certificate revocation list expired"; break; case SEC_ERROR_CRL_NOT_YET_VALID: c = "certificate revocation list not yet valid"; break; case SEC_ERROR_UNKNOWN_ISSUER: c = "unknown issuer"; break; case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: c = "expired issuer certificate"; break; case SEC_ERROR_BAD_SIGNATURE: c = "bad signature"; break; case SEC_ERROR_BAD_KEY: c = "bad key"; break; case SEC_ERROR_NOT_FORTEZZA_ISSUER: c = "not fortezza issuer"; break; case SEC_ERROR_CA_CERT_INVALID: c = "Certificate Authority certificate invalid"; break; case SEC_ERROR_EXTENSION_NOT_FOUND: c = "extension not found"; break; case SEC_ERROR_CERT_NOT_IN_NAME_SPACE: c = "certificate not in name space"; break; case SEC_ERROR_UNTRUSTED_ISSUER: c = "untrusted issuer"; break; default: sprintf(errstring, "security error %ld", code); c = errstring; break; } return c; } /*************************************************************** * * d i s p l a y V e r i f y L o g * * Prints the log of a cert verification. */ void displayVerifyLog(CERTVerifyLog *log) { CERTVerifyLogNode *node; CERTCertificate *cert; char *name; if( !log || (log->count <= 0) ) { return; } for(node = log->head; node != NULL; node = node->next) { if( !(cert = node->cert) ) { continue; } /* Get a name for this cert */ if(cert->nickname != NULL) { name = cert->nickname; } else if(cert->emailAddr != NULL) { name = cert->emailAddr; } else { name = cert->subjectName; } printf( "%s%s:\n", name, (node->depth > 0) ? " [Certificate Authority]" : "" ); printf("\t%s\n", secErrorString(node->error)); } } /* * J a r L i s t M o d u l e s * * Print a list of the PKCS11 modules that are * available. This is useful for smartcard people to * make sure they have the drivers loaded. * */ void JarListModules(void) { int i; int count = 0; SECMODModuleList *modules = NULL; static SECMODListLock *moduleLock = NULL; SECMODModuleList *mlp; modules = SECMOD_GetDefaultModuleList(); if (modules == NULL) { PR_fprintf(errorFD, "%s: Can't get module list\n", PROGRAM_NAME); errorCount++; exit (ERRX); } if ((moduleLock = SECMOD_NewListLock()) == NULL) { /* this is the wrong text */ PR_fprintf(errorFD, "%s: unable to acquire lock on module list\n", PROGRAM_NAME); errorCount++; exit (ERRX); } SECMOD_GetReadLock (moduleLock); PR_fprintf(outputFD, "\nListing of PKCS11 modules\n"); PR_fprintf(outputFD, "-----------------------------------------------\n"); for (mlp = modules; mlp != NULL; mlp = mlp->next) { count++; PR_fprintf(outputFD, "%3d. %s\n", count, mlp->module->commonName); if (mlp->module->internal) PR_fprintf(outputFD, " (this module is internally loaded)\n"); else PR_fprintf(outputFD, " (this is an external module)\n"); if (mlp->module->dllName) PR_fprintf(outputFD, " DLL name: %s\n", mlp->module->dllName); if (mlp->module->slotCount == 0) PR_fprintf(outputFD, " slots: There are no slots attached to this module\n"); else PR_fprintf(outputFD, " slots: %d slots attached\n", mlp->module->slotCount); if (mlp->module->loaded == 0) PR_fprintf(outputFD, " status: Not loaded\n"); else PR_fprintf(outputFD, " status: loaded\n"); for (i = 0; i < mlp->module->slotCount; i++) { PK11SlotInfo *slot = mlp->module->slots[i]; PR_fprintf(outputFD, "\n"); PR_fprintf(outputFD, " slot: %s\n", PK11_GetSlotName(slot)); PR_fprintf(outputFD, " token: %s\n", PK11_GetTokenName(slot)); } } PR_fprintf(outputFD, "-----------------------------------------------\n"); if (count == 0) PR_fprintf(outputFD, "Warning: no modules were found (should have at least one)\n"); SECMOD_ReleaseReadLock (moduleLock); } /********************************************************************** * c h o p * * Eliminates leading and trailing whitespace. Returns a pointer to the * beginning of non-whitespace, or an empty string if it's all whitespace. */ char* chop(char *str) { char *start, *end; if(str) { start = str; /* Nip leading whitespace */ while(isspace(*start)) { start++; } /* Nip trailing whitespace */ if(strlen(start) > 0) { end = start + strlen(start) - 1; while(isspace(*end) && end > start) { end--; } *(end+1) = '\0'; } return start; } else { return NULL; } } /*********************************************************************** * * F a t a l E r r o r * * Outputs an error message and bails out of the program. */ void FatalError(char *msg) { if(!msg) msg = ""; PR_fprintf(errorFD, "FATAL ERROR: %s\n", msg); errorCount++; exit(ERRX); } /************************************************************************* * * I n i t C r y p t o */ int InitCrypto(char *cert_dir, PRBool readOnly) { SECStatus rv; static int prior = 0; PK11SlotInfo *slotinfo; CERTCertDBHandle *db; if (prior == 0) { /* some functions such as OpenKeyDB expect this path to be * implicitly set prior to calling */ SECU_ConfigDirectory (cert_dir); if ((rv = SECU_PKCS11Init(readOnly)) != SECSuccess) { PR_fprintf(errorFD, "%s: Unable to initialize PKCS11, code %d\n", PROGRAM_NAME, rv); errorCount++; exit (ERRX); } SEC_Init(); /* Been there done that */ prior++; /* open cert database and set the default certificate DB */ db = OpenCertDB(readOnly); if (db == NULL) return -1; CERT_SetDefaultCertDB (db); if(password) { PK11_SetPasswordFunc(pk11_password_hardcode); } /* Must login to FIPS before you do anything else */ if(PK11_IsFIPS()) { slotinfo = PK11_GetInternalSlot(); if(!slotinfo) { fprintf(stderr, "%s: Unable to get PKCS #11 Internal Slot." "\n", PROGRAM_NAME); return -1; } if(PK11_Authenticate(slotinfo, PR_FALSE /*loadCerts*/, NULL /*wincx*/) != SECSuccess) { fprintf(stderr, "%s: Unable to authenticate to %s.\n", PROGRAM_NAME, PK11_GetSlotName(slotinfo)); return -1; } } /* Make sure there is a password set on the internal key slot */ slotinfo = PK11_GetInternalKeySlot(); if(!slotinfo) { fprintf(stderr, "%s: Unable to get PKCS #11 Internal Key Slot." "\n", PROGRAM_NAME); return -1; } if(PK11_NeedUserInit(slotinfo)) { PR_fprintf(errorFD, "\nWARNING: No password set on internal key database. Most operations will fail." "\nYou must use Communicator to create a password.\n"); warningCount++; } /* Make sure we can authenticate to the key slot in FIPS mode */ if(PK11_IsFIPS()) { if(PK11_Authenticate(slotinfo, PR_FALSE /*loadCerts*/, NULL /*wincx*/) != SECSuccess) { fprintf(stderr, "%s: Unable to authenticate to %s.\n", PROGRAM_NAME, PK11_GetSlotName(slotinfo)); return -1; } } } return 0; } /* Windows foolishness is now in the secutil lib */ /***************************************************************** * g e t _ d e f a u l t _ c e r t _ d i r * * Attempt to locate a certificate directory. * Failing that, complain that the user needs to * use the -d(irectory) parameter. * */ char *get_default_cert_dir (void) { char *home; char *cd = NULL; static char db [FNSIZE]; #ifdef XP_UNIX home = getenv ("HOME"); if (home && *home) { sprintf (db, "%s/.netscape", home); cd = db; } #endif #ifdef XP_PC FILE *fp; /* first check the environment override */ home = getenv ("JAR_HOME"); if (home && *home) { sprintf (db, "%s/cert7.db", home); if ((fp = fopen (db, "r")) != NULL) { fclose (fp); cd = home; } } /* try the old navigator directory */ if (cd == NULL) { home = "c:/Program Files/Netscape/Navigator"; sprintf (db, "%s/cert7.db", home); if ((fp = fopen (db, "r")) != NULL) { fclose (fp); cd = home; } } /* Try the current directory, I wonder if this is really a good idea. Remember, Windows only.. */ if (cd == NULL) { home = "."; sprintf (db, "%s/cert7.db", home); if ((fp = fopen (db, "r")) != NULL) { fclose (fp); cd = home; } } #endif if (!cd) { PR_fprintf(errorFD, "You must specify the location of your certificate directory\n"); PR_fprintf(errorFD, "with the -d option. Example: -d ~/.netscape in many cases with Unix.\n"); errorCount++; exit (ERRX); } return cd; } /************************************************************************ * g i v e _ h e l p */ void give_help (int status) { if (status == SEC_ERROR_UNKNOWN_ISSUER) { PR_fprintf(errorFD, "The Certificate Authority (CA) for this certificate\n"); PR_fprintf(errorFD, "does not appear to be in your database. You should contact\n"); PR_fprintf(errorFD, "the organization which issued this certificate to obtain\n"); PR_fprintf(errorFD, "a copy of its CA Certificate.\n"); } } /************************************************************************** * * p r _ f g e t s * * fgets implemented with NSPR. */ char* pr_fgets(char *buf, int size, PRFileDesc *file) { int i; int status; char c; i=0; while(i < size-1) { status = PR_Read(file, (void*) &c, 1); if(status==-1) { return NULL; } else if(status==0) { break; } buf[i++] = c; if(c=='\n') { break; } } buf[i]='\0'; return buf; }