/* $Id: isdnrate.c,v 1.41 2004/01/10 16:43:24 tobiasb Exp $ * ISDN accounting for isdn4linux. (rate evaluation) * * Copyright 1995 .. 2000 by Andreas Kool (akool@isdn4linux.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * $Log: isdnrate.c,v $ * Revision 1.41 2004/01/10 16:43:24 tobiasb * New options -c and -i for overriding the RATECONF and RATEFILE setting * in isdn.conf. May be useful for rate-file testing. * * Revision 1.40 2003/09/04 19:45:07 tobiasb * New option for isdnrate: `-rvNN' requires a vbn starting with NN. * Suggested by Thomas Richter on isdn4linux mailinglist, for reference see * http://listserv.isdn4linux.de/pipermail/isdn4linux/2003-September/000011.html * * Revision 1.39 2002/08/15 17:22:43 akool * isdnlog-4.63 * - new rates/provider ... * - isdnrate now display EuroCent as the default * * Revision 1.38 2001/03/01 14:59:16 paul * Various patches to fix errors when using the newest glibc, * replaced use of insecure tempnam() function * and to remove warnings etc. * * Revision 1.37 2000/07/17 16:34:23 akool * isdnlog-4.32 * - added new Prefixes 0160 (D1) and 0162 (D2) to "country-de.dat" * - corrected all german mobil phone numbers (many thank's to * Tobias Becker for the tool "fix_rates.pl") * - isdnlog/tools/rate.c ... New R:-tag * - isdnlog/tools/isdnrate.c ... print warnings from getRate if verbose * - isdnlog/tools/rate-files.man ... New R:-tag * - isdnlog/tools/NEWS ... New R:-tag * - isdnlog/README ... New R:-tag * - isdnlog/samples/rtest.dat ... example rate-file for testing R: * * Revision 1.36 2000/03/09 18:50:03 akool * isdnlog-4.16 * - isdnlog/samples/isdn.conf.no ... changed VBN * - isdnlog/isdnlog/isdnlog.c .. ciInterval * - isdnlog/isdnlog/processor.c .. ciInterval * - isdnlog/tools/tools.h .. ciInterval, abclcr conf option * - isdnlog/tools/isdnconf.c .. ciInterval, abclcr conf option * - isdnlog/tools/isdnrate.c .. removed a warning * - isdnlog/NEWS ... updated * - isdnlog/README ... updated * - isdnlog/isdnlog/isdnlog.8.in ... updated * - isdnlog/isdnlog/isdnlog.5.in ... updated * - isdnlog/samples/provider ... NEW * * ==> Please run a make clean, and be sure to read isdnlog/NEWS for changes * ==> and new features. * * Revision 1.35 2000/03/06 07:03:21 akool * isdnlog-4.15 * - isdnlog/tools/tools.h ... moved one_call, sum_calls to isdnrep.h * ==> DO A 'make clean' PLEASE * - isdnlog/tools/telnum.c ... fixed a small typo * - isdnlog/isdnrep/rep_main.c ... incl. dest.h * - isdnlog/isdnrep/isdnrep.c ... fixed %l, %L * - isdnlog/isdnrep/isdnrep.h ... struct one_call, sum_calls are now here * * Support for Norway added. Many thanks to Tore Ferner * - isdnlog/rate-no.dat ... NEW * - isdnlog/holiday-no.dat ... NEW * - isdnlog/samples/isdn.conf.no ... NEW * - isdnlog/samples/rate.conf.no ... NEW * * Revision 1.34 2000/02/28 19:53:56 akool * isdnlog-4.14 * - Patch from Roland Rosenfeld fix for isdnrep * - isdnlog/tools/rate.c ... epnum * - isdnlog/tools/rate-at.c ... new rates * - isdnlog/rate-at.dat * - isdnlog/tools/rate-files.man ... %.3f * - doc/Configure.help ... unknown cc * - isdnlog/configure.in ... unknown cc * - isdnlog/.Config.in ... unknown cc * - isdnlog/Makefile.in ... unknown cc * - isdnlog/tools/dest/Makefile.in ... LANG => DEST_LANG * - isdnlog/samples/rate.conf.pl ... NEW * - isdnlog/samples/isdn.conf.pl ... NEW * - isdnlog/rate-pl.dat ... NEW * - isdnlog/tools/isdnrate.c ... fixed -P pid_dir, restarts on HUP now * - isdnlog/tools/isdnrate.man ... SIGHUP documented * * Revision 1.33 2000/02/12 16:40:24 akool * isdnlog-4.11 * - isdnlog/Makefile.in ... sep install-targets, installs samples, no isdnconf * - isdnlog/samples/rate.conf.{lu,nl} ... NEW * - isdnlog/samples/isdn.conf.lu ... chg provider * - isdnlog/samples/stop ... chg \d,\d => \d.\d * - isdnlog/samples/isdnlog.isdnctrl0.options ... NEW * - isdnlog/samples/isdnlog.users ... NEW * - isdnlog/country-de.dat ... _DEMF again * - isdnlog/isdnlog/processor.c ... LCR * - isdnlog/tools/isdnrate.c ... fmt of s * * Old config is not installed anymore, to acomplish this do * * make install-old-conf * make install * * A running isdnlog is now HUP-ed not KILL-ed * * Revision 1.32 2000/02/03 18:24:51 akool * isdnlog-4.08 * isdnlog/tools/rate.c ... LCR patch again * isdnlog/tools/isdnrate.c ... LCR patch again * isdnbill enhanced/fixed * DTAG AktivPlus fixed * * Revision 1.31 1999/12/31 13:57:20 akool * isdnlog-4.00 (Millenium-Edition) * - Oracle support added by Jan Bolt (Jan.Bolt@t-online.de) * - resolved *any* warnings against rate-de.dat * - Many new rates * - CREDITS file added * * Revision 1.30 1999/12/24 14:17:05 akool * isdnlog-3.81 * - isdnlog/tools/NEWS * - isdnlog/tools/telrate/info.html.in ... bugfix * - isdnlog/tools/telrate/telrate.cgi.in ... new Service query * - isdnlog/tools/telrate/Makefile.in ... moved tmp below telrate * - isdnlog/samples/rate.conf.at ... fixed * - isdnlog/tools/rate-at.c ... some changes * - isdnlog/rate-at.dat ... ditto * - isdnlog/tools/Makefile ... added path to pp_rate * - isdnlog/tools/rate.{c,h} ... getServiceNames, Date-Range in T:-Tag * - isdnlog/tools/isdnrate.c ... fixed sorting of services, -X52 rets service names * - isdnlog/tools/rate-files.man ... Date-Range in T:-Tag, moved from doc * - isdnlog/tools/isdnrate.man ... moved from doc * - doc/Makefile.in ... moved man's from doc to tools * - isdnlog/Makefile.in ... man's, install isdn.conf.5 * - isdnlog/configure{,.in} ... sed, awk for man's * - isdnlog/tools/zone/Makefile.in ... dataclean * - isdnlog/tools/dest/Makefile.in ... dataclean * - isdnlog/isdnlog/isdnlog.8.in ... upd * - isdnlog/isdnlog/isdn.conf.5.in ... upd * * Revision 1.29 1999/12/17 22:51:55 akool * isdnlog-3.79 * - isdnlog/isdnrep/isdnrep.{c,h} ... error -handling, print_msg * - isdnlog/isdnrep/rep_main.c * - isdnlog/isdnrep/isdnrep.1.in * - isdnlog/tools/rate.c ... dupl entry in rate.conf * - isdnlog/tools/NEWS * - isdnlog/tools/isdnrate.c * - isdnlog/tools/dest/configure{,.in} * - isdnlog/tools/zone/configure{,.in} * * Revision 1.28 1999/12/16 23:08:28 akool * isdnlog-3.78 * - isdnlog/tools/isdnrate.c ... new sOcket option * - isdnlog/tools/NEWS ... new sOcket option * - doc/isdnrate.man ... ditto * - isdnlog/tools/telrate/config.in ... ditto * - isdnlog/tools/telrate/telrate.cgi.in ... ditto * - isdnlog/tools/telrate/README-telrate ditto * * Revision 1.27 1999/12/02 19:28:02 akool * isdnlog-3.73 * - isdnlog/tools/telrate/telrate.cgi.in faster * - doc/isdnrate.man ... -P * - isdnlog/tools/isdnrate.c ... -P * - isdnlog/tools/NEWS ... -P * - isdnlog/tools/rate-at.c ... 194040 * - isdnlog/rate-at.dat * - isdnlog/tools/rate.c ... SIGSEGV * * Revision 1.26 1999/12/01 21:47:25 akool * isdnlog-3.72 * - new rates for 01051 * - next version of isdnbill * * - isdnlog/tools/telnum.c ... cleanup * - isdnlog/tools/isdnrate.c ... -s Service * - isdnlog/tools/rate.{c,h} ... -s * - isdnlog/tools/NEWS ... -s * - doc/isdnrate.man .. updated -o, -s * - doc/rate-files.man ... updated * - isdnlog/tools/dest/README.makedest ... updt. * - isdnlog/isdnlog/isdnlog.8.in .. updt. * * Telrate * - isdnlog/tools/telrate/README-telrate * - isdnlog/tools/telrate/config.in NEW * - isdnlog/tools/telrate/configure NEW * - isdnlog/tools/telrate/Makefile.in NEW * - isdnlog/tools/telrate/index.html.in was index.html * - isdnlog/tools/telrate/info.html.in was info.html * - isdnlog/tools/telrate/telrate.cgi.in was telrate.cgi * - isdnlog/tools/telrate/leo.sample NEW sample config * - isdnlog/tools/telrate/alex.sample NEW sample config * * Revision 1.25 1999/11/08 21:09:41 akool * isdnlog-3.65 * - added "B:" Tag to "rate-xx.dat" * * Revision 1.24 1999/11/07 13:29:28 akool * isdnlog-3.64 * - new "Sonderrufnummern" handling * * Revision 1.22 1999/10/26 18:17:14 akool * isdnlog-3.62 * - many new rates * - next try to fix "Sonderrufnummern" * * isdnlog-3.58 * - big cleanup ( > 1.3 Mb removed!) * - v0.02 of destination support - better, but not perfect * (does't work with gcc-2.7.2.3 yet - use egcs!) * * Revision 1.21 1999/10/25 18:30:03 akool * isdnlog-3.57 * WARNING: Experimental version! * Please use isdnlog-3.56 for production systems! * * Revision 1.20 1999/09/19 14:16:27 akool * isdnlog-3.53 * * Revision 1.19 1999/09/16 20:27:21 akool * isdnlog-3.52 * * Revision 1.18 1999/09/13 09:09:44 akool * isdnlog-3.51 * - changed getProvider() to not return NULL on unknown providers * many thanks to Matthias Eder * - corrected zone-processing when doing a internal -> world call * * Revision 1.17 1999/09/09 11:21:05 akool * isdnlog-3.49 * * Revision 1.16 1999/08/04 23:06:52 akool * Better codeall for .at * * Revision 1.15 1999/07/31 09:25:36 akool * getRate() speedup * * Revision 1.14 1999/07/26 16:28:41 akool * getRate() speedup from Leo * * Revision 1.13 1999/07/25 15:57:46 akool * isdnlog-3.43 * added "telnum" module * * Revision 1.12 1999/07/24 08:45:17 akool * isdnlog-3.42 * rate-de.dat 1.02-Germany [18-Jul-1999 10:44:21] * better Support for Ackermann Euracom * WEB-Interface for isdnrate * many small fixes * * Revision 1.11 1999/07/15 16:42:04 akool * small enhancement's and fixes * * Revision 1.10 1999/07/07 19:44:07 akool * patches from Michael and Leo * * Revision 1.9 1999/07/04 20:47:05 akool * rate-de.dat V:1.02-Germany [04-Jul-1999 22:56:37] * * Revision 1.8 1999/07/03 10:24:14 akool * fixed Makefile * * Revision 1.7 1999/07/02 19:18:00 akool * rate-de.dat V:1.02-Germany [02-Jul-1999 21:27:20] * * Revision 1.6 1999/07/02 18:20:50 akool * rate-de.dat V:1.02-Germany [02-Jul-1999 20:29:21] * country-de.dat V:1.02-Germany [02-Jul-1999 19:13:54] * * Revision 1.5 1999/07/01 20:40:07 akool * isdnrate optimized * * Revision 1.4 1999/06/30 20:53:28 akool * added "-t" option to "isdnrate" * * Revision 1.3 1999/06/30 17:17:37 akool * isdnlog Version 3.39 * * Revision 1.2 1999/06/29 20:11:25 akool * now compiles with ndbm * (many thanks to Nima ) * * Revision 1.1 1999/06/28 19:16:33 akool * isdnlog Version 3.38 * - new utility "isdnrate" started * */ #include "isdnlog.h" #include "tools/zone.h" #include #include "dest.h" #define WIDTH 19 #define _MAXLAST 20 /* the real max */ #define MAXLAST ((best>=_MAXLAST||best<=2)?5:best) static void print_header(void); static char *myname, *myshortname; static char options[] = "ab:c:d:f:h:i:l:op:r:st:v::x:CD::G:HLNP:O:S:TUVX::Z"; static char usage[] = "%s: usage: %s [ -%s ] Destination ...\n"; static int header = 0, best = MAXPROVIDER, table = 0, explain = 0; int verbose = 0; static int usestat = 0; static int duration = LCR_DURATION; static time_t start; static int day, month, year, hour, min, sec; static char ignore[MAXPROVIDER]; static char *fromarea = 0; static char wanted_day; static int list = 0; static char *comment; static char **providers = 0; /* incl these */ static int n_providers = 0; static int business = 0; static char **xproviders = 0; /* excl these */ static int nx_providers = 0; static int xbusiness = 0; static int all = 0; static int booked = 0; static int service = 0; static char *require_vbn = NULL; static char *opt_rateconf = 0; static char *opt_ratefile = 0; #define SOCKNAME "/tmp/isdnrate" static int is_daemon = 0; static int is_client = 0; static int we_are_daemon = 0; static int takt = 99999; static char sortby; static int need_dest; static int h_param = 0; static int lcr = 0; static TELNUM srcnum, destnum; static char *pid_dir = 0; static char *pid_file = 0; static char *socket_file = 0; static char **hup_argv; /* args to restart with */ typedef struct { int prefix; double rate; char *name; char *explain; } SORT; typedef struct { int weight; int index; } SORT2; static SORT sort[MAXPROVIDER]; #undef MAXPROVIDER #define MAXPROVIDER getNProvider() #undef BUFSIZ /* sorry but 8192 is too much for me */ #define BUFSIZ 256 int print_msg(int Level, const char *fmt,...) { auto va_list ap; auto char String[BUFSIZ * 3]; if ((Level > PRT_ERR && !verbose) || (Level > PRT_WARN && verbose < 2)) return (1); va_start(ap, fmt); (void) vsnprintf(String, BUFSIZ * 3, fmt, ap); va_end(ap); fprintf(stderr, "%s", String); return (0); } /* print_msg */ static void init() { auto char *version, **message; if (readconfig(myshortname) < 0) exit(1); if (verbose) message = &version; else message = NULL; initHoliday(holifile, message); if (verbose && *version) print_msg(PRT_V, "%s\n", version); initDest(destfile, message); if (verbose && *version) print_msg(PRT_V, "%s\n", version); initRate(opt_rateconf?opt_rateconf:rateconf, opt_ratefile?opt_ratefile:ratefile, zonefile, message); if (verbose && *version) print_msg(PRT_V, "%s\n", version); } /* init */ #if 0 static void deinit(void) { exitRate(); exitDest(); exitHoliday(); } #endif /* calc a day/time W | E | H */ static void get_day(char d) { struct tm *tm; bitfield mask; int what = 0; tm = localtime(&start); /* now */ switch (d) { case 'W': /* we need a normal weekday, so we take today and inc. day if today is holiday */ what = WORKDAY; if (!h_param) hour = 10; break; case 'N': what = WORKDAY; if (!h_param) hour = 23; break; case 'E': what = SUNDAY; if (!h_param) hour = 10; break; } mask = 1 << what; while (isDay(tm, mask, 0) != what) { tm->tm_mday++; } min = sec = 0; day = tm->tm_mday; month = tm->tm_mon + 1; year = tm->tm_year + 1900; } static void post_init() { clearNum(&srcnum); if (fromarea) { Strncpy(srcnum.area, fromarea, TN_MAX_AREA_LEN); free(fromarea); fromarea = 0; } initNum(&srcnum); if (wanted_day) get_day(wanted_day); } /* post_init */ static int opts(int argc, char *argv[]) { register int c; register char *p; int x; h_param = 0; optind = 0; /* make it repeatable */ need_dest = 1; while ((c = getopt(argc, argv, options)) != EOF) { switch (c) { case 'a': /* all rates old/newer */ all++; break; case 'b': best = strtol(optarg, NIL, 0); break; case 'c': opt_rateconf = optarg; break; case 'd': for (p = optarg; *p && isspace(*p); p++) ; if (isdigit(*p)) { wanted_day = '\0'; day = atoi(optarg); if ((p = strchr(optarg, '.'))) { month = atoi(p + 1); if ((p = strchr(p + 1, '.'))) { year = atoi(p + 1); if (year < 50) year += 2000; else if (year < 100) year += 1900; } } } /* isdigit */ else { wanted_day = *p; } break; case 'f': if (optarg) { /* from */ for (p = optarg; (isspace(*p) || *p == '0') && *p; p++) ; fromarea = strdup(p); } break; case 'h': hour = atoi(optarg); h_param++; sec = 0; if ((p = strchr(optarg + 1, ':'))) { min = atoi(p + 1); if ((p = strchr(p + 1, ':'))) sec = atoi(p + 1); } else min = 0; break; case 'i': opt_ratefile = optarg; break; case 'l': duration = strtol(optarg, NIL, 0); /* l wie lt */ break; case 'o': /* booked only */ booked++; break; case 's': /* service */ service++; break; case 't': x = strtol(optarg, NIL, 0); if (x > 0) takt = x; break; case 'x': /* eXclude Poviders */ { char *arg = strdup(optarg); p = strtok(arg, ","); while (p) { if (*p == 'B') { /* Business Provider */ xbusiness = 1; p = strtok(0, ","); continue; } xproviders = realloc(xproviders, (nx_providers + 1) * sizeof(char *)); xproviders[nx_providers] = strdup(p); p = strtok(0, ","); nx_providers++; } free(arg); } break; case 'p': /* Providers ... */ { char *arg = strdup(optarg); p = strtok(arg, ","); while (p) { if (*p == 'B') { /* Business Provider */ business = 1; p = strtok(0, ","); continue; } providers = realloc(providers, (n_providers + 1) * sizeof(char *)); providers[n_providers] = strdup(p); p = strtok(0, ","); n_providers++; } free(arg); } break; case 'r': /* require certain providers properties */ if (optarg[0]=='v') /* only (start of) vbn supported so far */ require_vbn = strdup(optarg+1); break; case 'v': verbose++; if (optarg && (x = atoi(optarg))) verbose = x; break; /* Uppercase options are for output format */ case 'C': is_client = 1; break; case 'D': is_daemon = 1; need_dest = 0; if (optarg) { x = atoi(optarg); is_daemon = x; if (we_are_daemon && x == 3) { kill(getppid(), SIGTERM); exit(EXIT_SUCCESS); } } break; case 'G': explain = atoi(optarg); break; case 'H': header++; break; case 'L': list++; explain = 9; break; case 'N': explain = 55; break; case 'P': pid_dir = strdup(optarg); break; case 'O': free(socket_file); socket_file = strdup(optarg); break; case 'S': sortby = *optarg; break; case 'T': table++; break; /* Fixme: check/warn illegal kombinations of options */ case 'U': usestat++; break; case 'V': print_version(myshortname); exit(0); case 'X': if (explain == 0) { explain++; if (optarg && isdigit(*optarg) && (x = atoi(optarg))) { explain = x; if (x == 50 || x == 51 || x == 52) need_dest = 0; } else if (optarg) { comment = strdup(optarg); explain = 8; need_dest = 0; } break; } /* fallthrough */ case '?': print_msg(PRT_A, usage, myshortname, myshortname, options); break; case 'Z': lcr++; break; } /* switch */ } /* while */ if (is_client && is_daemon != 3 && is_daemon) { is_daemon = 0; print_msg(PRT_V, "Conflicting options, -D disabled\n"); } if (is_client && opt_rateconf) { print_msg(PRT_V, "Conflicting options, -c ignored in client mode (-C)\n"); } if (is_client && opt_ratefile) { print_msg(PRT_V, "Conflicting options, -i ignored in client mode (-C)\n"); } if (list && table) { table = 0; print_msg(PRT_V, "Conflicting options, -T disabled\n"); } if (usestat && !table) print_msg(PRT_V, "Conflicting options, -U ignored\n"); if ((list || table) && explain >= 10) { list = table = 0; print_msg(PRT_V, "Conflicting options, -T, -L disabled\n"); } if (best < 1) { best = MAXPROVIDER; print_msg(PRT_V, "Illegal options, -b ignored\n"); } if ((explain == 50 || explain == 51 || explain == 52) && header) { print_msg(PRT_V, "Conflicting options, -H ignored\n"); } if (explain >= 10 && service) { print_msg(PRT_V, "Conflicting options, -s ignored\n"); service = 0; } if (argc > optind) return (optind); else return (0); } /* opts */ static int (*compare_func) (const void *s1, const void *s2); static int compare(const void *s1, const void *s2) { return (((SORT *) s1)->rate > ((SORT *) s2)->rate); } /* compare */ static int compare_vbn(const void *s1, const void *s2) { return (((SORT *) s1)->prefix > ((SORT *) s2)->prefix); } static int compare_name(const void *s1, const void *s2) { return strcasecmp(((SORT *) s1)->name, ((SORT *) s2)->name); } static int compare2(const void *s1, const void *s2) { return (((SORT2 *) s1)->weight < ((SORT2 *) s2)->weight); } /* compare2 */ char *short_explainRate(RATE * Rate) { static char buffer[BUFSIZ]; char *p = buffer; if (service && Rate->dst[1] && *Rate->dst[1]) p += sprintf(p, "%s, ", Rate->dst[1]); if (Rate->Zone && *Rate->Zone) p += sprintf(p, "%s", Rate->Zone); else p += sprintf(p, "Zone %d", Rate->zone); if (!Rate->domestic && Rate->Country && *Rate->Country) p += sprintf(p, " (%s)", Rate->Country); if (Rate->Day && *Rate->Day) p += sprintf(p, ", %s", Rate->Day); if (Rate->Hour && *Rate->Hour) p += sprintf(p, ", %s", Rate->Hour); return buffer; } static char *printrate(RATE * Rate) { static char message[BUFSIZ]; if (Rate->Basic > 0) sprintf(message, "%s + %s/%.1fs = %s + %s/Min (%s)", printRate(Rate->Basic), printRate(Rate->Price), Rate->Duration, printRate(Rate->Basic), printRate(60 * Rate->Price / Rate->Duration), short_explainRate(Rate)); else sprintf(message, "%s/%.1fs = %s/Min (%s)", printRate(Rate->Price), Rate->Duration, printRate(60 * Rate->Price / Rate->Duration), short_explainRate(Rate)); return (message); } /* printrate */ static void buildtime() { auto struct tm tm; tm.tm_sec = sec; tm.tm_min = min; tm.tm_hour = hour; tm.tm_mday = day; tm.tm_mon = month - 1; tm.tm_year = year - 1900; /* ja, ja, ich weiá ;-) */ tm.tm_isdst = -1; start = mktime(&tm); } /* buildtime */ static void splittime() { auto struct tm *tm; tm = localtime(&start); sec = tm->tm_sec; min = tm->tm_min; hour = tm->tm_hour; day = tm->tm_mday; month = tm->tm_mon + 1; year = tm->tm_year + 1900; } /* splittime */ static char *Provider(int prefix) { register char *p, *p1; register int l; static char s[BUFSIZ]; char prov[TN_MAX_PROVIDER_LEN]; if (prefix == UNKNOWN) return ("?"); p = getProvider(prefix); l = max(WIDTH, strlen(p)) - strlen(p); p1 = prefix2provider_variant(prefix, prov); l += (8 - strlen(p1)); sprintf(s, "%s:%s%*s", p1, p, l, ""); return (s); } /* Provider */ static char *takt_str(RATE * Rate) { static char s[BUFSIZ]; if (floor(Rate->Rhythm[0]) == Rate->Rhythm[0]) sprintf(s, "%.0f/", Rate->Rhythm[0]); else sprintf(s, "%.3f/", Rate->Rhythm[0]); if (floor(Rate->Rhythm[1]) == Rate->Rhythm[1]) sprintf(s + strlen(s), "%.0f", Rate->Rhythm[1]); else sprintf(s + strlen(s), "%.3f", Rate->Rhythm[1]); return s; } static inline char *P_EMPTY(char *s) { char *p = s; return p ? p : ""; } #define DEL ';' static int compute(char *num) { register int i, n = 0; register int low = 0, high = MAXPROVIDER - 1; auto RATE Rate; auto char s[BUFSIZ]; struct tm *tm; char prov[TN_MAX_PROVIDER_LEN]; int first = 1; static char BUSINESS[] = "Business"; /* in C:GT:Tag */ if (destnum.nprovider != UNKNOWN) { low = high = destnum.nprovider; } /* if */ compare_func = compare; switch (sortby) { case 'v': compare_func = compare_vbn; break; case 'n': compare_func = compare_name; break; } buildtime(); if (explain == 98 || explain == 97) { /* Minutenpreis fuer diese Woche */ tm = localtime(&start); tm->tm_hour = 0; tm->tm_min = 1; tm->tm_sec = 0; start = mktime(tm); if (explain == 98) { while (tm->tm_wday) { /* find last monday */ start -= (60 * 60 * 24); tm = localtime(&start); } /* while */ start += (60 * 60 * 24); } } if (service) { num = getServiceNum(num); if (!num || !*num) { return 0; } } do { for (i = low; i <= high; i++) { int found, p; char *t; if (ignore[i]) /* Fixme: */ continue; if (booked && !isProviderBooked(i)) continue; if (!all && !isProviderValid(i, start)) continue; if ( require_vbn && strncmp(getProviderVBN(i), require_vbn, strlen(require_vbn)) ) continue; t = getProvider(i); if (!t || t[strlen(t) - 1] == '?') /* UNKNOWN Provider */ continue; t = getComment(i, "GT"); /* get Geb. Text comment */ if (business) { /* only business wanted */ if (t == 0) continue; else if (strstr(t, BUSINESS) == 0) continue; } if (xbusiness) { /* no business wanted */ if (t && strstr(t, BUSINESS) > 0) continue; } found = 0; if (n_providers) { for (p = 0; p < n_providers; p++) if (pnum2prefix_variant(providers[p], start) == i) { found = 1; break; } if (!found) continue; } if (nx_providers) { for (p = 0; p < nx_providers; p++) if (pnum2prefix_variant(xproviders[p], start) == i) { found = 1; break; } if (found) continue; } clearRate(&Rate); Rate.src[0] = srcnum.country; Rate.src[1] = srcnum.area; Rate.src[2] = ""; destnum.nprovider = i; Strncpy(destnum.provider, getProvider(i), TN_MAX_PROVIDER_LEN); if (normalizeNumber(num, &destnum, TN_ALL) == UNKNOWN) { continue; } Rate.dst[0] = destnum.country; Rate.dst[1] = destnum.area; Rate.dst[2] = destnum.msn; /* Rate.Service = "Internet by call"; */ Rate.prefix = i; Rate.start = start; Rate.now = start + duration - 1; if (explain == 99) { int j; double oldCharge = -1.0; if (first && header) print_header(); first = 0; printf("@ %s\n", prefix2provider_variant(Rate.prefix, prov)); Rate.now = start + 1; for (j = 1; j < duration; j++) { if (!getRate(&Rate, NULL) && (Rate.Price != 99.99)) { if (Rate.Charge != oldCharge || j == duration - 1) { printf("%d %.4f\n", j, Rate.Charge); oldCharge = Rate.Charge; } } else break; Rate.now++; } if (Rate.Duration <= takt) printf("@----- %s %s\n", currency, Rate.Provider); } if (explain == 98 || explain == 97) { /* Minutenpreis fuer diese Woche/Tag */ int j; if (first && header) print_header(); first = 0; printf("@ %s\n", prefix2provider_variant(Rate.prefix, prov)); for (j = 0; j < (explain == 98 ? 7 * 24 : 24); j++) { if (!getRate(&Rate, NULL) && (Rate.Price != 99.99)) { printf("%d %.4f\n", j, Rate.Charge); } else break; Rate.now += 3600; Rate.start += 3600; } if (Rate.Duration <= takt) printf("@----- %s %s\n", currency, Rate.Provider); } else if (explain == 50 || explain == 51) { int fi = 1; while (getZoneRate(&Rate, explain - 50, fi) == 0) { double cpm = Rate.Duration > 0 ? 60 * Rate.Price / Rate.Duration : 99.99; if (Rate.Price == 0) cpm = Rate.Basic; fi = 0; if (Rate.Price != 99.99) printf("%s%c%s%c%s%c%.2f%c%.2f%c%s\n", prefix2provider(Rate.prefix, prov), DEL, Rate.Provider, DEL, currency, DEL, Rate.Charge, DEL, cpm, DEL, P_EMPTY(Rate.Country)); free(Rate.Country); } } else { /* kludge to suppress "impossible" Rates */ char **message, *msg; if(verbose) message = &msg; else message = NULL; if (!getRate(&Rate, message) && (Rate.Price != 99.99)) { if (!(Rate.Duration <= takt)) continue; sort[n].prefix = Rate.prefix; sort[n].rate = Rate.Charge; sort[n].name = Rate.Provider; switch (explain) { case 1: if (service) sprintf(s, " (%s, %s)", P_EMPTY(Rate.dst[1]), Rate.Zone); else sprintf(s, " (%s)", Rate.Zone); sort[n].explain = strdup(s); break; case 2: sprintf(s, " (%s)", printrate(&Rate)); sort[n].explain = strdup(s); break; case 8: sort[n].explain = strdup(P_EMPTY(getComment(i, comment))); break; case 9: /* used by list */ { double cpm = Rate.Duration > 0 ? 60 * Rate.Price / Rate.Duration : 99.99; if (Rate.Price == 0) cpm = Rate.Basic; sprintf(s, "%s%c" "%s%c%s%c%s%c%s%c" "%s%c" "%.3f%c%.4f%c%.4f%c%.2f%c%.3f%c" "%s%c%.2f", prefix2provider_variant(Rate.prefix, prov), DEL, Rate.Provider, DEL, P_EMPTY(Rate.Zone), DEL, P_EMPTY(Rate.Day), DEL, P_EMPTY(Rate.Hour), DEL, currency, DEL, /* Fixme: global or per Provider?? wg. EURO */ Rate.Charge, DEL, Rate.Basic, DEL, Rate.Price, DEL, Rate.Duration, DEL, cpm, DEL, takt_str(&Rate), DEL, Rate.Sales); sort[n].explain = strdup(s); } break; default: sort[n].explain = strdup(""); break; } n++; } /* if */ else if(verbose && *msg) print_msg(PRT_V, "%s\n", msg); } /* else 99 */ } /* for i */ if (service) num = getServiceNum(0); /* get next service num */ else num = 0; } while (num && *num); if (explain < 10) { qsort((void *) sort, n, sizeof(SORT), compare_func); if (lcr && n) { RATE Cheap; int res = getLeastCost(&Rate, &Cheap, booked, -1); if (res != UNKNOWN) { sort[n].prefix = Cheap.prefix; sort[n].rate = Cheap.Charge; sort[n].name = Cheap.Provider; sprintf(s, "(Cheapest: %s %s %s %s)", Cheap.dst[0], Cheap.dst[1], Cheap.dst[2], P_EMPTY(Cheap.Zone)); sort[n].explain = strdup(s); n++; } /* res */ } /* lcr */ } /* explain */ return (n); } /* compute */ static void print_header(void) { printf("Eine %d Sekunden lange Verbindung von %s nach %s kostet am %s\n", duration, formatNumber("%F", &srcnum), formatNumber("%F", &destnum), ctime(&start)); } static void printList(int n) { int i; if (header) print_header(); if (n > best) n = best; for (i = 0; i < n; i++) printf("%s\n", sort[i].explain); } static void result(int n) { register int i; if (header) print_header(); if (n > best) n = best; for (i = 0; i < n; i++) printf("%s %s %s\n", Provider(sort[i].prefix), printRate(sort[i].rate), sort[i].explain); } /* result */ static void purge(int n) { register int i; for (i = 0; i < n; i++) if (sort[i].explain) free(sort[i].explain); } /* purge */ /* Werktag Wochenende Ortszone Regionalzone Fernzone Handy Internet 0..23 Uhr */ #define STARTHOUR 8 static void printTable(char *num) { register int n, d, i, h, lasthour; auto struct tm *tm; auto SORT last[_MAXLAST]; auto int used[MAXPROVIDER]; auto int hours[MAXPROVIDER]; auto int weight[MAXPROVIDER]; auto int useds = 0, maxhour; auto SORT2 wsort[MAXPROVIDER]; static int firsttime = 1; int first; int prefix; memset(used, 0, sizeof(used)); memset(hours, 0, sizeof(hours)); memset(weight, 0, sizeof(weight)); for (d = 0; d < 3; d++) { /* Werktag, Samstag, Sonntag */ last[0].prefix = UNKNOWN; lasthour = UNKNOWN; buildtime(); tm = localtime(&start); if (!d) { /* first time */ while (tm->tm_wday != 5) { /* find next Friday */ start += (60 * 60 * 24); tm = localtime(&start); } /* while */ } else /* erst Samstag, dann */ start += (60 * 60 * 24); /* Sonntag */ splittime(); buildtime(); hour = STARTHOUR; min = 0; first = 1; while (1) { destnum.nprovider = UNKNOWN; if (provider2prefix(num, &prefix)) /* set provider if it is in number */ normalizeNumber(num, &destnum, TN_PROVIDER); n = compute(num); if (header && first && d == 0 && firsttime) print_header(); if (header && first) { switch (d) { case 0 : printf("\nWerktag:\n"); break; case 1 : printf("\nSamstag:\n"); break; case 2 : printf("\nSonntag:\n"); break; } /* switch */ first = 0; } /* if */ if (last[0].prefix == UNKNOWN) { for (i = 0; i < min(n, MAXLAST); i++) { if (sort[i].prefix) { last[i].prefix = sort[i].prefix; last[i].rate = sort[i].rate; last[i].explain = strdup(sort[i].explain); } /* if */ } /* for */ } /* if */ if (lasthour == UNKNOWN) lasthour = hour; if (sort[0].prefix != last[0].prefix) { for (i = 0; i < min(n, MAXLAST); i++) { if (!i) printf(" %02d:00 .. %02d:59 %s = %s %s%s\n", lasthour, hour - 1, Provider(last[i].prefix), currency, double2str(last[i].rate, 5, 3, DEB), last[i].explain); else printf(" %s = %s %s%s\n", Provider(last[i].prefix), currency, double2str(last[i].rate, 5, 3, DEB), last[i].explain); } /* for */ used[last[0].prefix]++; if (lasthour >= hour) h = ((24 - lasthour) + hour); else h = hour - lasthour; hours[last[0].prefix] += h; if ((lasthour > 8) && (lasthour < 21)) h *= 2; weight[last[0].prefix] += h * (d ? 1 : 5); for (i = 0; i < min(n, MAXLAST); i++) { last[i].prefix = sort[i].prefix; last[i].rate = sort[i].rate; last[i].explain = strdup(sort[i].explain); } /* for */ lasthour = hour; } /* if */ purge(n); hour++; if (hour == 24) hour = 0; else if (hour == STARTHOUR) break; } /* while */ for (i = 0; i < min(n, MAXLAST); i++) { if (!i) { if ((lasthour == STARTHOUR) && (hour == STARTHOUR)) printf(" immer %s = %s %s%s\n", Provider(last[i].prefix), currency, double2str(last[i].rate, 5, 3, DEB), last[i].explain); else printf(" %02d:00 .. %02d:59 %s = %s %s%s\n", lasthour, hour - 1, Provider(last[i].prefix), currency, double2str(last[i].rate, 5, 3, DEB), last[i].explain); } else printf(" %s = %s %s%s\n", Provider(last[i].prefix), currency, double2str(last[i].rate, 5, 3, DEB), last[i].explain); } /* for */ used[last[0].prefix]++; if (lasthour >= hour) h = ((24 - lasthour) + hour); else h = hour - lasthour; hours[last[0].prefix] += h; if ((lasthour > 8) && (lasthour < 21)) h *= 2; weight[last[0].prefix] += h * (d ? 1 : 5); } /* for */ if (usestat) { printf("\nProvider(s) used:\n"); maxhour = 9999999; useds = 0; for (i = 0; i < MAXPROVIDER; i++) if (used[i]) { printf("%s %d times, %d hours, weight = %d\n", Provider(i), used[i], hours[i], weight[i]); wsort[useds].weight = weight[i]; wsort[useds].index = i; useds++; if (hours[i] < maxhour) maxhour = hours[i]; } /* if */ if ((best < MAXPROVIDER) && (best < useds)) { printf("Retrying with only %d provider(s), eliminating %d provider(s)\n", best, useds - best); qsort((void *) wsort, useds, sizeof(SORT2), compare2); for (i = 0; i < useds; i++) { printf("%s %d times, %d hours, weight = %d\n", Provider(wsort[i].index), used[wsort[i].index], hours[wsort[i].index], weight[wsort[i].index]); if (i == best - 1) printf("\n"); if (i >= best - 1) ignore[wsort[i].index]++; } /* for */ if (firsttime) printTable(num); firsttime = 0; } /* if */ } /* if */ } /* printTable */ static void clean_up() { if (providers) free(providers); providers = 0; fromarea = 0; table = list = header = explain = 0; usestat = 0; duration = LCR_DURATION; wanted_day = '\0'; takt = 99999; if (comment) free(comment); comment = 0; sortby = 0; need_dest = 1; } static char * sub_sp(char *p) { char *o = p; int allupper=1; for (; *p; p++) if(!isupper(*p) && *p != '_' && !isdigit(*p)) { /* e.g. _DEMD1 */ allupper = 0; break; } if (!allupper) for (p = o; *p; p++) if(*p == '_') *p = ' '; return o; } static void doit(int i, int argc, char *argv[]) { int n; int prefix; char *num; post_init(); memset(ignore, 0, sizeof(ignore)); if (!need_dest && i == 0) { i = 0; argc = 1; argv[0] = "2345"; } while (i < argc) { num = sub_sp(argv[i]); if (explain == 55) { if (n_providers) { destnum.nprovider = pnum2prefix_variant(providers[0], 0); Strncpy(destnum.provider, getProvider(destnum.nprovider), TN_MAX_PROVIDER_LEN); normalizeNumber(num, &destnum, TN_NO_PROVIDER); } else normalizeNumber(num, &destnum, TN_ALL); printf("%s => %s \n", num, formatNumber("%l - %p", &destnum)); i++; continue; } else if (explain == 52) { // enum Servce names int first = 1; char *p; do { p = getServiceNames(first); if (p) printf("%s\n", p); first = 0; } while (p); return; } destnum.nprovider = UNKNOWN; if (provider2prefix(num, &prefix)) /* set provider if it is in number */ normalizeNumber(num, &destnum, TN_PROVIDER); if (table) printTable(num); else { n = compute(num); if (list) printList(n); else if (explain < 10) result(n); purge(n); } i++; } /* while */ clean_up(); } static void err(char *s) { print_msg(PRT_A, "%s - '%s'\n", s, strerror(errno)); if (is_daemon == 2) kill(getppid(), SIGTERM); exit(2); } static int handle_client(int fd) { char buffer[BUFSIZ]; int argc, n, i; char **argv; char *p; if ((n = read(fd, buffer, BUFSIZ)) < 0) err("Read"); if (n) { argv = calloc(sizeof(char *), 20); buffer[n] = '\0'; if (verbose) fprintf(stderr, "got '%s'\n", buffer); argc = 0; argv[argc++] = strdup(myname); p = strtok(buffer, "\t\n "); while (p) { argv[argc++] = strdup(p); p = strtok(0, "\t\n "); if (argc >= 20) break; } time(&start); /* set time of call */ splittime(); /* date time my be overridden by opts */ we_are_daemon = 1; if ((i = opts(argc, argv)) || need_dest == 0) { if (shutdown(fd, 0) < 0) /* no read any more */ err("shutdown"); if (dup2(fd, STDOUT_FILENO) < 0) /* stdout to sock */ err("dup"); doit(i, argc, argv); fflush(stdout); fclose(stdout); } for (i = 0; i < argc; i++) free(argv[i]); free(argv); } return n == 0 ? -1 : 0; } void catch_sig(int sig) { print_msg(PRT_A, "Signal %d\n", sig); unlink(socket_file); if (pid_dir) unlink(pid_file); err("Sig"); } static void del_sock(void) { if (getppid() > 0) { unlink(socket_file); if (pid_dir) unlink(pid_file); } } static volatile sig_atomic_t stopped = 0; static volatile sig_atomic_t reinit = 0; static void catch_term(int sig) { stopped = 1; } static void catch_hup(int sig) { print_msg(PRT_A, "Signal %d restarting %s\n", sig, myname); del_sock(); execvp(myname, hup_argv); print_msg(PRT_A, "- failed\n"); } static void do_reinit(void) { /* deinit(), init() doesn't */ reinit=0; } /* thank's to Jochen Erwied for this: */ #ifndef TEMP_FAILURE_RETRY #define TEMP_FAILURE_RETRY(expression) \ (__extension__ \ ({ long int __result; \ do __result = (long int) (expression); \ while (__result == -1L && errno == EINTR); \ __result; })) \ #endif static void setup_daemon() { int sock; struct sockaddr_un sa; struct sockaddr_in client; fd_set active_fd_set, read_fd_set; char *sock_name = socket_file; socklen_t size; struct stat stat_buf; int i; pid_t pid; char pidname[] = "isdnrate.pid"; FILE *fp; if (verbose) fprintf(stderr, "Setup sockets\n"); signal(SIGTERM, catch_term); signal(SIGINT, catch_sig); signal(SIGHUP, catch_hup); if (is_daemon == 2) { /* go background */ fprintf(stderr, "Going background\n"); verbose = 0; pid = fork(); if (pid < 0) err("Going bg failed"); else if (pid > 0) { exit(EXIT_SUCCESS); } } if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) err("Can't open socket"); sa.sun_family = AF_UNIX; strcpy(sa.sun_path, sock_name); size = offsetof(struct sockaddr_un, sun_path) + strlen(sa.sun_path) + 1; if (bind(sock, (struct sockaddr *) &sa, size) < 0) err("Can't bind sock"); stat(sock_name, &stat_buf); chmod(sock_name, stat_buf.st_mode | S_IWOTH | S_IWGRP); /* should better have perm wwwrun.nogroup */ if (listen(sock, SOMAXCONN) < 0) err("Can't listen"); if (pid_dir) { pid_file = malloc(strlen(pid_dir) + strlen(pidname) + 2); strcpy(pid_file, pid_dir); if (pid_file[strlen(pid_file) - 1] != '/') strcat(pid_file, "/"); strcat(pid_file, pidname); if ((fp = fopen(pid_file, "w")) == 0) fprintf(stderr, "Can't write %s\n", pid_file); else { fprintf(fp, "%d\n", getpid()); fclose(fp); } } atexit(del_sock); FD_ZERO(&active_fd_set); FD_SET(sock, &active_fd_set); while (!stopped) { if (reinit) do_reinit(); read_fd_set = active_fd_set; if (TEMP_FAILURE_RETRY(select(FD_SETSIZE, &read_fd_set, 0, 0, 0)) < 0) err("select"); for (i = 0; i < FD_SETSIZE; i++) if (FD_ISSET(i, &read_fd_set)) { if (i == sock) { /* request on orig */ int new; size = sizeof(client); if ((new = accept(sock, (struct sockaddr *) &client, &size)) < 0) err("accept"); if (verbose) fprintf(stderr, "Accepted %d\n", new); FD_SET(new, &active_fd_set); } else { /* already connected */ pid_t pid; int status; if (verbose) fprintf(stderr, "Handle client %d\n", i); pid = fork(); if (pid == 0) { handle_client(i); _exit(EXIT_SUCCESS); } else if (pid < 0) err("fork"); else { if (waitpid(pid, &status, 0) != pid) err("waitpid"); close(i); FD_CLR(i, &active_fd_set); } } /* if i */ } /* if ISSET */ } /* while */ del_sock(); exit(EXIT_SUCCESS); } static int connect_2_daemon(int argc, char *argv[]) { int sock; struct sockaddr_un sa; char *sock_name = socket_file; size_t size; int i, c, len; char *p, *q, buffer[BUFSIZ]; if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) err("Can't open socket"); sa.sun_family = AF_UNIX; strcpy(sa.sun_path, sock_name); size = offsetof(struct sockaddr_un, sun_path) + strlen(sa.sun_path) + 1; if (connect(sock, (struct sockaddr *) &sa, size) < 0) err("Can't connect sock"); p = buffer; optind = 0; /* reinit */ len = 0; while ((c = getopt(argc, argv, options)) != EOF && len < BUFSIZ - 10) { switch (c) { case '?': case 'C': break; case 'D': if (optarg && atoi(optarg) == 3) ; /* goon, kill a running daemon */ else break; default: *p++ = '-'; *p++ = c; len += 3; if (optarg) for (q = optarg; *q && len < BUFSIZ - 10; len++) *p++ = *q++; *p++ = ' '; break; } } for (i = optind; i < argc; i++) { for (q = argv[i]; *q && len < BUFSIZ - 10; len++) *p++ = *q++; *p++ = ' '; len++; } *--p = '\x0'; i = write(sock, buffer, strlen(buffer) + 1); if (i < 0) err("Write sock"); while ((i = read(sock, buffer, BUFSIZ)) > 0) write(1, buffer, i); close(sock); return EXIT_SUCCESS; } int main(int argc, char *argv[], char *envp[]) { register int i; sigset_t unblock_set; myname = argv[0]; myshortname = basename(myname); time(&start); splittime(); socket_file = strdup(SOCKNAME); /* borrowed from isdnlog.c, thanks */ hup_argv = argv; sigemptyset(&unblock_set); sigaddset(&unblock_set, SIGHUP); sigprocmask(SIG_UNBLOCK, &unblock_set, NULL); if ((i = opts(argc, argv)) || need_dest == 0) { if (is_client) exit(connect_2_daemon(argc, argv)); else init(); if (is_daemon) { clean_up(); setup_daemon(); } else doit(i, argc, argv); } else { print_msg(PRT_A, usage, myshortname, myshortname, options); print_msg(PRT_A, "\n"); print_msg(PRT_A, "\t-a \tall=show old and newer rates (default actual only)\n", MAXPROVIDER); print_msg(PRT_A, "\t-b best\tshow only the first provider(s) (default %d)\n", MAXPROVIDER); print_msg(PRT_A, "\t-c file\tuse as rate.conf\n"); print_msg(PRT_A, "\t-d d[.m[.y]] | {W|N|E}\tstart date of call (default now)\n"); print_msg(PRT_A, "\t-f areacode\tyou are calling from \n"); print_msg(PRT_A, "\t-h h[:m[:s]]\tstart time of call (default now)\n"); print_msg(PRT_A, "\t-i file\tuse as ratefile (rate-CC.dat)\n"); print_msg(PRT_A, "\t-l duration\tduration of call in seconds (default %d seconds)\n", LCR_DURATION); print_msg(PRT_A, "\t-o \t show only booked providers\n"); print_msg(PRT_A, "\t-p prov|B[,prov...]\t show only these providers\n"); print_msg(PRT_A, "\t-r vNN\tonly providers whose vbn begins with \n"); print_msg(PRT_A, "\t-s \t consider 'Destination' as a service name\n"); print_msg(PRT_A, "\t-t takt\t\tshow providers if chargeduration<=takt\n"); print_msg(PRT_A, "\t-v [level]\tverbose\n"); print_msg(PRT_A, "\t-x prov|B[,prov...]\t exclude these providers\n"); print_msg(PRT_A, "\tOutput and run options\n"); print_msg(PRT_A, "\t-C\trun as client, connecting to a running daemon\n"); print_msg(PRT_A, "\t-CD3\tkill a running daemon\n"); print_msg(PRT_A, "\t-D\trun as daemon\n"); print_msg(PRT_A, "\t-D2\trun as daemon & go background\n"); print_msg(PRT_A, "\t-G which\tshow raw data\n"); print_msg(PRT_A, "\t-H\tshow a header\n"); print_msg(PRT_A, "\t-L\tshow a detailed list\n"); print_msg(PRT_A, "\t-O sOcket-path\twrite socket to this file (def:tmp/isdnrate)\n"); print_msg(PRT_A, "\t-P pid-dir\twrite own PID to pid-dir/isdnrate.pid\n"); print_msg(PRT_A, "\t-N\tparse the given telefon numbers\n"); print_msg(PRT_A, "\t-S[v|n]\tsort by v=VBN, n=Name, default=Charge\n"); print_msg(PRT_A, "\t-T\tshow a table of day/night week/weekend\n"); print_msg(PRT_A, "\t-U\tshow usage stats for table\n"); print_msg(PRT_A, "\t-V\tshow version info\n"); print_msg(PRT_A, "\t-X\texplain each rate\n"); print_msg(PRT_A, "\t-X2\texplain more\n"); print_msg(PRT_A, "\t-X comment\tprint from C:tag\n"); print_msg(PRT_A, "\t-Z show entry from getLeastCost\n"); print_msg(PRT_A, "\n\te.g.\t%s -b5 -f30 -TH -t1 Zaire\n", myshortname); } /* else */ /* deinit(); Fixme: this SIGSEGVs in exitHoliday */ return (0); } /* isdnrate */