siproc

git clone git://xatko.vsos.ethz.ch/siproc.git
Log | Files | Refs | README | LICENSE

siproc.cpp (14546B)


      1 /*
      2  * A call-automation program spawning processes for each call.
      3  * Copyright © 2019 Dominik Schmidt
      4  *
      5  * This program is free software: you can redistribute it and/or modify
      6  * it under the terms of the GNU General Public License as published by
      7  * the Free Software Foundation, either version 3 of the License, or
      8  * (at your option) any later version.
      9  *
     10  * This program is distributed in the hope that it will be useful,
     11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13  * GNU General Public License for more details.
     14  *
     15  * You should have received a copy of the GNU General Public License
     16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
     17 */
     18 
     19 #include <pjsua2.hpp>
     20 #include <stdlib.h>
     21 #include <stdio.h>
     22 #include <iostream>
     23 #include <unistd.h>
     24 #include <signal.h>
     25 #include <sys/wait.h>
     26 #include <sys/epoll.h>
     27 
     28 struct EV{
     29 	int efd;
     30 
     31 	EV(){
     32 		efd = epoll_create1(0);
     33 		if(efd < 0){
     34 			throw "Could not create epoll fd";
     35 		}
     36 	}
     37 
     38 	~EV(){
     39 		close(efd);
     40 	}
     41 
     42 	void ev_add(int fd, void *data){
     43 		struct epoll_event ev = {0};
     44 
     45 		ev.events = EPOLLIN;
     46 		ev.data.ptr = data;
     47 		epoll_ctl(efd, EPOLL_CTL_ADD, fd, &ev);
     48 	}
     49 
     50 	void ev_del(int fd){
     51 		epoll_ctl(efd, EPOLL_CTL_DEL, fd, NULL);
     52 	}
     53 
     54 	void *wait(int timeout){
     55 		struct epoll_event ev = {0};
     56 
     57 		ev.data.u64 = 0;
     58 		int ret = epoll_wait(efd, &ev, 1, timeout);
     59 		if(ret < 0){
     60 			throw "Could not wait on epollfd";
     61 		}
     62 		else if(ret == 0){
     63 			return NULL;
     64 		}
     65 		else{
     66 			return ev.data.ptr;
     67 		}
     68 	}
     69 
     70 	void *wait(){
     71 		return wait(-1);
     72 	}
     73 };
     74 
     75 ssize_t readall(int fd, char **buffer, size_t *buffer_length){
     76 	ssize_t ret = 0;
     77 	size_t offset = 0;
     78 
     79 	while((ret = read(fd, (*buffer + offset), *buffer_length - offset)) == *buffer_length - offset){
     80 		*buffer_length *= 2;
     81 		*buffer = (char *)realloc(*buffer, *buffer_length);
     82 		offset += ret;
     83 	}
     84 	if(ret == -1){
     85 		PJ_LOG(1, ("Failed to read from fd: %s\n"\
     86 		"ADDRESS: %p, %lu\n", strerror(errno), *buffer, *buffer_length));
     87 		return -1;
     88 	}
     89 	return offset + ret;
     90 }
     91 
     92 
     93 EV ev_table = EV();
     94 
     95 using namespace pj;
     96 
     97 Endpoint ep;
     98 
     99 class MyAccount;
    100 class MyCall;
    101 
    102 class MyAudioMediaPlayer : public AudioMediaPlayer {
    103 	MyCall* call;
    104 	public:
    105 	char* path;
    106 	MyAudioMediaPlayer(MyCall* c, char *what) : AudioMediaPlayer(){
    107 		call = c;
    108 		path = what;
    109 	}
    110 
    111 	virtual bool onEof();
    112 };
    113 
    114 class MyCall: public Call{
    115 	FILE *c_stdin;
    116 	int c_stdout;
    117 	MyAccount *myacc;
    118 	pid_t child;
    119 	char *line;
    120 	size_t line_length;
    121 	std::vector<MyAudioMediaPlayer*> players;
    122 	std::map<int,ExtraAudioDevice*> audio_devs;
    123 	AudioMediaRecorder* recorder=NULL;
    124 	public:
    125 		MyCall(Account &acc, int call_id = PJSUA_INVALID_ID) : Call(acc, call_id){
    126 			c_stdin = NULL;
    127 			c_stdout = -1;
    128 			child = -1;
    129 			line = (char *)malloc(256);
    130 			line_length = 256;
    131 		}
    132 
    133 		~MyCall(){
    134 			if(line != NULL){
    135 				free(line);
    136 				line = NULL;
    137 				line_length = 0;
    138 			}
    139 			if(child != -1){
    140 				fork_off();
    141 			}
    142 			for(unsigned int i=0; i<players.size(); i++){
    143 				delete players[i];
    144 			}
    145 
    146 			for(auto it = audio_devs.begin(); it != audio_devs.end(); it++){
    147 				delete it->second;
    148 			}
    149 			audio_devs.clear();
    150 
    151 			if(recorder != NULL){
    152 				delete recorder;
    153 				recorder = NULL;
    154 			}
    155 		}
    156 
    157 		AudioMedia& call_audio(){
    158 			//return Endpoint::instance().audDevManager().getPlaybackDevMedia();
    159 			CallInfo ci = getInfo();
    160 			AudioMedia *aud_med = NULL;
    161 
    162 			// Find out which media index is the audio
    163 			for(unsigned i = 0; i < ci.media.size(); ++i){
    164 				if(ci.media[i].type == PJMEDIA_TYPE_AUDIO){
    165 					aud_med = (AudioMedia *)getMedia(i);
    166 					break;
    167 				}
    168 			}
    169 
    170 			return *aud_med;
    171 		}
    172 
    173 		void fork_off(){
    174 			if(child == -1){
    175 				return;
    176 			}
    177 			PJ_LOG(3, ("MyCall", "Shutting down child process"));
    178 			ev_table.ev_del(c_stdout);
    179 			fclose(c_stdin);
    180 			close(c_stdout);
    181 			c_stdin = NULL;
    182 			c_stdout = -1;
    183 			kill(child, SIGTERM);
    184 			int statloc;
    185 			waitpid(child, &statloc, 0);
    186 			child = -1;
    187 		}
    188 
    189 		void fork_on(char **args, bool outgoing);
    190 
    191 		virtual void onCallState(OnCallStateParam &prm){
    192 			CallInfo ci = getInfo();
    193 
    194 			if(ci.state == PJSIP_INV_STATE_DISCONNECTED){
    195 				fork_off();
    196 				delete this;
    197 				return;
    198 			}
    199 			else if(ci.state == PJSIP_INV_STATE_CONFIRMED){
    200 				command("CONNECTED\n");
    201 			}
    202 		}
    203 		
    204 		bool connected(){
    205 			CallInfo ci = getInfo();
    206 			return ci.state == PJSIP_INV_STATE_CONFIRMED;
    207 		}
    208 
    209 		bool onPlayerFinished(char *path, MyAudioMediaPlayer* who){
    210 			command("STOPPED %s\n", path);
    211 			delete who;
    212 			for(unsigned int i=0; i<players.size(); i++){
    213 				if(players[i] == who){
    214 					players.erase(players.begin()+i);
    215 				}
    216 			}
    217 			return false;
    218 		}
    219 
    220 		int command(const char *cmd, ...){
    221 			if(c_stdin == NULL){
    222 				return -1;
    223 			}
    224 			int ret;
    225 			va_list ap;
    226 			va_start(ap, cmd);
    227 			ret = vfprintf(c_stdin, cmd, ap);
    228 			va_end(ap);
    229 			fflush(c_stdin);
    230 			return ret;
    231 		}
    232 
    233 		virtual void onDtmfDigit(OnDtmfDigitParam &prm){
    234 			command("DTMF %s\n", prm.digit.c_str());
    235 		}
    236 
    237 		virtual void onInstantMessage(OnInstantMessageParam &prm){
    238 			command("MESSAGE %s\n", prm.msgBody.c_str());
    239 		}
    240 
    241 		void cmd_hangup(){
    242 			// Just hangup for now
    243 			CallOpParam op;
    244 
    245 			op.statusCode = PJSIP_SC_DECLINE;
    246 			hangup(op);
    247 		}
    248 
    249 		void cmd_answer(){
    250 			CallOpParam prm;
    251 
    252 			prm.statusCode = PJSIP_SC_OK;
    253 			answer(prm);
    254 		}
    255 
    256 		void cmd_dtmf(char *args){
    257 			dialDtmf(args);
    258 		}
    259 
    260 		void cmd_message(char *msg){
    261 			SendInstantMessageParam prm = SendInstantMessageParam();
    262 
    263 			prm.content = std::string(msg);
    264 			sendInstantMessage(prm);
    265 		}
    266 
    267 		void cmd_play(char *path){
    268 			AudioMedia& play_dev_med = call_audio();
    269 
    270 			MyAudioMediaPlayer* player = new MyAudioMediaPlayer(this, path);
    271 			try {
    272 				player->createPlayer(path, PJMEDIA_FILE_NO_LOOP);
    273 				player->startTransmit(play_dev_med);
    274 			}
    275 			catch(Error& err){
    276 				//std::cerr << err <<std::endl;
    277 			}
    278 			players.push_back(player);
    279 		}
    280 
    281 		void cmd_record(char *path){
    282 			AudioMedia& cap_dev_med = call_audio();
    283 			if(recorder != NULL){
    284 				cap_dev_med.stopTransmit(*recorder);
    285 				delete recorder;
    286 				recorder = NULL;
    287 			}
    288 
    289 			if(path != NULL){
    290 				recorder = new AudioMediaRecorder();
    291 				recorder->createRecorder(path);
    292 				cap_dev_med.startTransmit(*recorder);
    293 			}
    294 		}
    295 
    296 		void cmd_stop(char *path){
    297 			if(players.size()>0){
    298 				AudioMedia& play_dev_med = call_audio();
    299 
    300 				MyAudioMediaPlayer* player = players.back();
    301 				player->stopTransmit(play_dev_med);
    302 				onPlayerFinished(player->path, player);
    303 			}
    304 		}
    305 
    306 		void cmd_ringing(){
    307 			CallOpParam prm;
    308 
    309 			prm.statusCode = PJSIP_SC_RINGING;
    310 			answer(prm);
    311 		}
    312 
    313 		void cmd_transfer(char *number){
    314 			CallOpParam prm;
    315 
    316 			prm.statusCode = PJSIP_SC_RINGING;
    317 			xfer(std::string(number), prm);
    318 		}
    319 
    320 		int get_dev_id(char *devname){
    321 			AudDevManager &adm = ep.audDevManager();
    322 			return adm.lookupDev("ALSA", string(devname));
    323 		}
    324 
    325 		ExtraAudioDevice* get_extra_audio(char *devname){
    326 			int id = get_dev_id(devname);
    327 			ExtraAudioDevice *dev = NULL;
    328 			auto res = audio_devs.find(id);
    329 			if(res == audio_devs.end()){
    330 				dev = new ExtraAudioDevice(id,id);
    331 				audio_devs[id] = dev;
    332 				dev->open();
    333 			}
    334 			else{
    335 				dev = res->second;
    336 			}
    337 			return dev;
    338 		}
    339 
    340 		void cmd_aplay(char *source){
    341 			ExtraAudioDevice *ea = get_extra_audio(source);
    342 			AudioMedia& play_dev_med = call_audio();
    343 			ea->startTransmit(play_dev_med);
    344 		}
    345 
    346 		void cmd_arecord(char *sink){
    347 			ExtraAudioDevice *ea = get_extra_audio(sink);
    348 			AudioMedia& play_dev_med = call_audio();
    349 			play_dev_med.startTransmit(*ea);
    350 		}
    351 
    352 		void cmd_astop(char *sourcesink){
    353 			int id = get_dev_id(sourcesink);
    354 			auto res = audio_devs.find(id);
    355 			if(res == audio_devs.end()){
    356 				return;
    357 			}
    358 			ExtraAudioDevice *ea = res->second;
    359 			delete ea;
    360 			audio_devs.erase(res);
    361 		}
    362 
    363 
    364 		void command_machine(char *command){
    365 			char *args = strstr(command, " ");
    366 
    367 			if(args != NULL){
    368 				*args++ = '\0';
    369 			}
    370 			if(strcmp(command, "HANGUP") == 0){
    371 				cmd_hangup();
    372 			}
    373 			else if(strcmp(command, "ANSWER") == 0){
    374 				cmd_answer();
    375 			}
    376 			else if(strcmp(command, "RINGING") == 0){
    377 				cmd_ringing();
    378 			}
    379 			else if(!connected()){
    380 				PJ_LOG(2, ("MyCall", "Command %s coming at an inopportune time...", command));
    381 			}
    382 			else if(strcmp(command, "DTMF") == 0){
    383 				cmd_dtmf(args);
    384 			}
    385 			else if(strcmp(command, "MESSAGE") == 0){
    386 				cmd_message(args);
    387 			}
    388 			else if(strcmp(command, "PLAY") == 0){
    389 				cmd_play(args);
    390 			}
    391 			else if(strcmp(command, "STOP") == 0){
    392 				cmd_stop(args);
    393 			}
    394 			else if(strcmp(command, "RECORD") == 0){
    395 				cmd_record(args);
    396 			}
    397 			else if(strcmp(command, "TRANSFER") == 0){
    398 				cmd_transfer(args);
    399 			}
    400 			else if(strcmp(command, "APLAY") == 0){
    401 				cmd_aplay(args);
    402 			}
    403 			else if(strcmp(command, "ARECORD") == 0){
    404 				cmd_arecord(args);
    405 			}
    406 			else if(strcmp(command, "ASTOP") == 0){
    407 				cmd_astop(args);
    408 			}
    409 		}
    410 
    411 		void handle_line(){
    412 			PJ_LOG(3, ("MyCall", "Handling line"));
    413 			ssize_t ret = readall(c_stdout, &line, &line_length);
    414 			PJ_LOG(6, ("MyCall", "Read %lu bytes", ret));
    415 			if(ret < 0){
    416 				throw "Error reading from stdout";
    417 			}
    418 			else if(ret == 0){
    419 				PJ_LOG(3, ("MyCall", "Child closed stdout, killing it"));
    420 				fork_off();
    421 			}
    422 			else{
    423 				char *begin = line;
    424 				while(begin){
    425 					char *end = strstr(begin, "\n");
    426 					if(end != NULL){
    427 						*end = '\0';
    428 					}
    429 					command_machine(begin);
    430 					begin = end;
    431 				}
    432 			}
    433 		}
    434 };
    435 
    436 bool MyAudioMediaPlayer::onEof(){
    437 	return call->onPlayerFinished(path, this);
    438 }
    439 
    440 
    441 class MyAccount: public Account{
    442 	public:
    443 		char **args;
    444 		MyAccount(char **args){
    445 			this->args = args;
    446 		}
    447 		~MyAccount(){
    448 		}
    449 
    450 		virtual void onIncomingCall(OnIncomingCallParam &iprm){
    451 			MyCall *call = new MyCall(*this, iprm.callId);
    452 
    453 			call->fork_on(args, false);
    454 		}
    455 };
    456 
    457 void MyCall::fork_on(char **args, bool outgoing){
    458 	PJ_LOG(4, ("MyCall", "Setting up Fork"));
    459 	int pipes_stdin[2], pipes_stdout[2];
    460 
    461 	if(pipe(pipes_stdin) != 0){
    462 		throw "Pipe creation failed";
    463 	}
    464 
    465 	if(pipe(pipes_stdout) != 0){
    466 		throw "Pipe creation failed";
    467 	}
    468 	c_stdin = fdopen(pipes_stdin[1], "w");
    469 	setvbuf(c_stdin, NULL, _IOLBF, 256);
    470 	c_stdout = pipes_stdout[0];
    471 
    472 	CallInfo ci = getInfo();
    473 	PJ_LOG(4, ("MyCall", "Calling fork"));
    474 	pid_t pid = fork();
    475 	if(pid == -1){
    476 		PJ_LOG(1, ("MyCall", "Fork failed"));
    477 		throw "Fork failed";
    478 	}
    479 	else if(pid == 0){
    480 		dup2(pipes_stdin[0], STDIN_FILENO);
    481 		dup2(pipes_stdout[1], STDOUT_FILENO);
    482 		close(pipes_stdin[1]);
    483 		close(pipes_stdout[0]);
    484 		setenv("SIPROC_REMOTE_ID", ci.callIdString.c_str(), 1);
    485 		setenv("SIPROC_REMOTE_URI", ci.remoteUri.c_str(), 1);
    486 		setenv("SIPROC_REMOTE_CONTACT", ci.remoteContact.c_str(), 1);
    487 		setenv("SIPROC_OUTGOING", (outgoing) ? "y" : "n", 1);
    488 		if(execv(args[0], args) < 0){
    489 			perror("exec");
    490 		}
    491 		exit(1);
    492 	}
    493 	else{
    494 		PJ_LOG(3, ("MyCall", "Fork successfull"));
    495 		child = pid;
    496 		close(pipes_stdin[0]);
    497 		close(pipes_stdout[1]);
    498 		ev_table.ev_add(pipes_stdout[0], this);
    499 	}
    500 }
    501 
    502 const char* env_or_default(const char* name, const char* default_value){
    503 	char *env = getenv(name);
    504 	if(env == NULL){
    505 		return default_value;
    506 	}
    507 	return env;
    508 }
    509 
    510 void usage(){
    511 	fprintf(stderr,
    512 			"Usage: siproc <executable> [args...]\n\n"
    513 			"Make sure to define the following environment variables:\n"
    514 			"\t* SIPROC_USERNAME:\tThe username used for authentication, e.g. Foo\n"
    515 			"\t* SIPROC_PASSWORD:\tThe password used for authentication, e.g. Bar\n"
    516 			"\t* SIPROC_REGISTRAR_URI:\tThe server to connect to, e.g. \"sip:fritz.box\"\n"
    517 			"\t* SIPROC_ID_URI:\tThe ID URI of your account, e.g. \"Foo Baz <sip:Foo@fritz.box>\"\n"
    518 			"\nThanks for riding siproc!\n"
    519 			);
    520 }
    521 
    522 int main(int argc, char **argv){
    523 	char *user, *password, *idUri, *reguri;
    524 
    525 	if(!(user = getenv("SIPROC_USERNAME"))){
    526 		fprintf(stderr, "SIPROC_USERNAME not in environment variables\n\n");
    527 		usage();
    528 		return 1;
    529 	}
    530 	if(!(password = getenv("SIPROC_PASSWORD"))){
    531 		fprintf(stderr, "SIPROC_PASSWORD not in environment variables\n\n");
    532 		usage();
    533 		return 1;
    534 	}
    535 
    536 	if(!(reguri = getenv("SIPROC_REGISTRAR_URI"))){
    537 		fprintf(stderr, "SIPROC_REGISTRAR_URI not in environment variables\n\n");
    538 		usage();
    539 		return 1;
    540 	}
    541 
    542 	if(!(idUri = getenv("SIPROC_ID_URI"))){
    543 		fprintf(stderr, "SIPROC_ID_URI not in environment variables\n\n");
    544 		usage();
    545 		return 1;
    546 	}
    547 
    548 	try{
    549 		ep.libCreate();
    550 		EpConfig ep_cfg;
    551 		ep_cfg.logConfig.level = atoi(env_or_default("SIPROC_LOG_LEVEL", "2"));;
    552 		ep.libInit(ep_cfg);
    553 		
    554 		char *codec_prios;
    555 		if((codec_prios = getenv("SIPROC_CODEC_PRIORITIES"))){
    556 			
    557 			while(codec_prios != NULL){
    558 				char *name = codec_prios;
    559 				codec_prios = strstr(codec_prios, ",");
    560 				if(codec_prios != NULL){
    561 					*codec_prios++='\0';
    562 				}
    563 				char *prio = strstr(name, ":");
    564 				if(prio == NULL){
    565 					PJ_LOG(1, ("Main", "Codecs string malformed. Should be <name>:<prio>[,<name>:<prio>...]"));
    566 					return 1;
    567 				}
    568 				*prio++='\0';
    569 				int prio_i = atoi(prio);
    570 				if(prio_i < 0 || prio_i > 255){
    571 					PJ_LOG(1, ("Main", "Codec priority must be in [0,255]"));
    572 				}
    573 				ep.codecSetPriority(string(name), prio_i);
    574 			}
    575 		}
    576 		
    577 		AudDevManager &adm = ep.audDevManager();
    578 		adm.setNullDev();
    579 
    580 		TransportConfig tcfg;
    581 		tcfg.port = atoi(env_or_default("SIPROC_TRANSPORT_PORT", "5060"));
    582 		try {
    583 			ep.transportCreate(PJSIP_TRANSPORT_UDP, tcfg);
    584 		} catch(Error &err){
    585 			PJ_LOG(1, ("Setup", "Error setting up transport: %s", err.info().c_str()));
    586 			return 1;
    587 		}
    588 
    589 		ep.libStart();
    590 
    591 
    592 		AccountConfig acfg;
    593 		acfg.idUri = idUri;
    594 		acfg.regConfig.registrarUri = reguri;
    595 		AuthCredInfo cred("digest", "*", user, 0, password);
    596 		acfg.sipConfig.authCreds.push_back(cred);
    597 
    598 		MyAccount *acc = new MyAccount(&argv[1]);
    599 		acc->create(acfg);
    600 
    601 		char *line = NULL;
    602 		size_t line_length = 0;
    603 		ev_table.ev_add(STDIN_FILENO, &line_length);
    604 		while(void *ptr = ev_table.wait()){
    605 			if(ptr == &line_length){
    606 				ssize_t bytes = getline(&line, &line_length, stdin);
    607 				if(bytes > 0){
    608 					line[--bytes] = '\0';
    609 					PJ_LOG(6, ("Main", "Got line with length %lu: %s", bytes, line));
    610 					size_t cmdlen=line_length;
    611 					char *args = strstr(line, " ");
    612 					if(args){
    613 						line_length -= args-line;
    614 						*args++ = '\0';
    615 					}
    616 
    617 					if(strncmp(line, "QUIT", cmdlen) == 0){
    618 						break;
    619 					}
    620 					else if(strncmp(line, "CALL", cmdlen) == 0){
    621 						PJ_LOG(4, ("Main", "Placing call to %s", args));
    622 						MyCall *mc = new MyCall(*acc);
    623 						CallOpParam prm(true);
    624 						try{
    625 							mc->makeCall(std::string(args), prm);
    626 							mc->fork_on(acc->args, true);
    627 						}
    628 						catch(Error& err) {
    629 						}
    630 					}
    631 				}
    632 			}
    633 			else{
    634 				MyCall *c = (MyCall *)ptr;
    635 				c->handle_line();
    636 			}
    637 		}
    638 		delete acc;
    639 	}
    640 	catch(char const *c){
    641 		PJ_LOG(1, ("Main", "Fatal Exception: %s",c));
    642 	}
    643 
    644 	return 0;
    645 }