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 }