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 <time.h>
     29 #include <iomanip>
     30 #include <cstdio>
     31 #include <cstring>
     32 #include <vector>
     33 #include "talk/base/logging.h"
     34 #include "talk/base/flags.h"
     35 #include "talk/base/pathutils.h"
     36 #include "talk/base/stream.h"
     37 #include "talk/base/ssladapter.h"
     38 #include "talk/base/win32socketserver.h"
     39 #include "talk/p2p/base/constants.h"
     40 #include "talk/xmpp/xmppclientsettings.h"
     41 #include "talk/examples/login/xmppthread.h"
     42 #include "talk/examples/login/xmppauth.h"
     43 #include "talk/examples/login/xmpppump.h"
     44 #include "talk/examples/call/callclient.h"
     45 #include "talk/examples/call/console.h"
     46 #include "talk/session/phone/filemediaengine.h"
     47 #include "talk/session/phone/mediasessionclient.h"
     48 
     49 class DebugLog : public sigslot::has_slots<> {
     50  public:
     51   DebugLog() :
     52     debug_input_buf_(NULL), debug_input_len_(0), debug_input_alloc_(0),
     53     debug_output_buf_(NULL), debug_output_len_(0), debug_output_alloc_(0),
     54     censor_password_(false)
     55       {}
     56   char * debug_input_buf_;
     57   int debug_input_len_;
     58   int debug_input_alloc_;
     59   char * debug_output_buf_;
     60   int debug_output_len_;
     61   int debug_output_alloc_;
     62   bool censor_password_;
     63 
     64   void Input(const char * data, int len) {
     65     if (debug_input_len_ + len > debug_input_alloc_) {
     66       char * old_buf = debug_input_buf_;
     67       debug_input_alloc_ = 4096;
     68       while (debug_input_alloc_ < debug_input_len_ + len) {
     69         debug_input_alloc_ *= 2;
     70       }
     71       debug_input_buf_ = new char[debug_input_alloc_];
     72       memcpy(debug_input_buf_, old_buf, debug_input_len_);
     73       delete[] old_buf;
     74     }
     75     memcpy(debug_input_buf_ + debug_input_len_, data, len);
     76     debug_input_len_ += len;
     77     DebugPrint(debug_input_buf_, &debug_input_len_, false);
     78   }
     79 
     80   void Output(const char * data, int len) {
     81     if (debug_output_len_ + len > debug_output_alloc_) {
     82       char * old_buf = debug_output_buf_;
     83       debug_output_alloc_ = 4096;
     84       while (debug_output_alloc_ < debug_output_len_ + len) {
     85         debug_output_alloc_ *= 2;
     86       }
     87       debug_output_buf_ = new char[debug_output_alloc_];
     88       memcpy(debug_output_buf_, old_buf, debug_output_len_);
     89       delete[] old_buf;
     90     }
     91     memcpy(debug_output_buf_ + debug_output_len_, data, len);
     92     debug_output_len_ += len;
     93     DebugPrint(debug_output_buf_, &debug_output_len_, true);
     94   }
     95 
     96   static bool IsAuthTag(const char * str, size_t len) {
     97     if (str[0] == '<' && str[1] == 'a' &&
     98                          str[2] == 'u' &&
     99                          str[3] == 't' &&
    100                          str[4] == 'h' &&
    101                          str[5] <= ' ') {
    102       std::string tag(str, len);
    103 
    104       if (tag.find("mechanism") != std::string::npos)
    105         return true;
    106     }
    107     return false;
    108   }
    109 
    110   void DebugPrint(char * buf, int * plen, bool output) {
    111     int len = *plen;
    112     if (len > 0) {
    113       time_t tim = time(NULL);
    114       struct tm * now = localtime(&tim);
    115       char *time_string = asctime(now);
    116       if (time_string) {
    117         size_t time_len = strlen(time_string);
    118         if (time_len > 0) {
    119           time_string[time_len-1] = 0;    // trim off terminating \n
    120         }
    121       }
    122       LOG(INFO) << (output ? "SEND >>>>>>>>>>>>>>>>" : "RECV <<<<<<<<<<<<<<<<")
    123                 << " : " << time_string;
    124 
    125       bool indent;
    126       int start = 0, nest = 3;
    127       for (int i = 0; i < len; i += 1) {
    128         if (buf[i] == '>') {
    129           if ((i > 0) && (buf[i-1] == '/')) {
    130             indent = false;
    131           } else if ((start + 1 < len) && (buf[start + 1] == '/')) {
    132             indent = false;
    133             nest -= 2;
    134           } else {
    135             indent = true;
    136           }
    137 
    138           // Output a tag
    139           LOG(INFO) << std::setw(nest) << " "
    140                     << std::string(buf + start, i + 1 - start);
    141 
    142           if (indent)
    143             nest += 2;
    144 
    145           // Note if it's a PLAIN auth tag
    146           if (IsAuthTag(buf + start, i + 1 - start)) {
    147             censor_password_ = true;
    148           }
    149 
    150           // incr
    151           start = i + 1;
    152         }
    153 
    154         if (buf[i] == '<' && start < i) {
    155           if (censor_password_) {
    156             LOG(INFO) << std::setw(nest) << " " << "## TEXT REMOVED ##";
    157             censor_password_ = false;
    158           } else {
    159             LOG(INFO) << std::setw(nest) << " "
    160                       << std::string(buf + start, i - start);
    161           }
    162           start = i;
    163         }
    164       }
    165       len = len - start;
    166       memcpy(buf, buf + start, len);
    167       *plen = len;
    168     }
    169   }
    170 };
    171 
    172 static DebugLog debug_log_;
    173 static const int DEFAULT_PORT = 5222;
    174 
    175 
    176 cricket::MediaEngine* CreateFileMediaEngine(const char* voice_in,
    177                                             const char* voice_out,
    178                                             const char* video_in,
    179                                             const char* video_out) {
    180   cricket::FileMediaEngine* file_media_engine = new cricket::FileMediaEngine;
    181   // Set the RTP dump file names.
    182   if (voice_in) {
    183     file_media_engine->set_voice_input_filename(voice_in);
    184   }
    185   if (voice_out) {
    186     file_media_engine->set_voice_output_filename(voice_out);
    187   }
    188   if (video_in) {
    189     file_media_engine->set_video_input_filename(video_in);
    190   }
    191   if (video_out) {
    192     file_media_engine->set_video_output_filename(video_out);
    193   }
    194 
    195   // Set voice and video codecs. TODO: The codecs actually depend on
    196   // the the input voice and video streams.
    197   std::vector<cricket::AudioCodec> voice_codecs;
    198   voice_codecs.push_back(
    199       cricket::AudioCodec(9, "G722", 16000, 0, 1, 0));
    200   file_media_engine->set_voice_codecs(voice_codecs);
    201   std::vector<cricket::VideoCodec> video_codecs;
    202   video_codecs.push_back(
    203       cricket::VideoCodec(97, "H264", 320, 240, 30, 0));
    204   file_media_engine->set_video_codecs(video_codecs);
    205 
    206   return file_media_engine;
    207 }
    208 
    209 int main(int argc, char **argv) {
    210   // This app has three threads. The main thread will run the XMPP client,
    211   // which will print to the screen in its own thread. A second thread
    212   // will get input from the console, parse it, and pass the appropriate
    213   // message back to the XMPP client's thread. A third thread is used
    214   // by MediaSessionClient as its worker thread.
    215 
    216   // define options
    217   DEFINE_bool(a, false, "Turn on auto accept.");
    218   DEFINE_bool(d, false, "Turn on debugging.");
    219   DEFINE_string(
    220       protocol, "hybrid",
    221       "Initial signaling protocol to use: jingle, gingle, or hybrid.");
    222   DEFINE_string(
    223       secure, "disable",
    224       "Disable or enable encryption: disable, enable, require.");
    225   DEFINE_bool(testserver, false, "Use test server");
    226   DEFINE_bool(plainserver, false, "Turn off tls and allow plain password.");
    227   DEFINE_int(portallocator, 0, "Filter out unwanted connection types.");
    228   DEFINE_string(filterhost, NULL, "Filter out the host from all candidates.");
    229   DEFINE_string(pmuc, "groupchat.google.com", "The persistant muc domain.");
    230   DEFINE_string(s, "talk.google.com", "The connection server to use.");
    231   DEFINE_string(voiceinput, NULL, "RTP dump file for voice input.");
    232   DEFINE_string(voiceoutput, NULL, "RTP dump file for voice output.");
    233   DEFINE_string(videoinput, NULL, "RTP dump file for video input.");
    234   DEFINE_string(videooutput, NULL, "RTP dump file for video output.");
    235   DEFINE_bool(help, false, "Prints this message");
    236 
    237   // parse options
    238   FlagList::SetFlagsFromCommandLine(&argc, argv, true);
    239   if (FLAG_help) {
    240     FlagList::Print(NULL, false);
    241     return 0;
    242   }
    243 
    244   bool auto_accept = FLAG_a;
    245   bool debug = FLAG_d;
    246   std::string protocol = FLAG_protocol;
    247   bool test_server = FLAG_testserver;
    248   bool plain_server = FLAG_plainserver;
    249   int32 portallocator_flags = FLAG_portallocator;
    250   std::string pmuc_domain = FLAG_pmuc;
    251   std::string server = FLAG_s;
    252   std::string secure = FLAG_secure;
    253 
    254   cricket::SignalingProtocol initial_protocol = cricket::PROTOCOL_HYBRID;
    255   if (protocol == "jingle") {
    256     initial_protocol = cricket::PROTOCOL_JINGLE;
    257   } else if (protocol == "gingle") {
    258     initial_protocol = cricket::PROTOCOL_GINGLE;
    259   } else if (protocol == "hybrid") {
    260     initial_protocol = cricket::PROTOCOL_HYBRID;
    261   } else {
    262     printf("Invalid protocol.  Must be jingle, gingle, or hybrid.\n");
    263     return 1;
    264   }
    265 
    266   cricket::SecureMediaPolicy secure_policy = cricket::SEC_DISABLED;
    267   if (secure == "disable") {
    268     secure_policy = cricket::SEC_DISABLED;
    269   } else if (secure == "enable") {
    270     secure_policy = cricket::SEC_ENABLED;
    271   } else if (secure == "require") {
    272     secure_policy = cricket::SEC_REQUIRED;
    273   } else {
    274     printf("Invalid encryption.  Must be enable, disable, or require.\n");
    275     return 1;
    276   }
    277 
    278   // parse username and password, if present
    279   buzz::Jid jid;
    280   std::string username;
    281   talk_base::InsecureCryptStringImpl pass;
    282   if (argc > 1) {
    283     username = argv[1];
    284     if (argc > 2) {
    285       pass.password() = argv[2];
    286     }
    287   }
    288 
    289   if (debug)
    290     talk_base::LogMessage::LogToDebug(talk_base::LS_VERBOSE);
    291 
    292   if (username.empty()) {
    293     std::cout << "JID: ";
    294     std::cin >> username;
    295   }
    296   if (username.find('@') == std::string::npos) {
    297     username.append("@localhost");
    298   }
    299   jid = buzz::Jid(username);
    300   if (!jid.IsValid() || jid.node() == "") {
    301     printf("Invalid JID. JIDs should be in the form user@domain\n");
    302     return 1;
    303   }
    304   if (pass.password().empty() && !test_server) {
    305     Console::SetEcho(false);
    306     std::cout << "Password: ";
    307     std::cin >> pass.password();
    308     Console::SetEcho(true);
    309     std::cout << std::endl;
    310   }
    311 
    312   buzz::XmppClientSettings xcs;
    313   xcs.set_user(jid.node());
    314   xcs.set_resource("call");
    315   xcs.set_host(jid.domain());
    316   xcs.set_use_tls(!test_server);
    317 
    318   if (plain_server) {
    319     xcs.set_use_tls(false);
    320     xcs.set_allow_plain(true);
    321   }
    322   if (test_server) {
    323     pass.password() = jid.node();
    324     xcs.set_allow_plain(true);
    325   }
    326   xcs.set_pass(talk_base::CryptString(pass));
    327 
    328   std::string host;
    329   int port;
    330 
    331   int colon = server.find(':');
    332   if (colon == -1) {
    333     host = server;
    334     port = DEFAULT_PORT;
    335   } else {
    336     host = server.substr(0, colon);
    337     port = atoi(server.substr(colon + 1).c_str());
    338   }
    339 
    340   xcs.set_server(talk_base::SocketAddress(host, port));
    341   printf("Logging in to %s as %s\n", server.c_str(), jid.Str().c_str());
    342 
    343   talk_base::InitializeSSL();
    344 
    345 
    346 #if WIN32
    347   // Need to pump messages on our main thread on Windows.
    348   talk_base::Win32Thread w32_thread;
    349   talk_base::ThreadManager::SetCurrent(&w32_thread);
    350 #endif
    351   talk_base::Thread* main_thread = talk_base::Thread::Current();
    352 
    353   XmppPump pump;
    354   CallClient *client = new CallClient(pump.client());
    355 
    356   if (FLAG_voiceinput || FLAG_voiceoutput ||
    357       FLAG_videoinput || FLAG_videooutput) {
    358     // If any dump file is specified, we use FileMediaEngine.
    359     cricket::MediaEngine* engine = CreateFileMediaEngine(FLAG_voiceinput,
    360                                                          FLAG_voiceoutput,
    361                                                          FLAG_videoinput,
    362                                                          FLAG_videooutput);
    363     // The engine will be released by the client later.
    364     client->SetMediaEngine(engine);
    365   }
    366 
    367   Console *console = new Console(main_thread, client);
    368   client->SetConsole(console);
    369   client->SetAutoAccept(auto_accept);
    370   client->SetPmucDomain(pmuc_domain);
    371   client->SetPortAllocatorFlags(portallocator_flags);
    372   client->SetAllowLocalIps(true);
    373   client->SetInitialProtocol(initial_protocol);
    374   client->SetSecurePolicy(secure_policy);
    375   console->Start();
    376 
    377   if (debug) {
    378     pump.client()->SignalLogInput.connect(&debug_log_, &DebugLog::Input);
    379     pump.client()->SignalLogOutput.connect(&debug_log_, &DebugLog::Output);
    380   }
    381 
    382   pump.DoLogin(xcs, new XmppSocket(true), NULL);
    383   main_thread->Run();
    384   pump.DoDisconnect();
    385 
    386   console->Stop();
    387   delete console;
    388   delete client;
    389 
    390   return 0;
    391 }
    392