/* * Miniphone: A simple, command line telephone * * IAX Support for talking to Asterisk and other Gnophone clients * * Copyright (C) 1999, Linux Support Services, Inc. * * Mark Spencer * * This program is free software, distributed under the terms of * the GNU General Public License */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "busy.h" #include "dialtone.h" #include "answer.h" #include "ringtone.h" #include "ring10.h" #include "options.h" #define FRAME_SIZE 160 static char callerid[80]; struct peer { int time; gsm gsmin; gsm gsmout; struct iax_session *session; struct peer *next; }; static char *audiodev = "/dev/dsp"; static int audiofd = -1; static struct peer *peers; static int answered_call = 0; static struct peer *find_peer(struct iax_session *); static int audio_setup(char *); static void sighandler(int); static void parse_args(FILE *, unsigned char *); void do_iax_event(FILE *); void call(FILE *, char *); void answer_call(void); void reject_call(void); static void handle_event(FILE *, struct iax_event *e, struct peer *p); void parse_cmd(FILE *, int, char **); void issue_prompt(FILE *); void dump_array(FILE *, char **); struct sound { short *data; int datalen; int samplen; int silencelen; int repeat; }; static int cursound = -1; static int sampsent = 0; static int offset = 0; static int silencelen = 0; static int nosound = 0; static int offhook = 0; static int ringing = 0; static int writeonly = 0; static struct iax_session *registry = NULL; static struct timeval regtime; #define TONE_NONE -1 #define TONE_RINGTONE 0 #define TONE_BUSY 1 #define TONE_CONGEST 2 #define TONE_RINGER 3 #define TONE_ANSWER 4 #define TONE_DIALTONE 5 #define OUTPUT_NONE 0 #define OUTPUT_SPEAKER 1 #define OUTPUT_HANDSET 2 #define OUTPUT_BOTH 3 static struct sound sounds[] = { { ringtone, sizeof(ringtone)/2, 16000, 32000, 1 }, { busy, sizeof(busy)/2, 4000, 4000, 1 }, { busy, sizeof(busy)/2, 2000, 2000, 1 }, { ring10, sizeof(ring10)/2, 16000, 32000, 1 }, { answer, sizeof(answer)/2, 2200, 0, 0 }, { dialtone, sizeof(dialtone)/2, 8000, 0, 1 }, }; static char *help[] = { "Welcome to the miniphone telephony client, the commands are as follows:\n", "Help\t\t-\tDisplays this screen.", "Help \t-\tInqueries specific information on a command.", "Dial \t-\tDials the number supplied in the first arguement", "Status\t\t-\tLists the current sessions and their current status.", "Quit\t\t-\tShuts down the client.", "", 0 }; static short silence[FRAME_SIZE]; static struct peer *most_recent_answer; static struct iax_session *newcall = 0; static struct peer *find_peer(struct iax_session *session) { struct peer *cur = peers; while(cur) { if (cur->session == session) return cur; cur = cur->next; } return NULL; } static int audio_setup(char *dev) { int fd; int fmt = AFMT_S16_LE; int channels = 1; int speed = 8000; int fragsize = (40 << 16) | 6; if ( (fd = open(dev, O_RDWR | O_NONBLOCK)) < 0) { fprintf(stderr, "Unable to open audio device %s: %s\n", dev, strerror(errno)); return -1; } if (ioctl(fd, SNDCTL_DSP_SETFMT, &fmt) || (fmt != AFMT_S16_LE)) { fprintf(stderr, "Unable to set in signed linear format.\n"); return -1; } if (ioctl(fd, SNDCTL_DSP_SETDUPLEX, 0)) { fprintf(stderr, "Unable to set full duplex operation.\n"); writeonly = 1; /* return -1; */ } if (ioctl(fd, SNDCTL_DSP_CHANNELS, &channels) || (channels != 1)) { fprintf(stderr, "Unable to set to mono\n"); return -1; } if (ioctl(fd, SNDCTL_DSP_SPEED, &speed) || (speed != 8000)) { fprintf(stderr, "Unable to set speed to 8000 hz\n"); return -1; } if (ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &fragsize)) { fprintf(stderr, "Unable to set fragment size...\n"); return -1; } return fd; } static int send_sound(int soundfd) { /* Send FRAME_SIZE samples of whatever */ short myframe[FRAME_SIZE]; short *frame = NULL; int total = FRAME_SIZE; int amt=0; int res; int myoff; audio_buf_info abi; if (cursound > -1) { res = ioctl(soundfd, SNDCTL_DSP_GETOSPACE ,&abi); if (res) { fprintf(stderr,"Unable to read output space\n"); return -1; } /* Calculate how many samples we can send, max */ if (total > (abi.fragments * abi.fragsize / 2)) total = abi.fragments * abi.fragsize / 2; res = total; if (sampsent < sounds[cursound].samplen) { myoff=0; while(total) { amt = total; if (amt > (sounds[cursound].datalen - offset)) amt = sounds[cursound].datalen - offset; memcpy(myframe + myoff, sounds[cursound].data + offset, amt * 2); total -= amt; offset += amt; sampsent += amt; myoff += amt; if (offset >= sounds[cursound].datalen) offset = 0; } /* Set it up for silence */ if (sampsent >= sounds[cursound].samplen) silencelen = sounds[cursound].silencelen; frame = myframe; } else { if (silencelen > 0) { frame = silence; silencelen -= res; } else { if (sounds[cursound].repeat) { /* Start over */ sampsent = 0; offset = 0; } else { cursound = -1; nosound = 0; } } } #if 0 if (frame) printf("res is %d, frame[0] is %d\n", res, frame[0]); #endif res = write(soundfd, frame, res * 2); if (res > 0) return 0; return res; } return 0; } static int iax_regtimeout(int timeout) { if (timeout) { gettimeofday(®time, NULL); regtime.tv_sec += timeout; } else { regtime.tv_usec = 0; regtime.tv_sec = 0; } return 0; } static int check_iax_register(void) { int res; if (strlen(regpeer) && strlen(server)) { registry = iax_session_new(); res = iax_register(registry, server,regpeer,regsecret, refresh); if (res) { fprintf(stderr, "Failed registration: %s\n", iax_errstr); return -1; } iax_regtimeout(5 * refresh / 6); } else { iax_regtimeout(0); refresh = 60; } return 0; } static int check_iax_timeout(void) { struct timeval tv; int ms; if (!regtime.tv_usec || !regtime.tv_sec) return -1; gettimeofday(&tv, NULL); if ((tv.tv_usec >= regtime.tv_usec) && (tv.tv_sec >= regtime.tv_sec)) { check_iax_register(); /* Have it check again soon */ return 100; } ms = (regtime.tv_sec - tv.tv_sec) * 1000 + (regtime.tv_usec - tv.tv_usec) / 1000; return ms; } static int gentone(int sound, int uninterruptible) { cursound = sound; sampsent = 0; offset = 0; silencelen = 0; nosound = uninterruptible; printf("Sending tone %d\n", sound); return 0; } void sighandler(int sig) { if(sig == SIGHUP) { puts("rehashing!"); } else if(sig == SIGINT) { static int prev = 0; int cur; if ( (cur = time(0))-prev <= 5) { printf("Terminating!\n"); exit(0); } else { prev = cur; printf("Press interrupt key again in the next %d seconds to really terminate\n", 5-(cur-prev)); } } } void parse_args(FILE *f, unsigned char *cmd) { static char *argv[MAXARGS]; unsigned char *parse = cmd; int argc = 0, t = 0; // Don't mess with anything that doesn't exist... if(!*parse) return; bzero(argv, sizeof(argv)); while(*parse) { if(*parse < 33 || *parse > 128) { *parse = 0, t++; if(t > MAXARG) { fprintf(f, "Warning: Argument exceeds maximum argument size, command ignored!\n"); return; } } else if(t || !argc) { if(argc == MAXARGS) { fprintf(f, "Warning: Command ignored, too many arguments\n"); return; } argv[argc++] = parse; t = 0; } parse++; } if(argc) parse_cmd(f, argc, argv); } int main(int argc, char *argv[]) { int port; int netfd; int c, h=0, m, regm; FILE *f; int fd = STDIN_FILENO; char rcmd[RBUFSIZE]; fd_set readfd; fd_set writefd; struct timeval timer; struct timeval *timerptr = NULL; gsm_frame fo; load_options(); if (!strlen(callerid)) gethostname(callerid, sizeof(callerid)); signal(SIGHUP, sighandler); signal(SIGINT, sighandler); if ( !(f = fdopen(fd, "w+"))) { fprintf(stderr, "Unable to create file on fd %d\n", fd); return -1; } if ( (audiofd = audio_setup(audiodev)) == -1) { fprintf(stderr, "Fatal error: failed to open sound device"); return -1; } if ( (port = iax_init(0) < 0)) { fprintf(stderr, "Fatal error: failed to initialize iax with port %d\n", port); return -1; } iax_set_formats(AST_FORMAT_GSM); netfd = iax_get_fd(); check_iax_register(); fprintf(f, "Text Based Telephony Client.\n\n"); issue_prompt(f); timer.tv_sec = 0; timer.tv_usec = 0; while(1) { FD_ZERO(&readfd); FD_ZERO(&writefd); FD_SET(fd, &readfd); if(fd > h) h = fd; if(answered_call && !writeonly) { FD_SET(audiofd, &readfd); if(audiofd > h) h = audiofd; } if (cursound > -1) { FD_SET(audiofd, &writefd); if (audiofd > h) h = audiofd; } FD_SET(netfd, &readfd); if(netfd > h) h = netfd; if ( (c = select(h+1, &readfd, &writefd, 0, timerptr)) >= 0) { if(FD_ISSET(fd, &readfd)) { if ( ( fgets(&*rcmd, 256, f))) { rcmd[strlen(rcmd)-1] = 0; parse_args(f, &*rcmd); } else fprintf(f, "Fatal error: failed to read data!\n"); issue_prompt(f); } if(answered_call) { if(FD_ISSET(audiofd, &readfd)) { static int ret, rlen = 0; static short rbuf[FRAME_SIZE]; if ( (ret = read(audiofd, rbuf + rlen, 2 * (FRAME_SIZE-rlen))) == -1) { puts("Failed to read audio."); return -1; } rlen += ret/2; if(rlen == FRAME_SIZE) { rlen = 0; if(!most_recent_answer->gsmout) most_recent_answer->gsmout = gsm_create(); gsm_encode(most_recent_answer->gsmout, rbuf, fo); if(iax_send_voice(most_recent_answer->session, AST_FORMAT_GSM, (char *)fo, sizeof(fo)) == -1) puts("Failed to send voice!"); } } } do_iax_event(f); m = iax_time_to_next_event(); if(m > -1) { timerptr = &timer; timer.tv_sec = m /1000; timer.tv_usec = (m % 1000) * 1000; } else timerptr = 0; regm = check_iax_timeout(); if (!timerptr || (m > regm)) { timerptr = &timer; timer.tv_sec = regm /1000; timer.tv_usec = (regm % 1000) * 1000; } if (FD_ISSET(audiofd, &writefd)) { send_sound(audiofd); } } else { if(errno == EINTR) continue; fprintf(stderr, "Fatal error in select(): %s\n", strerror(errno)); return -1; } } return 0; } void do_iax_event(FILE *f) { int sessions = 0; struct iax_event *e = 0; struct peer *peer; while ( (e = iax_get_event(0))) { peer = find_peer(e->session); if(peer) { handle_event(f, e, peer); } else if (e->session == registry) { fprintf(stderr, "Registration complete: %s (%d)\n", (e->event.regreply.status == IAX_REG_SUCCESS) ? "Success" : "Failed", e->event.regreply.status); registry = NULL; } else { if(e->etype != IAX_EVENT_CONNECT) { fprintf(stderr, "Huh? This is an event for a non-existant session?\n"); continue; } sessions++; if(sessions >= MAX_SESSIONS) { fprintf(f, "Missed a call... too many sessions open.\n"); } if(e->event.connect.callerid && e->event.connect.dnid) fprintf(f, "Call from '%s' for '%s'", e->event.connect.callerid, e->event.connect.dnid); else if(e->event.connect.dnid) { fprintf(f, "Call from '%s'", e->event.connect.dnid); } else if(e->event.connect.callerid) { fprintf(f, "Call from '%s'", e->event.connect.callerid); } else printf("Call from"); fprintf(f, " (%s)\n", inet_ntoa(iax_get_peer_addr(e->session).sin_addr)); if(most_recent_answer) { fprintf(f, "Incoming call ignored, there's already a call waiting for answer... \ please accept or reject first\n"); iax_reject(e->session, "Too many calls, we're busy!"); } else { if ( !(peer = malloc(sizeof(struct peer)))) { fprintf(f, "Warning: Unable to allocate memory!\n"); return; } peer->time = time(0); peer->session = e->session; if (peer->gsmin) free(peer->gsmin); peer->gsmin = 0; if (peer->gsmout) free(peer->gsmout); peer->gsmout = 0; peer->next = peers; peers = peer; iax_accept(peer->session); iax_ring_announce(peer->session); most_recent_answer = peer; ringing = 1; gentone(TONE_RINGER, 0); fprintf(f, "Incoming call!\n"); } issue_prompt(f); } iax_event_free(e); } } void call(FILE *f, char *num) { struct peer *peer; if(!newcall) newcall = iax_session_new(); else { fprintf(f, "Already attempting to call somewhere, please cancel first!\n"); return; } if ( !(peer = malloc(sizeof(struct peer)))) { fprintf(f, "Warning: Unable to allocate memory!\n"); return; } peer->time = time(0); peer->session = newcall; peer->gsmin = 0; peer->gsmout = 0; peer->next = peers; peers = peer; most_recent_answer = peer; offhook = 1; iax_call(peer->session, callerid, num, NULL, 10); } void answer_call(void) { if(most_recent_answer) iax_answer(most_recent_answer->session); printf("Answering call!\n"); answered_call = 1; offhook = 1; ringing = 0; gentone(TONE_ANSWER, 1); } void reject_call(void) { iax_reject(most_recent_answer->session, "Call rejected manually."); most_recent_answer = 0; ringing = 0; gentone(TONE_NONE, 1); } void handle_event(FILE *f, struct iax_event *e, struct peer *p) { short fr[FRAME_SIZE]; int len; switch(e->etype) { case IAX_EVENT_HANGUP: iax_hangup(most_recent_answer->session, "Byeee!"); fprintf(f, "Call disconnected by peer\n"); free(most_recent_answer); most_recent_answer = 0; answered_call = 0; peers = 0; newcall = 0; if (offhook) gentone(TONE_CONGEST, 0); break; case IAX_EVENT_REJECT: fprintf(f, "Authentication was rejected\n"); break; case IAX_EVENT_ACCEPT: fprintf(f, "Accepted...\n"); issue_prompt(f); break; case IAX_EVENT_RINGA: fprintf(f, "Ringing...\n"); issue_prompt(f); gentone(TONE_RINGTONE, 0); break; case IAX_EVENT_ANSWER: answer_call(); gentone(TONE_ANSWER, 1); break; case IAX_EVENT_VOICE: switch(e->event.voice.format) { case AST_FORMAT_GSM: if(e->event.voice.datalen % 33) { fprintf(stderr, "Weird gsm frame, not a multiple of 33.\n"); break; } if (!p->gsmin) p->gsmin = gsm_create(); len = 0; while(len < e->event.voice.datalen) { if(gsm_decode(p->gsmin, e->event.voice.data + len, fr)) { fprintf(stderr, "Bad GSM data\n"); break; } else { int res; res = write(audiofd, fr, sizeof(fr)); if (res < 0) fprintf(f, "Write failed: %s\n", strerror(errno)); } len += 33; } break; default : fprintf(f, "Don't know how to handle that format %d\n", e->event.voice.format); } break; default: fprintf(f, "Unknown event: %d\n", e->etype); } } void dump_call(void) { if(most_recent_answer) { printf("Dumping call!\n"); iax_hangup(most_recent_answer->session,""); free(most_recent_answer); } answered_call = 0; most_recent_answer = 0; answered_call = 0; peers = 0; newcall = 0; offhook = 0; ringing = 0; gentone(TONE_NONE, 0); } void parse_cmd(FILE *f, int argc, char **argv) { if(!strcasecmp(argv[0], "HELP")) { if(argc == 1) dump_array(f, help); else if(argc == 2) { if(!strcasecmp(argv[1], "HELP")) fprintf(f, "Help \t-\tDisplays general help or specific help on command if supplied an arguement\n"); else if(!strcasecmp(argv[1], "QUIT")) fprintf(f, "Quit\t\t-\tShuts down the miniphone\n"); else fprintf(f, "No help available on %s\n", argv[1]); } else { fprintf(f, "Too many arguements for command help.\n"); } } else if(!strcasecmp(argv[0], "STATUS")) { if(argc == 1) { int c = 0; struct peer *peerptr = peers; if(!peerptr) fprintf(f, "No session matches found.\n"); else while(peerptr) { fprintf(f, "Listing sessions:\n\n"); fprintf(f, "Session %d\n", ++c); fprintf(f, "Session existed for %d seconds\n", (int)time(0)-peerptr->time); if(answered_call) fprintf(f, "Call answered.\n"); else fprintf(f, "Call ringing.\n"); peerptr = peerptr->next; } } else fprintf(f, "Too many arguments for command status.\n"); } else if(!strcasecmp(argv[0], "ANSWER")) { if(argc > 1) fprintf(f, "Too many arguements for command answer\n"); else answer_call(); } else if(!strcasecmp(argv[0], "REJECT")) { if(argc > 1) fprintf(f, "Too many arguements for command reject\n"); else { fprintf(f, "Rejecting current phone call.\n"); reject_call(); } } else if (!strcasecmp(argv[0], "DUMP")) { dump_call(); } else if (!strcasecmp(argv[0], "HANGUP")) { dump_call(); } else if(!strcasecmp(argv[0], "CALL")) { if(argc > 2) fprintf(f, "Too many arguements for command call\n"); else { call(f, argv[1]); } } else if(!strcasecmp(argv[0], "QUIT")) { if(argc > 1) fprintf(f, "Too many arguements for command quit\n"); else { fprintf(f, "Good bye!\n"); exit(1); } } else fprintf(f, "Unknown command of %s\n", argv[0]); } void issue_prompt(FILE *f) { fprintf(f, "TeleClient> "); fflush(f); } void dump_array(FILE *f, char **array) { while(*array) fprintf(f, "%s\n", *array++); }