/*
* $Id: mirror.c,v 1.10 2002/10/17 20:02:31 ljb Exp $
* originally Id: mirror.c,v 1.26 1998/08/03 21:55:23 gerald Exp
*/
#include <stdio.h>
#include <string.h>
#include "mrt.h"
#include "trace.h"
#include <time.h>
#include <signal.h>
#include "config_file.h"
#include "select.h"
#include <ctype.h>
#include "irrd.h"
#include "irrd_prototypes.h"
extern trace_t *default_trace;
static int mirror_read_data ();
int dump_serial_updates (irr_connection_t *irr, char * dbname, int journal_ext,
u_long from, u_long to);
int build_update (char *file, irr_connection_t *irr, FILE *fp, u_long curr_serial,
u_long to);
int valid_start_line (irr_database_t *db, FILE *fp, u_long *serial_num);
int request_mirror (irr_database_t *db, uii_connection_t *uii, int last) {
char tmp[BUFSIZE], name[BUFSIZE], logfile[BUFSIZE];
struct timeval tv;
fd_set write_fds;
int n, i, ret;
if (db->db_fp == NULL) {
trace (ERROR, default_trace,
"Aborting mirror -- we don't have an open file pointer for %s\n",
db->name);
return (-1);
}
if (last > 0 &&
(db->serial_number + 1) > last) {
trace (ERROR, default_trace,
" user supplied serial # last (%d) is <= current serial (%d)\n",
last, db->serial_number);
return (-1);
}
db->mirror_error_message[0] = '\0';
/*
* Check if already mirroring.
* If so, check if we want to time out old attempt
*/
if (db->mirror_fd != -1) {
if ( (time (NULL) - db->mirror_started) < MIRROR_TIMEOUT) {
trace (ERROR, default_trace, "(%s) already mirroring...!\n",
db->name);
/*irr_unlock (db);*/
return (-1);
}
/*irr_unlock (db);*/
trace (ERROR, default_trace, "**** TIMING OUT old mirror attempt to %s\n",
db->name);
strcpy (db->mirror_error_message, "Mirroring timed out...");
select_delete_fd (db->mirror_fd);
fclose (db->mirror_disk_fp);
db->mirror_disk_fp = NULL;
db->mirror_fd = -1;
return (-1);
}
/*irr_unlock (db);*/
if ((db->mirror_fd = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
trace (ERROR, default_trace, "Could not get socket: %s!\n",
strerror (errno));
db->mirror_fd = -1;
return(0);
}
/* non blocking connect */
trace (NORM, default_trace, "(%s) Connecting to Mirror %s:%d\n",
db->name, prefix_toa (db->mirror_prefix), db->mirror_port);
n = nonblock_connect (default_trace, db->mirror_prefix,
db->mirror_port, db->mirror_fd);
if (n !=1 ) {
trace (ERROR, default_trace, "(%s) Connect to mirror %s:%d failed\n",
db->name, prefix_toa (db->mirror_prefix), db->mirror_port);
close (db->mirror_fd);
db->mirror_fd = -1;
return (-1);
}
strcpy (name, db->name);
convert_toupper(name);
if (last == 0)
sprintf (tmp, "-g %s:1:%ld-LAST\n", name, db->serial_number+1);
else
sprintf (tmp, "-g %s:1:%ld-%d\n", name, db->serial_number+1, last);
trace (NORM, default_trace, "(%s) Requesting mirror: %s",
db->name, tmp);
sprintf (logfile, "%s/.%s.mirror", IRR.database_dir, db->name);
trace (NORM, default_trace, " (%s) Saving updates to %s\n",
db->name, logfile);
if ((db->mirror_disk_fp = fopen (logfile, "w+")) == NULL) {
trace (NORM, default_trace, " (%s) Error opening %s (%s)\n",
db->name, logfile, strerror (errno));
close (db->mirror_fd);
db->mirror_fd = -1;
return(0);
}
/* JMH - I think this section of code is redundant. nonblock_connect
already checks to see if the connection is ready to be written to */
/* check if mirror server is ready for writing */
tv.tv_sec = 1; /* one second is plenty */
tv.tv_usec = 0;
FD_ZERO(&write_fds);
FD_SET(db->mirror_fd, &write_fds);
ret = select (FD_SETSIZE, NULL, &write_fds, NULL, &tv);
if (ret <= 0) {
trace (NORM, default_trace, " (%s) Error writing request (timeout) to %s:%d\n",
db->name, prefix_toa (db->mirror_prefix), db->mirror_port);
close (db->mirror_fd);
fclose (db->mirror_disk_fp); /* and delete tmp file??? */
db->mirror_fd = -1;
return 0;
}
/* write request to mirror server */
if (write (db->mirror_fd, tmp, strlen (tmp)) != strlen (tmp)) {
trace (NORM, default_trace, " (%s) Error writing request (write failed) to %s:%d\n",
db->name, prefix_toa (db->mirror_prefix), db->mirror_port);
close (db->mirror_fd);
fclose (db->mirror_disk_fp); /* and delete tmp file??? */
db->mirror_fd = -1;
return 0;
}
/* reset statistics */
db->num_changes = 0;
for (i=0; i<= IRR_MAX_KEYS; i++) {
db->num_objects_deleted[i] = 0;
db->num_objects_changed[i] = 0;
db->num_objects_notfound[i] = 0;
}
db->mirror_update_size = 0;
/* save when we started, so we can check for a timeout */
db->mirror_started = time (NULL);
trace (TRACE, default_trace, " (%s) About to perform a select_add_fd\n",
db->name);
select_add_fd (db->mirror_fd, SELECT_READ, (void_fn_t) mirror_read_data, db);
return (1);
}
static int mirror_read_data (irr_database_t *database) {
struct timeval tv;
fd_set read_fds;
char tmp[MIRROR_BUFFER];
int ret, n, valid_start_line_ret;
tv.tv_sec = 0;
tv.tv_usec = 0;
FD_ZERO(&read_fds);
FD_SET(database->mirror_fd, &read_fds);
while (1) {
ret = select (FD_SETSIZE, &read_fds, NULL, NULL, &tv);
if (ret <= 0) {
select_enable_fd (database->mirror_fd);
return 0;
}
/* If read = 0, connection is close and we are done */
if ((n = read (database->mirror_fd, tmp, MIRROR_BUFFER)) <= 0) break;
database->mirror_update_size += n;
if (fwrite (tmp, 1, n, database->mirror_disk_fp) != n) {
trace (NORM, default_trace, " (%s) Mirroring Error: Failed on writing mirroring data to disk.\n", database->name);
fclose (database->mirror_disk_fp);
select_delete_fd (database->mirror_fd);
database->mirror_fd = -1;
return (-1);
}
select_enable_fd (database->mirror_fd);
}
trace (NORM, default_trace, " (%s) Read %d bytes\n",
database->name, database->mirror_update_size);
if ((valid_start_line_ret = valid_start_line (database,
database->mirror_disk_fp,
&database->new_serial_number)) > 0) {
irr_update_lock (database);
if (scan_irr_file (database, "mirror", 2, database->mirror_disk_fp) != NULL) {
trace (NORM, default_trace,
" (%s) Mirroring Error: Serial number unchanged: %d\n",
database->name, database->serial_number);
irr_update_unlock (database);
fclose (database->mirror_disk_fp);
select_delete_fd (database->mirror_fd);
database->mirror_fd = -1;
return (-1);
}
select_delete_fd (database->mirror_fd);
database->mirror_fd = -1;
database->mirror_error_message[0] = '\0';
trace (NORM, default_trace,
" (%s) Serial number now %d, Mirroring header said: %d\n",
database->name, database->serial_number, database->new_serial_number);
irr_update_unlock (database);
database->last_mirrored = time (NULL);
trace (NORM, default_trace, " (%s) Route Objects %4d changed, %4d deleted\n",
database->name,
database->num_objects_changed[ROUTE],
database->num_objects_deleted[ROUTE]);
trace (NORM, default_trace, " (%s) AutNum Objects %4d changed, %4d deleted\n",
database->name,
database->num_objects_changed[AUT_NUM],
database->num_objects_deleted[AUT_NUM]);
}
else {
if (valid_start_line_ret < 0) {
trace (NORM, default_trace, " (%s) Mirroring failed... no valid START line\n", database->name);
}
select_delete_fd (database->mirror_fd);
database->mirror_fd = -1;
}
fclose (database->mirror_disk_fp);
return (1);
}
/*
* valid_start_line
*
* Returns 1 if a valid start line is found and sets serial_num to the
* last number in the %START line
* Returns 0 if only blank lines and comments (start with %) are found
* (no updates)
* Returns -1 if invalid tokens are before a start line.
*
*/
int valid_start_line (irr_database_t *database, FILE *fp, u_long *serial_num) {
char buffer[BUFSIZE], *cp, *last;
enum STATES state = BLANK_LINE, save_state;
int ret = -1;
fseek (fp, 0L, SEEK_SET);
while (state != DB_EOF) {
cp = fgets (buffer, sizeof (buffer) - 1, fp);
state = get_state (buffer, cp, state, &save_state);
/* dump comment lines and lines that exceed the buffer size */
if (state == OVRFLW ||
state == OVRFLW_END ||
state == COMMENT ||
state == DB_EOF)
continue;
if (state == START_F) {
if (!strncmp ("%START Version: 1", buffer, 17)) {
strtok_r (buffer, "-", &last);
if ((cp = strtok_r (NULL, "-", &last)) != NULL) {
*serial_num = atol (cp);
trace (NORM, default_trace," (%s) Mirror SERIAL Last-(%d)\n", database->name, *serial_num);
ret = 1;
break;
}
}
else if (buffer[0] == '%') {
/* Guarantee newline termination */
if (strlen(buffer) == (sizeof(buffer) - 1))
buffer[sizeof(buffer) - 1] = '\n';
/* Deal with RIPE error messages here */
/* % ERROR: 2: Invalid range: 11747158-11747157 don't exist
We're likely to get this one if we're up to date */
if (!strncasecmp (buffer, "% ERROR: 2", 10)) {
trace (NORM, default_trace, " (%s) Up to date (or requesting bogus range)\n", database->name);
ret = 0;
break;
}
/* % ERROR: 4: Invalid range: serial(s) 1-11532083 don't exist
We're likely to get this if we're starting from scratch */
else if (!strncasecmp (buffer, "% ERROR: 4", 10)) {
trace (ERROR, default_trace, " (%s) Requesting out of range mirror:\n%s", database->name, buffer);
ret = -1;
break;
}
/* % ERROR: 3: Invalid range: serial(s) 11747145-99999999999 don't exist
We're getting this one from an explicit request for stuff past LAST */
else if (!strncasecmp (buffer, "% ERROR: 3", 10)) {
trace (ERROR, default_trace, " (%s) Requesting out of range mirror:\n%s", database->name, buffer);
ret = -1;
break;
}
/* Deal with IRRd error messages here */
/* % ERROR: serials (1 - 8206) don't exist!
% ERROR: range error 'from > to' (99999999 > 20084) */
else if (!strncmp (buffer, "% ERROR: range error", 20) ||
!strncmp (buffer, "% ERROR: serials", 16)) {
trace (ERROR, default_trace, " (%s) Requesting out of range mirror:\n%s", database->name, buffer);
ret = -1;
break;
}
/* If we still have an ERROR line here, since its the first line of
a stream, we'll consider it a fatal error. This serves as a
catchall and will specifically deal with not being in RIPE
mirroring ACL's. */
else if (!strncmp (buffer, "% ERROR", 7)) {
trace (ERROR, default_trace, " (%s) Received error in response to mirror request:\n%s", database->name, buffer);
ret = -1;
break;
}
/* LAST is special.
If we ask from the last serial by number, we get this:
% ERROR: range error 'from > to' (20085 > 20084)
And we ONLY get this if we ask for LAST+1-LAST
% Warning: there are no newer updates available! */
else if (!strncmp (buffer, "% Warning: there are no newer updates available!", 48)){
trace (NORM, default_trace, " (%s) Up to date (or requesting bogus range)\n", database->name);
ret = 0;
break;
}
continue;
}
else
break;
}
}
if (state == DB_EOF) {
trace (NORM, default_trace, " (%s) No updates found in mirror stream.\n",
database->name);
return (0);
}
else if ((state == START_F) && (ret != 1) && (*buffer != '%')) {
trace (ERROR, default_trace, " (%s) Invalid mirror header line: %s\n",
database->name, buffer);
strncpy (database->mirror_error_message, buffer, MAX_MIRROR_ERROR_LEN);
database->mirror_error_message[MAX_MIRROR_ERROR_LEN] = '\0';
}
return (ret);
}
void irr_mirror_timer (mtimer_t *timer, irr_database_t *db) {
request_mirror (db, NULL, 0);
}
int irr_service_mirror_request (irr_connection_t *irr, char *command) {
char buffer[BUFSIZE];
irr_database_t *database;
u_long oldestserial, currentserial, first_in_new;
u_long from, to;
int old_journal_exists, new_journal_exists;
int ret_code = 1;
char name[BUFSIZE], buffer1[BUFSIZE];
/* Parse a valid -g mirror request line and set request "from" - "to" */
if (sscanf (command, " %[^:]:1:%[^-]-%s", name, buffer1, buffer) != 3) {
sprintf (buffer, "\n\n\n%% ERROR: syntax error in -g command: (%s)\n", command);
irr_write_nobuffer (irr, buffer, strlen (buffer));
return (-1);
}
else {
if (convert_to_lu (buffer1, &from) < 0) {
sprintf (buffer, "\n\n\n%% ERROR: syntax error in 'from' value (%s)\n", buffer1);
irr_write_nobuffer (irr, buffer, strlen (buffer));
return (-1);
}
if (strncasecmp (buffer, "LAST", 4) != 0 &&
convert_to_lu (buffer, &to) < 0) {
sprintf (buffer1, "\n\n\n%% ERROR: syntax error in 'to' value (%s)\n", buffer);
irr_write_nobuffer (irr, buffer1, strlen (buffer1));
return (-1);
}
}
/* See if the db name is one we know about and is mirrored */
if ((database = find_database (name)) == NULL) {
sprintf (buffer, "\n\n\n%% ERROR: Unknown db (%s) in mirror request\n", name);
irr_write_nobuffer (irr, buffer, strlen (buffer));
return (-1);
}
else if (database->mirror_prefix == NULL &&
!(database->flags & IRR_AUTHORITATIVE)) {
sprintf (buffer, "\n\n\n%% ERROR: Database (%s) is a non-mirrored db!\n", database->name);
irr_write_nobuffer (irr, buffer, strlen (buffer));
return (-1);
}
if (get_current_serial (name, ¤tserial) < 0) {
if (database->db_syntax != EMPTY_DB) {
sprintf (buffer, "\n\n\n%% ERROR: (%s).CURRENTSERIAL file missing or corrupted!\n", name);
irr_write_nobuffer (irr, buffer, strlen (buffer));
return (-1);
}
/* DB is empty so there are no updates to mirror */
currentserial = 0;
}
if (currentserial == 0) {
sprintf (buffer, "\n\n\n%%Warning: No updates yet, journal file is empty!\n");
irr_write_nobuffer (irr, buffer, strlen (buffer));
return (-1);
}
if (!strncasecmp (buffer, "LAST", 4)) {
to = currentserial;
if (from == (currentserial + 1)) {
sprintf (buffer, "\n\n\n%% Warning: there are no newer updates available!\n");
irr_write_nobuffer (irr, buffer, strlen (buffer));
return (-1);
}
}
trace (NORM, default_trace, "Processing mirror request from %s for %s %d - %d\n",
prefix_toa (irr->from), database->name, from, to);
/* check acls */
if (!apply_access_list (database->mirror_access_list, irr->from)) {
trace (NORM | INFO, default_trace, "IRR DENIED to %s\n",
prefix_toa (irr->from));
irr_send_error (irr, NULL);
return (-1);
}
irr_lock (database); /* lock to build buffer with mirror data */
/* find the oldest serial we have in *.JOURNAL.1 */
old_journal_exists = find_oldest_serial (database->name, JOURNAL_OLD, &oldestserial);
/* now find the first value in the *.JOURNAL file */
if ((new_journal_exists =
find_oldest_serial (database->name, JOURNAL_NEW, &first_in_new)) == 0) {
irr_unlock (database);
sprintf (buffer, "\n\n%% WARN: No serials to mirror yet or the first serial is corrupted!\n" );
irr_write_nobuffer (irr, buffer, strlen (buffer));
return (-1);
}
/* oldestserial is the first in *.JOURNAL if *.JOURNAL.1 doesn't exist */
if (!old_journal_exists)
oldestserial = first_in_new;
/* check and see if there are any "from-to" range errors in the request */
if (from > to) {
irr_unlock (database);
sprintf (buffer, "\n\n%% ERROR: range error 'from > to' (%lu > %lu)\n", from, to);
irr_write_nobuffer (irr, buffer, strlen (buffer));
return (-1);
}
if (from < oldestserial) {
irr_unlock (database);
sprintf (buffer, "\n\n%% ERROR: serials (%lu - %lu) don't exist!\n", from, oldestserial - 1);
irr_write_nobuffer (irr, buffer, strlen (buffer));
return (-1);
}
if (to > currentserial) {
irr_unlock (database);
sprintf (buffer, "\n\n%% ERROR: serials (%lu - %lu) don't exist!\n", currentserial + 1, to);
irr_write_nobuffer (irr, buffer, strlen (buffer));
return (-1);
}
if (old_journal_exists && from < first_in_new) {
ret_code = dump_serial_updates (irr, database->name, JOURNAL_OLD, from, to);
}
if (ret_code < 0) {
irr_unlock (database);
return (-1);
}
if (to >= first_in_new) {
ret_code = dump_serial_updates (irr, database->name, JOURNAL_NEW, from, to);
}
if (ret_code < 0) {
irr_unlock (database);
return (-1);
}
irr_unlock (database);
sprintf (buffer, "%%START Version: 1 %s %lu-%lu\n\n", database->name, from, to);
irr_write_nobuffer(irr, buffer, strlen(buffer));
if (irr->scheduled_for_deletion) {
LL_Destroy (irr->ll_final_answer);
trace (NORM, default_trace, "Mirror send %%START error\n");
return (-1);
}
irr_write_buffer_flush(irr); /* attempt to send our mirror data */
if (irr->scheduled_for_deletion) {
trace (NORM, default_trace, "Mirror send DATA error\n");
return (-1);
}
sprintf (buffer, "%%END %s\n", database->name);
/* traditional perl clients only accept 'DB name'
* in upper case
*/
convert_toupper (buffer);
irr_write_nobuffer(irr, buffer, strlen(buffer));
if (irr->scheduled_for_deletion) {
trace (NORM, default_trace, "Mirror send %%END error\n");
return -1;
}
return (0);
}
int dump_serial_updates (irr_connection_t *irr, char * dbname, int journal_ext,
u_long from, u_long to) {
char buf[BUFSIZE], file[BUFSIZE];
u_long serial;
int ret_code = 1;
FILE *journal_fp;
make_journal_name (dbname, journal_ext, file);
if ((journal_fp = fopen (file, "r")) == NULL)
return (-1);
/* find the correct spot in the journal file, then pump the updates onto the socket */
while (fgets (buf, BUFSIZE - 1, journal_fp) != NULL) {
if (sscanf (buf, "%% SERIAL %lu", &serial) == 1) {
if (serial >= from) {
ret_code = build_update (file, irr, journal_fp, serial, to);
break;
}
}
}
fclose (journal_fp);
return ret_code;
}
int build_update (char *file, irr_connection_t *irr, FILE *fp, u_long curr_serial,
u_long to) {
char buf[BUFSIZE];
u_long chk_serial;
while ((fgets (buf, BUFSIZE - 1, fp) != NULL) &&
(curr_serial <= to)) {
if (sscanf (buf, "%% SERIAL %lu", &chk_serial) == 1) {
if (++curr_serial != chk_serial) {
trace (ERROR, default_trace, "Skipped sequence mirror update, should be (%lu) (%s) says (%lu)\n", curr_serial, file, chk_serial);
return -1;
}
continue;
}
irr_write (irr, buf, strlen (buf));
}
return 1;
}
syntax highlighted by Code2HTML, v. 0.9.1