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