Home | History | Annotate | Download | only in call
      1 /*
      2  * libjingle
      3  * Copyright 2004--2005, Google Inc.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are met:
      7  *
      8  *  1. Redistributions of source code must retain the above copyright notice,
      9  *     this list of conditions and the following disclaimer.
     10  *  2. Redistributions in binary form must reproduce the above copyright notice,
     11  *     this list of conditions and the following disclaimer in the documentation
     12  *     and/or other materials provided with the distribution.
     13  *  3. The name of the author may not be used to endorse or promote products
     14  *     derived from this software without specific prior written permission.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
     17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
     18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
     19  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
     22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
     23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
     24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
     25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     26  */
     27 
     28 #include <stdio.h>
     29 #include <string.h>
     30 #include <time.h>
     31 
     32 #include <iomanip>
     33 #include <iostream>
     34 #include <vector>
     35 
     36 #include "talk/base/flags.h"
     37 #include "talk/base/logging.h"
     38 #ifdef OSX
     39 #include "talk/base/maccocoasocketserver.h"
     40 #endif
     41 #include "talk/base/pathutils.h"
     42 #include "talk/base/ssladapter.h"
     43 #include "talk/base/stream.h"
     44 #include "talk/base/win32socketserver.h"
     45 #include "talk/examples/call/callclient.h"
     46 #include "talk/examples/call/console.h"
     47 #include "talk/examples/call/mediaenginefactory.h"
     48 #include "talk/p2p/base/constants.h"
     49 #include "talk/session/media/mediasessionclient.h"
     50 #include "talk/session/media/srtpfilter.h"
     51 #include "talk/xmpp/xmppauth.h"
     52 #include "talk/xmpp/xmppclientsettings.h"
     53 #include "talk/xmpp/xmpppump.h"
     54 #include "talk/xmpp/xmppsocket.h"
     55 
     56 class DebugLog : public sigslot::has_slots<> {
     57  public:
     58   DebugLog() :
     59     debug_input_buf_(NULL), debug_input_len_(0), debug_input_alloc_(0),
     60     debug_output_buf_(NULL), debug_output_len_(0), debug_output_alloc_(0),
     61     censor_password_(false)
     62       {}
     63   char * debug_input_buf_;
     64   int debug_input_len_;
     65   int debug_input_alloc_;
     66   char * debug_output_buf_;
     67   int debug_output_len_;
     68   int debug_output_alloc_;
     69   bool censor_password_;
     70 
     71   void Input(const char * data, int len) {
     72     if (debug_input_len_ + len > debug_input_alloc_) {
     73       char * old_buf = debug_input_buf_;
     74       debug_input_alloc_ = 4096;
     75       while (debug_input_alloc_ < debug_input_len_ + len) {
     76         debug_input_alloc_ *= 2;
     77       }
     78       debug_input_buf_ = new char[debug_input_alloc_];
     79       memcpy(debug_input_buf_, old_buf, debug_input_len_);
     80       delete[] old_buf;
     81     }
     82     memcpy(debug_input_buf_ + debug_input_len_, data, len);
     83     debug_input_len_ += len;
     84     DebugPrint(debug_input_buf_, &debug_input_len_, false);
     85   }
     86 
     87   void Output(const char * data, int len) {
     88     if (debug_output_len_ + len > debug_output_alloc_) {
     89       char * old_buf = debug_output_buf_;
     90       debug_output_alloc_ = 4096;
     91       while (debug_output_alloc_ < debug_output_len_ + len) {
     92         debug_output_alloc_ *= 2;
     93       }
     94       debug_output_buf_ = new char[debug_output_alloc_];
     95       memcpy(debug_output_buf_, old_buf, debug_output_len_);
     96       delete[] old_buf;
     97     }
     98     memcpy(debug_output_buf_ + debug_output_len_, data, len);
     99     debug_output_len_ += len;
    100     DebugPrint(debug_output_buf_, &debug_output_len_, true);
    101   }
    102 
    103   static bool IsAuthTag(const char * str, size_t len) {
    104     if (str[0] == '<' && str[1] == 'a' &&
    105                          str[2] == 'u' &&
    106                          str[3] == 't' &&
    107                          str[4] == 'h' &&
    108                          str[5] <= ' ') {
    109       std::string tag(str, len);
    110 
    111       if (tag.find("mechanism") != std::string::npos)
    112         return true;
    113     }
    114     return false;
    115   }
    116 
    117   void DebugPrint(char * buf, int * plen, bool output) {
    118     int len = *plen;
    119     if (len > 0) {
    120       time_t tim = time(NULL);
    121       struct tm * now = localtime(&tim);
    122       char *time_string = asctime(now);
    123       if (time_string) {
    124         size_t time_len = strlen(time_string);
    125         if (time_len > 0) {
    126           time_string[time_len-1] = 0;    // trim off terminating \n
    127         }
    128       }
    129       LOG(INFO) << (output ? "SEND >>>>>>>>>>>>>>>>" : "RECV <<<<<<<<<<<<<<<<")
    130                 << " : " << time_string;
    131 
    132       bool indent;
    133       int start = 0, nest = 3;
    134       for (int i = 0; i < len; i += 1) {
    135         if (buf[i] == '>') {
    136           if ((i > 0) && (buf[i-1] == '/')) {
    137             indent = false;
    138           } else if ((start + 1 < len) && (buf[start + 1] == '/')) {
    139             indent = false;
    140             nest -= 2;
    141           } else {
    142             indent = true;
    143           }
    144 
    145           // Output a tag
    146           LOG(INFO) << std::setw(nest) << " "
    147                     << std::string(buf + start, i + 1 - start);
    148 
    149           if (indent)
    150             nest += 2;
    151 
    152           // Note if it's a PLAIN auth tag
    153           if (IsAuthTag(buf + start, i + 1 - start)) {
    154             censor_password_ = true;
    155           }
    156 
    157           // incr
    158           start = i + 1;
    159         }
    160 
    161         if (buf[i] == '<' && start < i) {
    162           if (censor_password_) {
    163             LOG(INFO) << std::setw(nest) << " " << "## TEXT REMOVED ##";
    164             censor_password_ = false;
    165           } else {
    166             LOG(INFO) << std::setw(nest) << " "
    167                       << std::string(buf + start, i - start);
    168           }
    169           start = i;
    170         }
    171       }
    172       len = len - start;
    173       memcpy(buf, buf + start, len);
    174       *plen = len;
    175     }
    176   }
    177 };
    178 
    179 static DebugLog debug_log_;
    180 static const int DEFAULT_PORT = 5222;
    181 
    182 #ifdef ANDROID
    183 static std::vector<cricket::AudioCodec> codecs;
    184 static const cricket::AudioCodec ISAC(103, "ISAC", 40000, 16000, 1, 0);
    185 
    186 cricket::MediaEngineInterface *CreateAndroidMediaEngine() {
    187     cricket::FakeMediaEngine *engine = new cricket::FakeMediaEngine();
    188 
    189     codecs.push_back(ISAC);
    190     engine->SetAudioCodecs(codecs);
    191     return engine;
    192 }
    193 #endif
    194 
    195 // TODO: Move this into Console.
    196 void Print(const char* chars) {
    197   printf("%s", chars);
    198   fflush(stdout);
    199 }
    200 
    201 bool GetSecurePolicy(const std::string& in, cricket::SecurePolicy* out) {
    202   if (in == "disable") {
    203     *out = cricket::SEC_DISABLED;
    204   } else if (in == "enable") {
    205     *out = cricket::SEC_ENABLED;
    206   } else if (in == "require") {
    207     *out = cricket::SEC_REQUIRED;
    208   } else {
    209     return false;
    210   }
    211   return true;
    212 }
    213 
    214 int main(int argc, char **argv) {
    215   // This app has three threads. The main thread will run the XMPP client,
    216   // which will print to the screen in its own thread. A second thread
    217   // will get input from the console, parse it, and pass the appropriate
    218   // message back to the XMPP client's thread. A third thread is used
    219   // by MediaSessionClient as its worker thread.
    220 
    221   // define options
    222   DEFINE_string(s, "talk.google.com", "The connection server to use.");
    223   DEFINE_string(tls, "require",
    224       "Select connection encryption: disable, enable, require.");
    225   DEFINE_bool(allowplain, false, "Allow plain authentication.");
    226   DEFINE_bool(testserver, false, "Use test server.");
    227   DEFINE_string(oauth, "", "OAuth2 access token.");
    228   DEFINE_bool(a, false, "Turn on auto accept for incoming calls.");
    229   DEFINE_string(signaling, "hybrid",
    230       "Initial signaling protocol to use: jingle, gingle, or hybrid.");
    231   DEFINE_string(transport, "hybrid",
    232       "Initial transport protocol to use: ice, gice, or hybrid.");
    233   DEFINE_string(sdes, "enable",
    234       "Select SDES media encryption: disable, enable, require.");
    235   DEFINE_string(dtls, "disable",
    236       "Select DTLS transport encryption: disable, enable, require.");
    237   DEFINE_int(portallocator, 0, "Filter out unwanted connection types.");
    238   DEFINE_string(pmuc, "groupchat.google.com", "The persistant muc domain.");
    239   DEFINE_string(capsnode, "http://code.google.com/p/libjingle/call",
    240                 "Caps node: A URI identifying the app.");
    241   DEFINE_string(capsver, "0.6",
    242                 "Caps ver: A string identifying the version of the app.");
    243   DEFINE_string(voiceinput, NULL, "RTP dump file for voice input.");
    244   DEFINE_string(voiceoutput, NULL, "RTP dump file for voice output.");
    245   DEFINE_string(videoinput, NULL, "RTP dump file for video input.");
    246   DEFINE_string(videooutput, NULL, "RTP dump file for video output.");
    247   DEFINE_bool(render, true, "Renders the video.");
    248   DEFINE_string(datachannel, "",
    249                 "Enable a data channel, and choose the type: rtp or sctp.");
    250   DEFINE_bool(d, false, "Turn on debugging.");
    251   DEFINE_string(log, "", "Turn on debugging to a file.");
    252   DEFINE_bool(debugsrtp, false, "Enable debugging for srtp.");
    253   DEFINE_bool(help, false, "Prints this message");
    254   DEFINE_bool(multisession, false,
    255               "Enable support for multiple sessions in calls.");
    256   DEFINE_bool(roster, false,
    257       "Enable roster messages printed in console.");
    258 
    259   // parse options
    260   FlagList::SetFlagsFromCommandLine(&argc, argv, true);
    261   if (FLAG_help) {
    262     FlagList::Print(NULL, false);
    263     return 0;
    264   }
    265 
    266   bool auto_accept = FLAG_a;
    267   bool debug = FLAG_d;
    268   std::string log = FLAG_log;
    269   std::string signaling = FLAG_signaling;
    270   std::string transport = FLAG_transport;
    271   bool test_server = FLAG_testserver;
    272   bool allow_plain = FLAG_allowplain;
    273   std::string tls = FLAG_tls;
    274   std::string oauth_token = FLAG_oauth;
    275   int32 portallocator_flags = FLAG_portallocator;
    276   std::string pmuc_domain = FLAG_pmuc;
    277   std::string server = FLAG_s;
    278   std::string sdes = FLAG_sdes;
    279   std::string dtls = FLAG_dtls;
    280   std::string caps_node = FLAG_capsnode;
    281   std::string caps_ver = FLAG_capsver;
    282   bool debugsrtp = FLAG_debugsrtp;
    283   bool render = FLAG_render;
    284   std::string data_channel = FLAG_datachannel;
    285   bool multisession_enabled = FLAG_multisession;
    286   talk_base::SSLIdentity* ssl_identity = NULL;
    287   bool show_roster_messages = FLAG_roster;
    288 
    289   // Set up debugging.
    290   if (debug) {
    291     talk_base::LogMessage::LogToDebug(talk_base::LS_VERBOSE);
    292   }
    293 
    294   if (!log.empty()) {
    295     talk_base::StreamInterface* stream =
    296         talk_base::Filesystem::OpenFile(log, "a");
    297     if (stream) {
    298       talk_base::LogMessage::LogToStream(stream, talk_base::LS_VERBOSE);
    299     } else {
    300       Print(("Cannot open debug log " + log + "\n").c_str());
    301       return 1;
    302     }
    303   }
    304 
    305   if (debugsrtp) {
    306     cricket::EnableSrtpDebugging();
    307   }
    308 
    309   // Set up the crypto subsystem.
    310   talk_base::InitializeSSL();
    311 
    312   // Parse username and password, if present.
    313   buzz::Jid jid;
    314   std::string username;
    315   talk_base::InsecureCryptStringImpl pass;
    316   if (argc > 1) {
    317     username = argv[1];
    318     if (argc > 2) {
    319       pass.password() = argv[2];
    320     }
    321   }
    322 
    323   if (username.empty()) {
    324     Print("JID: ");
    325     std::cin >> username;
    326   }
    327   if (username.find('@') == std::string::npos) {
    328     username.append("@localhost");
    329   }
    330   jid = buzz::Jid(username);
    331   if (!jid.IsValid() || jid.node() == "") {
    332     Print("Invalid JID. JIDs should be in the form user@domain\n");
    333     return 1;
    334   }
    335   if (pass.password().empty() && !test_server && oauth_token.empty()) {
    336     Console::SetEcho(false);
    337     Print("Password: ");
    338     std::cin >> pass.password();
    339     Console::SetEcho(true);
    340     Print("\n");
    341   }
    342 
    343   // Decide on the connection settings.
    344   buzz::XmppClientSettings xcs;
    345   xcs.set_user(jid.node());
    346   xcs.set_resource("call");
    347   xcs.set_host(jid.domain());
    348   xcs.set_allow_plain(allow_plain);
    349 
    350   if (tls == "disable") {
    351     xcs.set_use_tls(buzz::TLS_DISABLED);
    352   } else if (tls == "enable") {
    353     xcs.set_use_tls(buzz::TLS_ENABLED);
    354   } else if (tls == "require") {
    355     xcs.set_use_tls(buzz::TLS_REQUIRED);
    356   } else {
    357     Print("Invalid TLS option, must be enable, disable, or require.\n");
    358     return 1;
    359   }
    360 
    361   if (test_server) {
    362     pass.password() = jid.node();
    363     xcs.set_allow_plain(true);
    364     xcs.set_use_tls(buzz::TLS_DISABLED);
    365     xcs.set_test_server_domain("google.com");
    366   }
    367   xcs.set_pass(talk_base::CryptString(pass));
    368   if (!oauth_token.empty()) {
    369     xcs.set_auth_token(buzz::AUTH_MECHANISM_OAUTH2, oauth_token);
    370   }
    371 
    372   std::string host;
    373   int port;
    374 
    375   int colon = server.find(':');
    376   if (colon == -1) {
    377     host = server;
    378     port = DEFAULT_PORT;
    379   } else {
    380     host = server.substr(0, colon);
    381     port = atoi(server.substr(colon + 1).c_str());
    382   }
    383 
    384   xcs.set_server(talk_base::SocketAddress(host, port));
    385 
    386   // Decide on the signaling and crypto settings.
    387   cricket::SignalingProtocol signaling_protocol = cricket::PROTOCOL_HYBRID;
    388   if (signaling == "jingle") {
    389     signaling_protocol = cricket::PROTOCOL_JINGLE;
    390   } else if (signaling == "gingle") {
    391     signaling_protocol = cricket::PROTOCOL_GINGLE;
    392   } else if (signaling == "hybrid") {
    393     signaling_protocol = cricket::PROTOCOL_HYBRID;
    394   } else {
    395     Print("Invalid signaling protocol.  Must be jingle, gingle, or hybrid.\n");
    396     return 1;
    397   }
    398 
    399   cricket::TransportProtocol transport_protocol = cricket::ICEPROTO_HYBRID;
    400   if (transport == "ice") {
    401     transport_protocol = cricket::ICEPROTO_RFC5245;
    402   } else if (transport == "gice") {
    403     transport_protocol = cricket::ICEPROTO_GOOGLE;
    404   } else if (transport == "hybrid") {
    405     transport_protocol = cricket::ICEPROTO_HYBRID;
    406   } else {
    407     Print("Invalid transport protocol.  Must be ice, gice, or hybrid.\n");
    408     return 1;
    409   }
    410 
    411   cricket::DataChannelType data_channel_type = cricket::DCT_NONE;
    412   if (data_channel == "rtp") {
    413     data_channel_type = cricket::DCT_RTP;
    414   } else if (data_channel == "sctp") {
    415     data_channel_type = cricket::DCT_SCTP;
    416   } else if (!data_channel.empty()) {
    417     Print("Invalid data channel type.  Must be rtp or sctp.\n");
    418     return 1;
    419   }
    420 
    421   cricket::SecurePolicy sdes_policy, dtls_policy;
    422   if (!GetSecurePolicy(sdes, &sdes_policy)) {
    423     Print("Invalid SDES policy. Must be enable, disable, or require.\n");
    424     return 1;
    425   }
    426   if (!GetSecurePolicy(dtls, &dtls_policy)) {
    427     Print("Invalid DTLS policy. Must be enable, disable, or require.\n");
    428     return 1;
    429   }
    430   if (dtls_policy != cricket::SEC_DISABLED) {
    431     ssl_identity = talk_base::SSLIdentity::Generate(jid.Str());
    432     if (!ssl_identity) {
    433       Print("Failed to generate identity for DTLS.\n");
    434       return 1;
    435     }
    436   }
    437 
    438 #ifdef ANDROID
    439   MediaEngineFactory::SetCreateFunction(&CreateAndroidMediaEngine);
    440 #endif
    441 
    442 #if WIN32
    443   // Need to pump messages on our main thread on Windows.
    444   talk_base::Win32Thread w32_thread;
    445   talk_base::ThreadManager::Instance()->SetCurrentThread(&w32_thread);
    446 #endif
    447   talk_base::Thread* main_thread = talk_base::Thread::Current();
    448 #ifdef OSX
    449   talk_base::MacCocoaSocketServer ss;
    450   talk_base::SocketServerScope ss_scope(&ss);
    451 #endif
    452 
    453   buzz::XmppPump pump;
    454   CallClient *client = new CallClient(pump.client(), caps_node, caps_ver);
    455 
    456   if (FLAG_voiceinput || FLAG_voiceoutput ||
    457       FLAG_videoinput || FLAG_videooutput) {
    458     // If any dump file is specified, we use a FileMediaEngine.
    459     cricket::MediaEngineInterface* engine =
    460         MediaEngineFactory::CreateFileMediaEngine(
    461             FLAG_voiceinput, FLAG_voiceoutput,
    462             FLAG_videoinput, FLAG_videooutput);
    463     client->SetMediaEngine(engine);
    464   }
    465 
    466   Console *console = new Console(main_thread, client);
    467   client->SetConsole(console);
    468   client->SetAutoAccept(auto_accept);
    469   client->SetPmucDomain(pmuc_domain);
    470   client->SetPortAllocatorFlags(portallocator_flags);
    471   client->SetAllowLocalIps(true);
    472   client->SetSignalingProtocol(signaling_protocol);
    473   client->SetTransportProtocol(transport_protocol);
    474   client->SetSecurePolicy(sdes_policy, dtls_policy);
    475   client->SetSslIdentity(ssl_identity);
    476   client->SetRender(render);
    477   client->SetDataChannelType(data_channel_type);
    478   client->SetMultiSessionEnabled(multisession_enabled);
    479   client->SetShowRosterMessages(show_roster_messages);
    480   console->Start();
    481 
    482   if (debug) {
    483     pump.client()->SignalLogInput.connect(&debug_log_, &DebugLog::Input);
    484     pump.client()->SignalLogOutput.connect(&debug_log_, &DebugLog::Output);
    485   }
    486 
    487   Print(("Logging in to " + server + " as " + jid.Str() + "\n").c_str());
    488   pump.DoLogin(xcs, new buzz::XmppSocket(buzz::TLS_REQUIRED), new XmppAuth());
    489   main_thread->Run();
    490   pump.DoDisconnect();
    491 
    492   console->Stop();
    493   delete console;
    494   delete client;
    495 
    496   return 0;
    497 }
    498