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