Skip to content
Snippets Groups Projects
app_qcall.c 10.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • Mark Spencer's avatar
    Mark Spencer committed
    /** @file app_qcall.c 
     *
     * Asterisk -- A telephony toolkit for Linux.
     *
     * Call back a party and connect them to a running pbx thread
     * 
     * Copyright (C) 1999, Mark Spencer
     *
     * Mark Spencer <markster@linux-support.net>
     *
     * This program is free software, distributed under the terms of
     * the GNU General Public License
     *
     * Call a user from a file contained within a queue (/var/spool/asterisk/qcall)
     * 
     * The queue is a directory containing files with the call request information
     * as a single line of text as follows:
     * 
    
    Mark Spencer's avatar
    Mark Spencer committed
     * Dialstring Caller-ID Extension Maxsecs [Identifier] [Required-response]
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
     *  Dialstring -- A Dial String (The number to be called) in the
     *  format Technology/Number, such IAX/mysys/1234 or Zap/g1/1234
     * 
     *  Caller-ID -- A Standard nomalized representation of the Caller-ID of
    
    Mark Spencer's avatar
    Mark Spencer committed
     *  the number being dialed (generally 10 digits in the US). Leave as
     *  "asreceived" to use the default Caller*ID
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
     *  Extension -- The Extension (optionally Extension@context) that the
     *  user should be "transferred" to after acceptance of the call.
     *
     *  Maxsecs -- The Maximum time of the call in seconds. Specify 0 for infinite.
     *
     *  Identifier -- The "Identifier" of the request. This is used to determine
     *  the names of the audio prompt files played. The first prompt, the one that
     *  asks for the input, is just the exact string specified as the identifier.
     *  The second prompt, the one that is played after the correct input is given,
     *  (generally a "thank you" recording), is the specified string with "-ok" 
     *  added to the end. So, if you specify "foo" as the identifier, your first
     *  prompt file that will be played will be "foo" and the second one will be
    
    Mark Spencer's avatar
    Mark Spencer committed
     *  "foo-ok".  If omitted no prompt is given
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
     *  Required-Response (Optional) -- Specify a digit string to be used as the
     *  acceptance "code" if you desire it to be something other then "1". This
     *  can be used to implement some sort of PIN or security system. It may be
     *  more then a single character.
     *
     * NOTE: It is important to remember that the process that creates these
     * files needs keep and maintain a write lock (using flock with the LOCK_EX
     * option) when writing these files.
     *
     */
     
    
    Mark Spencer's avatar
    Mark Spencer committed
    #include <asterisk/lock.h>
    
    Mark Spencer's avatar
    Mark Spencer committed
    #include <asterisk/file.h>
    #include <asterisk/logger.h>
    #include <asterisk/channel.h>
    #include <asterisk/pbx.h>
    #include <asterisk/module.h>
    #include <asterisk/translate.h>
    #include <asterisk/options.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
    #include <pthread.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <errno.h>
    #include <dirent.h>
    #include <ctype.h>
    #include <sys/stat.h>
    #include <sys/time.h>
    #include <sys/file.h>
    
    Mark Spencer's avatar
    Mark Spencer committed
    #include "../astconf.h"
    
    static char qdir[255];
    
    Mark Spencer's avatar
    Mark Spencer committed
    static  char *tdesc = "Call from Queue";
    static  pthread_t qcall_thread;
    static int debug = 0;
    STANDARD_LOCAL_USER;
    LOCAL_USER_DECL;
    
    #define	OLDESTOK	14400		/* not any more then this number of secs old */
    
    Mark Spencer's avatar
    Mark Spencer committed
    #define	INITIALONE	1		/* initial wait before the first one in secs */
    
    Mark Spencer's avatar
    Mark Spencer committed
    #define	NEXTONE		600		/* wait before trying it again in secs */
    #define	MAXWAITFORANSWER 45000		/* max call time before answer */
    /* define either one or both of these two if your application requires it */
    #if	0
    #define	ACCTCODE	"SOMETHING"	/* Account code */
    #define	AMAFLAGS AST_CDR_BILLING	/* AMA flags */
    #endif
    /* define this if you want to have a particular CLID display on the user's
       phone when they receive the call */
    #if	0
    #define	OURCLID	"2564286275"		/* The callerid to be displayed when calling */
    #endif
    
    static void *qcall_do(void *arg);
    
    static void *qcall(void *ignore)
    {
    pthread_t dialer_thread;
    DIR *dirp;
    FILE *fp;
    struct dirent *dp;
    char fname[80];
    struct stat mystat;
    time_t	t;
    void *arg;
    pthread_attr_t attr;
    
    	time(&t);
    	if (debug) printf("@@@@ qcall starting at %s",ctime(&t));
    	for(;;)
    	   {
    		time(&t);
    		dirp = opendir(qdir);
    		if (!dirp)
    		   {
    			perror("app_qcall:Cannot open queue directory");
    			break;
    		   }
    		while((dp = readdir(dirp)) != NULL)
    		   {
    			if (dp->d_name[0] == '.') continue;
    			sprintf(fname,"%s/%s",qdir,dp->d_name);
    			if (stat(fname,&mystat) == -1)
    			   {
    				perror("app_qcall:stat");
    				fprintf(stderr,"%s\n",fname);
    				continue;
    			   }
    			  /* if not a regular file, skip it */
    			if ((mystat.st_mode & S_IFMT) != S_IFREG) continue;
    			  /* if not yet .... */
    
    Mark Spencer's avatar
    Mark Spencer committed
    			if (mystat.st_atime == mystat.st_mtime)
    
    Mark Spencer's avatar
    Mark Spencer committed
    			   {  /* first time */
    
    Mark Spencer's avatar
    Mark Spencer committed
    				if ((mystat.st_atime + INITIALONE) > t) 
    					continue;
    
    Mark Spencer's avatar
    Mark Spencer committed
    			   }
    			else
    			   { /* already looked at once */
    				if ((mystat.st_atime + NEXTONE) > t) continue;
    			   }
    			  /* if too old */
    			if (mystat.st_mtime < (t - OLDESTOK))
    			   {
    				/* kill it, its too old */
    				unlink(fname);
    				continue;
    			   }				
    			 /* "touch" file's access time */
    			fp = fopen(fname,"r");
    			if (fp) fclose(fp);
    			/* make a copy of the filename string, so that we
    				may go on and use the buffer */
    			arg = (void *) strdup(fname);
    		        pthread_attr_init(&attr);
     		        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    			if (pthread_create(&dialer_thread,&attr,qcall_do,arg) == -1)
    			   {
    				perror("qcall: Cannot create thread");
    				continue;
    			   }
    		   }
    		closedir(dirp);
    		sleep(1);
    	   }
    	pthread_exit(NULL);
    }
    	
    /* single thread with one file (request) to dial */
    static void *qcall_do(void *arg)
    {
    char fname[300],dialstr[300],extstr[300],ident[300],reqinp[300],buf[300];
    char clid[300],*tele,*context;
    FILE *fp;
    int ms = MAXWAITFORANSWER,maxsecs;
    struct ast_channel *channel;
    time_t	t;
    
    	  /* get the filename from the arg */
    	strcpy(fname,(char *)arg);
    	free(arg);
    	time(&t);
    	fp = fopen(fname,"r");
    	if (!fp) /* if cannot open request file */
    	   {
    		perror("qcall_do:fopen");
    		fprintf(stderr,"%s\n",fname);
    		unlink(fname);
    		pthread_exit(NULL);
    	   }
    	/* lock the file */
    	if (flock(fileno(fp),LOCK_EX) == -1)
    	   {
    		perror("qcall_do:flock");
    		fprintf(stderr,"%s\n",fname);
    		pthread_exit(NULL);
    	   }
    	strcpy(reqinp,"1");  /* default required input for acknowledgement */
    
    Mark Spencer's avatar
    Mark Spencer committed
    	strcpy(ident, "");	/* default no ident */
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (fscanf(fp,"%s %s %s %d %s %s",dialstr,clid,
    
    Mark Spencer's avatar
    Mark Spencer committed
    		extstr,&maxsecs,ident,reqinp) < 4)
    
    Mark Spencer's avatar
    Mark Spencer committed
    	   {
    		fprintf(stderr,"qcall_do:file line invalid in file %s:\n",fname);
    		pthread_exit(NULL);
    	   }
    	flock(fileno(fp),LOCK_UN);
    	fclose(fp);
    	tele = strchr(dialstr,'/');
    	if (!tele)
    	   {
    		fprintf(stderr,"qcall_do:Dial number must be in format tech/number\n");
    		unlink(fname);
    		pthread_exit(NULL);
    	   }
    	*tele++ = 0;
    	channel = ast_request(dialstr,AST_FORMAT_SLINEAR,tele);
    	if (channel)
    	   {
    		ast_set_read_format(channel,AST_FORMAT_SLINEAR);
    		ast_set_write_format(channel,AST_FORMAT_SLINEAR);
    #ifdef	OURCLID
    
    Mark Spencer's avatar
    Mark Spencer committed
    		if (channel->callerid)
    			free(channel->callerid);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		channel->callerid = strdup(OURCLID);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		if (channel->ani)
    			free(channel->ani);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		channel->ani = strdup(OURCLID);
    
    Mark Spencer's avatar
    Mark Spencer committed
    #endif		
    		channel->whentohangup = 0;
    		channel->appl = "AppQcall";
    		channel->data = "(Outgoing Line)";
    		if (option_verbose > 2)
    			ast_verbose(VERBOSE_PREFIX_3 "Qcall initiating call to %s/%s on %s (%s)\n",
    				dialstr,tele,channel->name,fname);
    		ast_call(channel,tele,MAXWAITFORANSWER);
    	   }
    	else
    	   {
    		fprintf(stderr,"qcall_do:Sorry unable to obtain channel\n");
    		pthread_exit(NULL);
    	   }
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (strcasecmp(clid, "asreceived")) {
    		if (channel->callerid) free(channel->callerid);
    		channel->callerid = NULL;
    		if (channel->ani) free(channel->ani);
    		channel->ani = NULL;
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (channel->_state == AST_STATE_UP)
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (debug) printf("@@@@ Autodial:Line is Up\n");
    	if (option_verbose > 2)
    	ast_verbose(VERBOSE_PREFIX_3 "Qcall waiting for answer on %s\n",
    		channel->name);
    	while(ms > 0){
    		struct ast_frame *f;
    		ms = ast_waitfor(channel,ms);
    		f = ast_read(channel);
    		if (!f)
    		   {
    			if (debug) printf("@@@@ qcall_do:Hung Up\n");
    			unlink(fname);
    			break;
    		   }
    		if (f->frametype == AST_FRAME_CONTROL)
    		   {
    			if (f->subclass == AST_CONTROL_HANGUP)
    			   {
    				if (debug) printf("@@@@ qcall_do:Hung Up\n");
    				unlink(fname);
    				ast_frfree(f);
    				break;
    			   }
    			if (f->subclass == AST_CONTROL_ANSWER)
    			   {
    				if (debug) printf("@@@@ qcall_do:Phone Answered\n");
    
    Mark Spencer's avatar
    Mark Spencer committed
    				if (channel->_state == AST_STATE_UP)
    
    Mark Spencer's avatar
    Mark Spencer committed
    				   {
    					unlink(fname);
    					if (option_verbose > 2)
    						ast_verbose(VERBOSE_PREFIX_3 "Qcall got answer on %s\n",
    							channel->name);
    					usleep(1500000);
    
    Mark Spencer's avatar
    Mark Spencer committed
    					if (strlen(ident)) {
    						ast_streamfile(channel,ident,0);
    						if (ast_readstring(channel,buf,strlen(reqinp),10000,5000,"#"))
    						{
    							ast_stopstream(channel);
    							if (debug) printf("@@@@ qcall_do: timeout or hangup in dtmf read\n");
    							ast_frfree(f);
    							break;
    						}
    
    Mark Spencer's avatar
    Mark Spencer committed
    						ast_stopstream(channel);
    
    Mark Spencer's avatar
    Mark Spencer committed
    						if (strcmp(buf,reqinp)) /* if not match */
    						{
    							if (debug) printf("@@@@ qcall_do: response (%s) does not match required (%s)\n",buf,reqinp);
    							ast_frfree(f);
    							break;
    						}
    
    Mark Spencer's avatar
    Mark Spencer committed
    						ast_frfree(f);
    
    Mark Spencer's avatar
    Mark Spencer committed
    					}
    
    Mark Spencer's avatar
    Mark Spencer committed
    					/* okay, now we go for it */
    					context = strchr(extstr,'@');
    					if (!context) context = "default";
    					else *context++ = 0;
    					if (option_verbose > 2)
    						ast_verbose(VERBOSE_PREFIX_3 "Qcall got accept, now putting through to %s@%s on %s\n",
    							extstr,context,channel->name);
    
    Mark Spencer's avatar
    Mark Spencer committed
    					if (strlen(ident)) {
    						strcat(ident,"-ok");
    						/* if file existant, play it */
    						if (!ast_streamfile(channel,ident,0))
    						{
    							ast_waitstream(channel,"");
    							ast_stopstream(channel);
    						}
    					}
    					if (strcasecmp(clid, "asreceived")) {
    						channel->callerid = strdup(clid);
    						channel->ani = strdup(clid);
    					}
    
    Mark Spencer's avatar
    Mark Spencer committed
    					channel->language[0] = 0;
    					channel->dnid = strdup(extstr);
    #ifdef	AMAFLAGS
    					channel->amaflags = AMAFLAGS;
    #endif
    #ifdef	ACCTCODE
    					strcpy(channel->accountcode,ACCTCODE);
    #else
    					channel->accountcode[0] = 0;
    #endif
    					if (maxsecs)  /* if finite length call */
    					   {
    						time(&channel->whentohangup);
    						channel->whentohangup += maxsecs;
    					   }
    					strcpy(channel->exten,extstr);
    					strcpy(channel->context,context);
    					channel->priority = 1;
    
    Mark Spencer's avatar
    Mark Spencer committed
    					printf("Caller ID is %s\n", channel->callerid);
    
    Mark Spencer's avatar
    Mark Spencer committed
    					ast_pbx_run(channel);
    					pthread_exit(NULL);
    				}
    			}
    			else if(f->subclass==AST_CONTROL_RINGING)
    				if (debug) printf("@@@@ qcall_do:Phone Ringing end\n");
    		}
    		ast_frfree(f);
    	}
    	ast_hangup(channel);
    	if (debug) printf("@@@@ qcall_do:Hung up channel\n");
    	pthread_exit(NULL);
    	return NULL;
    }
    
    int unload_module(void)
    {
    	STANDARD_HANGUP_LOCALUSERS;
    	return 0;
    }
    
    int load_module(void)
    {
    
    Mark Spencer's avatar
    Mark Spencer committed
    	snprintf((char *)qdir,sizeof(qdir)-1,"%s/%s",(char *)ast_config_AST_SPOOL_DIR,"qcall");
    
    Mark Spencer's avatar
    Mark Spencer committed
    	mkdir(qdir,0760);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	pthread_create(&qcall_thread,NULL,qcall,NULL);
    	return 0;
    }
    
    char *description(void)
    {
    	return tdesc;
    }
    
    int usecount(void)
    {
    	int res;
    	STANDARD_USECOUNT(res);
    	return res;
    }
    
    char *key()
    {
    	return ASTERISK_GPL_KEY;
    }