/* Copyright 1998, Merit Network, Inc. and the University of Michigan */ /* $Id: util.c,v 1.3 2002/10/17 20:16:15 ljb Exp $ * originally Id: util.c,v 1.3 1998/08/10 19:24:24 dogcow Exp */ #include #include #include #include #include #include #include #include #include #include #include #include #include /* * See if the object in file *fd1 is the same as the * object in file *fd2. * * Return: * 1 if the files are the same * 0 Otherwise */ int noop_check (trace_t *tr, FILE *fd1, long fpos1, FILE *fd2, long fpos2) { long offset1, offset2; char *p, *q, *r, *s; char line1[MAXLINE], line2[MAXLINE], *eof1, *eof2; /* fprintf (dfile, "enter noop_check () old pos (%ld) new pos (%ld)\n", fpos1, fpos2);*/ if (fd1 == NULL || fd2 == NULL) return 0; offset1 = ftell (fd1); offset2 = ftell (fd2); fseek (fd1, fpos1, SEEK_SET); fseek (fd2, fpos2, SEEK_SET); while (1) { eof1 = fgets (line1, MAXLINE - 1, fd1); eof2 = fgets (line2, MAXLINE - 1, fd2); if (eof1 == NULL || eof2 == NULL) break; /*fprintf (dfile, "line1:%s", line1); fprintf (dfile, "line2:%s", line2);*/ if ((p = strchr (line1, ' ')) == NULL) break; q = p; if (find_token (&p, &q) < 0) break; if ((r = strchr (line2, ' ')) == NULL) break; s = r; if (find_token (&r, &s) < 0) break; /*fprintf (dfile, "line1:%s", p); fprintf (dfile, "line2:%s", r);*/ if (eof1 == NULL || eof2 == NULL || line1[0] == '\n' || line2[0] == '\n' || strcmp (p, r)) break; } /* restore file pos's */ fseek (fd1, offset1, SEEK_SET); fseek (fd2, offset2, SEEK_SET); if (eof1 != NULL && eof2 != NULL && line1[0] == '\n' && line2[0] == '\n') return 1; return 0; } /* This routine concat's x and y, putting a space * beteen x and y. It assumes that the * caller is placing the result in x, so the routine * free's x if it points to something. * * The calling convention should be like this: * x = myconcat (x, y); * x and/or y may be NULL and routine will work. */ char *myconcat (char *x, char *y) { char buf[MAXLINE]; if (x == NULL) buf[0] = '\0'; else { strcpy (buf, x); free_mem (x); } if (y != NULL) { if (buf[0] != '\0') strcat (buf, " "); strcat (buf, y); } if (buf[0] == '\0') return NULL; else return strdup (buf); } /* This routine finds a token in the string. *x will * point to the first character in the string and *y will * point to the first character after the token. A token * is a printable character string. A '\n' is not considered * part of a legal token. * This function is rpsl-capable. It will look for '#'s in * the string and assume everything after is a comment. * Invoke this routine by setting 'x' and 'y' to the beginning * of the target string like this: 'x = y = target_string; * if (find_token (&x, &y) < 0) ... * each successive call to find_token () will move the pointer * along. * * Return: * 1 if a token is found (*x points to token, *y first token after) * -1 if no token is found (*x and *y are to be ignored) */ int find_token (char **x, char **y) { /* It's possible the target string is NULL * or we are in a rpsl comment */ if (*y == NULL || **y == '#') return -1; *x = *y; /* find the start of a token */ while (**x == ' ' || **x == '\t' || **x == '\n') (*x)++; if (**x == '\0' || **x == '#') return -1; /* find the first space at the end of the token */ *y = *x + 1; while (**y != '\0' && (isgraph (**y) && **y != '#')) (*y)++; return 1; } /* * Free the memory point to by *p. p may be NULL. * Function should be invoked as 'x = free_mem (x)' * * Return: * NULL */ char *free_mem (char *p) { if (p != NULL) free (p); return NULL; } /* Filter out elements in list1 that are present * in list2 and return the new list. Routine assumes * that list1 will be replaced with the result, so invoke * like this, "list1 = filter_duplicates (list1, list2)". * * Return: * char string of space seperated list1 elements not * in list2. Result could be NULL if all list1 elements * are in list2. */ char *filter_duplicates (trace_t *tr, char *list1, char *list2) { char *ret_list = NULL; char *p, *q, *r, *s; char c, d; int match; /*fprintf (dfile, "\n---\nenter filter_duplicates ()...\n");*/ if (list1 == NULL || list2 == NULL) return list1; /* fprintf (dfile, "list1 (%s)\n", list1); fprintf (dfile, "list2 (%s)\n", list2);*/ p = q = list1; while (find_token (&p, &q) > 0) { c = *q; *q = '\0'; match = 0; r = s = list2; /*fprintf (dfile, "list1: %s\n", p);*/ while (find_token (&r, &s) > 0) { d = *s; *s = '\0'; /*fprintf (dfile, "list2: %s\n", r);*/ if (!strcmp (p, r)) { /*fprintf (dfile, "match (%s)\n", r);*/ match = 1; *s = d; break; } *s = d; } if (!match) ret_list = myconcat (ret_list, p); *q = c; } free_mem (list1); /*if (ret_list != NULL) fprintf (dfile, "ret_list (%s)\n---\n", ret_list); else fprintf (dfile, "ret_list is NULL ie, all matches...\n---\n"); */ return ret_list; } /* Write the transaction object pointed to by fpos to * the output file. The output file is assumed to be * in the correct position. Also, function will trim * large objects (ie, # lines > max_line_size) if they * have errors (since they won't be used in an update). * Error output from irr_auth and irr_check will not be * filtered. * Filtering on large objects that will be sent to * IRRd (to avoid sending large objects in email) must * be filtered in irr_notify, since irr_notify sends the * updates to IRRd. * * Return: * void */ void write_trans_obj (trace_t *tr, FILE *fin, long fpos, FILE *fout, int max_line_size, int has_errors) { char buf[MAXLINE]; int print_snip = 1; fseek (fin, fpos, SEEK_SET); while (fgets (buf, MAXLINE - 1, fin) != NULL) { if (buf[0] == '\n') break; if (has_errors && --max_line_size < 0 && !(is_changed (buf) || is_source (buf) || buf[0] == ERROR_TAG[0] || buf[0] == WARN_TAG[0])) { if (print_snip && max_line_size < 0) { fputs (SNIP_MSG, fout); print_snip = 0; } continue; } if (EOF == fputs (buf, fout)) fprintf (stderr, "ERROR: writing object to file (%s)\n", strerror (errno)); } /* make sure the last line ends with a '\n' */ if (buf[0] != '\n') fputs ("\n", fout); } char *pick_members (char *s) { char c, *p, *q, *r = NULL; p = q = s; while (find_token (&p, &q) > 0) { if (*(q - 1) == ',') { c = *(q - 1); *(q - 1) = '\0'; } else { c = *q; *q = '\0'; } r = myconcat (r, p); if (c == ',') *(q - 1) = ','; else *q = c; } return r; } /* * Return a list of the "attr's" in a char string seperated by blanks. * Function assumes fpos pointer is at the beginning of the object. * * Return: * List of attr values or NULL if none exist * Restores the file position pointer on exit */ char *cull_attribute (trace_t *tr, FILE *fd, long obj_start, u_int attr) { char buf[MAXLINE]; char *p = NULL, *q; int in_attr = 0; long fpos; int m=0, n=0; /* restore file pos upon exit */ fpos = ftell (fd); /* seek the object start */ if (fseek (fd, obj_start, SEEK_SET)) { fprintf (stderr, "cull_attribute () file seek error: (%s)\n", strerror(errno)); exit (0); } /*fprintf (dfile, "\n---\nenter cull_attribute (attr %d) curr fpos (%ld) obj fpos (%ld)...\n", attr, fpos, obj_start);*/ while (fgets (buf, MAXLINE - 1, fd) != NULL && buf[0] != '\n') { m++; if ((in_attr = find_attr (tr, buf, in_attr, attr, &q))) { n++; p = myconcat (p, pick_members (q)); } } /* restore prior file position */ fseek (fd, fpos, SEEK_SET); /* fprintf (dfile, "cull_attr (): looked at (%d) lines found (%d) matches\n", m, n); if (p == NULL) fprintf (dfile, "cull_attr () returns empty list...\n---\n\n"); else fprintf (dfile, "cull_attr () returns (%s)\n---\n\n", p);*/ return p; } /* Given an attribute from a DB object return it's value part * if the attribute is a member of 'target_attrs' (ie, a bit * word of attributes to match). * * This routine is RPSL-capable, ie it handles rpsl-style * line continuation, multiple addr's on a single line, * and embedded comments. It of course also works for ripe181. * * Return: * ATTR_ID of attribute matched, attribute value is pointed * to by **data. * X_ATTR (ie, no match) if the attribute is not in 'target_attrs'. */ enum ATTR_ID find_attr (trace_t *tr, char *line, int in_attr, u_int target_attrs, char **data) { char *p, *q; int line_cont = 0; newline_remove(line); *data = NULL; if (line[0] == '#') return (enum ATTR_ID) in_attr; /* handle RPSL line continuation */ if (in_attr) in_attr = line_cont = (line[0] == ' ' || line[0] == '\t' || line[0] == '+'); if (!in_attr) { if ((target_attrs & AUTH_ATTR) && is_auth (line)) in_attr = AUTH_ATTR; else if ((target_attrs & MNT_NFY_ATTR) && is_mnt_nfy (line)) in_attr = MNT_NFY_ATTR; else if ((target_attrs & NOTIFY_ATTR) && is_notify (line)) in_attr = NOTIFY_ATTR; else if ((target_attrs & MNT_BY_ATTR) && is_mnt_by (line)) in_attr = MNT_BY_ATTR; else if ((target_attrs & UPD_TO_ATTR) && is_upd_to (line)) in_attr = UPD_TO_ATTR; else if ((target_attrs & MNTNER_ATTR) && is_mntner (line)) in_attr = MNTNER_ATTR; } if (in_attr) { /* read past the *xx: or xxxxx: field id */ p = q = line; if (!line_cont) { if ((q = strchr (line, ':')) == NULL) return X_ATTR; p = ++q; } else /* line cont, read past the first char, could be a '+' */ p++; /* JW 11-22-99 did not work for non-canonical case. eg, auth:MAIL-FROM ... p = q = line; if (!line_cont && find_token (&p, &q) < 0) return X_ATTR; */ /* find the attr token if (find_token (&p, &q) < 0) return X_ATTR; else */ find_token (&p, &q); if ((q = strchr (p, '#')) != NULL) *q = '\0'; /* old else if (*q == '#') *q = '\0'; */ *data = p; } return (enum ATTR_ID) in_attr; } /* Open up a streams file. If the file mode starts with 'w' * then routine makes up a name using the mkstemp () utility. * '*tag' should be an identifier string for error reporting * such as "maint_check () output file". * * Return: * A stream file pointer if the operation was a success. * NULL if the file could not be opened. */ FILE *myfopen (trace_t *tr, char *fname, char *fmode, char *tag) { FILE *fp; int fd; if (*fmode == 'w') { strcpy (fname, tmpfntmpl); fd = mkstemp(fname); if (fd == -1) { trace(ERROR, tr, "mkstemp error: %s \"%s\": (%s)\n", tag, fname, strerror(errno)); return(NULL); } fp = fdopen(fd, fmode); if (fp == NULL) { close(fd); unlink(fname); } } else fp = fopen(fname, fmode); if (fp == NULL) trace(ERROR, tr, "fopen error: %s \"%s\": (%s)\n", tag, fname, strerror (errno)); return fp; } /* Lock file 'filename'. The lockfile name will not * be 'filename'. The lockfile name will be 'filename.LOCK'. * This convention makes it easier for the log_roll () function. * * Return: * -1 flock () error * 0 no errors, LOCK file is locked * 1 could not open the LOCK file (ie, fopen () fails) */ int lock_file (int *fd, char *filename) { char lockname[256]; sprintf (lockname, "%s.LOCK", filename); if ((*fd = open (lockname, O_CREAT|O_RDWR, 0644)) < 0) return 1; #ifdef USE_FLOCK return flock (*fd, LOCK_EX); #elif USE_LOCKF return lockf (*fd, F_LOCK, 0); #else return 0; #endif } /* Unlock and close the lock file pointed to by 'fp'. * * Return: * void */ void unlock_file (int fd) { #ifdef USE_FLOCK flock (fd, LOCK_UN); #elif USE_LOCKF lockf (fd, F_ULOCK, 0); #endif close (fd); } /* Given a file name template, 'fntmpl' (eg, "%s/ack.log") and * a log directory path, 'log_dir', roll the current log onto * the old log if the current log exceeds 'MAX_LOG_FILE_SIZE'. * * Return: * void */ void log_roll (char *fntmpl, char *log_dir, int SKIP_LOCK) { struct stat stat_buf; char logfn[256], oldfn[256]; int fd, lock_code = 0; int lfp; if (log_dir == NULL) return; sprintf (logfn, fntmpl, log_dir); if (!SKIP_LOCK) { if ((lock_code = lock_file (&lfp, logfn)) == 1) return; } /* no errors, got the lock */ if (lock_code == 0) { if ((fd = open (logfn, O_RDONLY, 0774)) > 0) { fstat (fd, &stat_buf); close (fd); if (stat_buf.st_size > MAX_LOG_FILE_SIZE) { sprintf (oldfn, "%s.1", logfn); rename(logfn, oldfn); } } if (!SKIP_LOCK) unlock_file (lfp); } else /* lock error */ close (lfp); }