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 "webrtc/base/flags.h"
37 #include "webrtc/base/logging.h"
38 #ifdef OSX
39 #include "webrtc/base/maccocoasocketserver.h"
40 #endif
41 #include "talk/examples/call/callclient.h"
42 #include "talk/examples/call/console.h"
43 #include "talk/examples/call/mediaenginefactory.h"
44 #include "talk/p2p/base/constants.h"
45 #include "talk/session/media/mediasessionclient.h"
46 #include "talk/session/media/srtpfilter.h"
47 #include "talk/xmpp/xmppauth.h"
48 #include "talk/xmpp/xmppclientsettings.h"
49 #include "talk/xmpp/xmpppump.h"
50 #include "talk/xmpp/xmppsocket.h"
51 #include "webrtc/base/pathutils.h"
52 #include "webrtc/base/ssladapter.h"
53 #include "webrtc/base/stream.h"
54 #include "webrtc/base/win32socketserver.h"
55
56 class DebugLog : public sigslot::has_slots<> {
57 public:
DebugLog()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
Input(const char * data,int len)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
Output(const char * data,int len)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
IsAuthTag(const char * str,size_t len)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
DebugPrint(char * buf,int * plen,bool output)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
CreateAndroidMediaEngine()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.
Print(const char * chars)196 void Print(const char* chars) {
197 printf("%s", chars);
198 fflush(stdout);
199 }
200
GetSecurePolicy(const std::string & in,cricket::SecurePolicy * out)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
main(int argc,char ** argv)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 rtc::FlagList::SetFlagsFromCommandLine(&argc, argv, true);
261 if (FLAG_help) {
262 rtc::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 rtc::SSLIdentity* ssl_identity = NULL;
287 bool show_roster_messages = FLAG_roster;
288
289 // Set up debugging.
290 if (debug) {
291 rtc::LogMessage::LogToDebug(rtc::LS_VERBOSE);
292 }
293
294 if (!log.empty()) {
295 rtc::StreamInterface* stream =
296 rtc::Filesystem::OpenFile(log, "a");
297 if (stream) {
298 rtc::LogMessage::LogToStream(stream, rtc::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 rtc::InitializeSSL();
311
312 // Parse username and password, if present.
313 buzz::Jid jid;
314 std::string username;
315 rtc::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(rtc::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(rtc::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 = rtc::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 rtc::Win32Thread w32_thread;
445 rtc::ThreadManager::Instance()->SetCurrentThread(&w32_thread);
446 #endif
447 rtc::Thread* main_thread = rtc::Thread::Current();
448 #ifdef OSX
449 rtc::MacCocoaSocketServer ss;
450 rtc::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