else if(CONF_VAR_IS(var, "SPA_SERVER_PORT"))
{
tmpint = atoi(val);
- if(tmpint < 0 || tmpint > 65535)
+ if(tmpint < 0 || tmpint > MAX_PORT)
return(-1);
else
options->spa_dst_port = tmpint;
else if(CONF_VAR_IS(var, "SPA_SOURCE_PORT"))
{
tmpint = atoi(val);
- if(tmpint < 0 || tmpint > 65535)
+ if(tmpint < 0 || tmpint > MAX_PORT)
return(-1);
else
options->spa_src_port = tmpint;
else if(strcasecmp(val, "resolve") == 0)
options->resolve_ip_http = 1;
else /* Assume IP address */
- strlcpy(options->allow_ip_str, val, MAX_IP_STR_LEN);
+ strlcpy(options->allow_ip_str, val, MAX_IPV4_STR_LEN);
}
/* Time Offset */
else if(CONF_VAR_IS(var, "TIME_OFFSET"))
/* Spoof Source IP */
else if(CONF_VAR_IS(var, "SPOOF_SOURCE_IP"))
{
- strlcpy(options->spoof_ip_src_str, val, MAX_IP_STR_LEN);
+ strlcpy(options->spoof_ip_src_str, val, MAX_IPV4_STR_LEN);
}
/* ACCESS request */
else if(CONF_VAR_IS(var, "ACCESS"))
else if(CONF_VAR_IS(var, "NAT_PORT"))
{
tmpint = atoi(val);
- if(tmpint < 0 || tmpint > 65535)
+ if(tmpint < 0 || tmpint > MAX_PORT)
return(-1);
else
options->nat_port = tmpint;
/* Reset the options index so we can run through them again.
*/
optind = 0;
-
+
while ((cmd_arg = getopt_long(argc, argv,
GETOPTS_OPTION_STRING, cmd_opts, &index)) != -1) {
switch(cmd_arg) {
case 'a':
- strlcpy(options->allow_ip_str, optarg, MAX_IP_STR_LEN);
+ strlcpy(options->allow_ip_str, optarg, MAX_IPV4_STR_LEN);
break;
case 'A':
strlcpy(options->access_str, optarg, MAX_LINE_LEN);
break;
case 'p':
options->spa_dst_port = atoi(optarg);
- if (options->spa_dst_port < 0 || options->spa_dst_port > 65535)
+ if (options->spa_dst_port < 0 || options->spa_dst_port > MAX_PORT)
{
fprintf(stderr, "Unrecognized port: %s\n", optarg);
exit(EXIT_FAILURE);
}
break;
case 'Q':
- strlcpy(options->spoof_ip_src_str, optarg, MAX_IP_STR_LEN);
+ strlcpy(options->spoof_ip_src_str, optarg, MAX_IPV4_STR_LEN);
break;
case 'r':
options->rand_port = 1;
options->show_last_command = 1;
break;
case 's':
- strlcpy(options->allow_ip_str, "0.0.0.0", MAX_IP_STR_LEN);
+ strlcpy(options->allow_ip_str, "0.0.0.0", MAX_IPV4_STR_LEN);
break;
case 'S':
options->spa_src_port = atoi(optarg);
- if (options->spa_src_port < 0 || options->spa_src_port > 65535)
+ if (options->spa_src_port < 0 || options->spa_src_port > MAX_PORT)
{
fprintf(stderr, "Unrecognized port: %s\n", optarg);
exit(EXIT_FAILURE);
break;
case NAT_PORT:
options->nat_port = atoi(optarg);
- if (options->nat_port < 0 || options->nat_port > 65535)
+ if (options->nat_port < 0 || options->nat_port > MAX_PORT)
{
fprintf(stderr, "Unrecognized port: %s\n", optarg);
exit(EXIT_FAILURE);
int run_last_command;
int no_save_args;
char spa_server_str[MAX_SERVER_STR_LEN]; /* may be a hostname */
- char allow_ip_str[MAX_IP_STR_LEN];
- char spoof_ip_src_str[MAX_IP_STR_LEN];
+ char allow_ip_str[MAX_IPV4_STR_LEN];
+ char spoof_ip_src_str[MAX_IPV4_STR_LEN];
char spoof_user[MAX_USERNAME_LEN];
int rand_port;
char gpg_recipient_key[MAX_GPG_KEY_ID];
if(e_ndx != NULL)
{
port = atoi(e_ndx+1);
- if(port < 1 || port > 65535)
+ if(port < 1 || port > MAX_PORT)
{
fprintf(stderr, "resolve-url port value is invalid.\n");
return(-1);
* Note: We are expecting the content to be just an IP address
* (possibly followed by whitespace or other not-digit value).
*/
- for(i=0; i<MAX_IP_STR_LEN; i++) {
+ for(i=0; i<MAX_IPV4_STR_LEN; i++) {
if(! isdigit(*(ndx+i)) && *(ndx+i) != '.')
break;
}
&& o3 >= 0 && o3 <= 255
&& o4 >= 0 && o4 <= 255)
{
- strlcpy(options->allow_ip_str, ndx, MAX_IP_STR_LEN);
+ strlcpy(options->allow_ip_str, ndx, MAX_IPV4_STR_LEN);
if(options->verbose)
printf("Resolved external IP (via http://%s%s) as: %s\n",
#define MAX_PORT 65535
#define MAX_PORT_STR_LEN 6
#define MAX_PROTO_STR_LEN 6
-#define MAX_IP_STR_LEN 16
-#define MIN_IP_STR_LEN 9
+#define MAX_IPV4_STR_LEN 16
+#define MIN_IPV4_STR_LEN 9
#define MAX_SERVER_STR_LEN 50
#define MAX_LINE_LEN 1024
Specify IP address that should be permitted through the destination
*fwknopd* server firewall (this IP is encrypted within the SPA packet
itself). This is useful to prevent a MTIM attack where a SPA packet
- can be intercepted enroute and sent from a different IP than the
+ can be intercepted en-route and sent from a different IP than the
original. Hence, if the *fwknopd* server trusts the source address
on the SPA packet IP header then the attacker gains access.
The *-a* option puts the source address within the encrypted SPA
The '.fwknoprc' file contains a default configuration area or stanza which
holds global configuration directives that override the program defaults.
-You can edit this file and create additonal 'named stanzas' that can be
+You can edit this file and create additional 'named stanzas' that can be
specified with the *-n* or *--named-config* option. Parameters defined in
the named stanzas will override any matching 'default' stanza directives.
Note that command-line options will still override any corresponding
The main configuration for *fwknopd* is maintained within two files:
'fwknopd.conf' and 'access.conf'. The default location for these files
is determined at package configuration (typically '@sysconfdir@/fwknop')The
-configuration variables within these files are desribed below.
+configuration variables within these files are described below.
COMMAND-LINE OPTIONS
*ACCESS_EXPIRE_EPOCH* '<seconds>'::
Defines an expiration date for the access stanza as the epoch time, and is
useful if a more accurate expiration time needs to be given than the day
- resolution offered by the ACCESS_EXPIRE variable agove. All SPA packets
+ resolution offered by the ACCESS_EXPIRE variable above. All SPA packets
that match an expired stanza will be ignored. This parameter is optional.
*ENABLE_DIGEST_PERSISTENCE* '<Y/N>'::
when ``ENABLE_IPT_SNAT'' is set to ``Y'' and by default SNAT rules are
built with the MASQUERADE target (since then the internal IP does not
have to be defined here in the 'fwknopd.conf' file), but if you want
- *fwknopd* to use the SNAT target, you mus also define an IP address with
+ *fwknopd* to use the SNAT target, you must also define an IP address with
the ``SNAT_TRANSLATE_IP'' variable.
*ENABLE_IPT_OUTPUT* '<Y/N>'::
encrypted message.
The 'access.conf' variables described below provide the access directives
-for the SPA packets with a source (or embeded request) IP that matches an
+for the SPA packets with a source (or embedded request) IP that matches an
address or network range defined by the ``SOURCE'' variable. All variables
following ``SOURCE'' apply to the source 'stanza'. Each ``SOURCE''
directive starts a new stanza.
to be used to automatically resolve the external address (if the
client behind a NAT) or the client must know the external IP.
+*FORCE_NAT*: '<IP> <PORT>'::
+ For any valid SPA packet, force the requested connection to be NAT'd
+ through to the specified (usually internal) IP and port value. This is
+ useful if there are multiple internal systems running a service such as
+ SSHD, and you want to give transparent access to only one internal system
+ for each stanza in the access.conf file. This way, multiple external
+ users can each directly access only one internal system per SPA key.
+
*GPG_HOME_DIR*: '<path>'::
Define the path to the GnuPG directory to be used by the *fwknopd*
server. If this keyword is not specified within 'access.conf' then
SEE ALSO
--------
-fwknop(8), iptables(8), libfko docmentation.
+fwknop(8), iptables(8), libfko documentation.
AUTHOR
memset(&tm, 0, sizeof(struct tm));
- if (sscanf(val, "%d/%d/%d", &tm.tm_mon, &tm.tm_mday, &tm.tm_year) != 3)
+ if (sscanf(val, "%2d/%2d/%4d", &tm.tm_mon, &tm.tm_mday, &tm.tm_year) != 3)
{
log_msg(LOG_ERR,
- "Fatal invalid epoch seconds value for access stanza expiration time"
+ "Fatal: invalid date value '%s' (need MM/DD/YYYY) for access stanza expiration time",
+ val
);
clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE);
}
if (errno == ERANGE || (errno != 0 && expire_time == 0))
{
log_msg(LOG_ERR,
- "Fatal invalid epoch seconds value for access stanza expiration time"
+ "Fatal: invalid epoch seconds value '%s' for access stanza expiration time",
+ val
);
clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE);
}
return;
}
+static void
+add_acc_force_nat(fko_srv_options_t *opts, acc_stanza_t *curr_acc, const char *val)
+{
+ char ip_str[MAX_IPV4_STR_LEN] = {0};
+
+ if (sscanf(val, "%15s %5u", ip_str, &curr_acc->force_nat_port) != 2)
+ {
+
+ log_msg(LOG_ERR,
+ "Fatal: invalid FORCE_NAT arg '%s', need <IP> <PORT>",
+ val
+ );
+ clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE);
+ }
+
+ if (curr_acc->force_nat_port > MAX_PORT)
+ {
+ log_msg(LOG_ERR,
+ "Fatal: invalid FORCE_NAT port '%d'", curr_acc->force_nat_port);
+ clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE);
+ }
+
+ curr_acc->force_nat = 1;
+ add_acc_string(&(curr_acc->force_nat_ip), ip_str);
+
+ return;
+}
+
/* Take an IP or Subnet/Mask and convert it to mask for later
* comparisons of incoming source IPs against this mask.
*/
add_source_mask(acc_stanza_t *acc, const char *ip)
{
char *ndx;
- char ip_str[16] = {0};
+ char ip_str[MAX_IPV4_STR_LEN] = {0};
uint32_t mask;
struct in_addr in;
free_acc_port_list(acc->rport_list);
}
+ if(acc->force_nat_ip != NULL)
+ free(acc->force_nat_ip);
+
if(acc->key != NULL)
free(acc->key);
{
add_acc_expire_time_epoch(opts, &(curr_acc->access_expire_time), val);
}
+ else if(CONF_VAR_IS(var, "FORCE_NAT"))
+ {
+ if(strncasecmp(opts->config[CONF_ENABLE_IPT_FORWARDING], "Y", 1) !=0 )
+ {
+ fprintf(stderr,
+ "[*] FORCE_NAT requires ENABLE_IPT_FORWARDING to be enabled in fwknopd.conf\n");
+ clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE);
+ }
+ add_acc_force_nat(opts, curr_acc, val);
+ }
else
{
fprintf(stderr,
int fw_cleanup(const fko_srv_options_t *opts);
void check_firewall_rules(const fko_srv_options_t *opts);
int fw_dump_rules(const fko_srv_options_t *opts);
-int process_spa_request(const fko_srv_options_t *opts, spa_data_t *spdat);
+int process_spa_request(const fko_srv_options_t *opts, const acc_stanza_t *acc, spa_data_t *spdat);
#endif /* FW_UTIL_H */
/* Rule Processing - Create an access request...
*/
int
-process_spa_request(const fko_srv_options_t *opts, spa_data_t *spadat)
+process_spa_request(const fko_srv_options_t *opts, const acc_stanza_t *acc, spa_data_t *spadat)
{
/* TODO: Implement me */
- char nat_ip[16] = {0};
+ char nat_ip[MAX_IPV4_STR_LEN] = {0};
char *ndx;
unsigned int nat_port = 0;;
/* Rule Processing - Create an access request...
*/
int
-process_spa_request(const fko_srv_options_t *opts, spa_data_t *spadat)
+process_spa_request(const fko_srv_options_t *opts, const acc_stanza_t *acc, spa_data_t *spadat)
{
unsigned short rule_num;
/* Rule Processing - Create an access request...
*/
int
-process_spa_request(const fko_srv_options_t *opts, spa_data_t *spadat)
+process_spa_request(const fko_srv_options_t *opts, const acc_stanza_t *acc, spa_data_t *spadat)
{
- char nat_ip[16] = {0};
+ char nat_ip[MAX_IPV4_STR_LEN] = {0};
char snat_target[SNAT_TARGET_BUFSIZE] = {0};
char *ndx;
/* For straight access requests, we currently support multiple proto/port
* request.
*/
- if(spadat->message_type == FKO_ACCESS_MSG
- || spadat->message_type == FKO_CLIENT_TIMEOUT_ACCESS_MSG)
+ if((spadat->message_type == FKO_ACCESS_MSG
+ || spadat->message_type == FKO_CLIENT_TIMEOUT_ACCESS_MSG) && !acc->force_nat)
{
/* Check to make sure that the jump rules exist for each
in_chain->next_expire = exp_ts;
}
else
- log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf);
+ log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf);
/* If we have to make an corresponding OUTPUT rule if out_chain target
* is not NULL.
else if(spadat->message_type == FKO_LOCAL_NAT_ACCESS_MSG
|| spadat->message_type == FKO_CLIENT_TIMEOUT_LOCAL_NAT_ACCESS_MSG
|| spadat->message_type == FKO_NAT_ACCESS_MSG
- || spadat->message_type == FKO_CLIENT_TIMEOUT_NAT_ACCESS_MSG )
+ || spadat->message_type == FKO_CLIENT_TIMEOUT_NAT_ACCESS_MSG
+ || acc->force_nat)
{
/* Parse out the NAT IP and Port components.
*/
- ndx = strchr(spadat->nat_access, ',');
- if(ndx != NULL)
+ if(acc->force_nat)
{
- strlcpy(nat_ip, spadat->nat_access, (ndx-spadat->nat_access)+1);
- nat_port = atoi(ndx+1);
+ strlcpy(nat_ip, acc->force_nat_ip, MAX_IPV4_STR_LEN);
+ nat_port = acc->force_nat_port;
+ }
+ else
+ {
+ ndx = strchr(spadat->nat_access, ',');
+ if(ndx != NULL)
+ {
+ strlcpy(nat_ip, spadat->nat_access, (ndx-spadat->nat_access)+1);
+ nat_port = atoi(ndx+1);
+ }
}
-
-// --DSS temp
-//fprintf(stderr, "NAT IP: '%s', NAT PORT: '%i'\n", nat_ip, nat_port);
/* Make our FORWARD and NAT rules
*/
/* Rule Processing - Create an access request...
*/
int
-process_spa_request(const fko_srv_options_t *opts, spa_data_t *spadat)
+process_spa_request(const fko_srv_options_t *opts, const acc_stanza_t *acc, spa_data_t *spadat)
{
char new_rule[MAX_PF_NEW_RULE_LEN];
char write_cmd[CMD_BUFSIZE];
if(strncasecmp(opts.config[CONF_ENABLE_TCP_SERVER], "Y", 1) == 0)
{
if(atoi(opts.config[CONF_TCPSERV_PORT]) <= 0
- || atoi(opts.config[CONF_TCPSERV_PORT]) > 65535)
+ || atoi(opts.config[CONF_TCPSERV_PORT]) > MAX_PORT)
{
log_msg(LOG_WARNING,
"WARNING: ENABLE_TCP_SERVER is set, but TCPSERV_PORT is not valid. TCP server not started!"
acc_string_list_t *gpg_remote_id_list;
time_t access_expire_time;
int expired;
+ unsigned char force_nat;
+ char *force_nat_ip;
+ char *force_nat_proto;
+ unsigned int force_nat_port;
struct acc_stanza *next;
} acc_stanza_t;
char *version;
short message_type;
char *spa_message;
- char spa_message_src_ip[MAX_IP_STR_LEN];
- char pkt_source_ip[MAX_IP_STR_LEN];
+ char spa_message_src_ip[MAX_IPV4_STR_LEN];
+ char pkt_source_ip[MAX_IPV4_STR_LEN];
char spa_message_remain[1024]; /* --DSS FIXME: arbitrary bounds */
char *nat_access;
char *server_auth;
* access stanza loop (first valid access stanza stops us looking
* for others).
*/
- process_spa_request(opts, &spadat);
+ process_spa_request(opts, acc, &spadat);
if(ctx != NULL)
fko_destroy(ctx);
break;
fd_set sfd_set;
struct sockaddr_in saddr, caddr;
struct timeval tv;
- char sipbuf[MAX_IP_STR_LEN];
+ char sipbuf[MAX_IPV4_STR_LEN];
unsigned short port = atoi(opts->config[CONF_TCPSERV_PORT]);
if(opts->verbose)
{
- memset(sipbuf, 0x0, MAX_IP_STR_LEN);
- inet_ntop(AF_INET, &(caddr.sin_addr.s_addr), sipbuf, MAX_IP_STR_LEN);
+ memset(sipbuf, 0x0, MAX_IPV4_STR_LEN);
+ inet_ntop(AF_INET, &(caddr.sin_addr.s_addr), sipbuf, MAX_IPV4_STR_LEN);
log_msg(LOG_INFO, "tcp_server: Got TCP connection from %s.", sipbuf);
}
SOURCE: ANY;
KEY: fwknoptest;
FW_ACCESS_TIMEOUT: 3;
-ACCESS_EXPIRE: 3/10/1999; ### very old
+ACCESS_EXPIRE: 3/10/01; ### very old
--- /dev/null
+SOURCE: ANY;
+KEY: fwknoptest;
+FW_ACCESS_TIMEOUT: 3;
+FORCE_NAT: 192.168.1.123 22;
--- /dev/null
+SOURCE: ANY;
+KEY: fwknoptest;
+FW_ACCESS_TIMEOUT: 3;
+ACCESS_EXPIRE: 3/10/2500; ### way in the future
--- /dev/null
+SOURCE: ANY;
+KEY: fwknoptest;
+FW_ACCESS_TIMEOUT: 3;
+ACCESS_EXPIRE: 3-10-01; ### / separators required
my $default_conf = "$conf_dir/default_fwknopd.conf";
my $default_access_conf = "$conf_dir/default_access.conf";
my $expired_access_conf = "$conf_dir/expired_stanza_access.conf";
+my $future_expired_access_conf = "$conf_dir/future_expired_stanza_access.conf";
my $expired_epoch_access_conf = "$conf_dir/expired_epoch_stanza_access.conf";
+my $invalid_expire_access_conf = "$conf_dir/invalid_expire_access.conf";
+my $force_nat_access_conf = "$conf_dir/force_nat_access.conf";
my $gpg_access_conf = "$conf_dir/gpg_access.conf";
my $default_digest_file = "$run_dir/digest.cache";
my $default_pid_file = "$run_dir/fwknopd.pid";
my $loopback_ip = '127.0.0.1';
my $fake_ip = '127.0.0.2';
my $internal_nat_host = '192.168.1.2';
+my $force_nat_host = '192.168.1.123';
my $default_spa_port = 62201;
my $non_std_spa_port = 12345;
{
'category' => 'Rijndael SPA',
'subcategory' => 'client+server',
+ 'detail' => 'invalid expire date (tcp/22 ssh)',
+ 'err_msg' => 'SPA packet accepted',
+ 'function' => \&spa_cycle,
+ 'cmdline' => $default_client_args,
+ 'fwknopd_cmdline' => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+ "$fwknopdCmd -c $default_conf -a $invalid_expire_access_conf " .
+ "-d $default_digest_file -p $default_pid_file $intf_str",
+ 'server_positive_output_matches' => [qr/invalid\sdate\svalue/],
+ 'fw_rule_created' => $REQUIRE_NO_NEW_RULE,
+ 'fatal' => $NO
+ },
+ {
+ 'category' => 'Rijndael SPA',
+ 'subcategory' => 'client+server',
'detail' => 'expired epoch stanza (tcp/22 ssh)',
'err_msg' => 'SPA packet accepted',
'function' => \&spa_cycle,
'fw_rule_created' => $REQUIRE_NO_NEW_RULE,
'fatal' => $NO
},
+ {
+ 'category' => 'Rijndael SPA',
+ 'subcategory' => 'client+server',
+ 'detail' => 'future expired stanza (tcp/22 ssh)',
+ 'err_msg' => 'SPA packet not accepted',
+ 'function' => \&spa_cycle,
+ 'cmdline' => $default_client_args,
+ 'fwknopd_cmdline' => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+ "$fwknopdCmd -c $default_conf -a $future_expired_access_conf " .
+ "-d $default_digest_file -p $default_pid_file $intf_str",
+ 'fw_rule_created' => $NEW_RULE_REQUIRED,
+ 'fw_rule_removed' => $NEW_RULE_REMOVED,
+ 'fatal' => $NO
+ },
{
'category' => 'Rijndael SPA',
'fwknopd_cmdline' => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
"$fwknopdCmd -c $nat_conf -a $open_ports_access_conf " .
"-d $default_digest_file -p $default_pid_file $intf_str",
+ 'server_positive_output_matches' => [qr/to\:$internal_nat_host\:22/i],
+ 'fw_rule_created' => $NEW_RULE_REQUIRED,
+ 'fw_rule_removed' => $NEW_RULE_REMOVED,
+ 'server_conf' => $nat_conf,
+ 'fatal' => $NO
+ },
+
+ {
+ 'category' => 'Rijndael SPA',
+ 'subcategory' => 'client+server',
+ 'detail' => "force NAT $force_nat_host (tcp/22 ssh)",
+ 'err_msg' => "could not complete NAT SPA cycle",
+ 'function' => \&spa_cycle,
+ 'cmdline' => $default_client_args,
+ 'fwknopd_cmdline' => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+ "$fwknopdCmd -c $nat_conf -a $force_nat_access_conf " .
+ "-d $default_digest_file -p $default_pid_file $intf_str",
+ 'server_positive_output_matches' => [qr/to\:$force_nat_host\:22/i],
+ 'server_negative_output_matches' => [qr/to\:$internal_nat_host\:22/i],
'fw_rule_created' => $NEW_RULE_REQUIRED,
'fw_rule_removed' => $NEW_RULE_REMOVED,
'server_conf' => $nat_conf,
'fw_rule_removed' => $NEW_RULE_REMOVED,
'fatal' => $NO
},
+
+ {
+ 'category' => 'Rijndael SPA',
+ 'subcategory' => 'client+server',
+ 'detail' => 'random SPA port (tcp/22 ssh)',
+ 'err_msg' => 'could not complete SPA cycle',
+ 'function' => \&spa_cycle,
+ 'cmdline' => "$default_client_args -r",
+ 'fwknopd_cmdline' => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+ "$fwknopdCmd $default_server_conf_args $intf_str " .
+ qq|-P "udp"|,
+ 'fw_rule_created' => $NEW_RULE_REQUIRED,
+ 'fw_rule_removed' => $NEW_RULE_REMOVED,
+ 'fatal' => $NO
+ },
+
{
'category' => 'Rijndael SPA',
'subcategory' => 'client+server',
$multi_stanzas_access_conf,
$expired_access_conf,
$expired_epoch_access_conf,
+ $future_expired_access_conf,
+ $invalid_expire_access_conf,
+ $force_nat_access_conf,
) {
die "[*] $file does not exist" unless -e $file;
}