Annotation of rsaref/rdemo/dhdemo.c, revision 1.1

1.1     ! root        1: /* DHDEMO.C - demonstration program for Diffie-Hellman extensions to
        !             2:               RSAREF
        !             3:  */
        !             4: 
        !             5: /* Copyright (C) 1993 RSA Laboratories, a division of RSA Data
        !             6:    Security, Inc. All rights reserved.
        !             7:  */
        !             8: 
        !             9: #include <stdio.h>
        !            10: #include <string.h>
        !            11: #include <stdlib.h>
        !            12: #include "global.h"
        !            13: #include "rsaref.h"
        !            14: 
        !            15: int main PROTO_LIST ((int, char **));
        !            16: static int SetOptions PROTO_LIST ((int, char **));
        !            17: static void InitRandomStruct PROTO_LIST ((R_RANDOM_STRUCT *));
        !            18: static void DoSetupAgreement PROTO_LIST ((R_RANDOM_STRUCT *));
        !            19: static void DoComputeAgreedKey PROTO_LIST ((void));
        !            20: static void DoGenerateParams PROTO_LIST ((R_RANDOM_STRUCT *));
        !            21: static void WriteParams2 PROTO_LIST ((void));
        !            22: static void WriteBigInteger PROTO_LIST
        !            23:   ((FILE *, unsigned char *, unsigned int));
        !            24: static int ReadBlock PROTO_LIST
        !            25:   ((unsigned char *, unsigned int *, unsigned int, char *));
        !            26: static int WriteBlock PROTO_LIST ((unsigned char *, unsigned int, char *));
        !            27: static int GetParams PROTO_LIST ((R_DH_PARAMS **, char *));
        !            28: static void PrintMessage PROTO_LIST ((char *));
        !            29: static void PrintError PROTO_LIST ((char *, int));
        !            30: static void GetCommand PROTO_LIST ((char *, unsigned int, char *));
        !            31: 
        !            32: static int SILENT_PROMPT = 0;
        !            33: 
        !            34: static unsigned char PRIME1[64] = {
        !            35:   0xd0, 0x45, 0x1f, 0xfe, 0x2c, 0x64, 0xc4, 0xed, 0x6b, 0x0a, 0xe6,
        !            36:   0x36, 0x5b, 0x7f, 0xef, 0x9c, 0x15, 0x42, 0x5e, 0x40, 0xa3, 0x7c,
        !            37:   0xa5, 0xf8, 0x39, 0x86, 0x5e, 0x2c, 0xfb, 0x41, 0x69, 0xa0, 0xd8,
        !            38:   0x25, 0xc9, 0x13, 0x0f, 0x88, 0x64, 0xff, 0xfc, 0xf3, 0xbf, 0xbe,
        !            39:   0xb0, 0x27, 0x36, 0x60, 0x67, 0xaa, 0x27, 0xe2, 0x7b, 0xfc, 0xaf,
        !            40:   0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
        !            41: };
        !            42: static unsigned char GENERATOR1[64] = {
        !            43:   0x0a, 0xcf, 0x95, 0x8c, 0x40, 0xd3, 0x01, 0xef, 0xc5, 0x15, 0x3e,
        !            44:   0x7d, 0xcd, 0x5e, 0xf7, 0x5f, 0xec, 0x9e, 0x8f, 0xb0, 0xfa, 0xe6,
        !            45:   0xa8, 0x0e, 0xe5, 0xc3, 0xb8, 0x4b, 0x9c, 0x0e, 0x51, 0x30, 0x51,
        !            46:   0xb2, 0xb7, 0x54, 0x2e, 0x66, 0xb8, 0xd3, 0xa2, 0x5e, 0x93, 0x89,
        !            47:   0x11, 0xad, 0x6b, 0xe5, 0xc2, 0x43, 0x95, 0x09, 0x9c, 0x6d, 0xda,
        !            48:   0xa8, 0x6e, 0x18, 0x94, 0x2f, 0x29, 0x84, 0x27, 0x5a
        !            49: };
        !            50: 
        !            51: static R_DH_PARAMS PARAMS1 = {
        !            52:   PRIME1, sizeof (PRIME1), GENERATOR1, sizeof (GENERATOR1)
        !            53: };
        !            54: R_DH_PARAMS PARAMS2;
        !            55: int PARAMS2_READY = 0;
        !            56: 
        !            57: int main (argc, argv)
        !            58: int argc;
        !            59: char *argv[];
        !            60: {
        !            61:   R_RANDOM_STRUCT randomStruct;
        !            62:   char command[80];
        !            63:   int done = 0;
        !            64: 
        !            65:   if (SetOptions (argc, argv))
        !            66:     return (0);
        !            67:   
        !            68:   InitRandomStruct (&randomStruct);
        !            69:   PrintMessage
        !            70:     ("NOTE: When saving to a file, a filename of \"-\" will output to the screen.");
        !            71: 
        !            72:   while (!done) {
        !            73:     PrintMessage ("");
        !            74:     PrintMessage ("S - Set up a key agreement");
        !            75:     PrintMessage ("C - Compute an agreed-upon key");
        !            76:     PrintMessage ("G - Generate parameters (may take a long time)");
        !            77:     PrintMessage ("Q - Quit");
        !            78:     GetCommand (command, sizeof (command), "  Enter choice: ");
        !            79:     
        !            80:     switch (*command) {
        !            81:     case '\0':
        !            82:     case '#':
        !            83:       /* entered a blank line or a comment */
        !            84:       break;
        !            85:       
        !            86:     case 's':
        !            87:     case 'S':
        !            88:       DoSetupAgreement (&randomStruct);
        !            89:       break;
        !            90:       
        !            91:     case 'c':
        !            92:     case 'C':
        !            93:       DoComputeAgreedKey ();
        !            94:       break;
        !            95: 
        !            96:     case 'g':
        !            97:     case 'G':
        !            98:       DoGenerateParams (&randomStruct);
        !            99:       break;
        !           100:       
        !           101:     case 'Q':
        !           102:     case 'q':
        !           103:       done = 1;
        !           104:       break;
        !           105:       
        !           106:     default:
        !           107:       PrintError ("ERROR: Unrecognized command.  Try again.", 0);
        !           108:       break;
        !           109:     }
        !           110:   }
        !           111:   
        !           112:   R_RandomFinal (&randomStruct);
        !           113:   return (0);
        !           114: }
        !           115: 
        !           116: /* Set options from command line and return 0 for success, 1 for bad format.
        !           117:  */
        !           118: static int SetOptions (argc, argv)
        !           119: int argc;
        !           120: char *argv[];
        !           121: {
        !           122:   int i, status = 0;
        !           123:   
        !           124:   for (i = 1; i < argc; i++) {
        !           125:     if (argv[i][0] != '-') {
        !           126:       status = 1;
        !           127:       break;
        !           128:     }
        !           129:     
        !           130:     if (argv[i][1] == 's')
        !           131:       SILENT_PROMPT = 1;
        !           132:     else {
        !           133:       status = 1;
        !           134:       break;
        !           135:     }
        !           136:   }
        !           137: 
        !           138:   if (status)
        !           139:     puts ("Usage: dhdemo [-s]\n\
        !           140:   -s silent prompts");
        !           141: 
        !           142:   return (status);
        !           143: }
        !           144: 
        !           145: /* Initialize the random structure with all zero seed bytes for test purposes.
        !           146:    NOTE that this will cause the output of the "random" process to be
        !           147:      the same every time.  To produce random bytes, the random struct
        !           148:      needs random seeds!
        !           149:  */
        !           150: static void InitRandomStruct (randomStruct)
        !           151: R_RANDOM_STRUCT *randomStruct;
        !           152: {
        !           153:   static unsigned char seedByte = 0;
        !           154:   unsigned int bytesNeeded;
        !           155:   
        !           156:   R_RandomInit (randomStruct);
        !           157:   
        !           158:   /* Initialize with all zero seed bytes, which will not yield an actual
        !           159:        random number output.
        !           160:    */
        !           161:   while (1) {
        !           162:     R_GetRandomBytesNeeded (&bytesNeeded, randomStruct);
        !           163:     if (bytesNeeded == 0)
        !           164:       break;
        !           165:     
        !           166:     R_RandomUpdate (randomStruct, &seedByte, 1);
        !           167:   }
        !           168: }
        !           169: 
        !           170: static void DoSetupAgreement (randomStruct)
        !           171: R_RANDOM_STRUCT *randomStruct;
        !           172: {
        !           173:   R_DH_PARAMS *params;
        !           174:   char command[80];
        !           175:   int status;
        !           176:   unsigned char *privateValue, *publicValue;
        !           177:   unsigned int privateValueLen;
        !           178: 
        !           179:   if (GetParams
        !           180:       (&params, "  Set up with parameters 1 or 2? (blank to cancel): "))
        !           181:     return;
        !           182: 
        !           183:   GetCommand
        !           184:     (command, sizeof (command),
        !           185:      "  Enter length in bytes of private value (blank to cancel): ");
        !           186:   if (! *command)
        !           187:     return;
        !           188:   sscanf (command, "%d", &privateValueLen);
        !           189: 
        !           190:   privateValue = (unsigned char *)malloc (privateValueLen);
        !           191:   publicValue = (unsigned char *)malloc (params->primeLen);
        !           192: 
        !           193:   /* Set up a break point with a do {} while (0) so that we can
        !           194:        zeroize the sensitive buffers before exiting.
        !           195:    */
        !           196:   do {
        !           197:     if (status = R_SetupDHAgreement
        !           198:         (publicValue, privateValue, privateValueLen, params, randomStruct)) {
        !           199:       PrintError ("setting up key agreement", status);
        !           200:       break;
        !           201:     }
        !           202:   
        !           203:     if (WriteBlock
        !           204:         (publicValue, params->primeLen,
        !           205:          "  Enter filename to save the public value (blank to cancel): "))
        !           206:       break;
        !           207:   
        !           208:     if (WriteBlock
        !           209:         (privateValue, privateValueLen,
        !           210:          "  Enter filename to save the private value (blank to cancel): "))
        !           211:       break;
        !           212:   } while (0);
        !           213: 
        !           214:   memset ((POINTER)privateValue, 0, privateValueLen);
        !           215:   free (privateValue);
        !           216:   free (publicValue);
        !           217: }
        !           218: 
        !           219: static void DoComputeAgreedKey ()
        !           220: {
        !           221:   R_DH_PARAMS *params;
        !           222:   int status;
        !           223:   unsigned char *agreedKey, *otherPublicValue, *privateValue;
        !           224:   unsigned int otherPublicValueLen, privateValueLen;
        !           225: 
        !           226:   if (GetParams
        !           227:       (&params, "  Compute with parameters 1 or 2? (blank to cancel): "))
        !           228:     return;
        !           229: 
        !           230:   otherPublicValue = (unsigned char *)malloc (params->primeLen);
        !           231:   privateValue = (unsigned char *)malloc (params->primeLen);
        !           232:   agreedKey = (unsigned char *)malloc (params->primeLen);
        !           233: 
        !           234:   /* Set up a break point with a do {} while (0) so that we can
        !           235:        zeroize the sensitive buffers before exiting.
        !           236:    */
        !           237:   do {
        !           238:     if (ReadBlock
        !           239:         (otherPublicValue, &otherPublicValueLen, params->primeLen,
        !           240:          "  Enter filename of other party's public value (blank to cancel): "))
        !           241:       break;
        !           242:     if (otherPublicValueLen != params->primeLen) {
        !           243:       PrintError ("ERROR: Other party's public value has wrong length", 0);
        !           244:       break;
        !           245:     }
        !           246: 
        !           247:     if (ReadBlock
        !           248:         (privateValue, &privateValueLen, params->primeLen,
        !           249:          "  Enter filename of private value (blank to cancel): "))
        !           250:       break;
        !           251:   
        !           252:     if (status = R_ComputeDHAgreedKey
        !           253:         (agreedKey, otherPublicValue, privateValue, privateValueLen, params)) {
        !           254:       PrintError ("computing agreed-upon key", status);
        !           255:       break;
        !           256:     }
        !           257:   
        !           258:     if (WriteBlock
        !           259:         (agreedKey, params->primeLen,
        !           260:          "  Enter filename to save the agreed-upon key (blank to cancel): "))
        !           261:       break;
        !           262:   } while (0);
        !           263:   
        !           264:   memset ((POINTER)privateValue, 0, privateValueLen);
        !           265:   memset ((POINTER)agreedKey, 0, params->primeLen);
        !           266:   free (otherPublicValue);
        !           267:   free (privateValue);
        !           268:   free (agreedKey);
        !           269: }
        !           270: 
        !           271: static void DoGenerateParams (randomStruct)
        !           272: R_RANDOM_STRUCT *randomStruct;
        !           273: {
        !           274:   char command[80];
        !           275:   int status, primeBits, subPrimeBits;
        !           276: 
        !           277:   GetCommand
        !           278:     (command, sizeof (command),
        !           279:      "  Enter prime size in bits, (16 to 1024) (blank to cancel): ");
        !           280:   if (! *command)
        !           281:     return;
        !           282:   sscanf (command, "%d", &primeBits);
        !           283: 
        !           284:   GetCommand
        !           285:     (command, sizeof (command),
        !           286:      "  Enter subprime size in bits, (16 to 1024) (blank to cancel): ");
        !           287:   if (! *command)
        !           288:     return;
        !           289:   sscanf (command, "%d", &subPrimeBits);
        !           290: 
        !           291:   if (PARAMS2_READY) {
        !           292:     free (PARAMS2.prime);
        !           293:     free (PARAMS2.generator);
        !           294:   }
        !           295:   PARAMS2.prime = (unsigned char *)malloc (DH_PRIME_LEN (primeBits));
        !           296:   PARAMS2.generator = (unsigned char *)malloc (DH_PRIME_LEN (primeBits));
        !           297:   
        !           298:   if (status = R_GenerateDHParams
        !           299:       (&PARAMS2, primeBits, subPrimeBits, randomStruct)) {
        !           300:     PrintError ("generating parameters", status);
        !           301:     return;
        !           302:   }
        !           303: 
        !           304:   PrintMessage ("Parameters 2 are now ready to use.");
        !           305:   PARAMS2_READY = 1;
        !           306:   
        !           307:   WriteParams2 ();
        !           308: }
        !           309: 
        !           310: static void WriteParams2 ()
        !           311: {
        !           312:   FILE *file;
        !           313:   char filename[256];
        !           314:   
        !           315:   while (1) {
        !           316:     GetCommand
        !           317:       (filename, sizeof (filename),
        !           318:        "Enter filename to save the parameters (blank to not save): ");
        !           319:     if (! *filename)
        !           320:       return;
        !           321:     
        !           322:     if (filename[0] == '-' && filename[1] == '\0') {
        !           323:       /* use stdout */
        !           324:       file = stdout;
        !           325:       break;
        !           326:     }
        !           327:     if ((file = fopen (filename, "w")) != NULL)
        !           328:       /* successfully opened */
        !           329:       break;
        !           330:     
        !           331:     PrintError ("ERROR: Cannot open a file with that name.  Try again.", 0);
        !           332:   }
        !           333: 
        !           334:   fprintf (file, "Parameters:\n");
        !           335:   fprintf (file, "  prime: ");
        !           336:   WriteBigInteger (file, PARAMS2.prime, PARAMS2.primeLen);
        !           337:   fprintf (file, "  generator: ");
        !           338:   WriteBigInteger (file, PARAMS2.generator, PARAMS2.generatorLen);
        !           339: 
        !           340:   if (file != stdout)
        !           341:     fclose (file);
        !           342: }
        !           343: 
        !           344: /* Write the byte string 'integer' to 'file', skipping over leading zeros.
        !           345:  */
        !           346: static void WriteBigInteger (file, integer, integerLen)
        !           347: FILE *file;
        !           348: unsigned char *integer;
        !           349: unsigned int integerLen;
        !           350: {
        !           351:   while (*integer == 0 && integerLen > 0) {
        !           352:     integer++;
        !           353:     integerLen--;
        !           354:   }
        !           355:   
        !           356:   if (integerLen == 0) {
        !           357:     /* Special case, just print a zero. */
        !           358:     fprintf (file, "00\n");
        !           359:     return;
        !           360:   }
        !           361:   
        !           362:   for (; integerLen > 0; integerLen--)
        !           363:     fprintf (file, "%02x ", (unsigned int)(*integer++));
        !           364: 
        !           365:   fprintf (file, "\n");
        !           366: }
        !           367: 
        !           368: /* Use the prompt to ask the user to use parameters 1 or 2 and
        !           369:      point params to the answer.
        !           370:    Return 0 on success or 1 if user cancels by entering a blank.
        !           371:  */
        !           372: static int GetParams (params, prompt)
        !           373: R_DH_PARAMS **params;
        !           374: char *prompt;
        !           375: {
        !           376:   char command[80];
        !           377:   
        !           378:   while (1) {
        !           379:     GetCommand (command, sizeof (command), prompt);
        !           380: 
        !           381:     switch (*command) {
        !           382:     case '\0':
        !           383:       return (1);
        !           384:       
        !           385:     case '1':
        !           386:       *params = &PARAMS1;
        !           387:       return (0);
        !           388:       
        !           389:     case '2':
        !           390:       if (!PARAMS2_READY) {
        !           391:         PrintError
        !           392:           ("ERROR: Parameters 2 have not been generated yet.  Try Again.", 0);
        !           393:         break;
        !           394:       }
        !           395:       else {
        !           396:         *params = &PARAMS2;
        !           397:         return (0);
        !           398:       }
        !           399:       
        !           400:     default:
        !           401:       if (PARAMS2_READY)
        !           402:         PrintError ("ERROR: Please enter 1 or 2.  Try again.", 0);
        !           403:       else
        !           404:         PrintError ("ERROR: Please enter 1.  Try again.", 0);
        !           405:       break;
        !           406:     }
        !           407:   }
        !           408: }
        !           409: 
        !           410: /* Read a file of up to length maxBlockLen bytes, storing it in
        !           411:      block and returning its length in blockLen.
        !           412:    Ask for the filename using the given prompt string.
        !           413:    Return 0 on success or 1 if error or if user cancels by entering a blank.
        !           414:  */
        !           415: static int ReadBlock (block, blockLen, maxBlockLen, prompt) 
        !           416: unsigned char *block;
        !           417: unsigned int *blockLen;
        !           418: unsigned int maxBlockLen;
        !           419: char *prompt;
        !           420: {
        !           421:   FILE *file;
        !           422:   int status;
        !           423:   char filename[256];
        !           424:   unsigned char dummy;
        !           425:   
        !           426:   while (1) {
        !           427:     GetCommand (filename, sizeof (filename), prompt);
        !           428:     if (! *filename)
        !           429:       return (1);
        !           430:     
        !           431:     if ((file = fopen (filename, "rb")) != NULL)
        !           432:       /* successfully opened */
        !           433:       break;
        !           434:     
        !           435:     PrintError ("ERROR: Cannot open a file with that name.  Try again.", 0);
        !           436:   }
        !           437:   
        !           438:   /* fread () returns the number of items read in.  Expect an end of file
        !           439:        after the read.
        !           440:    */
        !           441:   *blockLen = fread (block, 1, maxBlockLen, file);
        !           442:   if (*blockLen == maxBlockLen)
        !           443:     /* Read exactly maxBlockLen bytes, so reading one more will set 
        !           444:          end of file if there were exactly maxBlockLen bytes in the file.
        !           445:      */
        !           446:     fread (&dummy, 1, 1, file);
        !           447:   
        !           448:   if (!feof (file)) {
        !           449:     PrintError ("ERROR: Cannot read file or file is too large.", 0);
        !           450:     status = 1;
        !           451:   }
        !           452:   else
        !           453:     status = 0;
        !           454:   
        !           455:   fclose (file);
        !           456:   return (status);
        !           457: }
        !           458: 
        !           459: /* Write block oflength blockLen to a file.
        !           460:    Ask for the filename using the given prompt string.
        !           461:    Return 0 on success or 1 if error or if user cancels by entering a blank.
        !           462:  */
        !           463: static int WriteBlock (block, blockLen, prompt) 
        !           464: unsigned char *block;
        !           465: unsigned int blockLen;
        !           466: char *prompt;
        !           467: {
        !           468:   FILE *file;
        !           469:   int status;
        !           470:   char filename[256];
        !           471:   
        !           472:   while (1) {
        !           473:     GetCommand (filename, sizeof (filename), prompt);
        !           474:     if (! *filename)
        !           475:       return (1);
        !           476:     
        !           477:     if (filename[0] == '-' && filename[1] == '\0') {
        !           478:       /* use stdout */
        !           479:       file = stdout;
        !           480:       break;
        !           481:     }
        !           482:     if ((file = fopen (filename, "wb")) != NULL)
        !           483:       /* successfully opened */
        !           484:       break;
        !           485:     
        !           486:     PrintError ("ERROR: Cannot open a file with that name.  Try again.", 0);
        !           487:   }
        !           488:   
        !           489:   status = 0;
        !           490:   if (fwrite (block, 1, blockLen, file) < blockLen) {
        !           491:     PrintError ("ERROR: Cannot write file.", 0);
        !           492:     status = 1;
        !           493:   }
        !           494:   else {
        !           495:     if (file == stdout)
        !           496:       /* Printing to screen, so print a new line. */
        !           497:       printf ("\n");
        !           498:   }
        !           499: 
        !           500:   if (file != stdout)
        !           501:     fclose (file);
        !           502:   return (status);
        !           503: }
        !           504: 
        !           505: static void PrintMessage (message)
        !           506: char *message;
        !           507: {
        !           508:   if (!SILENT_PROMPT) {
        !           509:     puts (message);
        !           510:     fflush (stdout);
        !           511:   }
        !           512: }
        !           513: 
        !           514: /* If type is zero, simply print the task string, otherwise convert the
        !           515:      type to a string and print task and type.
        !           516:  */
        !           517: static void PrintError (task, type)
        !           518: char *task;
        !           519: int type;
        !           520: {
        !           521:   char *typeString, buf[80];
        !           522: 
        !           523:   if (type == 0) {
        !           524:     puts (task);
        !           525:     return;
        !           526:   }
        !           527:   
        !           528:   /* Convert the type to a string if it is recognized.
        !           529:    */
        !           530:   switch (type) {
        !           531:   case RE_CONTENT_ENCODING:
        !           532:     typeString = "(Encrypted) content has RFC 1113 encoding error";
        !           533:     break;
        !           534:   case RE_DIGEST_ALGORITHM:
        !           535:     typeString = "Message-digest algorithm is invalid";
        !           536:     break;
        !           537:   case RE_KEY:
        !           538:     typeString = "Recovered DES key cannot decrypt encrypted content or encrypt signature";
        !           539:     break;
        !           540:   case RE_KEY_ENCODING:
        !           541:     typeString = "Encrypted key has RFC 1113 encoding error";
        !           542:     break;
        !           543:   case RE_MODULUS_LEN:
        !           544:     typeString = "Modulus length is invalid";
        !           545:     break;
        !           546:   case RE_NEED_RANDOM:
        !           547:     typeString = "Random structure is not seeded";
        !           548:     break;
        !           549:   case RE_PRIVATE_KEY:
        !           550:     typeString = "Private key cannot encrypt message digest, or cannot decrypt encrypted key";
        !           551:     break;
        !           552:   case RE_PUBLIC_KEY:
        !           553:     typeString = "Public key cannot encrypt DES key, or cannot decrypt signature";
        !           554:     break;
        !           555:   case RE_SIGNATURE:
        !           556:     typeString = "Signature on content or block is incorrect";
        !           557:     break;
        !           558:   case RE_SIGNATURE_ENCODING:
        !           559:     typeString = "(Encrypted) signature has RFC 1113 encoding error";
        !           560:     break;
        !           561:     
        !           562:   default:
        !           563:     sprintf (buf, "Code 0x%04x", type);
        !           564:     typeString = buf;
        !           565:   }
        !           566: 
        !           567:   printf ("ERROR: %s while %s\n", typeString, task);  
        !           568:   fflush (stdout);
        !           569: }
        !           570: 
        !           571: static void GetCommand (command, maxCommandSize, prompt)
        !           572: char *command;
        !           573: unsigned int maxCommandSize;
        !           574: char *prompt;
        !           575: {
        !           576:   unsigned int i;
        !           577:   
        !           578:   if (!SILENT_PROMPT) {
        !           579:     printf ("%s\n", prompt);  
        !           580:     fflush (stdout);
        !           581:   }
        !           582: 
        !           583:   fgets (command, maxCommandSize, stdin);
        !           584:   
        !           585:   /* Replace the line terminator with a '\0'.
        !           586:    */
        !           587:   for (i = 0; command[i] != '\0'; i++) {
        !           588:     if (command[i] == '\012' || command[i] == '\015' ||
        !           589:         i == (maxCommandSize - 1)) {
        !           590:       command[i] = '\0';
        !           591:       return;
        !           592:     }
        !           593:   }
        !           594: }

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.