Flawfinder version 2.0.10, (C) 2001-2019 David A. Wheeler. Number of rules (primarily dangerous function names) in C/C++ ruleset: 223 Examining data/dnstracer-1.9/getopt.h Examining data/dnstracer-1.9/getopt.c Examining data/dnstracer-1.9/dnstracer_broken.h Examining data/dnstracer-1.9/dnstracer.c FINAL RESULTS: data/dnstracer-1.9/dnstracer.c:465:2: [4] (buffer) sprintf: Does not check for buffer overflows (CWE-120). Use sprintf_s, snprintf, or vsnprintf. sprintf(hostname + strlen(hostname), "%s", p + 1); data/dnstracer-1.9/dnstracer.c:570:2: [4] (buffer) strcpy: Does not check for buffer overflows when copying to destination [MS-banned] (CWE-120). Consider using snprintf, strcpy_s, or strlcpy (warning: strncpy easily misused). strcpy(retval, printablename(getname(session, &buffer), dots)); data/dnstracer-1.9/dnstracer.c:574:2: [4] (buffer) strcpy: Does not check for buffer overflows when copying to destination [MS-banned] (CWE-120). Consider using snprintf, strcpy_s, or strlcpy (warning: strncpy easily misused). strcpy(retval, printablename(getname(session, &buffer), dots)); data/dnstracer-1.9/dnstracer.c:583:2: [4] (buffer) sprintf: Does not check for buffer overflows (CWE-120). Use sprintf_s, snprintf, or vsnprintf. sprintf(retval, "%hu %s", us, printablename(getname(session, &buffer), dots)); data/dnstracer-1.9/dnstracer.c:588:2: [4] (buffer) strcpy: Does not check for buffer overflows when copying to destination [MS-banned] (CWE-120). Consider using snprintf, strcpy_s, or strlcpy (warning: strncpy easily misused). strcpy(retval, printablename(getname(session, &buffer), dots)); data/dnstracer-1.9/dnstracer.c:592:2: [4] (buffer) strcpy: Does not check for buffer overflows when copying to destination [MS-banned] (CWE-120). Consider using snprintf, strcpy_s, or strlcpy (warning: strncpy easily misused). strcpy(retval, printablename(getname(session, &buffer), dots)); data/dnstracer-1.9/dnstracer.c:596:2: [4] (buffer) strcpy: Does not check for buffer overflows when copying to destination [MS-banned] (CWE-120). Consider using snprintf, strcpy_s, or strlcpy (warning: strncpy easily misused). strcpy(retval, printablename(getname(session, &buffer), dots)); data/dnstracer-1.9/dnstracer.c:606:2: [4] (buffer) strcpy: Does not check for buffer overflows when copying to destination [MS-banned] (CWE-120). Consider using snprintf, strcpy_s, or strlcpy (warning: strncpy easily misused). strcpy(mname, printablename(getname(session, &buffer), dots)); data/dnstracer-1.9/dnstracer.c:607:2: [4] (buffer) strcpy: Does not check for buffer overflows when copying to destination [MS-banned] (CWE-120). Consider using snprintf, strcpy_s, or strlcpy (warning: strncpy easily misused). strcpy(rname, printablename(getname(session, &buffer), dots)); data/dnstracer-1.9/dnstracer.c:610:2: [4] (buffer) sprintf: Does not check for buffer overflows (CWE-120). Use sprintf_s, snprintf, or vsnprintf. sprintf(retval, "serial: %ld mname: %s rname: %s", ul, mname, rname); data/dnstracer-1.9/dnstracer.c:962:5: [4] (buffer) strcpy: Does not check for buffer overflows when copying to destination [MS-banned] (CWE-120). Consider using snprintf, strcpy_s, or strlcpy (warning: strncpy easily misused). strcpy(session->send_question->query + 1, name); data/dnstracer-1.9/dnstracer.c:1023:6: [4] (format) printf: If format strings can be influenced by an attacker, they can be exploited (CWE-134). Use a constant for the format specification. printf(s, " "); data/dnstracer-1.9/dnstracer.c:1346:6: [4] (buffer) sprintf: Does not check for buffer overflows (CWE-120). Use sprintf_s, snprintf, or vsnprintf. sprintf(s, "%s%c%s", data/dnstracer-1.9/dnstracer.c:1359:3: [4] (buffer) strcpy: Does not check for buffer overflows when copying to destination [MS-banned] (CWE-120). Consider using snprintf, strcpy_s, or strlcpy (warning: strncpy easily misused). strcpy(nextserver_name, printablename(rradd->domainname, 1)); data/dnstracer-1.9/dnstracer.c:1360:3: [4] (buffer) strcpy: Does not check for buffer overflows when copying to destination [MS-banned] (CWE-120). Consider using snprintf, strcpy_s, or strlcpy (warning: strncpy easily misused). strcpy(nextserver_ip, rradd->data_string); data/dnstracer-1.9/dnstracer.c:1369:3: [4] (buffer) strcpy: Does not check for buffer overflows when copying to destination [MS-banned] (CWE-120). Consider using snprintf, strcpy_s, or strlcpy (warning: strncpy easily misused). strcpy(nextserver_name, rrauth->data_string); data/dnstracer-1.9/dnstracer.c:1622:5: [4] (buffer) strcpy: Does not check for buffer overflows when copying to destination [MS-banned] (CWE-120). Consider using snprintf, strcpy_s, or strlcpy (warning: strncpy easily misused). strcpy(argv0, argv[0]); data/dnstracer-1.9/dnstracer.c:39:13: [3] (random) random: This function is not sufficiently random for security-related functions such as key and nonce creation (CWE-327). Use a more secure technique for acquiring random values. #define random rand data/dnstracer-1.9/dnstracer.c:40:13: [3] (random) srandom: This function is not sufficiently random for security-related functions such as key and nonce creation (CWE-327). Use a more secure technique for acquiring random values. #define srandom srand data/dnstracer-1.9/dnstracer.c:40:21: [3] (random) srand: This function is not sufficiently random for security-related functions such as key and nonce creation (CWE-327). Use a more secure technique for acquiring random values. #define srandom srand data/dnstracer-1.9/dnstracer.c:947:48: [3] (random) random: This function is not sufficiently random for security-related functions such as key and nonce creation (CWE-327). Use a more secure technique for acquiring random values. session->send_header->identification = random() & 0x7F7F; data/dnstracer-1.9/dnstracer.c:1538:18: [3] (buffer) getopt: Some older implementations do not protect against internal buffer overflows (CWE-120, CWE-20). Check implementation on installation, or limit the size of all string inputs. while ((ch = getopt(argc, argv, "4cCoq:r:S:s:t:v")) != -1) { data/dnstracer-1.9/dnstracer.c:1628:5: [3] (random) srandom: This function is not sufficiently random for security-related functions such as key and nonce creation (CWE-327). Use a more secure technique for acquiring random values. srandom(time(NULL)); data/dnstracer-1.9/getopt.c:56:5: [3] (buffer) getopt: Some older implementations do not protect against internal buffer overflows (CWE-120, CWE-20). Check implementation on installation, or limit the size of all string inputs. int getopt (int argc, char *argv[], char *optstring) data/dnstracer-1.9/getopt.h:28:5: [3] (buffer) getopt: Some older implementations do not protect against internal buffer overflows (CWE-120, CWE-20). Check implementation on installation, or limit the size of all string inputs. int getopt (int argc, char *argv[], char *optstring); data/dnstracer-1.9/dnstracer.c:180:1: [2] (buffer) char: Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120). Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length. char *rr_types[256] = { data/dnstracer-1.9/dnstracer.c:261:12: [2] (buffer) char: Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120). Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length. static char hostname[NS_MAXDNAME]; data/dnstracer-1.9/dnstracer.c:271:2: [2] (buffer) strcpy: Does not check for buffer overflows when copying to destination [MS-banned] (CWE-120). Consider using snprintf, strcpy_s, or strlcpy (warning: strncpy easily misused). Risk is low because the source is a constant string. strcpy(hostname, "\1."); data/dnstracer-1.9/dnstracer.c:308:2: [2] (buffer) memcpy: Does not check for buffer overflows when copying to destination (CWE-120). Make sure destination can always hold the source data. memcpy(hostname + strlen(hostname), p, p[0] + 1); data/dnstracer-1.9/dnstracer.c:342:5: [2] (buffer) memcpy: Does not check for buffer overflows when copying to destination (CWE-120). Make sure destination can always hold the source data. memcpy(RR->data, thisrr + 10, RR->datalength); data/dnstracer-1.9/dnstracer.c:378:5: [2] (buffer) memcpy: Does not check for buffer overflows when copying to destination (CWE-120). Make sure destination can always hold the source data. memcpy(header, session->recv_pkt, sizeof(struct dnsheader)); data/dnstracer-1.9/dnstracer.c:441:12: [2] (buffer) char: Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120). Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length. static char hostname[NS_MAXDNAME]; data/dnstracer-1.9/dnstracer.c:450:6: [2] (buffer) strcpy: Does not check for buffer overflows when copying to destination [MS-banned] (CWE-120). Consider using snprintf, strcpy_s, or strlcpy (warning: strncpy easily misused). Risk is low because the source is a constant string. strcpy(hostname, "(0)root"); data/dnstracer-1.9/dnstracer.c:460:6: [2] (buffer) sprintf: Does not check for buffer overflows (CWE-120). Use sprintf_s, snprintf, or vsnprintf. Risk is low because the source has a constant maximum length. sprintf(hostname + strlen(hostname), "(%d)", p[0]); data/dnstracer-1.9/dnstracer.c:505:12: [2] (buffer) char: Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120). Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length. static char retval[NS_MAXDNAME]; data/dnstracer-1.9/dnstracer.c:513:2: [2] (buffer) sprintf: Does not check for buffer overflows (CWE-120). Use sprintf_s, snprintf, or vsnprintf. Risk is low because the source has a constant maximum length. sprintf(retval, "%dw", ttl/(7*24*60*60)); data/dnstracer-1.9/dnstracer.c:517:2: [2] (buffer) sprintf: Does not check for buffer overflows (CWE-120). Use sprintf_s, snprintf, or vsnprintf. Risk is low because the source has a constant maximum length. sprintf(retval + strlen(retval), "%dd", ttl/(24*60*60)); data/dnstracer-1.9/dnstracer.c:521:2: [2] (buffer) sprintf: Does not check for buffer overflows (CWE-120). Use sprintf_s, snprintf, or vsnprintf. Risk is low because the source has a constant maximum length. sprintf(retval + strlen(retval), "%dh", ttl/(60*60)); data/dnstracer-1.9/dnstracer.c:525:2: [2] (buffer) sprintf: Does not check for buffer overflows (CWE-120). Use sprintf_s, snprintf, or vsnprintf. Risk is low because the source has a constant maximum length. sprintf(retval + strlen(retval), "%dm", ttl/(60)); data/dnstracer-1.9/dnstracer.c:529:2: [2] (buffer) sprintf: Does not check for buffer overflows (CWE-120). Use sprintf_s, snprintf, or vsnprintf. Risk is low because the source has a constant maximum length. sprintf(retval + strlen(retval), "%ds", ttl); data/dnstracer-1.9/dnstracer.c:538:12: [2] (buffer) char: Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120). Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length. static char retval[NS_MAXDNAME]; data/dnstracer-1.9/dnstracer.c:547:2: [2] (buffer) sprintf: Does not check for buffer overflows (CWE-120). Use sprintf_s, snprintf, or vsnprintf. Risk is low because the source has a constant maximum length. sprintf(retval, "%hu.%hu.%hu.%hu", data/dnstracer-1.9/dnstracer.c:555:2: [2] (buffer) sprintf: Does not check for buffer overflows (CWE-120). Use sprintf_s, snprintf, or vsnprintf. Risk is low because the source has a constant maximum length. sprintf(retval, data/dnstracer-1.9/dnstracer.c:601:9: [2] (buffer) char: Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120). Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length. static char retval[3*NS_MAXDNAME]; data/dnstracer-1.9/dnstracer.c:602:2: [2] (buffer) char: Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120). Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length. char mname[NS_MAXDNAME]; data/dnstracer-1.9/dnstracer.c:603:2: [2] (buffer) char: Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120). Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length. char rname[NS_MAXDNAME]; data/dnstracer-1.9/dnstracer.c:776:5: [2] (buffer) memcpy: Does not check for buffer overflows when copying to destination (CWE-120). Make sure destination can always hold the source data. memcpy(&nheader, session->send_header, sizeof(struct dnsheader)); data/dnstracer-1.9/dnstracer.c:777:5: [2] (buffer) memcpy: Does not check for buffer overflows when copying to destination (CWE-120). Make sure destination can always hold the source data. memcpy(&nquestion, session->send_question, sizeof(struct dnsquestion)); data/dnstracer-1.9/dnstracer.c:789:5: [2] (buffer) memcpy: Does not check for buffer overflows when copying to destination (CWE-120). Make sure destination can always hold the source data. memcpy(pkt, &nheader, sizeof(struct dnsheader)); data/dnstracer-1.9/dnstracer.c:790:5: [2] (buffer) memcpy: Does not check for buffer overflows when copying to destination (CWE-120). Make sure destination can always hold the source data. memcpy(pkt + sizeof(struct dnsheader), nquestion.query, nquestion.querylength); data/dnstracer-1.9/dnstracer.c:791:5: [2] (buffer) memcpy: Does not check for buffer overflows when copying to destination (CWE-120). Make sure destination can always hold the source data. memcpy(pkt + sizeof(struct dnsheader) + nquestion.querylength, &(nquestion.type), 4); data/dnstracer-1.9/dnstracer.c:895:5: [2] (buffer) char: Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120). Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length. char buffer[2048]; data/dnstracer-1.9/dnstracer.c:923:5: [2] (buffer) memcpy: Does not check for buffer overflows when copying to destination (CWE-120). Make sure destination can always hold the source data. memcpy(session->recv_pkt, buffer, len); data/dnstracer-1.9/dnstracer.c:1013:5: [2] (buffer) char: Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120). Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length. char s[10]; data/dnstracer-1.9/dnstracer.c:1022:6: [2] (buffer) sprintf: Does not check for buffer overflows (CWE-120). Use sprintf_s, snprintf, or vsnprintf. Risk is low because the source has a constant maximum length. sprintf(s, "%%%ds", 40 - i); data/dnstracer-1.9/dnstracer.c:1138:5: [2] (buffer) char: Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120). Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length. char s[NS_MAXDNAME]; data/dnstracer-1.9/dnstracer.c:1287:2: [2] (buffer) char: Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120). Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length. char nextserver_ip[NS_MAXDNAME]; data/dnstracer-1.9/dnstracer.c:1288:2: [2] (buffer) char: Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120). Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length. char nextserver_name[NS_MAXDNAME]; data/dnstracer-1.9/dnstracer.c:1394:4: [2] (buffer) memcpy: Does not check for buffer overflows when copying to destination (CWE-120). Make sure destination can always hold the source data. memcpy(addr_list[i], h->h_addr_list[i], h->h_length); data/dnstracer-1.9/dnstracer.c:1400:8: [2] (buffer) sprintf: Does not check for buffer overflows (CWE-120). Use sprintf_s, snprintf, or vsnprintf. Risk is low because the source has a constant maximum length. sprintf(nextserver_ip, data/dnstracer-1.9/dnstracer.c:1405:8: [2] (buffer) sprintf: Does not check for buffer overflows (CWE-120). Use sprintf_s, snprintf, or vsnprintf. Risk is low because the source has a constant maximum length. sprintf(nextserver_ip, data/dnstracer-1.9/dnstracer.c:1514:5: [2] (buffer) char: Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120). Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length. char ipaddress[NS_MAXDNAME]; data/dnstracer-1.9/dnstracer.c:1515:5: [2] (buffer) char: Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120). Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length. char argv0[NS_MAXDNAME]; data/dnstracer-1.9/dnstracer.c:1561:30: [2] (integer) atoi: Unless checked, the resulting number can exceed the expected range (CWE-190). If source untrusted, check both minimum and maximum, even if the input had no minus sign (large numbers can roll over into negative number; consider saving to an unsigned value if that is intended). if ((global_querytype = atoi(optarg)) < 1) { data/dnstracer-1.9/dnstracer.c:1585:28: [2] (integer) atoi: Unless checked, the resulting number can exceed the expected range (CWE-190). If source untrusted, check both minimum and maximum, even if the input had no minus sign (large numbers can roll over into negative number; consider saving to an unsigned value if that is intended). if ((global_retries = atoi(optarg)) < 1) { data/dnstracer-1.9/dnstracer.c:1605:23: [2] (integer) atoi: Unless checked, the resulting number can exceed the expected range (CWE-190). If source untrusted, check both minimum and maximum, even if the input had no minus sign (large numbers can roll over into negative number; consider saving to an unsigned value if that is intended). global_timeout = atoi(optarg); data/dnstracer-1.9/dnstracer.c:1644:6: [2] (buffer) sprintf: Does not check for buffer overflows (CWE-120). Use sprintf_s, snprintf, or vsnprintf. Risk is low because the source has a constant maximum length. sprintf(ipaddress, "%hu.%hu.%hu.%hu", s[0], s[1], s[2], s[3]); data/dnstracer-1.9/dnstracer.c:1648:6: [2] (buffer) sprintf: Does not check for buffer overflows (CWE-120). Use sprintf_s, snprintf, or vsnprintf. Risk is low because the source has a constant maximum length. sprintf(ipaddress, data/dnstracer-1.9/dnstracer.c:232:12: [1] (buffer) strlen: Does not handle strings that are not \0-terminated; if given one it may perform an over-read (it could cause a crash if unprotected) (CWE-126). lenl = strlen(little); data/dnstracer-1.9/dnstracer.c:233:12: [1] (buffer) strlen: Does not handle strings that are not \0-terminated; if given one it may perform an over-read (it could cause a crash if unprotected) (CWE-126). lenb = strlen(big); data/dnstracer-1.9/dnstracer.c:308:20: [1] (buffer) strlen: Does not handle strings that are not \0-terminated; if given one it may perform an over-read (it could cause a crash if unprotected) (CWE-126). memcpy(hostname + strlen(hostname), p, p[0] + 1); data/dnstracer-1.9/dnstracer.c:452:6: [1] (buffer) strcpy: Does not check for buffer overflows when copying to destination [MS-banned] (CWE-120). Consider using snprintf, strcpy_s, or strlcpy (warning: strncpy easily misused). Risk is low because the source is a constant character. strcpy(hostname, "."); data/dnstracer-1.9/dnstracer.c:460:25: [1] (buffer) strlen: Does not handle strings that are not \0-terminated; if given one it may perform an over-read (it could cause a crash if unprotected) (CWE-126). sprintf(hostname + strlen(hostname), "(%d)", p[0]); data/dnstracer-1.9/dnstracer.c:462:6: [1] (buffer) strcat: Does not check for buffer overflows when concatenating to destination [MS-banned] (CWE-120). Consider using strcat_s, strncat, strlcat, or snprintf (warning: strncat is easily misused). Risk is low because the source is a constant character. strcat(hostname, "."); data/dnstracer-1.9/dnstracer.c:465:21: [1] (buffer) strlen: Does not handle strings that are not \0-terminated; if given one it may perform an over-read (it could cause a crash if unprotected) (CWE-126). sprintf(hostname + strlen(hostname), "%s", p + 1); data/dnstracer-1.9/dnstracer.c:517:19: [1] (buffer) strlen: Does not handle strings that are not \0-terminated; if given one it may perform an over-read (it could cause a crash if unprotected) (CWE-126). sprintf(retval + strlen(retval), "%dd", ttl/(24*60*60)); data/dnstracer-1.9/dnstracer.c:521:19: [1] (buffer) strlen: Does not handle strings that are not \0-terminated; if given one it may perform an over-read (it could cause a crash if unprotected) (CWE-126). sprintf(retval + strlen(retval), "%dh", ttl/(60*60)); data/dnstracer-1.9/dnstracer.c:525:19: [1] (buffer) strlen: Does not handle strings that are not \0-terminated; if given one it may perform an over-read (it could cause a crash if unprotected) (CWE-126). sprintf(retval + strlen(retval), "%dm", ttl/(60)); data/dnstracer-1.9/dnstracer.c:529:19: [1] (buffer) strlen: Does not handle strings that are not \0-terminated; if given one it may perform an over-read (it could cause a crash if unprotected) (CWE-126). sprintf(retval + strlen(retval), "%ds", ttl); data/dnstracer-1.9/dnstracer.c:959:43: [1] (buffer) strlen: Does not handle strings that are not \0-terminated; if given one it may perform an over-read (it could cause a crash if unprotected) (CWE-126). session->send_question->querylength = strlen(name) + 2; data/dnstracer-1.9/dnstracer.c:1312:3: [1] (buffer) strlen: Does not handle strings that are not \0-terminated; if given one it may perform an over-read (it could cause a crash if unprotected) (CWE-126). strlen(server_authfor)) != 0) { data/dnstracer-1.9/dnstracer.c:1623:15: [1] (buffer) strlen: Does not handle strings that are not \0-terminated; if given one it may perform an over-read (it could cause a crash if unprotected) (CWE-126). if (argv0[strlen(argv[0]) - 1] == '.') argv0[strlen(argv[0]) - 1] = 0; data/dnstracer-1.9/dnstracer.c:1623:50: [1] (buffer) strlen: Does not handle strings that are not \0-terminated; if given one it may perform an over-read (it could cause a crash if unprotected) (CWE-126). if (argv0[strlen(argv[0]) - 1] == '.') argv0[strlen(argv[0]) - 1] = 0; ANALYSIS SUMMARY: Hits = 83 Lines analyzed = 1994 in approximately 0.08 seconds (26565 lines/second) Physical Source Lines of Code (SLOC) = 1364 Hits@level = [0] 94 [1] 15 [2] 43 [3] 8 [4] 17 [5] 0 Hits@level+ = [0+] 177 [1+] 83 [2+] 68 [3+] 25 [4+] 17 [5+] 0 Hits/KSLOC@level+ = [0+] 129.765 [1+] 60.8504 [2+] 49.8534 [3+] 18.3284 [4+] 12.4633 [5+] 0 Dot directories skipped = 1 (--followdotdir overrides) Minimum risk level = 1 Not every hit is necessarily a security vulnerability. There may be other security vulnerabilities; review your code! See 'Secure Programming HOWTO' (https://dwheeler.com/secure-programs) for more information.