/* * Copyright (C) 2001-2003 R. David Quattlebaum * * 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 of the License, 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */ /* * ACKNOWLEDGMENTS: * * This code contains software Copyrighted by Brian S. Dean under the * following conditions: * * Copyright 2002 Brian S. Dean * All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY BRIAN S. DEAN ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BRIAN S. DEAN BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. * */ /* $Id: cid.c,v 1.45 2003/06/14 14:35:02 drq Exp $ */ /* * cid.c * * caller id routines * */ #include #include #include #include #ifdef WIN32 #include #include "regex.h" #include #else #include #include #include #endif #include "scud.h" #include "cid.h" #include "lists.h" #include "modem.h" #include "util.h" extern char *progname; extern char *version; /* * option variables */ extern QUEUEID names; extern QUEUEID phones; extern int allow; extern int private; extern int unavailable; extern int verbose; extern char *modeminit; extern int sync_duration; extern int hangup_duration; extern int heartbeat; extern int action_delay; extern char call_action; extern char *input_file; extern int *double_byte; extern int action_start; extern int action_stop; time_t current_time; time_t action_continue = 0; int read_two_bytes = 0; int init_cid(char *device) { int fd = modem_open(device); int rc; if (fd == BAD_FD) { log_printf("ERROR: can't open modem: %s\n", device); } if (verbose >= 1) log_printf("; init_cid: initializing modem...\n"); rc = modem_send_command_reply(fd, "AT", "OK"); rc = modem_send_command_reply(fd, "ATZ", "OK"); rc = modem_send_command_reply(fd, "ATM0", "OK"); if (modeminit) { rc = modem_send_command_reply(fd, modeminit, "OK"); } else { modeminit = "AT#CID=2"; rc = modem_send_command_reply(fd, modeminit, "OK"); if (rc == -1) { modeminit = "AT+VCID=2"; rc = modem_send_command_reply(fd, modeminit, "OK"); } } if (rc == -1) { log_printf("ERROR: modem doesn't appear to support caller id\n"); exit(1); } return(fd); } /* * will check buffer against a queue of regular expressions * * returns - 0 (match) * 1 (no match) */ int check_for_action(QUEUEID regexpq, char *buffer) { int status = 1; QNODEID regexp; int qcount = QUEUELEN(regexpq); Selected *s; for (regexp = lfirst(regexpq); regexp; regexp = lnext(regexp)) { s = ldata(regexp); if (verbose >= 1) log_printf("; check_for_action: (%s == /%s/) ? ", buffer, s->pattern); status = regexec(s->re, buffer, (size_t)0, NULL, 0); if (status == 0) { if (verbose >= 1) log_printf("yes\n"); break; } if (verbose >= 1) log_printf("no\n"); qcount--; } return(status); } static int answer_call(int fd) { modem_send_command(fd, "ATA"); /* answer modem */ sleep(sync_duration); modem_send_command(fd, "+++"); sleep(2); modem_send_command(fd, "ATH0"); /* hangup modem */ return(0); } static int hangup_call(int fd) { modem_send_command(fd, "ATH1"); /* off hook */ sleep(hangup_duration); modem_send_command(fd, "ATH0"); /* hangup modem */ return(0); } int decode_cid(char *buf, int len, int fd) { int i, action = 0; char *p; char datefmt[12]; char *date; char name[128], phone[128]; char *namestr, *phonestr; char *praction = ""; int call_time = 0; /* remove any non printable characters from the front of date */ p = buf; while (len && !isprint(*p)) { p++; len--; } /* store date into it's field (formatted) */ date = p; i = 0; while (len && (i < BUFLEN-1) && isprint(*p)) { i++; p++; len--; } if (i == 8) { datefmt[0] = date[0]; datefmt[1] = date[1]; datefmt[2] = '/'; datefmt[3] = date[2]; datefmt[4] = date[3]; datefmt[5] = ' '; datefmt[6] = date[4]; datefmt[7] = date[5]; datefmt[8] = ':'; datefmt[9] = date[6]; datefmt[10] = date[7]; datefmt[11] = date[8] = 0; call_time = atoi(&date[4]); date = datefmt; } else { date = "bad format"; } /* remove any non printable characters from front of name */ while (len && !isprint(*p)) { p++; len--; } /* p points to the beginning of the name portion */ i = 0; while (len && (i < BUFLEN-1) && isprint(*p)) { name[i++] = *p++; len--; } name[i] = 0; /* remove any non printable characters from front of phone number */ while (len && !isprint(*p)) { p++; len--; } /* p points to the beginning of the phone number portion */ i = 0; while (len && (i < BUFLEN-1) && isprint(*p)) { phone[i++] = *p++; len--; } phone[i] = 0; /* * some modems send the phone number and name in backward * order */ if (double_byte) { phonestr = name; namestr = phone; } else { namestr = name; phonestr = phone; } /* convert "O" or "P" to printable name */ if (strcmp(namestr, "O") == 0) { strcpy(namestr, "unavailable"); } else if (strcmp(namestr, "P") == 0) { strcpy(namestr, "private"); } /* convert "O" or "P" to printable number */ if (strcmp(phonestr, "O") == 0) { if (unavailable) action = 1; strcpy(phonestr, "unavailable"); } else if (strcmp(phonestr, "P") == 0) { if (private) action = 1; strcpy(phonestr, "private"); } /* * check any names against caller name */ if (action == 0 && names) { if (check_for_action(names, namestr) == 0) action = 1; } /* * check any phone numbers against caller phone */ if (action == 0 && phones) { if (check_for_action(phones, phonestr) == 0) action = 1; } /* * if we want to allow only those listed in the .conf file thru * (-allow option), reverse the action */ if (allow) { action = !action; } /* * answer modem if we are an annoying caller and it has been * at least action_delay seconds since the last call we acted on * * if time is between action_start and action_stop proceed. */ current_time = time(NULL); if (action && (call_time >= action_start && call_time <= action_stop)) { if (current_time > action_continue) { /* * answer the modem only if we aren't working from an input file */ if (call_action == 'A') { praction = "answer"; if (!input_file) answer_call(fd); } else { praction = "hangup"; if (!input_file) hangup_call(fd); } /* figure the time_t when we will do this again */ if (action_delay) action_continue = current_time + action_delay; } else praction = "delayed"; } /* print log of caller information */ log_printf("Name: %-20s Phone: %-15s Time: %s %s\n", lowercase(namestr), phonestr, date, praction); return 0; } int print_list(QUEUEID *regexpq, char *listname) { QNODEID regexp; Selected *s; log_printf("; patterns in %s list:\n", listname); for (regexp = lfirst(regexpq); regexp; regexp = lnext(regexp)) { s = ldata(regexp); log_printf("; %s\n", s->pattern); } return(0); } int print_header(void) { time_t start_time = time(NULL); char *unavail_act = "no action"; char *private_act = "no action"; log_printf("; ==== %s (%s): started at %s", progname, version, ctime(&start_time)); if (verbose >= 1) { if (call_action == 'A') { if (unavailable) unavail_act = "answer"; if (private) private_act = "answer"; } else if (call_action == 'H') { if (unavailable) unavail_act = "hangup"; if (private) private_act = "hangup"; } log_printf("; unavailable calls action: %s\n", unavail_act); log_printf("; private calls action : %s\n", private_act); if (names) { print_list(names, "Names"); } if (phones) { print_list(phones, "Phones"); } if (double_byte) log_printf("; modem sends dblbyte ascii\n"); if (allow) log_printf("; only allow entries in scud.conf to pass thru\n"); } return(0); } /* * routines to read strings and then one byte from that string * from an input file (should be a log file with the -verbose 2 output) */ int input_read_string(int fd, char *buf, int buflen) { int len = 0; char *ch = buf; buf[0] = 0; /* set to null string */ while (len < buflen) { read(fd, ch, 1); if (*ch == '\n') { *ch = 0; break; } ch++; len++; } return(len); } int input_read_byte(int fd, char *ch) { int rc = 0; char buffer[256]; int value; while (1) { rc = input_read_string(fd, buffer, sizeof(buffer)); if (rc && !strncmp(buffer, "; modem_read_byte:", 18)) { char *start; start = &buffer[21]; sscanf(start, "%X", &value); if (read_two_bytes) { int value2; rc = input_read_string(fd, buffer, sizeof(buffer)); sscanf(start, "%X", &value2); sprintf(buffer, "0x%c%c", value, value2); sscanf(buffer, "%X", &value); } *ch = (char)value; rc = 1; break; } } return(rc); } int cid(int fd) { int datalen = 0; int i; int rc; int state; unsigned char ch; unsigned char cksum = 0; char buf[BUFLEN]; int last_init = time(NULL); char mesgbuf[7] = {0}; if (verbose >= 1) log_printf("; cid: waiting on callerid info...\n"); state = CID_INITIAL; i = 0; while (1) { if (input_file) rc = input_read_byte(fd, &ch); else rc = modem_read_byte(fd, &ch, 5); if (double_byte && !read_two_bytes) { if (ch == '8') { if (input_file) rc = input_read_byte(fd, &ch); else rc = modem_read_byte(fd, &ch, 5); if (ch == '0') { read_two_bytes = 1; ch = 0x80; } } } if (rc < 1) { /* * if it has been seconds since last time here, re-init * modem */ int now = time(NULL); if (now > last_init + heartbeat) { modem_send_command_reply(fd, modeminit, "OK"); last_init = now; } continue; } switch (state) { case CID_INITIAL: if (ch == 0x80) { if (verbose >= 3) log_printf("; cid: found start of cid info (x'80')\n"); state = CID_DATALEN; cksum = ch; i = 0; memset(mesgbuf, 0, sizeof(mesgbuf)); } break; case CID_DATALEN: datalen = ch; if (verbose >= 3) log_printf("; cid: cid datalen = %d\n", datalen); cksum += ch; state = CID_DATA; i = 0; break; case CID_DATA: buf[i++] = ch; cksum += ch; if ((i == datalen) || (i >= BUFLEN-1)) { buf[i] = 0; if (double_byte) { state = CID_INITIAL; if (verbose >= 3) log_printf("; cid: calling decode_cid(dblbyte)\n"); decode_cid(buf, i, fd); read_two_bytes = 0; } else { state = CID_CKSUM; } } break; case CID_CKSUM: if ((unsigned char)(cksum + ch) != 0) { log_printf("; cid: checksum error, 0x%02x + 0x%02x = 0x%02x\n", cksum, ch, (unsigned char)(cksum+ch)); } state = CID_INITIAL; if (verbose >= 3) log_printf("; cid: calling decode_cid\n"); decode_cid(buf, i, fd); break; default : log_printf("; cid: invalid state = %d\n", state); state = CID_INITIAL; break; } } return 0; }