//**************************************************************************** // fotp.cpp // One-time-pad encryption/decryption utility. // // Notice // Copyright ©1995-97, David R. Tribble. // // This source code is copyrighted. Distribution of this source code is // granted, provided that this copyright notice remains attached to the // source code, and that adequate acknowledgement is given to the // original author. // // History // 1.00 1995-05-05 dtibble. // First cut. // 1.01 1995-06-27 dtribble. // Renamed "fotplib.h" to "fotplib.hpp". // 1.02 1995-08-30 dtribble. // Changes added for porting to Unix. // Identification #define ID_TITLE "FOTP" #define ID_DATE "1995-08-30" #define ID_VERS "1.02" #define ID_FILE __FILE__ #define ID_AUTH "David R. Tribble" #define ID_COMP __DATE__ " " __TIME__ static const char copyright[] = "Copyright ©1995-97, " ID_AUTH "."; static const char id[] = "@(#) " ID_DATE " " ID_VERS " " ID_FILE; static const char id_compile[] = "[" ID_TITLE " " ID_VERS ", " ID_DATE "] <" ID_COMP ">"; static const char title[] = ID_TITLE; static const char date[] = ID_DATE; static const char vers[] = ID_VERS; static const char auth[] = ID_AUTH; static const char file[] = ID_FILE; static const char * prog = title; // System includes #include #include #include #include #include #include #include #include #include // Local includes #include "osdefs.h" #include "drtmem.h" #include "debug.h" #include "fotplib.hpp" #if !DEBUG #undef DL #define DL(e) (0) #endif // Manifest constants #define ENV_PASSWORD_k "CRYPTKEY" #define ENV_PASSWORD_P "PASSWORD" #define OTP_MAXFILES 30 // Max OTP file names // Return codes #define RC_OKAY 0 // Successful #define RC_ENV 1 // Environment var not set #define RC_OUTF 2 // Can't open/write output file #define RC_TTY 3 // Can't open/read tty device #define RC_NOTTY 4 // Tty stream is not a tty #define RC_READ 5 // Can't read input #define RC_WRITE 6 // Can't write output #define RC_STAT 7 // Can't stat a file #define RC_SEEK 8 // Can't seek a file #define RC_USAGE 255 // Invalid usage // Command actions #define ACT_ENCRYPT 'e' // Encrypt #define ACT_DECRYPT 'd' // Decrypt #define ACT__DFL ACT_ENCRYPT // Default action // Feedback mode types #define FEEDBK__DFL OTP_FEEDBK_CBC // Default mode // Private variables struct ltime { time_t sec; // Seconds unsigned long usec; // Microseconds }; struct option { int feedbk; // Feedback mode int action; // Action int preserve; // Preserve ownership and permissions int envpwd; // Get password from environment int cmdpwd; // Get password from command line int query; // Query for password const char * infname; // Input file name FILE * infp; // Input file const char * outfname; // Output file name FILE * outfp; // Output file const char * padfname[OTP_MAXFILES]; // OTP file names unsigned long padoff[OTP_MAXFILES]; // Initial OTP file offsets int npads; // Num of OTP file names unsigned char pwd[32+1]; // Password unsigned char iv[64/8]; // Initial feedback vector int verbose; // Verbose messages struct ltime starttm; // Encyrpt/decrypt start time struct ltime stoptm; // Encyrpt/decrypt stop time }; static struct option opt; static const char env_passwordk[] = ENV_PASSWORD_k; static const char env_passwordP[] = ENV_PASSWORD_P; static struct otp_file * otp_files[OTP_MAXFILES]; static int otp_nfiles = 0; //---------------------------------------------------------------------------- // encrypt() // Encrypt data from stream `in' out to stream `out'. // // Returns // RC_OKAY on success, otherwise a nonzero RC_xxx value. static int encrypt(FILE *in, FILE *out) { int rc = RC_OKAY; int i; int len; long byteno = 0; unsigned char buf[OTP_BLKSZ]; // Set OTP file offsets for (i = 0; i < otp_nfiles; i++) { DL(dbgf("Seek file[%u] to 0x%08lX\n", i, opt.padoff[i])); rc = otp_files[i]->seek(opt.padoff[i]); if (rc != 0) { // Can't seek OTP file rc = errno; DL(dbgf("Can't seek OTP file[%u], off=0x%08lX, errno=%d\n", i, otp_files[i]->o_off, rc)); fprintf(stderr, "%s: Can't seek on OTP file `%s': %s\n", prog, otp_files[i]->o_fname, strerror(rc)); errno = rc; rc = RC_SEEK; goto done; } DL(dbgf("Offset[%u]=0x%08lX\n", i, otp_files[i]->o_off)); DL(dbgf("\n")); } // Encrypt blocks of data from `in' to `out' while (len = fread(buf, 1, sizeof(buf), in), len > 0) { DL(dbgf("Read %u bytes:\n", len)); DL(dbg_hexdump(buf, len)); // Fill OTP data pads for (i = 0; i < otp_nfiles; i++) { DL(dbgf("OTP[%u] data, off=0x%08lX:\n", i, otp_files[i]->o_off)); if (otp_files[i]->readblk() != 0) { // Can't read OTP data rc = errno; DL(dbgf("Can't read OTP data block, off=0x%08lX, errno=%d\n", otp_files[i]->o_off, rc)); fprintf(stderr, "%s: Can't read OTP data from `%s': %s\n", prog, otp_files[i]->o_fname, strerror(rc)); errno = rc; rc = RC_READ; goto done; } else { // Have read a data block DL(dbg_hexdump(otp_files[i]->o_data, sizeof(otp_files[i]->o_data))); } } // Encrypt data block for (i = 0; i < otp_nfiles; i++) { memxor(buf, buf, otp_files[i]->o_data, len); } // Write encrypted data block DL(dbgf("Write %u bytes:\n", len)); DL(dbg_hexdump(buf, len)); if (fwrite(buf, 1, len, out) <= 0) { // Can't write rc = errno; fprintf(stderr, "%s: Can't write data: %s\n", prog, strerror(rc)); errno = rc; rc = RC_WRITE; goto done; } byteno += len; if (opt.verbose) fprintf(stderr, "%lu\r", byteno); } if (opt.verbose) fprintf(stderr, "\n"); done: // Clean up memset(buf, 0, sizeof(buf)); for (i = 0; i < otp_nfiles; i++) memset(otp_files[i]->o_data, 0, sizeof(otp_files[i]->o_data)); DL(dbgf("Return %d\n", rc)); return rc; } //---------------------------------------------------------------------------- // decrypt() // Decrypt data from stream `in' out to stream `out'. // // Returns // RC_OKAY on success, otherwise a nonzero RC_xxx value. static int decrypt(FILE *in, FILE *out) { int rc; // Decryption is the same as encryption (it's its own inverse) rc = encrypt(in, out); return rc; } //---------------------------------------------------------------------------- // query_pwd() // Queries the user interactively for a password, which is stored in // `pwd'. // The password is no longer than `max' chars. The password is read from // stream `tty', which should have been previously opened for update // mode. // // Leading and trailing spaces are ignored. // Entering an empty password causes the prompt to occur again. // // Returns // Length of the password, which is in the range [0,max]. static int query_pwd(FILE *tty, char *pwd, int max, const char *prompt) { int i, j, k; char rbuf[512]; char * resp; if (prompt == NULL) prompt = "Password:"; // Default prompt retry: // Write prompt string (to stderr) i = strlen(prompt); fprintf(stderr, "%*s ", i, ""); for (j = 0; j < 16; j++) putc('#', stderr); putc('\r', stderr); fprintf(stderr, "%*s ", i, ""); for (j = 0; j < 16; j++) putc('@', stderr); putc('\r', stderr); fprintf(stderr, "%*s ", i, ""); for (j = 0; j < 16; j++) putc(' ', stderr); putc('\r', stderr); fprintf(stderr, "%s ", prompt); fflush(stderr); // Read password response from user (tty) fgets(rbuf, sizeof(rbuf), tty); resp = rbuf; DL(dbgf("Response:\n")); DL(dbg_hexdump(resp, strlen(resp))); // Ignore leading spaces while (*resp != '\0' && isspace(*resp)) resp++; // Ignore trailing spaces for (k = strlen(resp); k > 0 && isspace(resp[k-1]); k--) continue; resp[k] = '\0'; // Copy response into password buffer if (k > 0) { if (k > max) k = max; // Truncate long password memcpy(pwd, resp, k); } pwd[k] = '\0'; DL(dbgf("Password:\n")); DL(dbg_hexdump(pwd, k)); // Wipe out sensitive info memset(rbuf, 0, sizeof(rbuf)); // Check for empty response if (pwd[0] == '\0') goto retry; return k; } //---------------------------------------------------------------------------- // query_for_pwd() // Query the user (tty) interactively for password. // // Returns // RC_OKAY on success, otherwise another RC_xxx code. static int query_for_pwd() { FILE * tty; long lflag; // Query user interactively (tty) for password tty = fopen(DEV_TTY, "r+"); if (tty == NULL) { // Can't open/read tty fprintf(stderr, "%s: Can't access `%s': %s\n", prog, DEV_TTY, strerror(errno)); return RC_TTY; } if (!isatty(fileno(tty))) { // Tty stream is not a tty fprintf(stderr, "%s: Not a tty: `%s'\n", prog, DEV_TTY); return RC_NOTTY; } #if unix // Set tty mode for no local echo #ifdef is_incomplete___ struct termio tio; if (ioctl(fileno(tty), TCGETA, &tio) == -1) ...error lflag = tio.c_lflag; tio.c_lflag |= ICANON; tio.c_lflag &= ~ECHO; tio.c_lflag |= ECHOE | ECHOKE | ECHOCTL | ECHONL; if (ioctl(fileno(tty), TCSETAW, &tio) == -1) ...error Note: struct termios may be used, with TCGETS and TCSETSW. Note: termios(2) call may be used instead (preferred). #endif #endif // Query user for password query_pwd(tty, (char *)opt.pwd, sizeof(opt.pwd)-1, "Password:"); DL(dbgf("opt.pwd:\n")); DL(dbg_hexdump(opt.pwd, sizeof(opt.pwd))); #if unix // Reset tty mode for local echo #ifdef is_incomplete___ tio.c_lflag = lflag; if (ioctl(fileno(tty), TCSETAW, &tio) == -1) ...error #endif #endif // Close tty fclose(tty); tty = NULL; return RC_OKAY; } //---------------------------------------------------------------------------- // gen_pwd_offsets() // Generate initial OTP offsets from password string `pwd'. static void gen_pwd_offsets(const unsigned char *pwd) { int i, j; unsigned long a, s; // Generate first bitstring from password DL(dbgf("Gen bitstring from pwd\n")); s = 0x00000000; a = 0x00000000; do { for (i = j = 0; pwd[i] != '\0'; i++, j++) { if (j >= 24) { a ^= s; s = 0x00000000; j = 0; } s = (s << 1) ^ pwd[i]; } } while (s < 0x80000000); if (a == 0x00000000) a ^= s; DL(dbgf("Gen'd 1st pwd bitstring=0x%08lX\n", a)); // Generate next bitstrings from initial bitstring for (i = 0; i < opt.npads; i++) { DL(dbgf("Offset[%u]=0x%08lX\n", i, a)); opt.padoff[i] = a; a = (a << 13) | (a >> 32-13); // Rotate left 13 bits } } //---------------------------------------------------------------------------- // about() // Print info about this program. static const char * about_m[] = { id_compile, #if DEBUG "(with debugs)", #endif "", copyright, "", "If you find this program useful, you may register it by sending the", "version and date shown above along with your name and address and a", "donation of $15.00(US) to:", "", " David R. Tribble", " 6004 Cave River Dr.", " Plano, TX 75093-6951", " USA", "", " dtribble@technologist.com", " dtribble@flash.net", NULL }; static void about() { int i; // Print `about' text for (i = 0; about_m[i] != NULL; i++) fprintf(stdout, "%s\n", about_m[i]); } //---------------------------------------------------------------------------- // usage() // Print usage message, then punt. // // Returns // Doesn't, calls exit(). static const char * usage_m[] = { "If `file' is `-', input is read from standard input. Passwords may be", "up to 32 characters long.", "", "Options:", " -? About this program.", #if DEBUG " -D Enable debugging output.", #endif " -d Decrypt.", " -e Encrypt (default).", " -f file One-time pad file `file'.", " -F n Feedback type, where `n' is one of:", " 0 ECB, No feedback.", " 1 CBC, Cipher block chaining (default).", " 2 PBC, Plaintext block chaining.", #ifdef is_unsupported___ " 3 CFB, Cipher feedback.", " 4 OFB, Output feedback.", " 5 PFB, Plaintext feedback.", #endif " -k Password is in environment `CRYPTKEY'.", " -o file Output is `file' (default is standard output).", " -p word Password is `word'.", // " -P Password is in environment `PASSWORD'.", " -q Query for password.", #ifdef is_unsupported___ " -t Output has same dates and permissions as input file.", #endif " -v Verbose output.", NULL }; static void usage() { int i; // Print usage message fprintf(stderr, "[%s %s, %s]%s\n\n", title, vers, date, (DEBUG ? " +debug" : "")); fprintf(stderr, "Encrypt one or more files.\n\n"); fprintf(stderr, "usage: %s [-option...] file...\n\n", prog); for (i = 0; usage_m[i] != NULL; i++) fprintf(stderr, "%s\n", usage_m[i]); exit(RC_USAGE); } //---------------------------------------------------------------------------- // get_options() // Parse command line options. // // Returns // Number of argv[] strings parsed. static int get_options(int argc, char **argv) { int optind = 1; int optch; char * optarg = NULL; char * env; // Parse command line options DL(dbgf("getoptions, argc=%d\n", argc)); // Set initial values opt.feedbk = -1; opt.action = -1; if (opt.verbose) fprintf(stderr, "Options:"); for (optind = 1; optind < argc && argv[optind][0] == '-' && argv[optind][1] != '\0'; optind++) { // Parse next command line arg DL(dbgf("argv[optind=%d]=`%s'\n", optind, argv[optind])); optarg = NULL; while (argv[optind]++, optch = argv[optind][0], optch != '\0') { // Parse next single command line option optarg = argv[optind]+1; DL(dbgf("optch=`%c' optarg=`%s'\n", optch, optarg)); switch (optch) { case 'D': // -D, debugging output #if DEBUG dbg_f = stderr; #endif optarg = NULL; break; case 'd': // -d, decrypt if (opt.action != -1 && opt.action != ACT_DECRYPT) goto conflict; opt.action = ACT_DECRYPT; optarg = NULL; break; case 'e': // -e, encrypt if (opt.action != -1 && opt.action != ACT_ENCRYPT) goto conflict; opt.action = ACT_ENCRYPT; optarg = NULL; break; case 'f': // -f, one-time pad file if (optarg[0] == '\0') { optarg = argv[optind+1]; optind++; if (optind >= argc) goto bad_usage; } if (opt.npads >= OTP_MAXFILES) { fprintf(stderr, "%s: Too many `-f' file names (%u max)\n", prog, OTP_MAXFILES); goto next_arg; } opt.padfname[opt.npads] = optarg; opt.npads++; optarg = NULL; DL(dbgf("opt.padfname[%u]=`%s'\n", opt.npads, opt.padfname[opt.npads-1])); optch = '\0'; goto next_arg; case 'F': // -F[num], cipher feedback type if (optarg[0] == '\0') { optarg = argv[optind+1]; optind++; if (optind >= argc) goto bad_usage; } opt.feedbk = atoi(optarg); DL(dbgf("opt.cfbtype=`%s'=%d\n", optarg, opt.feedbk)); goto next_arg; case 'k': // -k, encryption password is in environment var if (opt.cmdpwd || opt.query) goto conflict; opt.envpwd = TRUE; env = getenv(env_passwordk); if (env == NULL) { fprintf(stderr, "%s: %s is not set\n", prog, env_passwordk); exit(RC_ENV); } strncpy((char *)opt.pwd, env, sizeof(opt.pwd)-1); DL(dbgf("opt.pwd=`%s'\n", opt.pwd)); break; case 'o': // -o file, output file name if (optarg[0] == '\0') { optarg = argv[optind+1]; optind++; if (optind >= argc) goto bad_usage; } opt.outfname = optarg; DL(dbgf("opt.outfname=`%s'\n", opt.outfname)); goto next_arg; case 'p': // -p[word], encryption password if (opt.envpwd || opt.query) goto conflict; opt.cmdpwd = TRUE; if (optarg[0] == '\0') { optarg = argv[optind+1]; optind++; if (optind >= argc) goto bad_usage; } strncpy((char *)opt.pwd, optarg, sizeof(opt.pwd)-1); DL(dbgf("opt.pwd=`%s'\n", opt.pwd)); optch = '\0'; goto next_arg; case 'P': // -P, encryption password is in environment var if (opt.cmdpwd || opt.query) goto conflict; opt.envpwd = TRUE; env = getenv(env_passwordP); if (env == NULL) { fprintf(stderr, "%s: %s is not set\n", prog, env_passwordP); exit(RC_ENV); } strncpy((char *)opt.pwd, env, sizeof(opt.pwd)-1); DL(dbgf("opt.pwd=`%s'\n", opt.pwd)); break; case 'q': // -q, query interactively for password if (opt.cmdpwd || opt.envpwd) goto conflict; opt.query = TRUE; optarg = NULL; break; case 'v': // -v, verbose output opt.verbose = TRUE; optarg = NULL; break; case '?': // -?, help about(); optarg = NULL; exit(RC_OKAY); break; default: // Invalid option fprintf(stderr, "%s: Invalid option `-%c'\n\n", prog, optch); optarg = NULL; goto bad_usage; } if (opt.verbose && optch != '\0') { fprintf(stderr, " -%c", optch); if (optarg != NULL) fprintf(stderr, " \"%s\"", optarg); optch = '\0'; } } next_arg: if (opt.verbose && optch != '\0') { fprintf(stderr, " -%c", optch); if (optarg != NULL) fprintf(stderr, " \"%s\"", optarg); optch = '\0'; } } if (opt.verbose) fprintf(stderr, "\n"); // Set implicit defaults if (opt.action == -1) opt.action = ACT__DFL; if (opt.feedbk == -1) opt.feedbk = FEEDBK__DFL; DL(dbgf("Return optind=%d\n", optind)); return optind; conflict: // Two or more command line options conflict fprintf(stderr, "%s: Option `-%c' conflicts with other options\n\n", prog, optch); usage(); return -1; bad_usage: // Improper command usage usage(); return -1; } //---------------------------------------------------------------------------- // main() // Main function for program. // // Returns // One of the RC_xxx codes. int main(int argc, char **argv) { int rc = RC_OKAY; int i, j; #if DEBUG // Handle debug stuff fprintf(stderr, "=== %s %s %s ===\n", title, date, vers); for (i = 1; i < argc; i++) { if (strcmp(argv[i], "-D") == 0) dbg_f = stderr; } #endif // Precheck options for (i = 1; i < argc; i++) { if (strcmp(argv[i], "-v") == 0) opt.verbose = TRUE; } // Parse command line options i = get_options(argc, argv); argc -= i; argv += i; // Check args if (argc < 1) usage(); DL(dbgf("\n")); // Check for OTP files if (opt.npads == 0) fprintf(stderr, "%s: Warning: No pad files\n", prog); // Open one-time pad files for (i = 0; i < opt.npads; i++) { // Create new OTP file DL(dbgf("Create opt_file for `%s'\n", opt.padfname[i])); otp_files[otp_nfiles] = new otp_file; DL(dbgf("otp_files[%d]=%p\n", otp_nfiles, otp_files[otp_nfiles])); if (otp_files[otp_nfiles] == NULL) { // Can't allocate new OTP file rc = errno; fprintf(stderr, "%s: Can't allocate info for `%s': %s\n", prog, opt.padfname[i], strerror(rc)); continue; } // Open next OTP file if (otp_files[otp_nfiles]->open(opt.padfname[i]) != 0) { // Can't open OTP file rc = errno; fprintf(stderr, "%s: Can't open (read) `%s': %s\n", prog, opt.padfname[i], strerror(rc)); delete otp_files[otp_nfiles]; continue; } DL(dbgf("Opened otp_file[%d]=@%p `%s', fp=%p\n", otp_nfiles, otp_files[otp_nfiles], otp_files[otp_nfiles]->o_fname, otp_files[otp_nfiles]->o_fp)); otp_nfiles++; DL(dbgf("\n")); } // Open output file if (opt.outfname != NULL) { // Open output file DL(dbgf("Write to `%s'\n", opt.outfname)); opt.outfp = fopen(opt.outfname, "wb"); if (opt.outfp == NULL) { fprintf(stderr, "%s: Can't open (write) `%s': %s\n", prog, opt.outfname, strerror(errno)); rc = RC_OUTF; goto done; } } else { // Write output to stdout DL(dbgf("Output is stdout=%p\n", stdout)); opt.outfname = "-"; opt.outfp = stdout; #ifdef O_BINARY // Set binary file mode DL(dbgf("setmode binary, fp=%p fd=%d\n", opt.outfp, fileno(opt.outfp))); setmode(fileno(opt.outfp), O_BINARY); #endif } #if BUFSIZ < OUT_BUFSZ // Give output file a bigger buffer if (setvbuf(opt.outfp, obuf, _IOFBF, sizeof(obuf)) != 0) { fprintf(stderr, "%s: Can't setvbuf on `%s': %s\n", prog, opt.outfname, strerror(errno)); } #endif DL(dbgf("outfname=`%s', outfp=%p\n", opt.outfname, opt.outfp)); DL(dbgf("\n")); // Get password if (opt.query) { // Query user (tty) for password rc = query_for_pwd(); if (rc != RC_OKAY) { // Query failure DL(dbgf("Query failure, rc=%d\n", rc)); goto done; } DL(dbgf("\n")); } // Generate initial OTP offsets from password if (opt.pwd[0] != '\0') { DL(dbgf("Gen offsets from password\n")); gen_pwd_offsets(opt.pwd); // Wipe out sensitive info memset(opt.pwd, 0, sizeof(opt.pwd)); DL(dbgf("\n")); } // Set initial OTP file offsets for (i = 0; i < otp_nfiles; i++) { struct stat st; // Get OTP file size if (fstat(fileno(otp_files[i]->o_fp), &st) != 0) { // Can't stat OTP file rc = errno; DL(dbgf("Can't stat OTP file[%u], errno=%d\n", i, rc)); fprintf(stderr, "%s: Can't get status of OTP file `%s': %s\n", prog, otp_files[i]->o_fname, strerror(rc)); errno = rc; rc = RC_STAT; goto done; } // Pare initial offset down to be within file size limits opt.padoff[i] %= st.st_size; DL(dbgf("Offset[%u]=0x%08lX\n", i, opt.padoff[i])); DL(dbgf("\n")); } // Process command line args for (i = 0; i < argc; i++) { // Get input file opt.infname = argv[i]; DL(dbgf("infname=`%s'\n", opt.infname)); if (opt.verbose) fprintf(stderr, "%s:\n", opt.infname); if (strcmp(opt.infname, "-") == 0) { // Get input from stdin DL(dbgf("Input is stdin=%p\n", stdin)); opt.infp = stdin; #ifdef O_BINARY // Set binary file mode DL(dbgf("setmode binary, fp=%p, fd=%d\n", opt.infp, fileno(opt.infp))); setmode(fileno(opt.infp), O_BINARY); #endif } else { // Open input file opt.infp = fopen(opt.infname, "rb"); DL(dbgf("infname=`%s'\n", opt.infname)); if (opt.infp == NULL) { fprintf(stderr, "%s: Can't open (read) `%s': %s\n", prog, opt.infname, strerror(errno)); continue; } } #if BUFSIZ < IN_BUFSZ // Give input file a bigger buffer if (setvbuf(opt.infp, ibuf, _IOFBF, sizeof(ibuf)) != 0) { fprintf(stderr, "%s: Can't setvbuf on `%s': %s\n", prog, opt.infname, strerror(errno)); } #endif // Encrypt/decrypt a file switch (opt.action) { case ACT_ENCRYPT: DL(dbgf("Encrypt, in=%p out=%p\n", opt.infp, opt.outfp)); rc = encrypt(opt.infp, opt.outfp); break; case ACT_DECRYPT: DL(dbgf("Decrypt, in=%p out=%p\n", opt.infp, opt.outfp)); rc = decrypt(opt.infp, opt.outfp); break; } DL(dbgf("\n")); // Clean up if (opt.infp != NULL && opt.infp != stdin) { DL(dbgf("Close input fp=%p\n", opt.infp)); if (opt.verbose) fprintf(stderr, "Close input `%s'\n", opt.infname); fclose(opt.infp); opt.infp = NULL; } DL(dbgf("\n")); } done: // Wipe out sensitive info //... // Clean up // Close OTP files if (opt.verbose) fprintf(stderr, "Close OTP files\n"); for (i = 0; i < otp_nfiles; i++) { if (otp_files[i] != NULL) { DL(dbgf("Delete otp_files[%u]=%p\n", i, otp_files[i])); delete otp_files[i]; otp_files[i] = NULL; } } // Close output file if (opt.outfp != NULL && opt.outfp != stdout) { if (opt.verbose) fprintf(stderr, "Close output `%s'\n", opt.outfname); fclose(opt.outfp); opt.outfp = NULL; } DL(dbgf("\n")); // Done if (opt.verbose) fprintf(stderr, "Done\n"); DL(dbgf("Done, rc=%d\n", rc)); return rc; } // End fotp.cpp