1 /* Copyright (C) 2002 GFRN systems
2
3 This program is free software; you can redistribute it and/or
4 modify it under the terms of the GNU General Public License as
5 published by the Free Software Foundation; either version 2 of the
6 License, or (at your option) any later version.
7
8 This program is distributed in the hope that it will be useful, but
9 WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11 See the GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License
14 along with this program; if not, write to the Free Software
15 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
16 02111-1307, USA.
17
18 The latest version of this program may be found at
19 http://CQiNet.sourceforge.net
20
21 $Log: conference.c,v $
22 Revision 1.69 2012/12/09 18:49:48 wb6ymh
23 1. Removed old chan_rptdir style Asterisk support.
24 2. Don't set bDynamicConf in RtpInit. Prevents conference from being
25 deleted when last user logs out.
26 3. Added support for new configuration variable ConfCmdEnable.
27 4. Added -q (quiet) switch support to .users command.
28 5. Corrected memset() argument order n StartCmdHandler.
29 6. Added G.726 support to the .users command.
30
31 Revision 1.68 2010/11/20 15:45:59 wb6ymh
32 1. Added ability to create an RTP conference separate from the speak freely
33 conference.
34 2. Corrected a bug which disabled the command port (tlbcmd interface) when
35 EchoLink isn't enabled (EchoLinkEnable = 0).
36 3. Modified Send2() to take a boolean rather than a port number to more
37 correctly reflect what's happening internally.
38 4. Added (untested) -n command line switch to the .connect command to create
39 a "nailed up" connection.
40 5. Added -7 command line switch to the .connect command to select the G.726
41 codec (rtp connections only).
42 Revision 1.67 2010/01/17 16:45:29 wb6ymh
43 1. Corrected a bug that caused the user count to get out of sync when an
44 rtcp timeout occured on a permanent connection.
45
46 2. Corrected a bug which caused a timeout by a conference user to be logged
47 multiple times (once per packet). Added a log entry when the long winded
48 operator finally unkeys.
49
50 3. Corrected a bug that caused the user count to get out of sync when an
51 a node was disconnected and then reconnected quickly.
52
53 4. Cleaned up announcements generated by the connect command. The node's
54 callsign is now announced rather than it's node number or IP address.
55
56 Revision 1.66 2009/10/02 16:33:46 wb6ymh
57 1. Removed code added in 0.44 that sent "Bye" packets to stations which were
58 in the disconnecting state when anything is received from them. IRLP
59 reflectors reflect the user's "Bye" packet which caused an infinite
60 bandwith consuming loop. Extra "Bye" packets are now only sent in reply
61 to valid RTCP packets.
62 2. Added code to force the compression type for Echolink clients to GSM.
63 3. Removed code that set LastHeard for RTP packets, LastHeard should only
64 be set for RTCP packets. Ensures we can disconnect from nodes sending
65 an constant audio stream.
66
67 Revision 1.65 2009/09/15 16:59:30 wb6ymh
68 Added support for -x (mute text) to the .mute and .unmute commands.
69
70 Revision 1.64 2009/09/14 21:42:27 wb6ymh
71 Corrected compile for thebridge (#ifdef'ed out call to GetConfigVarsPtr in
72 CmdSet).
73
74 Revision 1.63 2009/09/13 20:06:56 wb6ymh
75 1. Replaced most usages of bInConf with new and better named flag bConnected.
76 2. Modified the way the connection node count is handled so connections that
77 nodes that are in the process of connecting or disconnecting are not
78 included in the total.
79 3. Modified code to send "Bye" packets to stations which are in the
80 disconnecting state when anything is received from them. This is to
81 cover the case where the initial "Bye" packet is lost.
82 4. Modified conferencing code to prevent doubling warning message from being
83 sent to full duplex stations.
84 5. Modified the StartCmdHandler to take a callsign argument for logging.
85 6. Added code to set bIRLP, bRepeater and bConf based on the first 3
86 characters of the "callsign" for connections running the speak freely
87 7. Added support for the -T switch to the .users command.
88 8. Added support for the -s switch to the .users command.
89 9. Added 'I' user attribute for stations which have been Isolated from the
90 packet level conference.
91 10. Modified CmdSet to call GetConfigVarsPtr when the first attempt to locate
92 a configuration variable fails to support configuration variables
93 that only apply to VoIP ports.
94 11. Add CmdRxLevel.
95 12. Enabled Saving of LastCmdText for all users to ease debug.
96
97 Revision 1.62 2009/06/30 23:14:49 wb6ymh
98 Added -f switch to the .connect command usage message.
99
100 Revision 1.61 2009/05/26 19:31:28 wb6ymh
101 Modified SendAudioFromFile to clear ssrc only when absolutely necessary.
102
103 Revision 1.60 2009/05/22 14:36:04 wb6ymh
104 Set new user's CompressType from conference's CompressType rather than
105 assuming GSM.
106
107 Revision 1.59 2009/04/04 18:10:16 wb6ymh
108 1. Added code to save the client's "tool" string if present.
109 2. Added -v command line switch to the .users command to display the user's
110 "tool" and (hopefully) Version information.
111
112 Revision 1.58 2009/03/29 16:29:53 wb6ymh
113 Added support for LogIPAddresses to optionally log station's IP address on
114 connects and disconnects contributed by va3jss.
115
116 Revision 1.57 2009/02/14 14:59:44 wb6ymh
117 1. Modified RTCP_Rx to ignore welcome files with an extension of .wav.
118 2. Corrected crhas in ConferenceCmd when called with a NULL ClientInfo. This
119 occurs when EchoLinkEnabled = 0.
120
121 Revision 1.56 2009/01/04 15:50:10 wb6ymh
122 Added some ifdef's to correct thebridge builds broken by thelinkbox 0.83
123 commits.
124
125 Revision 1.55 2009/01/03 22:43:31 wb6ymh
126 1. Added APRS-IS support.
127 2. Added entries for CmdPCM and CmdSendAX25Beacon to the command table.
128
129 Revision 1.54 2008/07/26 18:05:22 wb6ymh
130 1. Modified Asterisk code for compatibility with chan_rtpdir version 0.5.
131 NB: chan_rptdir version 0.2 is no longer supported.
132 2. Added the ability for Asterisk nodes to initiate connections to Echolink
133 nodes by entering the 6 digit EchoLink node number. Leading zeros must be
134 entered for 4 and 5 digit nodes.
135 3. Added the ability for Asterisk nodes to disconnect the last EchoLink node by
136 connected by entering "000000".
137 4. Added -b, -c and -t arguments to the .users command. The -b switch
138 suppresses the display of the user's attributes. The -c switch displays
139 the amount of time each user has been connected. The -t switch displays
140 the time since the user last transmitted.
141
142 Revision 1.53 2008/07/23 14:48:45 wb6ymh
143 1. Added support for full duplex operation via the conference. When full
144 2. Added the ability to dynamically control the Asterisk connection via the
145 the .connect, .disconnect and .kick commands and the AsteriskEnable
146 configuration variable.
147 3. Replaced the AllowADPCM configuration variable with CompressionType.
148 4. Added event hook support for Asterisk connections.
149
150 Revision 1.52 2008/07/16 22:38:25 wb6ymh
151 1. Added (untested) support for full duplex clients (currently just thelinkbox).
152 2. Added code to enable the stock EchoLink client's DTMF pad when the
153 configuration file variable EnableRemoteDTMF is set to 1.
154 3. Modified logic to set bSendSSRC whenever an rtp packet is received with a
155 nonzero ssrc. (memory hint: very old EchoLink clients crashed when they
156 received a nonzero ssrc.. they are probably long since dead an buried now,
157 but who really knows?)
158 4. Modified code to prevent 5 bytes of garbage from being written to the end
159 of saved .info files.
160 5. Modified code to prevent station list received from other conferences from
161 being saved as info and in .info files.
162 6. Added support for Asterisk's new chan_rtpdir driver.
163
164 Revision 1.51 2008/06/26 20:24:35 wb6ymh
165 1. Modified ConferenceCmd to properly handle a ';' within a quoted string.
166 2. Added .frequency command.
167 3. Added GetCmdOptions.
168
169 Revision 1.50 2008/06/25 17:33:06 wb6ymh
170 Cleaned up messages generated by .disconnect command.
171
172 Revision 1.49 2008/06/25 15:23:14 wb6ymh
173 Added support for ".disconnect ." - disconnect the station currently talking.
174
175 Revision 1.48 2008/06/15 13:34:53 wb6ymh
176 Corrected compile errors under Windoze.
177
178 Revision 1.47 2008/05/19 13:25:38 wb6ymh
179 Modified ConferenceCmd to make a copy of the command line before processing
180 the command. Previously commands that generated a lot of output (list)
181 could overwrite the command line causing odd log entries (to say the least).
182
183 Revision 1.46 2008/05/18 13:58:30 wb6ymh
184 1. Modified RTP_Data to record locally generated chat traffic even when the
185 conference is empty.
186
187 Revision 1.45 2008/05/14 18:31:23 wb6ymh
188 Added CmdTxOffset, CmdRxFrequency, CmdRxTone, CmdTxTone.
189
190 Revision 1.44 2008/04/11 18:33:49 wb6ymh
191 1. Added code to send an chat event to the event hook when a text chat message
192 is received from a user.
193
194 2. Added code to send an sent_chat event to the event hook when a chat message
195 is sent.
196
197 3. Deleted the obsolete .dtmfgen command.
198
199 Revision 1.43 2008/03/21 14:47:58 wb6ymh
200 Modified CmdSet to allow assignments without spaces around the '='.
201
202 Revision 1.42 2008/03/09 17:14:22 wb6ymh
203 1. Modified ConferenceCmd to allow a dot prefix when multiple commands
204 are entered on one line.
205 2. Corrected a bug in ConferenceCmd that caused the first command to be
206 executed multiple times when another command wasn't found or was partially
207 matched.
208
209 Revision 1.41 2008/03/08 06:52:41 wb6ymh
210 1. audio.h -> linkbox.h.
211 2. Corrected "Callsigns" for command and chat ports when compiled as part
212 of thelinkbox.
213 3. Moved where EndPoint(EVENT_INIT) is called so pCC->Callsign has been
214 initialized.
215 4. Modified RTCP_Rx to always process SDES packets for RTP as well as
216 EchoLink. Allows nodes to be named by callsign rather than ip addresses
217 in offgrid networks.
218 5. Removed references to the obsolete RxMap and TxMap variables.
219 6. Modified CmdInfo to suppress disable of "Currently displaying" unless
220 ShowStatusInInfo is enabled.
221 7. Modified ConferenceCmd to provide support multiple commands on one line
222 seperated by ';'.
223
224 Revision 1.40 2008/03/06 14:39:32 wb6ymh
225 Corrected initialization of pPDat in SendAudioFromFile. Corrects crashs
226 playing recordings for non-EchoLink clients.
227
228 Revision 1.39 2008/02/29 16:59:22 wb6ymh
229 1. Modified all recording related code to reference pMasterCS rather than
230 pCS. The recording flag, file handle, etc is only present in the master
231 conference.
232 2. Changed assumed codec type for new SF and RTP connections from ADPCM to
233 GSM.
234 3. Modified code to write *all* packets to recording file. When a protocol
235 conversion between SF and RTP/Echolink occurs RTP_Data is called once,
236 but multiple packets are generated.
237 4. Added code to display a usage message when the .monitor command is run
238 without arguments.
239 5. Modified the users command to display an 'a' user attribute for ADPCM users.
240
241 Revision 1.38 2008/02/28 23:08:35 wb6ymh
242 1. Corrected bug in Play4 (without -u option) command introduced in version
243 0.90. Version 0.90 -> 0.92 were unable to play a file for all users.
244 2. Fairly massive changes to fileplay back to be able to support .play4 -u
245 when the selected user is an RTP or SF client. ConvertProtocol is now
246 passed a pointer to the ProtoData structure so more than a single
247 protocol conversion may be active at one time.
248
249 Revision 1.37 2008/02/28 01:02:40 wb6ymh
250 1. Corrected a bug introduced by the last changes to AuthorizedClient
251 which broke EchoLink connections to -L and -R stations.
252
253 2. Fixed typo in the .connect command which caused duplicate dynamic
254 conferences to be created.
255
256 Revision 1.36 2008/02/26 18:00:59 wb6ymh
257 1. Added dmalloc support.
258 2. Modified GenBye to set the protocol version correctly for SF and RTP.
259 3. Modified CreateServerClient to prevent sockets for EchoLink ports from
260 being opened unless EchoLink is enabled.
261 4. Corrected bugs created in CreateServerClient when SFBind2IP was added.
262 5. Modified code to initialize command line client using SF conference when
263 EchoLink is disabled.
264 6. Modified RTP_Data to only forward audio packet to clients running the same
265 codec as client speaking.
266 7. Removed newline from playbackcomplete event.
267 8. Clear bPlayWhenFree flag in PlayBackComplete. Fixes crash which occured
268 when a client disconnected while a fileplay back was pending.
269 10. Modified the .connect command to default to the port specified by the
270 SF_Port configuration variable rather than 2074 for SF and RTP connections.
271 11. Corrected a bug in the .connect command that prevented a connection to
272 a node by IP address from being reestablished immediately after it had
273 been disconnected.
274 12. Modified Send2 to avoid using EchoLink sockets when Echolink is not enabled.
275 13. Modified GetPacketType to return PKT_TYPE_IGNORE for SF and RTP cleints
276 with unknown codecs rather than kicking the connection.
277 14. Corrected a bug in AuthorizedClient that prevented -R or -L stations from
278 being .allowed to connect via RTP or SF.
279 15. Modified AuthorizedClient to prevent callsign from RTCP packet from being
280 replaced with callsign from ACL unless needed. (Prevent loss of -R or -L
281 from the callsign)
282 16. Added ConferenceCleanup.
283
284 Revision 1.35 2008/02/09 17:23:57 wb6ymh
285 Add conditional compile around CallBacksRegistered, SigChilds and
286 ChildExits to fix Windoze build.
287
288 Revision 1.34 2008/02/09 17:05:43 wb6ymh
289 Removed left over debug printf in RTP_Data.
290
291 Revision 1.33 2008/02/09 17:03:12 wb6ymh
292 1. Added support for a new configuration variable ShowStatusInInfo to
293 enable EchoIRLP nodes to be configured to automatically show the name of
294 the current connection in the info field on the EchoLink directory servers.
295
296 2. Added Echolink firewall mitigation code courtesy of Johnathan K1RFD.
297 This code is invoked by the .connect command to assist with connections to
298 version 2.0 and above EchoLink nodes located behind unconfigured firewalls.
299
300 3. Added support for SFBind2IP to allow Speak Freely conferences to be bound
301 to a different IP address than EchoLink conferences.
302
303 4. Added support for sysop and admin private chats.
304
305 5. Modified the behavour for handling unknown EchoLink clients: ignore
306 unknown EchoLink users but to send a "bye" packets to banned users.
307
308 Revision 1.32 2008/01/13 17:09:18 wb6ymh
309 1. Replaced hardcoded phoney EchoLink number that is used when thelinkbox
310 is not logged into the EchoLink network with a crc of the user's callsign.
311 Fixes EchoLink and RTP connections between two nodes when neither node
312 are logged into EchoLink.
313
314 2. Modified the Play4 command so file playback is also sent to the currently
315 selected port.
316
317 Revision 1.31 2007/12/27 17:59:25 wb6ymh
318 1. Added "port" command.
319 2. Added code send the "NAME" RTCP field to SF and RTP clients. The
320 default value is ConferenceCall unless overridden by the new configuration
321 variable FullName. The CNAME field is now set to "CALLSIGN" for
322 compatibility, previously it was set to ConferenceCall.
323
324 Revision 1.30 2007/12/14 23:13:59 wb6ymh
325 1. Added avl tree of "conferences" indexed by audio port number.
326 2. Modified DeleteConf to clear pAudio->p and pControl->p before calling
327 DeleteClient. Otherwise the conference is free'ed three times.
328 3. Modified DeleteConf to only destroy local ConfTrees.
329 4. Eliminiated ControlPort member of ConfServer. According to the RFC
330 the audio port must be an even port and the control port must be the
331 next port.
332 5. Modified code to use the audio port number from the "conference" instead
333 of using hard coded values.
334 6. Modified RTP_Data to ignore closing connections that return
335 PKT_TYPE_UNSUPPROTED as the protocol type.
336 7. Modified RTCP_Handler to delete dynamic conferences when the user count
337 becomes zero.
338 8. Modified the .set command to call the new AccessFunc to get a display
339 string for variables with a AccessFunc.
340 9. Added -m command line parameter to the .connect command to establish
341 connections in monitor mode.
342 10. Added -p command line parameter to the connect command to allow
343 connections to arbitrary port numbers.
344 11. Modified the .connect command to create a dynamic conference on the fly
345 to handle clients at nonstandard port numbers.
346 12. Modified the .connect command to initialize the new client's RxMap
347 and TxMap variables from the new VoipOutMap configuration variable
348 rather than the current port number.
349 13. Modified Send2 to use pCS->pControl->Socket or pCS->pAudio->Socket
350 rather than a bit if tree conditioned on hard coded port numbers.
351
352 Revision 1.29 2007/12/14 22:28:32 wb6ymh
353 Modified code so data is always sent from the appropriate port. Previously
354 IRLP data (after conversion to EchoLink format) was sent to the EchoLink
355 ports, but from the IRLP ports. Normally this is not a problem, but if
356 a user's firewall is using dynamic rules created by outbound packets then
357 the these packets would be lost. Effect is some EchoLink clients can not
358 hear IRLP stations via integrated IRLP/EchoLink conferences.
359
360 Revision 1.28 2007/12/01 00:59:09 wb6ymh
361 1. Fixed bug in CmdSet, some pVars->var_ptr's weren't replaced with
362 var_ptr. Caused crashes when displaying configuration variables
363 that are now class members.
364
365 Revision 1.27 2007/11/26 14:51:15 wb6ymh
366 1. Modified CmdSet to handle configuration variables that are now class
367 members.
368 2. Added code to CmdConnect to set new RxMap and TxMap variables from
369 CurrentPort (thelinkbox builds).
370 3. Added link and unlink commands to commandtable.
371
372 Revision 1.26 2007/07/02 13:55:07 wb6ymh
373 Modified the logic in GenSDES to suppress the conference signature when
374 running as a user node and the number of external connections is one.
375
376 Revision 1.25 2007/06/29 15:51:02 wb6ymh
377 Modified RTCP_Rx to send a "bye" packet to Banned stations.
378 Previously banned stations were just ignored. This caused problems when
379 a conference was banned because thebridge is designed to continue to attempt
380 to connect forever unless stopped manually. Sending a "bye" packet stops
381 the connection attempt (and prevent log files from filling with
382 "Ignoring unauthorized user" messages).
383
384 Revision 1.24 2007/06/27 21:04:51 wb6ymh
385 1. Added hooks for tbdQt by Scott KI4LKF.
386 2. Added lookup command contributed by Scott KI4LKF.
387 3. Moved PacketType enum to conference.h.
388 4. Added set command.
389 5. Added -r and -a arguments to the connect command.
390 6. Added an "last" option to the disconnect command.
391 7. Added a separate interface for the EchoLink text mode via tbdchat.
392 8. Modified GenSDES to only send the conference signature when thebridge is
393 configured as a conference server.
394 9. Renamed the "quit" command to "quickexit" to prevent accidents.
395 10. Modified GetPacketType to allow duplicate commands to be entered via local
396 chat or command ports.
397
398 Revision 1.23 2006/08/05 23:30:04 wb6ymh
399 Corrected warnings and errors with GCC 4.x.x.
400
401 Revision 1.22 2005/01/08 23:57:53 wb6ymh
402 Give priority to repeater attribute over conference attribute in
403 autolurking code.
404
405 Revision 1.21 2004/11/29 01:10:00 wb6ymh
406 1. Added text messaging support to the command interface. The new ".chat"
407 command is used to enable and disable chat mode for compatibility with
408 existing scripts.
409 2. Added AVRS support for reporting the repeater or link location, frequency,
410 status and other parameters to EchoLink's server.
411 3. Added .refresh command.
412 4. Added a "status" option to the .busy command.
413 5. Modified RTP_Data to keep it from dumping Speak Freely clients on that
414 we started when the a packet with the wrong compression format is received.
415 This prevents problems staying connected to reflectors with misconfigured
416 clients.
417 6. Added patch from KF7FLY to improve the ability of EchoIRLP to track refused
418 outbound connections.
419 7. Removed code that attempted to deal with an IP address changes for
420 logged in clients. The code had bugs and was just about impossible to
421 test. If your ISP randomly forces you to change your IP address it's
422 time to find a new ISP! This code caused thebridge to crash if a logged
423 in user's IP address did change.
424 8. Modified .connect command to add ability to make outbound connections
425 to Speak Freely clients.
426 9. Modified AuthorizedClient() so it doesn't attempt to connect to the
427 EchoLink servers when LoginInterval is zero.
428 10.Corrected bug in statistics gathering for the RxMBytesAllConf variable.
429
430 Revision 1.20 2004/05/29 17:18:04 wb6ymh
431 1. Added a -p option to the .mute and .unmute commands to allow a station to be
432 muted or unmuted more persistently. Normally when a station is explicitly
433 muted or unmuted the action applies current connection only. If the station
434 disconnects and then reconnects he will return to the default mute state for
435 his station class. The -p option causes the stations mute state to presist
436 across connections and disconnections as long as the station's callsign
437 remains in the directory cache. This feature may be useful in certain
438 unusual circumstances when the a station class is muted, but there are
439 certain exceptions.
440
441 2. Corrected an infinite loop that occured when a "play4 -u ..." command was
442 issued when a file was already playing for the specified user.
443
444 3. Corrected a bug that caused the command ".mute -u" to mute conferences as
445 well as PC users.
446
447 4. Removed the code added in 0.70 that prevented the conference from
448 connecting to itself. This feature caused problems for the EchoIRLP project.
449
450 5. Corrected bug which caused the play4 pesudo user to timeout when the
451 BlabOffTimer was enabled.
452
453 6. Added code to update the IP address of persistent clients when the client's
454 IP address changes in the directory.
455
456 Revision 1.19 2003/09/09 21:03:12 wb6ymh
457 1. Changed the automute and autolurk logic to accomodate new EchoLink clients
458 which can be repeaters or links *as well* as conferences. The conference
459 attribute is now the highest priority. If conferences are muted, but not
460 links and a user is both a conference and a link he will now be muted.
461
462 2. Added a "-e" flag to the .mute and .unmute flags to allow EchoLink
463 conferences to be treated separately from tbd conferences. The original
464 -c flag now effects tbd conferences only.
465
466 3. Corrected infinite loop that occurred on some platforms when the command
467 ".mute -?" (or any other undefined flag) was entered.
468
469 4. Corrected a crash that occurred when an user attempted to connect to a
470 conference when it was busy or at the configured maximum number of users.
471
472 Revision 1.18 2003/09/08 03:50:09 wb6ymh
473 Moved auto mute code to *after* GetCall() in RTCP_RX(), helps to check
474 flags after they are set!
475
476 Revision 1.17 2003/09/07 15:12:49 wb6ymh
477 1. Added calls to EventHook() when users connect, disconnect, issued
478 undefined commands or when file playback completes.
479 2. Modified CmdListAdd() so the bulletin list order does not change when
480 thebridge was restarted.
481 3. Revised the way the callsign of the station talking was sent from one
482 conference to the next. Earlier versions used a (malformed) private
483 extension "txt" in the SDES info, now we just dynamically change the
484 CNAME field. This is simpler and more importantly compatible with newer
485 EchoLink clients that support multiconferencing.
486 4. Added CRC based duplicate text message checking. The previous scheme
487 was only partially effective for simple closed loops.
488 5. Added local command line interface to command processor.
489 6. Corrected a bug that caused Bind2IP to fail randomly on FreeBSD.
490 7. Added a new configuration variable SF_ReplyPort that may be used to
491 configure thebridge to talk to a Speak Freely or RTP client on the same
492 host.
493 8. Reworked SendSDES2Conf() -> SendSDES2All() now handles all protocols plus
494 now obsolete SDES w/ "txt" extension when needed.
495 9. Replaced all ERROR_CODE2MSG with Err2String().
496 10. Modified code to pass local users SSRC on unmodified. Previously local
497 user's SSRC was changed to the SSRC of the conference for audio loop
498 detection.
499 11. Added a ".play4" command that allows a recorded file to be played everyone
500 or a specified user.
501 12. Added -r, -c, -u, -s, -t, and -a options to the .mute and .unmute commands
502 to mute and unmute groups of stations with one command. New stations
503 connecting to the conference will be muted automatically when a group
504 mute for their station type is in effect.
505 13. Added ".busy" command to put the conference into a busy state.
506 14. Added support for WelcomeDelay that specifies a delay between when the
507 user connects and when the welcome file starts playing.
508 15. Modified RTCP handler to fully process all packets received. Previously
509 the user's callsign and name was extracted when the user first connected
510 and never looked at again. This prevented the dynamic information
511 generated by the new EchoLink client from being displayed from the name
512 field.
513 16. Added support for the "StartupCmd" configuration file variable that
514 provides the ability to execute commands automatically at startup.
515 17. Modified snprintf usage to handle more flavors correctly. (The C99
516 standard defined the behaviour differently than earlier implementations.)d
517 18. Modified GenSDES() to parse "<conference name> (talker)" strings to pass
518 on only the talker to prevent the line from growing at each hop.
519 19. Added returned codes to all commands for command line interface.
520 20. Added carriage returns at the end of all strings generated by commands for
521 the command line interface.
522 21. Added a ".message" command.
523 22. Added the ability to .connect to a node by node number. Numeric arguments
524 that do not contain dots are assumed to be node IDs.
525 23. Added an option to disconnect all users to the ".disconnect" command.
526 24. Added support for configuration variable HelpFile used to replace
527 the builtin help text with a customized version.
528 25. Added code to clear the waiting for response to .quote'ed command state
529 after 30 seconds. Previously this state was not cleared resulting in
530 sysops receiving station lists from connected EchoLink conferences.
531 26. Added the conference name to responses to commands issued with the .quote
532 command so that responses from multiple conferences can be differentiated.
533 27. Added code to prevent the conference from connecting to itself.
534 28. Added code to automatically disconnect stations that send too many
535 duplicate text messages.
536
537 Revision 1.16 2003/05/28 18:07:49 wb6ymh
538 1. Corrected belch filter bug that was introduced by the addition of the
539 timeout timer feature in 0.51.
540 2. Corrected bug in RTP_Rx() which would could cause memory corruption if
541 recvfrom() returned an error. Thanks to KD5MU for discovering and
542 reporting this problem by code inspection.
543
544 Revision 1.15 2003/04/30 21:31:29 wb6ymh
545 1. TxBytesAllConf, etc to track overall statistics of all conferences and
546 protocols. Modified LogStats() to display overall statistics.
547 2. Added support for Bind2IP configuration variable to CreateServerClient().
548 3. Added filename argument to SaveBadPacket() so packets with different
549 types of errors are saved to different files.
550 4. Modified RTP_Rx() to add a terminating NULL after string returned by
551 version queries.
552 5. Added a timeout timer controlled by the BlabOffTimer configuration
553 variable.
554 6. Added ".monitor" command.
555 7. Added code to optionally put specified types of stations into autolurk
556 mode upon login.
557 8. Added code to return stations that had been lurking to lurk mode after
558 AutoLurkTimeout seconds of inactivity.
559 9. Modified RTCP_RX() to send station list before playing welcome file.
560 10. Corrected a bug in the ACL handling that prevented .allow'ing a EchoLink
561 client by IP address.
562 11. Fixed a bug which sent a verify command to a iLink directory server.
563
564 Revision 1.14 2003/01/01 19:08:04 wb6ymh
565 Added commands .crash, .quit, and .shutdown.
566
567 Revision 1.13 2002/12/21 18:47:28 wb6ymh
568 1. Added the ability to display a new users information (brag) sheet during
569 his first transmission.
570 2. Added the ability to save user's brag sheets to disk.
571 3. Added new user .about command to display a specified station's brag sheet
572 on demand.
573 4. Added support for an test only conference (*ECHOTEST*).
574 5. Added new hourly log entry showing current bandwidths, amount of traffic
575 sent since boot and uptime.
576 6. Added code to delete files created by the .test command.
577 7. Modified the ".mute ." command to mute stations on linked conferences.
578 8. Added admin .rehash command to reload configuration file remotely.
579 9. Added reason argument to PlayBackComplete().
580 10. Added admin .quote command to allow commands to be sent to linked
581 conferences.
582 11. Enhanced audio file playback code to pauses between individual
583 tranmissions in the recording with configurable timing.
584 12. Corrected premature playback termination when thebridge is run on (very?)
585 fast computers.
586 13. Implemented EnableDiskCommands configuration file variable as documented,
587 previously it was ignored.
588
589 Revision 1.12 2002/11/05 05:26:29 wb6ymh
590 Added protection against PktHdr.Len < 0 in SendAudioFromFile.
591
592 Revision 1.11 2002/11/02 19:04:26 wb6ymh
593 1. Removed BannedStations, SaveBanedList(), LoadBannedList(), and
594 FreeBannedList(). These have been replaced with new ACL routines.
595 2. Added routines to convert between Speak Freely, RTP and EchoLink protocols.
596 3. Added support for a Speak Freely and RTP conference.
597 4. Added code to check connecting clients against ACL and EchoLink directory
598 servers.
599 5. Added code to suppress the first BelchTime milliseconds of transmissions
600 from -L, -R stations and conferences.
601 6. Removed code that transmitted a bye packet to banned stations. It's
602 better to just ignore the unwelcomed so we don't catch the attention
603 of port scanners.
604 7. Restored full validity checking for RTCP packets.
605 8. Added '*' first character test for detecting conference clients.
606 9. Added the ability for sysops to set the lurking mode of other stations
607 to the .lurk and .delurk commands.
608 10.Added the ability for sysops to suppress the forwarding of chat text
609 to themselfs.
610 11.Added .allow, .belchfilter, and .dns commands.
611 12.Modified the code to allow the help command to be entered without
612 a leading dot.
613 13.Added code to suppress looping of text messages when a conference
614 loop is formed. BUG: a message introduced by a conference that is
615 not in the loop (a conference on a fork) will still loop. This
616 will be fixed in a later version.
617 14.Muted stations are now displayed in the station list as muted.
618
619 Revision 1.10 2002/09/29 16:58:34 wb6ymh
620 1. Added enable and disable options to the .lurk command (sysops only).
621 2. Added .sysop command.
622 3. Added support for PauseTime variable and .pausetime command.
623 4. Added .users command.
624 5. Added code to send a message to all sysops when a .connect request is
625 refused by the target station.
626 6. Added conference callsign to the beginning of all warning messages so that
627 older versions of the EchoLink client will display it. (Apparently older
628 EchoLink clients suppress text messages which begin with users callsign,
629 newer version require the '>' character as well).
630 7. Added ability to list muted stations to the .mute command.
631 8. Added bMuted flag to conference client structure so text messages from
632 muted stations can be forwarded. bSWL is no longer used for muting.
633 9. Corrected bug in GenBye that overwrote the src field with the text
634 message.
635 10. Modified TimeLapse() to handle overflows properly. This bug was causing
636 the bandwidth calculations to be bypassed on occasion.
637 11. Modified code to prevent packets from being forward to stations that
638 don't have the bInConf flag set.
639 12. Added '.' as a shorthand for the station that is talking to the .mute
640 command.
641
642 Revision 1.9 2002/09/19 22:48:56 wb6ymh
643 1. Modified GetCall() to protect against (invalid) RTCP_SDES blocks that don't
644 terminate with a RTCP_SDES_END item. This was causing page faults in the
645 Windows version, but didn't appear to effect the *nix versions.
646 2. Modified RTP_Rx() to only forward audio packet to clients with bInConf set.
647
648 Revision 1.8 2002/09/15 15:03:48 wb6ymh
649 1. Added new administrator commands .connect and .disconnect.
650 2. Added additional statistics to the .stats command.
651 3. Added support for the Echolink client's "Request version Information"
652 packet.
653 4. Added configuration file variables to set the contents of the banner area.
654 5. Added configruation file variables to allow current and maximum user
655 counts to added to the location string and conference ID line.
656 6. Added new user command .stop to allow bulletin playback to be terminated
657 without disconnecting.
658 7. Added playback time to bulletin list for each entry.
659 8. Added the callsign of the station that has the floor to the doubling
660 warning.
661 9. Added timestamps to the recording file at the beginning of each audio
662 segment.
663 10. Made the file format of recordings platform independent by converting all
664 values to network order.
665 11. Changed the code to send SDES packets periodically rather than only in
666 response to a received SDES packet.
667 12. Changed the way the station list works (yet again). When a station
668 transmits he is now moved to the top of the station list and remains
669 there after he stops talking.
670 13. Added the configuration file varialbe to enable/disable the display of
671 the "<SB>" string.
672 14. Added code to remove any leading path from filename for record commands.
673 15. Correct a bug which would cause a crash if a user disconnected while
674 listening to a recorded bulletin.
675 16. Moved code that checks for RTCP timeouts to the RTCP routine (!).
676 Previously RTCP timeouts were only checked when an RTP packet was
677 received. This caused stations to appear to remain logged in for hours
678 when the conference was idle.
679
680 Revision 1.7 2002/09/02 15:20:35 wb6ymh
681 Changed "logged in for " time in logs to short (d/hh:mm:ss) format.
682
683 Revision 1.6 2002/09/01 00:12:32 wb6ymh
684 1. Added new administrator commands .admin, .admins, .record, .list, .mute,
685 .kick, .ban, and .info.
686 2. Added new user commands .list, .play, .stats and .debug.
687 3. Modified the station list so that new stations enter at the top of the list.
688 4. Modified the station list so the station talking is always displayed.
689 5. Added total number of connected stations to top of station list.
690 6. Added code to send a warning message to stations that double.
691 7. Modified all log entries to use %m for error message display when available.
692 8. Added amount to time client was connected to log entries.
693 9. Added code to track the number of open sockets.
694 10. Modified GetCall() to compress mutiple spaces between callsign and user's
695 name to a single space.
696 11. Move transmit indicator from to left hand side of station list.
697 12. Added (optional) welcome message at bottom of station list.
698
699 Revision 1.5 2002/08/18 16:42:06 wb6ymh
700 Automatically exit lurking mode when a lurker talks.
701 Corrected a bug that allowed two stations to talk at once when they doubled.
702
703 Revision 1.4 2002/08/15 05:36:51 wb6ymh
704 Fix bug that allows timed out talker to keep transmit token forever.
705 Added version command.
706 Added "?" command (alias for help command).
707
708 Revision 1.3 2002/08/12 17:08:35 wb6ymh
709 Removed some left over debug info from log.
710 Removed \r from end of commands before logging them.
711 Added abilty to record and playback audio from files.
712 Added .help and .test commands.
713
714 Revision 1.2 2002/08/10 20:52:06 wb6ymh
715 disable dumping of avl trees
716
717 Revision 1.1.1.1 2002/08/10 20:33:41 wb6ymh
718 initial import
719
720 */
721
722 #include "common.h"
723
724 #ifndef _WIN32
725 // FreeBSD, Linux, etc..
726 #include <stdio.h>
727 #include <stdlib.h>
728 #include <sys/types.h>
729 #ifdef TIME_WITH_SYS_TIME
730 #include <sys/time.h>
731 #include <time.h>
732 #else
733 #ifdef HAVE_SYS_TIME_H
734 #include <sys/time.h>
735 #else
736 #include <time.h>
737 #endif
738 #endif
739 #include <unistd.h>
740 #include <sys/socket.h>
741 #include <netinet/in.h>
742 #include <arpa/inet.h>
743 #include <errno.h>
744 #include <string.h>
745 #ifdef HAVE_FCNTL_H
746 #include <fcntl.h>
747 #endif
748 #include <netdb.h>
749 #include <ctype.h>
750 #ifdef HAVE_SYS_TIMEB_H
751 #include <sys/timeb.h>
752 #endif
753 #include <sys/stat.h>
754 #include <stdarg.h>
755 #else
756 // Windoze
757 #include <stdio.h>
758 #include <time.h>
759 #include <io.h>
760 #include <winsock2.h>
761 #include <sys\timeb.h>
762 #include <ctype.h>
763 #include <sys\stat.h>
764 #endif
765
766 // Use a non-case sensitive compare function if one is available
767 #ifdef HAVE_STRICMP
768 #define STRCMP stricmp
769 #else
770 #ifdef HAVE_STRCASECMP
771 #define STRCMP strcasecmp
772 #else
773 #define STRCMP strcmp
774 #endif
775 #endif
776
777 #include "avl.h"
778 #include "main.h"
779 #include "configvars.h"
780 #include "dirclient.h"
781 #include "rtp.h"
782 #include "users.h"
783 #include "conference.h"
784 #include "ilink.h"
785 #include "sf.h"
786 #include "hostfile.h"
787 #include <zlib.h>
788 #include "tbd.h"
789
790 #if defined _WIN32GUI || defined _X11GUI
791 #include "tbdthread_common.h"
792 #endif
793
794 #include "eventhook.h"
795
796 #ifdef LINK_BOX
797 #include "linkbox.h"
798 #define TBDCMD "tlbcmd"
799 #define TBDCHAT "tlbchat"
800 #else
801 #define EndPointInit() 0
802 #define EndPoint(x,y,z)
803 #define TBDCMD "tbdcmd"
804 #define TBDCHAT "tbdchat"
805 #endif
806
807 #ifdef USE_DMALLOC
808 #include "dmalloc.h"
809 #endif
810
811 #define HEADER_OVERHEAD 28 // 20 byte IP header + 8 byte UDP header
812 #define INFO_EXTENSION ".info"
813
814 #define FDUPLEX_TEXT "\003dpx1"
815
816 /* Macros for min/max. */
817 #ifndef MIN
818 #define MIN(a,b) (((a)<(b))?(a):(b))
819 #endif
820
821 #ifndef MAX
822 #define MAX(a,b) (((a)>(b))?(a):(b))
823 #endif
824
825
826 typedef struct Bulletin_TAG {
827 struct Bulletin_TAG *Link;
828 char *Filename;
829 char *Description;
830 char *RunTime;
831 } Bulletin;
832
833 Bulletin *BulletinList;
834 Bulletin *BulletinListTail;
835
836 ConfClient *ClientTalking = NULL;
837 ConfClient *LastTalker = NULL;
838
839 const char *CallSignString = "CALLSIGN";
840
841 char StatusMsg[512];
842 int StatusMsgLen = 0;
843
844 time_t NextLoginTime;
845 time_t NextStationListTime;
846 time_t NextAVRSTime;
847 time_t LasttAprsIsTime;
848
849 int ConferenceClients;
850 int PeakClients;
851 time_t PeakClientTime;
852 int ClientConnects;
853 int EchoLinkIPCompareFailures;
854 int EchoAuthenticationFailures;
855 int AuthenticationFailures;
856
857 ConfClient *StationList;
858
859 int BadRTCPPacketCount = 0;
860 int bLurkDisabled = FALSE;
861 ConfServer *piLinkConf = NULL;
862
863 int Rcode;
864 int LastRcode;
865 int ZeroSSRC;
866 char *LastCmdText;
867 char *LastCmd;
868 char ConnectedStatus[MAX_QTH_LEN+1];
869
870 int bConferenceBusy = FALSE;
871
872 // overall statistics of all conferences
873 int TxBytesAllConf = 0;
874 int TxMBytesAllConf = 0;
875 int RxBytesAllConf = 0;
876 int RxMBytesAllConf = 0;
877
878 char *StationDisconnected = NULL;
879
880
881 ProtoData gProtoData[NUM_PROTOCOLS];
882
883 ConfClient CmdLineCC;
884 ClientInfo *CmdClient = NULL;
885 ConfClient ChatCC;
886 ClientInfo *ChatClient = NULL;
887 int bCmdLineChatMode;
888 char *CmdArg;
889 int bLogCmd;
890
891 struct avl_table *Conferences; // sorted by Port number
892
893 void ConvertRTP2iLink(ProtoData *pPDat,char *inBuf,int inLen,int bNewStream);
894 void ConvertiLink2RTP(ProtoData *pPDat,char *inBuf,int inLen,int bNewStream);
895 void ConvertSF2iLink(ProtoData *pPDat,char *inBuf,int inLen,int bNewStream);
896 void ConvertiLink2SF(ProtoData *pPDat,char *inBuf,int inLen,int bNewStream);
897 void ConvertiLink2Ast(ProtoData *pPDat,char *inBuf,int inLen,int bNewStream);
898 void OpenAudioFile(ConfClient *pCC,char *Filename, char *Mode);
899 void StartPlayback(ClientInfo *p,ConfClient *pCC);
900 int FileRecord(ClientInfo *p,ConfClient *pCC,EventType Event);
901 int DuplicateTextMsg(ClientInfo *p,ConfClient *pCC);
902
903 typedef void (*ConversionF)(ProtoData *pPDat,char *inBuf,int inLen,int bNewStream);
904
905 ConversionF ProtoConvert[NUM_PROTOCOLS][NUM_PROTOCOLS] =
906 {
907 {NULL, // From Speak Freely to Speak Freely
908 NULL, // From Speak Freely to RTP
909 ConvertSF2iLink}, // From Speak Freely to ILink
910
911 {NULL, // From RTP to Speak Freely
912 NULL, // From RTP to RTP
913 ConvertRTP2iLink},// From RTP to ILink
914
915 {ConvertiLink2SF, // From iLink to SF
916 ConvertiLink2RTP, // From ilink to RTP
917 NULL}, // From iLink to iLink
918 };
919
920 #define DUP_TRACKING_TIMEOUT 10 // 10 seconds
921 #define MAX_DUP_TRACKING 32
922 typedef struct {
923 unsigned short crc;
924 time_t TimeStamp;
925 int bLogged:1;
926 char FirstFrom[MAX_CALL_LEN+1];
927 } DupTrackingEntry;
928
929 DupTrackingEntry DupTracking[MAX_DUP_TRACKING];
930
931 int ConfCompare(const void *avl_a,const void *avl_b,void *avl_param);
932 int CClientCompare(const void *avl_a,const void *avl_b,void *avl_param);
933 ConfClient *CreateNewConfClient(void);
934 void RemoveFromStationList(ConfClient *pCC);
935 void DeleteCClient(ConfClient *pCC);
936 char CheckRTCP(ConfServer *pCS);
937 int GetCall(ConfServer *pCS,ConfClient *pCC,rtcp_t *p,int len);
938 char *GetLocalIPAdr(void);
939 int GenBye(ConfClient *pCC,char *Temp,char *Reason);
940 void SendStationList(ConfServer *pCS);
941 void SetTimeoutRTCP(ClientInfo *p);
942 void ConferenceCmd(ClientInfo *p,ConfClient *pCC,char *cmd);
943 int TimeLapse(struct timeval *p);
944 void CmdHelp(ClientInfo *p,ConfClient *pCC1,char *Arg);
945 int FilePlayBack(ClientInfo *p);
946 void SendBuf2(ClientInfo *p,ConfClient *pCC,int bControLPort);
947 void CalcBW(ConfServer *pCS,int bClear);
948 void CmdVersion(ClientInfo *p,ConfClient *pCC1,char *Arg);
949 void SendToAllSysops(ClientInfo *p,ConfServer *pCS);
950 void SendSDES(ConfServer *pCS,ConfClient *pCC);
951 void ConvertProtocol(ProtoData *pProtoDat,Protocol InputProto,
952 Protocol OutputProto,int bNewStream);
953 PacketType GetPacketType(ClientInfo *p,ConfClient *pCC);
954 int FromUs(ClientInfo *p,ConfClient *pCC);
955 int AuthorizedClient(ClientInfo *p,ConfClient *pCC);
956 void SaveBadPacket(ClientInfo *p,char *Filename);
957 void SendFirewallOpenRequest(ConfServer *pCS, ConfClient *pCC);
958 char *FirstChatChar(char *Buf);
959 void SendChatEvent(char *Type,char *Buf);
960
CreateServerClient(ConfServer * pCS,int Port,int bLocal,ClientInfo ** pRet)961 int CreateServerClient(ConfServer *pCS,int Port,int bLocal,ClientInfo **pRet)
962 {
963 IPAdrUnion MyAdr;
964 ClientInfo *pClient;
965 int On = 1;
966 int Err = 0; // assume the best
967 int bEchoLinkPort = (Port == ILINK_RTP_PORT || Port == ILINK_RTCP_PORT);
968
969 for( ; ; ) {
970 pClient = CreateNewClient();
971 if(pClient == NULL) {
972 LOG_ERROR(("CreateServerClient(): CreateNewClient failed.\n"));
973 Err = ERR_MALLOC;
974 break;
975 }
976
977 memset(&MyAdr,0,sizeof(MyAdr));
978 if(bLocal) {
979 // listen to localhost port only
980 MyAdr.ADDR = inet_addr("127.0.0.1");
981 }
982 else if(Bind2IP != NULL && (SFBind2IP == NULL || bEchoLinkPort)) {
983 MyAdr.ADDR = inet_addr(Bind2IP);
984 if(MyAdr.ADDR == INADDR_NONE) {
985 LOG_ERROR(("CreateServerClient(): failed to convert \"%s\" to IP "
986 "address.\n",Bind2IP));
987 Err = ERR_BIND_IP;
988 break;
989 }
990 else {
991 LOG_ERROR(("Port %d bound to %s.\n",Port,Bind2IP));
992 }
993 }
994 else if(SFBind2IP != NULL && !bEchoLinkPort) {
995 MyAdr.ADDR = inet_addr(SFBind2IP);
996 if(MyAdr.ADDR == INADDR_NONE) {
997 LOG_ERROR(("CreateServerClient(): failed to convert \"%s\" to IP "
998 "address.\n",Bind2IP));
999 Err = ERR_BIND_IP;
1000 break;
1001 }
1002 else {
1003 LOG_ERROR(("Binding port %d to %s.\n",Port,SFBind2IP));
1004 }
1005 }
1006 else {
1007 MyAdr.ADDR = INADDR_ANY;
1008 }
1009
1010 SET_WAIT4_RD(pClient);
1011 pClient->p = pCS;
1012 pClient->BufSize = CONF_BUF_SIZE;
1013 pClient->Buf = malloc(pClient->BufSize);
1014
1015 pClient->Socket = socket(AF_INET,SOCK_DGRAM,0);
1016 if(pClient->Socket == SOCKET_ERROR) {
1017 Err = ERROR_CODE;
1018 LOG_ERROR(("CreateServerClient(): Socket() failed, %s",
1019 Err2String(Err)));
1020 break;
1021 }
1022
1023 OpenSockets++;
1024 MyAdr.i.sin_family = AF_INET;
1025 MyAdr.PORT = htons((unsigned short) Port);
1026 pClient->HisAdr = MyAdr;
1027 avl_insert(ClientTree,pClient);
1028 pClient->bInClientTree = TRUE;
1029
1030 if(setsockopt(pClient->Socket,SOL_SOCKET,SO_REUSEADDR,(char *) &On,
1031 sizeof(On)) == -1)
1032 {
1033 Err = ERROR_CODE;
1034 LOG_ERROR(("CreateServerClient(): setsockopt() failed %s",
1035 Err2String(ERROR_CODE)));
1036 break;
1037 }
1038
1039 if(bind(pClient->Socket,&MyAdr.s,sizeof(MyAdr)) == SOCKET_ERROR) {
1040 Err = ERROR_CODE;
1041 LOG_ERROR(("CreateServerClient(): bind() failed for %s:%d %s",
1042 inet_ntoa(MyAdr.i.sin_addr),Port,Err2String(ERROR_CODE)));
1043 }
1044 break;
1045 }
1046
1047 if(Err == 0) {
1048 *pRet = pClient;
1049 }
1050 else if(pClient != NULL) {
1051 DeleteClient(pClient);
1052 }
1053
1054 return Err;
1055 }
1056
1057
DestroyCC(void * avl_item,void * avl_param)1058 void DestroyCC(void *avl_item, void *avl_param)
1059 {
1060 ConfClient *pCC = (ConfClient *) avl_item;
1061
1062 DeleteCClient(pCC);
1063 }
1064
DeleteConf(ConfServer * pCS)1065 void DeleteConf(ConfServer *pCS)
1066 {
1067 LOG_NORM(("Deleting conference for port %d.\n",pCS->AudioPort));
1068 if(avl_delete(Conferences,pCS) == NULL) {
1069 LOG_ERROR(("DeleteConf: avl_delete() failed to find client.\n"));
1070 }
1071
1072 if(pCS->pAudio != NULL) {
1073 pCS->pAudio->p = NULL;
1074 DeleteClient(pCS->pAudio);
1075 }
1076
1077 if(pCS->pControl != NULL) {
1078 pCS->pControl->p = NULL;
1079 DeleteClient(pCS->pControl);
1080 }
1081
1082 if(pCS->fp != NULL) {
1083 fclose(pCS->fp);
1084 }
1085
1086 if(!pCS->bSlaveConf && pCS->ConfTree != NULL) {
1087 avl_destroy(pCS->ConfTree,NULL);
1088 }
1089
1090 free(pCS);
1091 }
1092
CreateConference(int AudioPort,ConfServer * pMainConf,ConfServer ** pRet)1093 int CreateConference(
1094 int AudioPort,
1095 ConfServer *pMainConf,
1096 ConfServer **pRet)
1097 {
1098 ConfServer *pCS;
1099 int Err = 0; // assume good things
1100
1101 for(; ; ) {
1102 if((pCS = (ConfServer *) malloc(sizeof(ConfServer))) == NULL) {
1103 LOG_ERROR(("CreateConference(): malloc failed.\n"));
1104 Err = ERR_MALLOC;
1105 break;
1106 }
1107 memset(pCS,0,sizeof(ConfServer));
1108 avl_insert(Conferences,pCS);
1109 pCS->AudioPort = AudioPort;
1110
1111 if(pMainConf == NULL) {
1112 pCS->ConfTree = avl_create(CClientCompare,pCS,NULL);
1113 pCS->pLastAudioIn = &pCS->LastAudioIn;
1114 pCS->pMasterCS = pCS;
1115 pCS->CompressionType = CompressionType;
1116 }
1117 else {
1118 pCS->bSlaveConf = TRUE;
1119 pCS->pMasterCS = pMainConf;
1120 pCS->ConfTree = pMainConf->ConfTree;
1121 pCS->pLastAudioIn = &pMainConf->LastAudioIn;
1122 pCS->CompressionType = pMainConf->CompressionType;
1123 }
1124
1125 if(pCS->ConfTree == NULL) {
1126 LOG_ERROR(("CreateConference(): avl_create failed.\n"));
1127 Err = ERR_AVL_CREATE;
1128 break;
1129 }
1130
1131 if(bEchoLinkEnabled || AudioPort != ILINK_RTP_PORT) {
1132 // Don't open ILINK_RTP_PORT unless EchoLink mode is enabled
1133 if((Err = CreateServerClient(pCS,AudioPort,FALSE,&pCS->pAudio)) != 0) {
1134 break;
1135 }
1136
1137 Err = CreateServerClient(pCS,AudioPort+1,FALSE,&pCS->pControl);
1138 }
1139 break;
1140 }
1141
1142 if(Err == 0) {
1143 *pRet = pCS;
1144 if(AudioPort == ILINK_RTP_PORT) {
1145 pCS->biLinkConf = TRUE;
1146 }
1147 }
1148 else if(pCS != NULL) {
1149 DeleteConf(pCS);
1150 }
1151
1152 return Err;
1153 }
1154
1155
1156 // Rtp_port = 0 // disable
1157 // Rtp_port = <evenport>
1158 // Rtp_port = <evenport>-<oddport> for a range
RtpInit()1159 int RtpInit()
1160 {
1161 int Ret = ERR_CONFIG_FILE; // assume the worse
1162 char *cp;
1163 int FirstPort;
1164 int LastPort;
1165 int i;
1166 int Err;
1167 ConfServer *pCS;
1168 ConfServer ConfLookup;
1169
1170 do {
1171 if((cp = strchr(RTP_Port,'-')) != NULL) {
1172 // Range
1173 *cp++ = 0;
1174 }
1175
1176 if(sscanf(RTP_Port,"%d",&FirstPort) != 1) {
1177 LOG_ERROR(("Error: Invalid RTP_Port \"%s\"\n",RTP_Port));
1178 break;
1179 }
1180 else if(FirstPort & 1) {
1181 LOG_ERROR(("Error: First RTP port must be even\n"));
1182 break;
1183 }
1184
1185 if(cp == NULL) {
1186 // Single port
1187 LastPort = FirstPort + 1;
1188 }
1189 else {
1190 // Range
1191 if(sscanf(cp,"%d",&LastPort) != 1) {
1192 *cp = '-';
1193 LOG_ERROR(("Error: Invalid RTP_Port \"%s\"\n",RTP_Port));
1194 break;
1195 }
1196 else if(!(LastPort & 1)) {
1197 LOG_ERROR(("Error: Last RTP port must be odd\n"));
1198 break;
1199 }
1200 else if(LastPort <= FirstPort) {
1201 LOG_ERROR(("Error: The last port must larger the first port\n"));
1202 break;
1203 }
1204 }
1205
1206 for(i = FirstPort; i < LastPort; i += 2) {
1207 ConfLookup.AudioPort = i;
1208
1209 if((pCS = (ConfServer *) avl_find(Conferences,&ConfLookup)) == NULL) {
1210 // need to create the conference
1211 if(LinkConferences) {
1212 Err = CreateConference(i,piLinkConf,&pCS);
1213 }
1214 else {
1215 Err = CreateConference(i,NULL,&pCS);
1216 }
1217
1218 if(Err != 0) {
1219 LOG_ERROR(("Error: Couldn't open port (%d)\r",i));
1220 break;
1221 }
1222 else {
1223 LOG_ERROR(("Created RTP conference for port %d\n",i));
1224 pCS->bRTPConf = TRUE;
1225 pCS->pAudio->State = RTP_Handler;
1226 pCS->pControl->State = RTCP_Handler;
1227 pCS->TimeNextSDES = TimeNow.tv_sec + SDES_Interval;
1228
1229 if(piLinkConf->pAudio == NULL) {
1230 piLinkConf->pAudio = pCS->pAudio;
1231 piLinkConf->pControl= pCS->pControl;
1232 }
1233 }
1234 }
1235
1236 Ret = 0;
1237 }
1238 } while(FALSE);
1239
1240 return Ret;
1241 }
1242
1243 // Init iLink/EchoLink conference
ConferenceInit()1244 int ConferenceInit()
1245 {
1246 time_t Time = time(NULL);
1247 int Err = 0; // Assume good things
1248 ClientInfo *pClient;
1249
1250 do {
1251 Conferences = avl_create(ConfCompare,NULL,NULL);
1252
1253 Err = CreateConference(ILINK_RTP_PORT,NULL,&piLinkConf);
1254
1255 if(Err != 0 || piLinkConf == NULL) {
1256 break;
1257 }
1258
1259 if(RTP_Port != NULL) {
1260 Err = RtpInit();
1261 }
1262
1263 #ifdef LINK_BOX
1264 if((Err = EndPointInit()) != 0) {
1265 break;
1266 }
1267 #endif
1268
1269 if(CmdPort != 0) {
1270 if((Err = CreateServerClient(piLinkConf,CmdPort,TRUE,&pClient)) != 0) {
1271 break;
1272 }
1273 CmdClient = pClient;
1274 pClient->State = CmdLine_Handler;
1275 CmdLineCC.pCS = piLinkConf;
1276 CmdLineCC.bCmdLine = TRUE;
1277 CmdLineCC.bSysop = TRUE;
1278 CmdLineCC.bAdmin = TRUE;
1279 CmdLineCC.Callsign = TBDCMD;
1280 CmdLineCC.CallPlus = CmdLineCC.Callsign;
1281 CmdLineCC.Proto = PROTO_ILINK;
1282 }
1283
1284 if(bEchoLinkEnabled) {
1285 // EchoLink enabled
1286 piLinkConf->pAudio->State = RTP_Handler;
1287 piLinkConf->pControl->State = RTCP_Handler;
1288 if(LoginInterval > 0) {
1289 // EchoLink directory server access enabled
1290 NextLoginTime = Time + 60; // wait a minute before the second login
1291 if(StationListInterval > 0) {
1292 NextStationListTime = Time + StationListInterval;
1293 }
1294 else {
1295 NextStationListTime = 0;
1296 }
1297 if(AvrsEnable) {
1298 NextAVRSTime = Time;
1299 }
1300 piLinkConf->TimeNextSDES = TimeNow.tv_sec + SDES_Interval;
1301 if(StationListInterval > 0) {
1302 // Get initial station list
1303 ServerRequest(SERV_REQ_LOGIN_AND_LIST,0,NULL);
1304 }
1305 else {
1306 // Do initial login
1307 ServerRequest(SERV_REQ_LOGIN,0,NULL);
1308 }
1309 SetTimeoutRTCP(piLinkConf->pControl);
1310 }
1311
1312 if(ChatPort!= 0) {
1313 if((Err = CreateServerClient(piLinkConf,ChatPort,TRUE,&pClient)) != 0) {
1314 break;
1315 }
1316 ChatClient = pClient;
1317 pClient->State = CmdLine_Handler;
1318 ChatCC.pCS = piLinkConf;
1319 ChatCC.bCmdLine = TRUE;
1320 ChatCC.bSysop = TRUE;
1321 ChatCC.bAdmin = TRUE;
1322 ChatCC.Callsign = TBDCHAT;
1323 ChatCC.CallPlus = ChatCC.Callsign;
1324 ChatCC.Proto = PROTO_ILINK;
1325 }
1326 }
1327
1328 if(LoginInterval == 0) {
1329 // We'll never login, set a phoney NodeID so SF will run
1330 OurNodeID = crc32(0,(Bytef *) ConferenceCall,strlen(ConferenceCall));
1331 NextLoginTime = 0;
1332 NextStationListTime = 0;
1333 }
1334
1335 } while(FALSE);
1336
1337 return Err;
1338 }
1339
1340
1341 // Init Speakfreely conference
SFConfInit()1342 int SFConfInit()
1343 {
1344 ConfServer *pCS;
1345 int Err = 0; // assume the best
1346
1347 if(LinkConferences) {
1348 Err = CreateConference(SF_Port,piLinkConf,&pCS);
1349 }
1350 else {
1351 Err = CreateConference(SF_Port,NULL,&pCS);
1352 }
1353
1354 if(Err == 0) {
1355 pCS->AudioPort = SF_ReplyPort;
1356 pCS->bSFConf = TRUE;
1357
1358 pCS->TimeNextSDES = TimeNow.tv_sec + SDES_Interval;
1359
1360 pCS->pAudio->State = RTP_Handler;
1361 pCS->pControl->State = RTCP_Handler;
1362 }
1363 return Err;
1364 }
1365
SendSDES2All(ClientInfo * p,ConfServer * pCS)1366 void SendSDES2All(ClientInfo *p,ConfServer *pCS)
1367 {
1368 ConfClient *pCC = NULL;
1369 struct avl_traverser avl_trans;
1370 char *pSDES;
1371 int Len;
1372 int i;
1373 int bNeedPass[NUM_PROTOCOLS+1];
1374 int Protocol = PROTO_SF;
1375 int bTxtExtension = FALSE;
1376
1377 memset(bNeedPass,0,sizeof(bNeedPass));
1378
1379 if(pCS->biLinkConf) {
1380 bNeedPass[PROTO_ILINK] = TRUE;
1381 }
1382 else if(pCS->bSFConf) {
1383 bNeedPass[PROTO_SF] = TRUE;
1384 }
1385 else if(pCS->bRTPConf) {
1386 bNeedPass[PROTO_RTP] = TRUE;
1387 }
1388
1389 // Loop thru clients N+1 times, once for each protocol
1390 // plus once for old versions of thebridge that used the private "txt"
1391 // extension to pass the callsign of the station talking
1392
1393 for(i = 0; i < NUM_PROTOCOLS+1; i++, Protocol++) {
1394 if(!bNeedPass[i]) {
1395 // Don't need this pass, skip it
1396 continue;
1397 }
1398
1399 if(i == NUM_PROTOCOLS) {
1400 // Ilink protocols /w private "txt" extension used by
1401 // old versions of thebridge
1402 Protocol = PROTO_ILINK;
1403 bTxtExtension = TRUE;
1404 }
1405
1406 GenSDES(Protocol+1,bTxtExtension);
1407
1408 pSDES = gProtoData[Protocol].OurSDES;
1409 Len = gProtoData[Protocol].SDESLen;
1410
1411 if(Len > 0) {
1412 pCC = (ConfClient *) avl_t_first(&avl_trans,pCS->ConfTree);
1413 }
1414
1415 while(pCC != NULL) {
1416 if(!pCC->bKicked && pCC->Proto == Protocol) {
1417 if(Protocol == PROTO_ILINK) {
1418 if(i == PROTO_ILINK) {
1419 if(pCC->bTxtExtension) {
1420 // Client needs txt extension, send in later pass
1421 bNeedPass[NUM_PROTOCOLS] = TRUE;
1422 }
1423 else {
1424 // send SDES w/o txt extension
1425 Send2(pCC,pSDES,Len,TRUE);
1426 }
1427 }
1428 else if(pCC->bTxtExtension) {
1429 // send SDES w txt extension
1430 Send2(pCC,pSDES,Len,TRUE);
1431 }
1432 }
1433 else {
1434 // send Speak freely / RTP SDES
1435 Send2(pCC,pSDES,Len,TRUE);
1436 }
1437 }
1438 pCC = (ConfClient *) avl_t_next(&avl_trans);
1439 }
1440 }
1441 pCS->TimeNextSDES = TimeNow.tv_sec + SDES_Interval;
1442 }
1443
DisconnectCC(ClientInfo * p,ConfClient * pCC,char * Reason)1444 void DisconnectCC(ClientInfo *p,ConfClient *pCC,char *Reason)
1445 {
1446 p->Count = GenBye(pCC,p->Buf,Reason);
1447 pCC->bKicked = TRUE;
1448 pCC->bSWL = TRUE;
1449 pCC->bPermanent = FALSE;
1450 pCC->bInConf = FALSE;
1451 pCC->bDisconnected = TRUE;
1452 if(pCC->bConnected) {
1453 pCC->bConnected = FALSE;
1454 ConferenceClients--;
1455 }
1456 SendBuf2(p,pCC,TRUE);
1457 }
1458
FwdTextCmdLine(ClientInfo * p,char * Buf,ConfClient * pCC)1459 void FwdTextCmdLine(ClientInfo *p,char *Buf,ConfClient *pCC)
1460 {
1461 char *cp;
1462 char TempBuf[CONF_BUF_SIZE];
1463 int Count;
1464
1465 if(pCC->HisAdr.PORT != 0) {
1466 // Strip Text header from message and add TBD_CHAT_TEXT result code
1467 // to the beginning of the packet
1468
1469 Count = snprintf(TempBuf,sizeof(TempBuf),"%d\r%s",TBD_CHAT_TEXT,
1470 &Buf[sizeof(NDATA)]);
1471
1472 if(Count == -1 || Count >= sizeof(TempBuf)) {
1473 // Pre-C99 style snprintf returns -1 on buffer overflow.
1474 // C99 style snprintf returns the number of characters that would
1475 // have been written on buffer overflow.
1476 // In either case the output was truncated and p->Count is bogus.
1477
1478 Count = sizeof(TempBuf) - 1;
1479 Buf[Count] = 0;
1480 }
1481
1482 // convert \r end of line convention into *nix standard \n
1483
1484 cp = TempBuf;
1485 while((cp = strchr(cp,'\r')) != NULL) {
1486 *cp = '\n';
1487 }
1488
1489 sendto(p->Socket,TempBuf,Count+1,0,&pCC->HisAdr.s,sizeof(IPAdrUnion));
1490 }
1491 }
1492
RTP_Data(ClientInfo * p,ConfServer * pCS,ConfClient * pLookup)1493 void RTP_Data(ClientInfo *p,ConfServer *pCS,ConfClient *pLookup)
1494 {
1495 ConfClient *pCC = NULL;
1496 ConfClient *pSource = NULL;
1497 ConfClient *pNextCC = NULL;
1498 struct avl_traverser avl_trans;
1499 char *cp;
1500 char *cp1;
1501 rtp_hdr_t *pRTP = (rtp_hdr_t *) p->Buf;
1502 int bConfSrc = FALSE;
1503 u_int32 OrigSSRC = 0;
1504 int bNewTalker = FALSE;
1505 int AudioDeltaT;
1506 int bFwdPacket = FALSE;
1507 int bFwdFullDpxOnly = FALSE;
1508 int i,j;
1509 int Count;
1510 char *Buf;
1511 PacketType Type;
1512 int bHaveiLinkData = FALSE;
1513 Protocol InputProto;
1514 int RxLen = p->Count;
1515 int bUserChat = TRUE;
1516 int bSysopChat = TRUE;
1517 u_int8 CompressionType;
1518 ConfServer *pMasterCS = pCS->pMasterCS;
1519
1520 if(pLookup->bFilePlayer || pLookup->bCmdLine) {
1521 pSource = pCC = pLookup;
1522 }
1523 else if((pSource = pCC = avl_find(pCS->ConfTree,pLookup)) == NULL) {
1524 LOG_NORM(("RTP_Data: Failed to find client %s\n",
1525 inet_ntoa(pLookup->HisAdr.i.sin_addr)));
1526 }
1527
1528 if(pCC != NULL) {
1529 pCC->RxBytes += RxLen + HEADER_OVERHEAD;
1530 InputProto = pCC->Proto;
1531 CompressionType = pCC->CompressionType;
1532
1533 for(i = 0; i < NUM_PROTOCOLS; i++) {
1534 gProtoData[i].bDataValid = FALSE;
1535 for(j = 0; j < MAX_CONV_PACKETS; j++) {
1536 gProtoData[i].DataLen[j] = 0;
1537 }
1538 }
1539
1540 if(InputProto == PROTO_ILINK) {
1541 bHaveiLinkData = TRUE;
1542 }
1543
1544 gProtoData[InputProto].Data[0] = p->Buf;
1545 gProtoData[InputProto].DataLen[0] = RxLen;
1546 gProtoData[InputProto].bDataValid = TRUE;
1547 gProtoData[InputProto].Type = Type = GetPacketType(p,pCC);
1548 bConfSrc = pCC->bConf;
1549
1550 if(pCC->bSWL || Type == PKT_TYPE_IGNORE) {
1551 // Ignore packet, client is an SWL or the packet is an unknown or
1552 // unhandled type
1553 }
1554 else if(pCC->State != NULL) {
1555 pCC->State(p,pCC,EVENT_RTP_RX);
1556 }
1557 else if(Type == PKT_TYPE_CMD && InputProto == PROTO_ILINK) {
1558 // Command
1559 if(STRCMP(p->Buf,"//ver.") == 0) {
1560 // Version request
1561 p->Count = 0; // init for BufPrintf
1562 BufPrintf(p,"%c" NDATA"%s ",ILINK_DATA_PACKET,ConferenceCall);
1563 CmdVersion(p,pCC,NULL);
1564 p->Count++; // include terminating null
1565 Send2(pCC,p->Buf,p->Count,FALSE);
1566 }
1567 else if(ConfCmdEnable) {
1568 cp = &p->Buf[strlen(NDATA)+1]; // Point to first character of data
1569 if((cp1 = strchr(cp,'>')) != NULL) {
1570 if((cp = strchr(&cp1[2],'\r')) != NULL) {
1571 *cp = 0;
1572 }
1573 ConferenceCmd(p,pCC,&cp1[2]);
1574 // Don't forward commands to other conference users
1575 bFwdPacket = FALSE;
1576 }
1577 }
1578 }
1579 else if(Type == PKT_TYPE_DATA) {
1580 if(!bHaveiLinkData) {
1581 ConvertProtocol(gProtoData,InputProto,PROTO_ILINK,bNewTalker);
1582 if(gProtoData[PROTO_ILINK].DataLen[0] > 0) {
1583 bHaveiLinkData = TRUE;
1584 }
1585 }
1586
1587 if(bHaveiLinkData && !pCC->bMuteChat) {
1588 bFwdPacket = TRUE;
1589 if(pCC->bSysop) {
1590 char *Temp = FirstChatChar(gProtoData[PROTO_ILINK].Data[0]);
1591 if(Temp != NULL) {
1592 if(*Temp == ';') {
1593 // First character is a ';' for forward this message to
1594 // Admins and sysop's only
1595 bUserChat = FALSE;
1596 }
1597 else if(*Temp == ',') {
1598 // First character is a ',' for forward this message to
1599 // Admins only
1600 bUserChat = FALSE;
1601 bSysopChat = FALSE;
1602 }
1603 }
1604 }
1605
1606 Buf = gProtoData[PROTO_ILINK].Data[0];
1607 SendChatEvent(pCC->bCmdLine ? "sent_chat" : "chat",Buf);
1608 if(!pCC->bCmdLine) {
1609 if(bCmdLineChatMode) {
1610 FwdTextCmdLine(p,Buf,&CmdLineCC);
1611 }
1612 if(ChatPort != 0) {
1613 FwdTextCmdLine(p,Buf,&ChatCC);
1614 }
1615 }
1616 }
1617 }
1618 else if(Type == PKT_TYPE_AUDIO && !pCC->bMuted && !FromUs(p,pCC)) {
1619 // Audio packet, not originally from us and client is not muted
1620 pCC->LastAudioIn = TimeNow;
1621 if(!pCC->bTalking) {
1622 pCC->FirstAudioIn = TimeNow;
1623 pCC->bTalking = TRUE;
1624 }
1625
1626 if(ClientTalking == NULL && !pCC->bTimedOut && pCC->bInConf) {
1627 // A new station is trying to talk
1628
1629 if(pCC->bSysop || pCC == LastTalker) {
1630 // Ok to talk now.
1631 bNewTalker = TRUE;
1632 }
1633 else {
1634 // First apply the minimum pause time
1635 AudioDeltaT = TimeLapse(pCS->pLastAudioIn);
1636 if(PauseTime == 0 || AudioDeltaT >= PauseTime) {
1637 // Ok to talk now.
1638 bNewTalker = TRUE;
1639 }
1640 else if(InputProto == PROTO_ILINK && !pCC->bPauseWarningSent &&
1641 !pCC->bConf)
1642 {
1643 pCC->bPauseWarningSent = TRUE;
1644 DPRINTF(("RTP_Rx(): Sending pause warning\n"));
1645 p->Count = 0;
1646 BufPrintf(p,"%c" NDATA "%s>%s please pause for at least %d.%d"
1647 " seconds between transmissions!\r",
1648 ILINK_DATA_PACKET,ConferenceCall,pCC->Callsign,
1649 PauseTime/1000,PauseTime % 1000);
1650 p->Count++; // include terminating null
1651 SendBuf2(p,pCC,FALSE);
1652 }
1653
1654 // Now apply the belch filter
1655 if(BelchTime > 0 && bNewTalker && pCC->bBelcher) {
1656 // A potential belcher, see if he's talked long
1657 // enough that it's not a belch.
1658 AudioDeltaT = TimeLapse(&pCC->FirstAudioIn);
1659 if(AudioDeltaT < BelchTime) {
1660 // Not yet your grossness
1661 bNewTalker = FALSE;
1662 }
1663 }
1664 }
1665
1666 if(bNewTalker) {
1667 // A new station is talking
1668 CalcBW(pCS,TRUE);
1669 SetTimeout(pCS->pAudio,ConfAudioTimeout);
1670 LastTalker = ClientTalking = pCC;
1671 pCS->bSendStationList = TRUE;
1672 pCC->bLurking = FALSE;
1673
1674 // Move him to the top of the station list
1675 RemoveFromStationList(pCC);
1676 pCC->Link = StationList;
1677 StationList = pCC;
1678
1679 if(pCS->bLinkedConfs) {
1680 // We have linked conferences listening, send them an
1681 // SDES before the audio packet so they can tell who's talking
1682 SendSDES2All(p,pCS);
1683 }
1684 }
1685 }
1686
1687 if(pCC == ClientTalking ||
1688 (FullDuplex && (pCC->bFullDuplex || pCC->Proto == PROTO_RTP) &&
1689 pCC->bInConf))
1690 {
1691 *pCS->pLastAudioIn = TimeNow;
1692 bFwdPacket = TRUE;
1693 if(pCC != ClientTalking) {
1694 // Not the station with the talk token, only forward his packets
1695 // to other full duplex stations
1696 bFwdFullDpxOnly = TRUE;
1697 }
1698 if(BlabOffTimer != 0) {
1699 if((TimeNow.tv_sec - pCC->FirstAudioIn.tv_sec) > BlabOffTimer) {
1700 // long winded so and so !
1701 bFwdPacket = FALSE;
1702 if(!pCC->bTimedOut) {
1703 p->Count = 0; // init for BufPrintf
1704 LOG_WARN(("%s timed out.\n",pCC->Callsign));
1705 BufPrintf(p,"%c" NDATA "%s>%s you have timed out!\r"
1706 "The timeout timer on this conference is set to "
1707 "%d seconds.\r",
1708 ILINK_DATA_PACKET,ConferenceCall,pCC->Callsign,
1709 BlabOffTimer);
1710 p->Count++; // include terminating null
1711 SendBuf2(p,pCC,FALSE);
1712 pCC->bTimedOut = TRUE;
1713 ClientTalking = NULL; // let someone else talk!
1714 pCS->bSendStationList = TRUE;
1715 }
1716 }
1717 }
1718 }
1719 else if(ClientTalking != NULL && !pCC->bDoublingWarningSent &&
1720 InputProto == PROTO_ILINK && !pCC->bConf &&
1721 !pCC->bFilePlayer && !pCC->bFullDuplex)
1722 { // Station is doubling !
1723 pCC->bDoublingWarningSent = TRUE;
1724 p->Count = 0; // init for BufPrintf
1725 BufPrintf(p,"%c" NDATA "%s>%s you are DOUBLING with %s!\r",
1726 ILINK_DATA_PACKET,ConferenceCall,pCC->Callsign,
1727 ClientTalking->Callsign);
1728 p->Count++; // include terminating null
1729 SendBuf2(p,pCC,FALSE);
1730 }
1731 }
1732
1733 if(bFwdPacket) {
1734 pNextCC = (ConfClient *) avl_t_first(&avl_trans,pCS->ConfTree);
1735 }
1736
1737 if(pNextCC != NULL && pMasterCS->bRecording && !bHaveiLinkData &&
1738 CompressionType == RTP_PT_GSM)
1739 {
1740 ConvertProtocol(gProtoData,InputProto,PROTO_ILINK,bNewTalker);
1741 if(gProtoData[PROTO_ILINK].DataLen[0] > 0) {
1742 bHaveiLinkData = TRUE;
1743 }
1744 }
1745
1746 if(pNextCC != NULL && pMasterCS->bRecording && bNewTalker &&
1747 bHaveiLinkData)
1748 {
1749 FilePacketHdr PktHdr;
1750
1751 // timestamp this entry, we have a new talker
1752 PktHdr.Flags = htonl(FILE_FLAG_TIME_STAMP);
1753 PktHdr.Len = htonl(TimeNow.tv_sec);
1754
1755 if(fwrite(&PktHdr,sizeof(PktHdr),1,pMasterCS->fp) != 1) {
1756 // Write error
1757 LOG_ERROR(("RTP_Rx(): fwrite failed, %s",Err2String(errno)));
1758 fclose(pMasterCS->fp);
1759 pMasterCS->fp = NULL;
1760 pMasterCS->bRecording = FALSE;
1761 }
1762 }
1763
1764 if(bFwdPacket && pMasterCS->bRecording && bHaveiLinkData) {
1765 FilePacketHdr PktHdr;
1766
1767 for(i = 0; i < MAX_CONV_PACKETS; i++) {
1768 if((Count = gProtoData[PROTO_ILINK].DataLen[i]) == 0) {
1769 break;
1770 }
1771 Buf = gProtoData[PROTO_ILINK].Data[i];
1772 Count = gProtoData[PROTO_ILINK].DataLen[i];
1773
1774 PktHdr.Len = htonl(Count);
1775 PktHdr.Flags = 0;
1776 if(Type == PKT_TYPE_DATA) {
1777 PktHdr.Flags |= FILE_FLAG_DATA_PKT;
1778 if(ClientTalking == NULL) {
1779 PktHdr.Flags |= FILE_FLAG_RATE_LIMIT;
1780 }
1781 }
1782 else {
1783 PktHdr.Flags |= FILE_FLAG_RATE_LIMIT;
1784 }
1785 PktHdr.Flags = htonl(PktHdr.Flags);
1786
1787 if(fwrite(&PktHdr,sizeof(PktHdr),1,pMasterCS->fp) != 1 ||
1788 fwrite(Buf,Count,1,pMasterCS->fp) != 1)
1789 { // Write error
1790 LOG_ERROR(("RTP_Rx(): fwrite failed, %s",Err2String(errno)));
1791 fclose(pMasterCS->fp);
1792 pMasterCS->fp = NULL;
1793 pMasterCS->bRecording = FALSE;
1794 }
1795 }
1796 }
1797
1798 if(!pCC->bCmdLine) {
1799 if(Type == PKT_TYPE_DATA) {
1800 if(!ConfTextEnable) {
1801 // Text conferencing isn't enabled, don't forward packet
1802 pNextCC = NULL;
1803 }
1804 }
1805 else if(!ConfEnable) {
1806 // Conferencing isn't enabled, only forward packets from the local client
1807 pNextCC = NULL;
1808 }
1809 }
1810
1811 // Forward packet to clients
1812 while((pCC = pNextCC) != NULL) {
1813 pNextCC = (ConfClient *) avl_t_next(&avl_trans);
1814 if(!pCC->bInConf || pCC->bMonitor || pCC == pSource) {
1815 // This guy isn't in the conference at the moment or is
1816 // in monitor mode, or is the guy that's talking, ignore him
1817 continue;
1818 }
1819
1820 if(Type == PKT_TYPE_AUDIO) {
1821 if(pCC->CompressionType != CompressionType) {
1822 // Audio packet of a different compression type than this client,
1823 // Don't confuse him.
1824 DLOG(DLOG_CODEC_TYPE,
1825 ("Not forwarding CType 0x%x, HisCtype: 0x%x to %s.\n",
1826 CompressionType,pCC->CompressionType,pCC->Callsign));
1827 continue;
1828 }
1829 if(bFwdFullDpxOnly && !pCC->bFullDuplex) {
1830 // only fowarding to full duplex clients and this guy isn't
1831 continue;
1832 }
1833 }
1834
1835 if(!gProtoData[pCC->Proto].bDataValid) {
1836 if(!gProtoData[PROTO_ILINK].bDataValid) {
1837 ConvertProtocol(gProtoData,InputProto,PROTO_ILINK,bNewTalker);
1838 }
1839 ConvertProtocol(gProtoData,PROTO_ILINK,pCC->Proto,bNewTalker);
1840 }
1841
1842 for(i = 0; i < MAX_CONV_PACKETS; i++) {
1843 if((Count = gProtoData[pCC->Proto].DataLen[i]) == 0) {
1844 break;
1845 }
1846 Buf = gProtoData[pCC->Proto].Data[i];
1847
1848 switch(pCC->Proto) {
1849 case PROTO_ILINK:
1850 if(Type == PKT_TYPE_DATA && !pCC->bNoChat) {
1851 // Sending Data packet and the user hasn't muted chat
1852 if(bUserChat || (bSysopChat && pCC->bSysop) || pCC->bAdmin) {
1853 // forward chat text
1854 Send2(pCC,Buf,Count,FALSE);
1855 }
1856 }
1857 else if(Type == PKT_TYPE_AUDIO) {
1858 OrigSSRC = pRTP->ssrc;
1859 if(pCC->bSendSSRC) {
1860 // Sending audio from another conference. If the sssrc is
1861 // zero then set our ssrc ID, otherwise just pass the
1862 // packet on unmodified.
1863 if(pRTP->ssrc == 0) {
1864 ZeroSSRC++;
1865 pRTP->ssrc = OurNodeID;
1866 }
1867 }
1868 else {
1869 // Sending audio to user that may crash if they see a
1870 // non-zero ssrc, make sure ssrc is zero
1871 pRTP->ssrc = 0;
1872 }
1873
1874 Send2(pCC,Buf,Count,FALSE);
1875
1876 // restore the original ssrc in case it was modified above
1877 pRTP->ssrc = OrigSSRC;
1878 }
1879 break;
1880
1881 case PROTO_SF:
1882 if(Type == PKT_TYPE_AUDIO) {
1883 // Audio packet and current user is not talking
1884 Send2(pCC,Buf,Count,FALSE);
1885 }
1886 break;
1887
1888 case PROTO_RTP:
1889 if(Type == PKT_TYPE_AUDIO) {
1890 // Audio packet and current user is not talking
1891 Send2(pCC,Buf,Count,FALSE);
1892 }
1893 break;
1894 }
1895 }
1896 }
1897
1898 if(Type == PKT_TYPE_AUDIO || Type == PKT_TYPE_OTHER_AUDIO) {
1899 EndPoint(p,pSource,EVENT_RTP_RX);
1900 }
1901 }
1902 }
1903
RTP_Rx(ClientInfo * p,ConfServer * pCS)1904 void RTP_Rx(ClientInfo *p,ConfServer *pCS)
1905 {
1906 ConfClient Lookup;
1907 int RxLen;
1908 socklen_t Adrlen = sizeof(Lookup.HisAdr);
1909
1910 memset(&Lookup,0,sizeof(Lookup));
1911
1912 RxLen = recvfrom(p->Socket,p->Buf,p->BufSize-1,0,&Lookup.HisAdr.s,&Adrlen);
1913 Lookup.HisAdr.PORT = pCS->AudioPort;
1914
1915 if(RxLen != SOCKET_ERROR) {
1916 p->Buf[RxLen] = 0;
1917 p->Count = RxLen;
1918 pCS->RxBytes += RxLen + HEADER_OVERHEAD;
1919 RxBytesAllConf += RxLen + HEADER_OVERHEAD;
1920 pCS->RxCount++;
1921 }
1922
1923 if(RxLen == SOCKET_ERROR) {
1924 pCS->RxErrs++;
1925 DPRINTF(("RTP_Handler(): recvfrom returned %u.\n",ERROR_CODE));
1926 }
1927 else {
1928 RxBytesAllConf += RxLen + HEADER_OVERHEAD;
1929 RTP_Data(p,pCS,&Lookup);
1930 }
1931 }
1932
RTP_Handler(ClientInfo * p)1933 int RTP_Handler(ClientInfo *p)
1934 {
1935 int bClientsWereTalking = ClientTalking != NULL;
1936 int bClientsTalking = FALSE;
1937 int bConfTalkerFound = FALSE;
1938 int bConfFound = FALSE;
1939 int AudioDeltaT;
1940 struct avl_traverser avl_trans;
1941 ConfServer *pCS = (ConfServer *) p->p;
1942 ConfClient *pCC = avl_t_first(&avl_trans,pCS->ConfTree);
1943
1944 GetTimeNow();
1945
1946 // Check RTP timeouts
1947 while(pCC != NULL) {
1948 if(pCC->bConf) {
1949 bConfFound = TRUE;
1950 }
1951
1952 if(pCC->bTalking) {
1953 AudioDeltaT = TimeLapse(&pCC->LastAudioIn);
1954
1955 if(AudioDeltaT > ConfAudioTimeout) {
1956 // This guy has stopped talking
1957 EndPoint(p,pCC,EVENT_RTP_TO);
1958 if(pCC->bTimedOut) {
1959 // remove "Timed out" from users entry in station list
1960 pCS->bSendStationList = TRUE;
1961 LOG_WARN(("timed out user %s unkeyed.\n",pCC->Callsign));
1962 }
1963 pCC->bTalking = FALSE;
1964 pCC->bTimedOut = FALSE;
1965
1966 if(pCC->State != NULL) {
1967 pCC->State(p,pCC,EVENT_RTP_TO);
1968 }
1969 }
1970 else {
1971 // This guy is still talking
1972 bClientsTalking = TRUE;
1973 if(pCC == ClientTalking && !pCC->bKicked) {
1974 bConfTalkerFound = TRUE;
1975 }
1976 }
1977 }
1978 pCC = (ConfClient *) avl_t_next(&avl_trans);
1979 }
1980
1981 pCS->bLinkedConfs = bConfFound;
1982
1983 if(!bConfTalkerFound && ClientTalking != NULL &&
1984 ClientTalking != pCS->pFilePlayer)
1985 { // The conference is free now
1986 CalcBW(pCS,TRUE);
1987 ClientTalking = NULL;
1988 pCS->bSendStationList = TRUE;
1989 if(pCS->bPlayWhenFree) {
1990 // Waiting to play an file
1991 pCS->bPlayWhenFree = FALSE;
1992 StartPlayback(pCS->pAudio,pCS->pFilePlayer);
1993 }
1994 }
1995
1996 if(bClientsTalking) {
1997 // Set a timeout
1998 SetTimeout(p,ConfAudioTimeout);
1999 }
2000 else {
2001 // No one's talking no need for a timeout
2002 p->bTimeWait = FALSE;
2003 }
2004
2005 if(p->bReadReady) {
2006 RTP_Rx(p,pCS);
2007 }
2008
2009 if(bClientsWereTalking && ClientTalking == NULL && pCS->bLinkedConfs) {
2010 // We have linked conferences listening, send them an
2011 // SDES update
2012 SendSDES2All(p,pCS);
2013 }
2014
2015 if(pCS->bSendStationList) {
2016 SendStationList(pCS);
2017 pCS->bSendStationList = FALSE;
2018 }
2019
2020 return FALSE;
2021 }
2022
AutoLurk(ConfClient * pCC)2023 int AutoLurk(ConfClient *pCC)
2024 {
2025 int bAutoLurk = FALSE;
2026
2027 if(pCC->bRepeater) {
2028 if(DefaultAutoLurk & AUTOLURK_REPEATERS) {
2029 bAutoLurk = TRUE;
2030 }
2031 }
2032 else if(pCC->bLink) {
2033 if(DefaultAutoLurk & AUTOLURK_LINKS) {
2034 bAutoLurk = TRUE;
2035 }
2036 }
2037 else if(pCC->bConf) {
2038 if(DefaultAutoLurk & AUTOLURK_CONFERENCES) {
2039 bAutoLurk = TRUE;
2040 }
2041 }
2042 else if(DefaultAutoLurk & AUTOLURK_HEADSETS) {
2043 bAutoLurk = TRUE;
2044 }
2045
2046 return bAutoLurk;
2047 }
2048
IncrementClientCount(ConfServer * pCS,ConfClient * pCC)2049 void IncrementClientCount(ConfServer *pCS,ConfClient *pCC)
2050 {
2051 ConferenceClients++;
2052 pCS->bSendStationList = TRUE;
2053
2054 if(ConferenceClients > PeakClients) {
2055 PeakClients = ConferenceClients;
2056 PeakClientTime = TimeNow.tv_sec;
2057 }
2058 }
2059
AddClient2Conf(ConfServer * pCS,ConfClient * pCC)2060 void AddClient2Conf(ConfServer *pCS,ConfClient *pCC)
2061 {
2062 gProtoData[pCC->Proto].Clients++;
2063 pCC->LoginTime = TimeNow.tv_sec;
2064 avl_insert(pCS->ConfTree,pCC);
2065 pCS->Users++;
2066 // put our new guy on the top of the station list
2067 pCC->Link = StationList;
2068 StationList = pCC;
2069 if(pCC->bConnected) {
2070 IncrementClientCount(pCS,pCC);
2071 }
2072
2073 if(pCS->ConfTree->avl_count == 1) {
2074 // reset the RTCP timer, the conference was empty before
2075 // so it wasn't running
2076 pCS->TimeNextSDES = TimeNow.tv_sec + SDES_Interval;
2077 SetTimeoutRTCP(pCS->pControl);
2078
2079 if(AvrsEnable) {
2080 // Force an AVRS update
2081 NextAVRSTime = TimeNow.tv_sec;
2082 }
2083 }
2084 }
2085
2086
RTCP_Rx(ClientInfo * p)2087 int RTCP_Rx(ClientInfo *p)
2088 {
2089 int RxLen;
2090 int Ret = FALSE;
2091 ConfClient Lookup;
2092 char *end;
2093 ConfClient *pCC;
2094 int bNewClient = FALSE;
2095 int Authorized = 0;
2096 ConfServer *pCS = (ConfServer *) p->p;
2097 socklen_t Adrlen = sizeof(Lookup.HisAdr);
2098 union {
2099 rtcp_common_t *p;
2100 rtcp_t *pRTCP;
2101 char *cp;
2102 } u;
2103
2104 union {
2105 rtcp_sdes_item_t *p;
2106 char *cp;
2107 } Item;
2108 char RTCP_Ver;
2109
2110 u.cp = p->Buf;
2111
2112 RxLen = recvfrom(p->Socket,p->Buf,p->BufSize,0,&Lookup.HisAdr.s,&Adrlen);
2113 p->Count = RxLen;
2114 Lookup.HisAdr.PORT = pCS->AudioPort;
2115
2116 if(RxLen == SOCKET_ERROR) {
2117 pCS->RxErrs++;
2118 DPRINTF(("RTCP_Rx(): recvfrom returned %u.\n",ERROR_CODE));
2119 return FALSE;
2120 }
2121 else {
2122 pCS->RxCount++;
2123 pCS->RxBytes += RxLen + HEADER_OVERHEAD;
2124 RxBytesAllConf += RxLen + HEADER_OVERHEAD;
2125 }
2126
2127 if((RTCP_Ver = CheckRTCP(pCS)) == 0) {
2128 // Bogus packet, ignore it
2129 LOG_WARN(("RTCP_Rx(): ignoring bogus packet from %s.\n",
2130 inet_ntoa(Lookup.HisAdr.i.sin_addr)));
2131 SaveBadPacket(p,"badrtcp.bin");
2132 return FALSE;
2133 }
2134
2135 if((pCC = avl_find(pCS->ConfTree,&Lookup)) == NULL) {
2136 // New conference client
2137 bNewClient = TRUE;
2138 pCC = CreateNewConfClient();
2139 pCC->pCS = pCS;
2140 pCC->Proto = RTCP_Ver - 1;
2141 pCC->HisAdr = Lookup.HisAdr;
2142
2143 pCC->RxBytes += RxLen + HEADER_OVERHEAD;
2144 RxBytesAllConf += RxLen + HEADER_OVERHEAD;
2145
2146 if(!GetCall(pCS,pCC,u.pRTCP,RxLen)) {
2147 // Not an SDES (a bye for some we don't have connection with?), ignore it
2148 free(pCC);
2149 return FALSE;
2150 }
2151
2152 if(pCC->bConf) {
2153 // Conference
2154 if(pCC->bTBD) {
2155 // thebridge
2156 pCC->bMuted = pCS->bMuteTBD;
2157 }
2158 else {
2159 // EchoLink or other type of conference
2160 pCC->bMuted = pCS->bMuteConf;
2161 }
2162 }
2163 else if(pCC->bRepeater || pCC->bLink) {
2164 // RF user
2165 pCC->bMuted = pCS->bMuteRF;
2166 }
2167 else {
2168 // PC User
2169 pCC->bMuted = pCS->bMuteUsers;
2170 }
2171
2172 pCC->bMuteChat = pCS->bMuteChat;
2173
2174 if(pCC->Proto == PROTO_ILINK && pCC->Callsign == NULL) {
2175 LOG_ERROR(("failed to get callsign from iLink client %s.\n",
2176 inet_ntoa(pCC->HisAdr.i.sin_addr)));
2177 SaveBadPacket(p,"nocall.bin");
2178 }
2179
2180 pCC->bAutoLurk = AutoLurk(pCC);
2181
2182 // LastAudioIn to the current time (a lie) so new stations don't
2183 // disappear immediately when DefaultAutoLurk is active.
2184 pCC->LastAudioIn = TimeNow;
2185
2186 if(pCC->Callsign == NULL) {
2187 // Didn't get a callsign, default to the IP address for now
2188 pCC->CallPlus = strdup(inet_ntoa(pCC->HisAdr.i.sin_addr));
2189 pCC->Callsign = strdup(pCC->CallPlus);
2190 }
2191
2192 Authorized = AuthorizedClient(p,pCC);
2193
2194 if(bConferenceBusy ||
2195 ConferenceClients >= MaxConferenceClients ||
2196 Authorized != 1)
2197 { // Sorry Jack
2198 if(Authorized == 1) {
2199 LOG_NORM(("%s failed to logon, conference %s.\n",CallLog(pCC),
2200 bConferenceBusy ? "busy" : "full"));
2201
2202 if(bConferenceBusy) {
2203 p->Count = GenBye(pCC,p->Buf,"Sorry the conference is busy");
2204 }
2205 else {
2206 p->Count = GenBye(pCC,p->Buf,"Sorry the conference is full");
2207 }
2208 }
2209 else if(Authorized == 0) {
2210 // Sorry Jack, you're not welcome here
2211 LOG_NORM(("Disconnecting unauthorized user %s.\n",CallLog(pCC)));
2212 p->Count = GenBye(pCC,p->Buf,"Bye Bye");
2213 }
2214 else {
2215 LOG_NORM(("Ignoring unknown user %s (%s).\n",pCC->Callsign,
2216 inet_ntoa(pCC->HisAdr.i.sin_addr)));
2217 }
2218
2219 if(Authorized == 0) {
2220 SendBuf2(p,pCC,TRUE);
2221 }
2222 free(pCC->Callsign);
2223 free(pCC->CallPlus);
2224 if(pCC->Password != NULL) {
2225 free(pCC->Password);
2226 }
2227 free(pCC);
2228 pCC = NULL;
2229 }
2230 else {
2231 // New user accepted
2232 pCC->bConnected = TRUE;
2233 AddClient2Conf(pCS,pCC);
2234 EndPoint(NULL,pCC,EVENT_INIT);
2235
2236 // NB: *ASSUME* the client is running the same codec as the conference
2237 // There's no way to tell if this is actually the case until he sends
2238 // an audio packet. This may be wrong, but currently thebridge only
2239 // supports welcome messages in GSM format. If a welcome message is
2240 // played in GSM format for some clients (thelinkbox) they will *SWITCH*
2241 // to GSM when they receive it. Meaning if you want an ADPCM conference
2242 // you should *NOT* configure the conference to send a welcome message.
2243 //
2244 // A possible solution would be for the client to sent a few empty
2245 // audio packets on connect to convey the audio type but AFAIK no
2246 // clients do that.
2247
2248 pCC->CompressionType = CompressionType;
2249 switch(pCC->Proto) {
2250 case PROTO_SF:
2251 LOG_NORM(("SF client %s logged in, current users: %d.\n",
2252 CallLog(pCC),ConferenceClients));
2253 EventHook("connected speakfreely %s %d",pCC->Callsign,
2254 ConferenceClients);
2255 break;
2256
2257 case PROTO_RTP:
2258 LOG_NORM(("RTP client %s logged in, current users: %d.\n",
2259 CallLog(pCC),ConferenceClients));
2260 EventHook("connected rtp %s %d",pCC->Callsign,ConferenceClients);
2261 break;
2262
2263 case PROTO_ILINK:
2264 // EchoLink only supports GSM
2265 pCC->CompressionType = RTP_PT_GSM;
2266 LOG_NORM(("%s%s logged in, current users: %d.\n",
2267 CallLog(pCC),pCC->bConf ? " conference" : "",
2268 ConferenceClients));
2269 EventHook("connected echolink %s %d",pCC->Callsign,
2270 ConferenceClients);
2271 break;
2272 }
2273
2274 if(WelcomeFile != NULL && strstr(WelcomeFile,".wav") == NULL) {
2275 if(WelcomeDelay != 0) {
2276 if(ClientTalking == NULL) {
2277 pCC->bWelcomePending = TRUE;
2278 }
2279 }
2280 else {
2281 // Play the welcome file for him.
2282
2283 // NB: Send the station list now, otherwise the new user won't
2284 // get a copy because the welcome file will be playing
2285
2286 SendStationList(pCS);
2287 pCS->bSendStationList = FALSE;
2288
2289 OpenAudioFile(pCC,WelcomeFile,MODE_RD_BIN);
2290 if(pCC->bFileIOActive) {
2291 StartPlayback(p,pCC);
2292 }
2293 }
2294 }
2295 else if(AudioTestConf && FileRecord(NULL,pCC,EVENT_INIT)) {
2296 pCC->bInConf = FALSE;
2297 pCC->State = FileRecord;
2298 }
2299 }
2300 }
2301 else {
2302 pCC->RxBytes += RxLen + HEADER_OVERHEAD;
2303 RxBytesAllConf += RxLen + HEADER_OVERHEAD;
2304 }
2305
2306 if(pCC != NULL && pCC->bKicked && pCC->bDisconnected) {
2307 // maybe he didn't get the news
2308 DisconnectCC(p,pCC,"Goodbye");
2309 }
2310
2311 if(pCC != NULL && !pCC->bKicked) {
2312 pCC->LastHeard = TimeNow.tv_sec;
2313
2314 if(pCC->State != NULL) {
2315 pCC->State(p,pCC,EVENT_RTCP_RX);
2316 }
2317
2318 Item.p = NULL;
2319 end = u.cp + RxLen;
2320
2321 while(u.cp < end) {
2322 D3PRINTF(("RTCP_Rx(): processing pt %d.\n",u.p->pt));
2323 switch(u.p->pt) {
2324 case RTCP_SR:
2325 break;
2326
2327 case RTCP_RR:
2328 break;
2329
2330 case RTCP_SDES:
2331 // Answer with our SDES if he's a new client
2332 if(bNewClient) {
2333 SendSDES(pCS,pCC);
2334 }
2335
2336 GetCall(pCS,pCC,u.pRTCP,RxLen);
2337
2338 if(pCC->bPermanent && !pCC->bConnected) {
2339 // A new connection to a permanent connection
2340 pCC->bInConf = TRUE;
2341 pCC->bConnected = TRUE;
2342 IncrementClientCount(pCS,pCC);
2343
2344 pCC->LoginTime = TimeNow.tv_sec;
2345 LOG_NORM(("%s%s connected, current users: %d.\n",
2346 CallLog(pCC),pCC->bConf ? " conference" : "",
2347 ConferenceClients));
2348 EndPoint(NULL,pCC,EVENT_INIT);
2349 EventHook("connected outbound %s %d",pCC->Callsign,
2350 ConferenceClients);
2351 pCS->bSendStationList = TRUE;
2352 }
2353 break;
2354
2355 case RTCP_BYE:
2356 if(pCC->bPermanent && !pCC->bConnected) {
2357 // Must be a .connect command that failed. Send the results
2358 // to all admins & sysops
2359 p->Count = 0; // init for BufPrintf
2360 BufPrintf(p,"%c" NDATA,ILINK_DATA_PACKET);
2361 BufPrintf(p,"%s refused connection request.\r",pCC->Callsign);
2362 SendToAllSysops(p,pCC->pCS);
2363 LOG_NORM(("%s refused connection request.\n",CallLog(pCC)));
2364 EventHook("disconnected refused %s %d",pCC->Callsign,
2365 ConferenceClients-1);
2366 }
2367 else {
2368 LOG_NORM(("%s logged out, on %s, current users: %d.\n",
2369 CallLog(pCC),TimeLapse2String(pCC->LoginTime,TRUE),
2370 ConferenceClients-1));
2371 EventHook("disconnected bye %s %d",pCC->Callsign,
2372 ConferenceClients-1);
2373 }
2374 DeleteCClient(pCC);
2375 // Update the station list
2376 pCS->bSendStationList = TRUE;
2377 return Ret;
2378
2379 case RTCP_APP:
2380 break;
2381 }
2382 u.cp = u.cp + sizeof(rtcp_common_t) + ntohs(u.p->length) * 4;
2383 }
2384 }
2385
2386 return Ret;
2387 }
2388
SetTimeoutRTCP(ClientInfo * p)2389 void SetTimeoutRTCP(ClientInfo *p)
2390 {
2391 ConfServer *pCS = (ConfServer *) p->p;
2392 int Timeout = 0x7fffffff;
2393
2394 if(pCS->ConfTree->avl_count > 0) {
2395 Timeout = MIN(Timeout,pCS->TimeNextSDES);
2396 }
2397
2398 if(NextLoginTime != 0) {
2399 Timeout = MIN(Timeout,NextLoginTime);
2400 }
2401
2402 if(NextStationListTime != 0) {
2403 Timeout = MIN(Timeout,NextStationListTime);
2404 }
2405
2406 if(NextAVRSTime != 0) {
2407 Timeout = MIN(Timeout,NextAVRSTime);
2408 }
2409
2410 if(Timeout != 0x7fffffff) {
2411 SetTimeout(p,(Timeout - TimeNow.tv_sec) * 1000);
2412 }
2413 else {
2414 p->bTimeWait = FALSE;
2415 }
2416 }
2417
LogStats(ConfServer * pCS)2418 void LogStats(ConfServer *pCS)
2419 {
2420 u_int TxBytes = TxBytesAllConf;
2421 u_int TxMBytes = TxMBytesAllConf;
2422 u_int RxBytes = RxBytesAllConf;
2423 u_int RxMBytes = RxMBytesAllConf;
2424
2425 CalcBW(pCS,FALSE);
2426
2427 if(TxBytes > 1000000) {
2428 TxMBytes += TxBytes / 1000000;
2429 TxBytes %= 1000000;
2430 }
2431
2432 if(RxBytes > 1000000) {
2433 RxMBytes += RxBytes / 1000000;
2434 RxBytes %= 1000000;
2435 }
2436
2437 LOG_NORM(("Users:%d BW T:%d R:%d T:%d.%02dMB R:%d.%02dMB Up:%s\n",
2438 ConferenceClients,pCS->TxBandWidth,pCS->RxBandWidth,
2439 TxMBytes,TxBytes*100/1000000,RxMBytes,RxBytes*100/1000000,
2440 TimeLapse2String(BootTime,TRUE)));
2441 }
2442
StartCmdHandler(char * Cmd,char * Callsign)2443 void StartCmdHandler(char *Cmd,char *Callsign)
2444 {
2445 ConfServer *pCS = piLinkConf;
2446 ClientInfo *p = pCS->pAudio;
2447 ConfClient cc;
2448 char *cp, *cp1;
2449
2450 if(pCS != NULL) {
2451 p = pCS->pAudio;
2452
2453 memset(&cc,0,sizeof(cc));
2454
2455 cc.bCmdLine = TRUE;
2456 cc.bSysop = TRUE;
2457 cc.bAdmin = TRUE;
2458 cc.Callsign = Callsign;
2459 cc.CallPlus = cc.Callsign;
2460 cc.pCS = pCS;
2461
2462 cp = Cmd;
2463 if(*cp == '.') {
2464 cp++;
2465 }
2466
2467 ConferenceCmd(p,&cc,cp);
2468 cp1 = &p->Buf[sizeof(NDATA)];
2469 if(strlen(cp1) > 0) {
2470 // log the startup command response
2471 while((cp = strchr(cp1,'\r')) != NULL) {
2472 *cp++ = 0;
2473 LOG_NORM(("StartupCmd: %s\n",cp1));
2474 cp1 = cp;
2475 }
2476
2477 if(strlen(cp1) > 0) {
2478 LOG_NORM(("StartupCmd: %s\n",cp1));
2479 }
2480 }
2481 }
2482 }
2483
CmdLine_Handler(ClientInfo * pClient)2484 int CmdLine_Handler(ClientInfo *pClient)
2485 {
2486 ConfServer *pCS = (ConfServer *) pClient->p;
2487 ClientInfo *p = pCS->pAudio;
2488 socklen_t Adrlen = sizeof(IPAdrUnion);
2489 int Ret = FALSE;
2490 int bIsCmd = FALSE;
2491 int RxLen = SOCKET_ERROR;
2492 char Cmd[CONF_BUF_SIZE];
2493 char *cp;
2494 ConfClient *pCC;
2495 unsigned short OurPort = ntohs(pClient->HisAdr.PORT);
2496
2497 for( ; ; ) {
2498 if(OurPort == ChatPort) {
2499 pCC = &ChatCC;
2500 }
2501 else if(OurPort == CmdPort) {
2502 pCC = &CmdLineCC;
2503 }
2504 else {
2505 LOG_ERROR(("CmdLine_Handler(): Error: unknown port number %d\n",
2506 OurPort));
2507 break;
2508 }
2509
2510 if(pClient->bReadReady) {
2511 RxLen = recvfrom(pClient->Socket,Cmd,sizeof(Cmd)-1,0,&pCC->HisAdr.s,
2512 &Adrlen);
2513 }
2514
2515 if(RxLen == SOCKET_ERROR) {
2516 LOG_ERROR(("CmdLine_Handler(): recvfrom failed %s\n",
2517 Err2String(errno)));
2518 break;
2519 }
2520
2521 if(ntohs(pCC->HisAdr.PORT) == OurPort) {
2522 LOG_ERROR(("CmdLine_Handler(): I'm talking to myself again!\n"));
2523 break;
2524 }
2525
2526 if(ChatCC.HisAdr.PORT == CmdLineCC.HisAdr.PORT) {
2527 // We're either running tbdcmd or tbdchat but it appears that we're
2528 // running both from the same port. This can happen if we exit
2529 // one program and start the other and it happens to be assigned the
2530 // same port number. Clear the port on the stale client
2531 if(OurPort == ChatPort) {
2532 CmdLineCC.HisAdr.PORT = 0;
2533 }
2534 else {
2535 ChatCC.HisAdr.PORT = 0;
2536 }
2537 }
2538
2539 Cmd[RxLen] = 0;
2540 if(bCmdLineChatMode || OurPort == ChatPort) {
2541 if(Cmd[0] == '.' && Cmd[1] == '.') {
2542 bIsCmd = TRUE;
2543 }
2544 }
2545 else {
2546 bIsCmd = TRUE;
2547 }
2548
2549 if(bIsCmd) {
2550 cp = Cmd;
2551 while(*cp == '.') {
2552 cp++;
2553 }
2554 ConferenceCmd(p,pCC,cp);
2555 // Strip Text header from result and add result code to the beginning
2556 // of the response
2557 cp = strdup(&p->Buf[sizeof(NDATA)]);
2558 p->Count = snprintf(p->Buf,p->BufSize,"%d\r%s",Rcode,cp);
2559 free(cp);
2560
2561 if(p->Count == -1 || p->Count >= p->BufSize) {
2562 // Pre-C99 style snprintf returns -1 on buffer overflow.
2563 // C99 style snprintf returns the number of characters that would
2564 // have been written on buffer overflow.
2565 // In either case the output was truncated and p->Count is bogus.
2566
2567 p->Count = p->BufSize - 1;
2568 p->Buf[p->Count] = 0;
2569 }
2570
2571 // convert \r end of line convention into *nix standard \n
2572
2573 cp = p->Buf;
2574 while((cp = strchr(cp,'\r')) != NULL) {
2575 *cp = '\n';
2576 }
2577 }
2578 else {
2579 // Format data into an EchoLink text data packet
2580 p->Count = 0; // init for BufPrintf
2581 BufPrintf(p,"%c" NDATA "%s>%s\r",ILINK_DATA_PACKET,
2582 ConferenceCall,Cmd);
2583 p->Count++; // include terminating null
2584 if(p->Count+4 <= p->BufSize) {
2585 // Add SSRC to end of text
2586 memcpy(&p->Buf[p->Count],&OurNodeID,4);
2587 p->Count += 4;
2588 }
2589 RTP_Data(p,pCS,pCC);
2590 p->Count = snprintf(p->Buf,p->BufSize,"%d\n",TBD_CHAT_TEXT_SENT);
2591 }
2592 sendto(pClient->Socket,p->Buf,p->Count+1,0,&pCC->HisAdr.s,
2593 sizeof(IPAdrUnion));
2594 break;
2595 }
2596
2597 return Ret;
2598 }
2599
RTCP_Handler(ClientInfo * p)2600 int RTCP_Handler(ClientInfo *p)
2601 {
2602 ConfClient *pCC;
2603 ConfClient *pNextCC;
2604 ConfClient *pOldestCC = NULL;
2605 time_t OldestConnectTime = 0x7fffffff;
2606 struct avl_traverser avl_trans;
2607 ConfServer *pCS = (ConfServer *) p->p;
2608 int Ret = FALSE;
2609 static int LastStatsHr = -1;
2610 time_t timenow = (time_t) TimeNow.tv_sec;
2611 struct tm *tm = localtime(&timenow);
2612
2613 if(p->bReadReady) {
2614 Ret |= RTCP_Rx(p);
2615 }
2616
2617 if(LastStatsHr != tm->tm_hour) {
2618 // Generate hourly stats while active
2619 if(LastStatsHr != -1) {
2620 LogStats(pCS);
2621 }
2622 LastStatsHr = tm->tm_hour;
2623 }
2624
2625 // Check RTCP timeouts
2626 pNextCC = (ConfClient *) avl_t_first(&avl_trans,pCS->ConfTree);
2627 while((pCC = pNextCC) != NULL) {
2628 pNextCC = (ConfClient *) avl_t_next(&avl_trans);
2629 if((TimeNow.tv_sec - pCC->LastHeard) > ConfMemberTimeout) {
2630 int Clients = ConferenceClients;
2631 // RTCP timeout
2632 if(pCC->bPermanent) {
2633 if(pCC->bConnected) {
2634 LOG_NORM(("RTCP timeout on permanent client %s. On %s.\n",
2635 CallLog(pCC),TimeLapse2String(pCC->LoginTime,TRUE)));
2636 EndPoint(p,pCC,EVENT_RTCP_TO);
2637 EventHook("disconnected rtcp_timeout %s %d",pCC->Callsign,
2638 Clients);
2639 if(pCC->bConnected) {
2640 pCC->bConnected = FALSE;
2641 ConferenceClients--;
2642 Clients--;
2643 }
2644 pCC->bInConf = FALSE;
2645 pCS->bSendStationList = TRUE;
2646 }
2647 }
2648 else {
2649 if(!pCC->bConnected) {
2650 LOG_NORM(("Dropping %s. On %s.\n",CallLog(pCC),
2651 TimeLapse2String(pCC->LoginTime,TRUE)));
2652 }
2653 else {
2654 LOG_NORM(("Dropping %s, RTCP timeout. On %s.\n",CallLog(pCC),
2655 TimeLapse2String(pCC->LoginTime,TRUE)));
2656 Clients--;
2657 }
2658 EndPoint(p,pCC,EVENT_RTCP_TO);
2659 if(pCC->State != NULL) {
2660 pCC->State(p,pCC,EVENT_RTCP_TO);
2661 }
2662 pCS->bSendStationList = TRUE;
2663 EventHook("disconnected rtcp_timeout %s %d",pCC->Callsign,Clients);
2664 DeleteCClient(pCC);
2665 }
2666 continue;
2667 }
2668
2669 if(pCC->bAutoLurk && !pCC->bLurking &&
2670 (TimeNow.tv_sec - pCC->LastAudioIn.tv_sec) > AutoLurkTimeout) {
2671 pCC->bLurking = TRUE;
2672 pCS->bSendStationList = TRUE;
2673 if(!AutoLurk(pCC)) {
2674 // Only sent back to lurking message to stations that selected
2675 // lurk mode manually
2676 p->Count = 0; // init for BufPrintf
2677 BufPrintf(p,"%c" NDATA "Back to lurking...\r",ILINK_DATA_PACKET);
2678 p->Count++; // include terminating null
2679 Send2(pCC,p->Buf,p->Count,FALSE);
2680 }
2681 }
2682
2683 if(pCC->bWelcomePending &&
2684 (TimeNow.tv_sec - pCC->LoginTime) > WelcomeDelay)
2685 { // Time to play welcome
2686 pCC->bWelcomePending = FALSE;
2687 OpenAudioFile(pCC,WelcomeFile,MODE_RD_BIN);
2688 if(pCC->bFileIOActive) {
2689 StartPlayback(p,pCC);
2690 }
2691 }
2692
2693 if(pCC->LoginTime < OldestConnectTime &&
2694 strcmp(pCC->Callsign,ConferenceCall) != 0)
2695 {
2696 OldestConnectTime = pCC->LoginTime;
2697 pOldestCC = pCC;
2698 }
2699 }
2700
2701 if(p->bTimeOut) {
2702 if(LoginInterval > 0 && NextLoginTime <= TimeNow.tv_sec) {
2703 // Time to login again
2704 NextLoginTime = TimeNow.tv_sec + LoginInterval;
2705 if(NextStationListTime != 0 && NextStationListTime <= TimeNow.tv_sec) {
2706 // Also time to refresh the station list
2707 NextStationListTime = TimeNow.tv_sec + StationListInterval;
2708 DPRINTF(("RTCP_Handler(): refreshing login and station list.\n"));
2709 ServerRequest(SERV_REQ_LOGIN_AND_LIST,0,NULL);
2710 }
2711 else {
2712 DPRINTF(("RTCP_Handler(): refreshing login.\n"));
2713 ServerRequest(SERV_REQ_LOGIN,0,NULL);
2714 }
2715 }
2716 else if(LoginInterval > 0 && NextStationListTime > 0 &&
2717 NextStationListTime <= TimeNow.tv_sec)
2718 {
2719 // Time to refresh our station list
2720 NextStationListTime = TimeNow.tv_sec + StationListInterval;
2721 DPRINTF(("RTCP_Handler(): refreshing station list.\n"));
2722 ServerRequest(SERV_REQ_STATION_LIST,0,NULL);
2723 }
2724
2725 if(pCS->TimeNextSDES <= TimeNow.tv_sec) {
2726 // Time to send a SDES to all of our clients
2727 D3PRINTF(("Sending SDES to clients\n"));
2728 SendSDES2All(p,pCS);
2729 }
2730
2731 if(pCS->bPlayWhenIdle &&
2732 TimeNow.tv_sec - pCS->LastAudioIn.tv_sec > IdleTimeout)
2733 {
2734 pCS->bPlayWhenIdle = FALSE;
2735 StartPlayback(pCS->pAudio,pCS->pFilePlayer);
2736 }
2737
2738 if(AvrsEnable && pCS->biLinkConf && NextAVRSTime <= TimeNow.tv_sec) {
2739 // Time to send an AVRS update
2740
2741 NextAVRSTime = TimeNow.tv_sec + AvrsInterval;
2742 if(PullerStatusBusy) {
2743 // Busy
2744 SendAvrsUpdate(AVRS_BUSY,NULL);
2745 }
2746 else if(pOldestCC != NULL) {
2747 // Connected to someone
2748 SendAvrsUpdate(AVRS_CONNECTED,pOldestCC->Callsign);
2749 }
2750 else {
2751 // Just hanging out
2752 SendAvrsUpdate(AVRS_ON,NULL);
2753 }
2754 }
2755
2756 #ifdef LINK_BOX
2757 if(AprsIsEnable && LasttAprsIsTime + AprsIsInterval <= TimeNow.tv_sec) {
2758 LasttAprsIsTime = TimeNow.tv_sec;
2759 if(PullerStatusBusy) {
2760 // Busy
2761 Send2Aprsd(AVRS_BUSY,NULL);
2762 }
2763 else if(pOldestCC != NULL) {
2764 // Connected to someone
2765 Send2Aprsd(AVRS_CONNECTED,pOldestCC->Callsign);
2766 }
2767 else {
2768 // Just hanging out
2769 Send2Aprsd(AVRS_ON,NULL);
2770 }
2771 }
2772 #endif
2773 }
2774 SetTimeoutRTCP(p);
2775
2776 if(pCS->bSendStationList) {
2777 SendStationList(pCS);
2778 pCS->bSendStationList = FALSE;
2779 }
2780
2781 if(pCS->bDynamicConf && pCS->Users == 0) {
2782 DeleteConf(pCS);
2783 }
2784
2785 return Ret;
2786 }
2787
2788 // Conference compare by port number
ConfCompare(const void * avl_a,const void * avl_b,void * avl_param)2789 int ConfCompare(const void *avl_a,const void *avl_b,void *avl_param)
2790 {
2791 ConfServer *p_a = (ConfServer *) avl_a;
2792 ConfServer *p_b = (ConfServer *) avl_b;
2793
2794 return p_a->AudioPort - p_b->AudioPort;
2795 }
2796
2797 // Client compare by IP address : port number
CClientCompare(const void * avl_a,const void * avl_b,void * avl_param)2798 int CClientCompare(const void *avl_a,const void *avl_b,void *avl_param)
2799 {
2800 ConfClient *p_a = (ConfClient *) avl_a;
2801 ConfClient *p_b = (ConfClient *) avl_b;
2802
2803 if(p_a->HisAdr.ADDR == p_b->HisAdr.ADDR) {
2804 return p_a->HisAdr.PORT - p_b->HisAdr.PORT;
2805 }
2806
2807 if(p_a->HisAdr.ADDR > p_b->HisAdr.ADDR) {
2808 return 1;
2809 }
2810 else {
2811 return -1;
2812 }
2813 }
2814
CreateNewConfClient()2815 ConfClient *CreateNewConfClient()
2816 {
2817 ConfClient *pCC = (ConfClient *) malloc(sizeof(ConfClient));
2818
2819 if(pCC != NULL) {
2820 memset(pCC,0,sizeof(ConfClient));
2821 pCC->SN = ++ClientConnects;
2822 pCC->bInConf = TRUE;
2823 }
2824 else {
2825 LOG_ERROR(("CreateNewConfClient(): malloc failed.\n"));
2826 }
2827
2828 return pCC;
2829 }
2830
RemoveFromStationList(ConfClient * pCC)2831 void RemoveFromStationList(ConfClient *pCC)
2832 {
2833 ConfClient *pCCLast = (ConfClient *) &StationList;
2834 ConfClient *pCC1 = StationList;
2835
2836 // Remove him from the station list
2837
2838 while(pCC1 != NULL) {
2839 if(pCC1 == pCC) {
2840 pCCLast->Link = pCC->Link;
2841 break;
2842 }
2843 pCCLast = pCC1;
2844 pCC1 = pCC1->Link;
2845 }
2846
2847 if(pCC1 == NULL && !pCC->bFilePlayer) {
2848 LOG_ERROR(("RemoveFromStationList: station %s not found in station list.\n",
2849 inet_ntoa(pCC->HisAdr.i.sin_addr)));
2850 }
2851 }
2852
FileCleanup(ConfClient * pCC)2853 void FileCleanup(ConfClient *pCC)
2854 {
2855 ClientFileIO *pFIO = (ClientFileIO *) pCC->p;
2856
2857 if(pFIO->fp != NULL) {
2858 fclose(pFIO->fp);
2859 }
2860
2861 if(pFIO->pClient != NULL) {
2862 pFIO->pClient->Socket = INVALID_SOCKET;
2863 DeleteClient(pFIO->pClient);
2864 }
2865
2866 if(pFIO->bDeleteOnClose) {
2867 unlink(pFIO->Filename);
2868 }
2869
2870 if(pFIO->Filename != NULL) {
2871 free(pFIO->Filename);
2872 }
2873
2874 if(pFIO->pPDat != NULL) {
2875 free(pFIO->pPDat);
2876 }
2877
2878 free(pFIO);
2879 pCC->p = NULL;
2880 pCC->bFileIOActive = FALSE;
2881 }
2882
DeleteCClient(ConfClient * pCC)2883 void DeleteCClient(ConfClient *pCC)
2884 {
2885 ConfServer *pCS = pCC->pCS;
2886 size_t Nodes = pCS->ConfTree->avl_count;
2887
2888 EndPoint(NULL,pCC,EVENT_DELETE);
2889
2890 if(pCC == ClientTalking) {
2891 ClientTalking = NULL;
2892 }
2893
2894 if(!pCC->bFilePlayer) {
2895 if(pCC->bConnected){
2896 ConferenceClients--;
2897 }
2898 gProtoData[pCC->Proto].Clients--;
2899 if(ConferenceClients == 0 && AvrsEnable) {
2900 // Force an AVRS update
2901 NextAVRSTime = TimeNow.tv_sec;
2902 }
2903 }
2904 RemoveFromStationList(pCC);
2905
2906 if(!pCC->bFilePlayer && avl_delete(pCS->ConfTree,pCC) == NULL) {
2907 LOG_ERROR(("DeleteCClient: avl_delete() failed to find client.\n"));
2908 }
2909 else {
2910 if(!pCC->bFilePlayer && (Nodes - 1) != pCS->ConfTree->avl_count) {
2911 LOG_NORM(("DeleteCClient: %d nodes before delete, %d after\n",Nodes,
2912 pCS->ConfTree->avl_count));
2913 }
2914
2915 if(pCC->bFileIOActive) {
2916 FileCleanup(pCC);
2917 }
2918
2919 if(pCC->CallPlus != NULL) {
2920 free(pCC->CallPlus);
2921 }
2922
2923 if(pCC->Callsign != NULL) {
2924 free(pCC->Callsign);
2925 }
2926
2927 if(pCC->Info != NULL) {
2928 free(pCC->Info);
2929 }
2930
2931 if(pCC->Password != NULL) {
2932 free(pCC->Password);
2933 }
2934
2935 if(pCC->Tool != NULL) {
2936 free(pCC->Tool);
2937 }
2938
2939 pCC->pCS->Users--;
2940 free(pCC);
2941 }
2942 }
2943
2944 // Sanity check RTCP packet
2945 // An Speak Freely packet has RTCP_Ver set to 1 in the first chunk,
2946 // but set to 2 in the remaining chunks.
2947 //
2948 // An standard RTP packet has RTCP_Ver set to 2 in all chunks
2949 //
2950 // An iLink packet has RTCP_Ver set to 3 in all chunks
2951 //
2952 // Return RTCP version or 0 if invalid
CheckRTCP(ConfServer * pCS)2953 char CheckRTCP(ConfServer *pCS)
2954 {
2955 char *end;
2956 union {
2957 rtcp_common_t *p;
2958 char *cp;
2959 } u;
2960 rtcp_common_t *p = (rtcp_common_t *) pCS->pControl->Buf;
2961 char Ret = 0;
2962
2963 u.p = p;
2964 end = u.cp + pCS->pControl->Count;
2965
2966 // Sanity check: Version correct, no padding in first packet,
2967 // first item is either RTCP_SR or RTCP_RR
2968
2969 while(u.cp < end) {
2970 if(u.p == p && (p->p || (p->pt != RTCP_SR && p->pt != RTCP_RR)) ){
2971 // Invalid RTCP packet
2972 LOG_WARN(("CheckRTCP(): First item invalid\n"));
2973 break;
2974 }
2975
2976 if(u.p == p && pCS->bSFConf && u.p->version == SF_RTP_VERSION) {
2977 // First chunk
2978 Ret = SF_RTP_VERSION;
2979 }
2980 else if((pCS->bSFConf || pCS->bRTPConf) && u.p->version == RTP_VERSION) {
2981 if(Ret == 0) {
2982 Ret = RTP_VERSION;
2983 }
2984 }
2985 else if(pCS->biLinkConf && u.p->version == ILINK_RTP_VERSION) {
2986 Ret = ILINK_RTP_VERSION;
2987 }
2988 else {
2989 // Invalid RTCP packet
2990 LOG_WARN(("CheckRTCP(): Invalid RTCP version %d\n",u.p->version));
2991 Ret = 0;
2992 break;
2993 }
2994 u.cp = u.cp + sizeof(rtcp_common_t) + ntohs(u.p->length) * 4;
2995 }
2996
2997 if(u.cp != end) {
2998 Ret = 0;
2999 LOG_WARN(("CheckRTCP(): End of packet mismatch\n"));
3000 }
3001
3002 if(Ret == 0) {
3003 BadRTCPPacketCount++;
3004 }
3005 return Ret;
3006 }
3007
GetCall(ConfServer * pCS,ConfClient * pCC,rtcp_t * p,int len)3008 int GetCall(ConfServer *pCS,ConfClient *pCC,rtcp_t *p,int len)
3009 {
3010 char *end;
3011 char *Temp;
3012 char *Callsign = NULL;
3013 char *cp;
3014 char *TextData = NULL;
3015 union {
3016 rtcp_common_t *p;
3017 rtcp_t *pRTCP;
3018 char *cp;
3019 } u;
3020
3021 union {
3022 rtcp_sdes_item_t *p;
3023 char *cp;
3024 } Item;
3025 int bFoundCallsign = FALSE;
3026 int x;
3027 int Ret = FALSE;
3028 int EchoLinkRevMajor;
3029 int EchoLinkRevMinor;
3030 int EchoLinkBuild;
3031
3032 Item.p = NULL;
3033 u.pRTCP = p;
3034 end = u.cp + len;
3035
3036 while(u.cp < end) {
3037 if(u.p->pt == RTCP_SDES) {
3038 // SDES packet
3039 Item.p = u.pRTCP->r.sdes.item;
3040 Ret = TRUE;
3041 break;
3042 }
3043 u.cp = u.cp + sizeof(rtcp_common_t) + ntohs(u.p->length) * 4;
3044 }
3045
3046 while(Item.p != NULL && Item.p->type != RTCP_SDES_END) {
3047 switch(Item.p->type) {
3048 case RTCP_SDES_CNAME: // CNAME is the only required SDES field
3049 if(Item.p->length == 8 && strncmp(Item.p->data,"CALLSIGN",8) == 0) {
3050 bFoundCallsign = TRUE;
3051 }
3052 break;
3053
3054 case RTCP_SDES_NAME:
3055 if(Item.p->length > 0) {
3056 if(Callsign != NULL) {
3057 // Two NAME fields !
3058 free(Callsign);
3059 }
3060
3061 Temp = malloc(Item.p->length+1);
3062
3063 if(Temp != NULL) {
3064 memcpy(Temp,Item.p->data,Item.p->length);
3065 Temp[Item.p->length] = 0;
3066 if((cp = strchr(Temp,' ')) != NULL) {
3067 // compress spaces between call and Name
3068 *cp++ = 0;
3069 while(*cp == ' ') {
3070 cp++;
3071 }
3072
3073 if(strstr(cp,"conf") != NULL || strstr(cp,"CONF") != NULL) {
3074 // if "conf" appears in the name field it's a conference.
3075 pCC->bConf = TRUE;
3076 }
3077
3078 Callsign = malloc(strlen(Temp)+strlen(cp)+2);
3079 if(Callsign != NULL) {
3080 strcpy(Callsign,Temp);
3081 strcat(Callsign," ");
3082 strcat(Callsign,cp);
3083 }
3084 free(Temp);
3085 }
3086 else {
3087 // no spaces to compress
3088 Callsign = Temp;
3089 }
3090 }
3091 }
3092 break;
3093
3094 case RTCP_SDES_TOOL:
3095 x = strlen(PACKAGE);
3096 if(Item.p->length > 0 && pCC->Tool == NULL) {
3097 // Save the Client's tool name
3098 if((pCC->Tool = malloc(Item.p->length+1)) != NULL) {
3099 memcpy(pCC->Tool,Item.p->data,Item.p->length);
3100 pCC->Tool[Item.p->length] = 0;
3101 }
3102 }
3103
3104 if(Item.p->length >= x && strncmp(Item.p->data,PACKAGE,x) == 0) {
3105 // One of our guys, set the flags
3106 pCC->bConf = pCC->bTBD = pCC->bSendSSRC = TRUE;
3107 }
3108 else if(Item.p->length > 6 && Item.p->data[0] == 'E') {
3109 // Possible EchoLink version
3110 Temp = malloc(Item.p->length+1);
3111
3112 if(Temp != NULL) {
3113 memcpy(Temp,Item.p->data,Item.p->length);
3114 Temp[Item.p->length] = 0;
3115 if(sscanf(&Temp[1],"%d.%d.%d",&EchoLinkRevMajor,
3116 &EchoLinkRevMinor,&EchoLinkBuild) == 3)
3117 { // Yup, it's a (recent) EchoLink client
3118 pCC->bSendSSRC = TRUE;
3119 }
3120 free(Temp);
3121 }
3122 }
3123 break;
3124
3125 case RTCP_SDES_PHONE: // We use the phone number field as a password
3126 if(RTP_Pass != NULL) {
3127 if(pCC->Password != NULL) {
3128 free(pCC->Password);
3129 }
3130
3131 pCC->Password = (char *) malloc(Item.p->length+1);
3132
3133 if(pCC->Password != NULL) {
3134 memcpy(pCC->Password,Item.p->data,Item.p->length);
3135 pCC->Password[Item.p->length] = 0;
3136 }
3137 }
3138 break;
3139
3140 case RTCP_SDES_PRIV:
3141 if(Item.p->length >= 3 && strncmp(Item.p->data,"txt",3) == 0) {
3142 // Our 'talker" or current station
3143 pCC->bTxtExtension = TRUE;
3144 if(TextData != NULL) {
3145 free(TextData);
3146 TextData = NULL;
3147 }
3148
3149 if(Item.p->length > 3) {
3150 TextData = (char *) malloc(Item.p->length-3+1);
3151
3152 if(TextData != NULL) {
3153 memcpy(TextData,&Item.p->data[3],Item.p->length-3);
3154 TextData[Item.p->length-3] = 0;
3155 }
3156 }
3157 }
3158 if(Item.p->length == 5 && strncmp(Item.p->data,FDUPLEX_TEXT,5) == 0) {
3159 pCC->bFullDuplex = TRUE;
3160 pCC->bSendSSRC = TRUE;
3161 }
3162 break;
3163
3164 default:
3165 break;
3166 }
3167 Item.cp = Item.cp + sizeof(rtcp_sdes_item_t) + Item.p->length - 1;
3168 if(Item.cp >= end) {
3169 // bogus packet RTCP_SDES packet
3170 BadRTCPPacketCount++;
3171 break;
3172 }
3173 }
3174
3175 if(!bFoundCallsign) {
3176 if(Callsign != NULL) {
3177 free(Callsign);
3178 Callsign = NULL;
3179 }
3180 // Only accept a password if the callsign is also specified
3181 if(pCC->Password != NULL) {
3182 free(pCC->Password);
3183 pCC->Password = NULL;
3184 }
3185 }
3186
3187 if(Callsign != NULL && pCC->CallPlus != NULL) {
3188 if(strcmp(Callsign,pCC->CallPlus) != 0) {
3189 // the callsign/name field has changed
3190 if(pCC == ClientTalking) {
3191 // This guy is currently talking, force a station list update.
3192 pCS->bSendStationList = TRUE;
3193 }
3194 }
3195 else if(TextData == NULL) {
3196 // Nothing has changed, nothing to do...
3197 if(Callsign != NULL) {
3198 free(Callsign);
3199 Callsign = NULL;
3200 }
3201 if(pCC->Password != NULL) {
3202 free(pCC->Password);
3203 pCC->Password = NULL;
3204 }
3205 }
3206 }
3207
3208 if(Callsign != NULL) {
3209 if(Callsign[0] == '*') {
3210 pCC->bConf = TRUE;
3211 }
3212
3213 if((cp = strchr(Callsign,' ')) != NULL) {
3214 // Remove the name from the callsign/name string
3215 *cp = 0;
3216 }
3217
3218 if(!pCC->bCallsignOverride) {
3219 if(pCC->Callsign != NULL) {
3220 free(pCC->Callsign);
3221 }
3222 pCC->Callsign = strdup(Callsign);
3223
3224 if(pCC->CallPlus != NULL) {
3225 free(pCC->CallPlus);
3226 }
3227
3228 if(TextData != NULL) {
3229 // "txt" data was present in SDES, use it
3230 pCC->CallPlus = malloc(strlen(Callsign)+strlen(TextData) + 2);
3231 if(pCC->CallPlus != NULL) {
3232 strcpy(pCC->CallPlus,Callsign);
3233 strcat(pCC->CallPlus," ");
3234 strcat(pCC->CallPlus,TextData);
3235 }
3236 }
3237 else {
3238 if(cp != NULL) {
3239 // Restore the name to the end of the callsign
3240 *cp = ' ';
3241 }
3242 if(pCC->bConf && (cp = strchr(Callsign,')')) != NULL) {
3243 // Remove "CONF" from end of the string
3244 cp[1] = 0;
3245 }
3246 pCC->CallPlus = Callsign;
3247 }
3248 }
3249
3250 if(strstr(pCC->Callsign,"-L")) {
3251 pCC->bLink = TRUE;
3252 }
3253 else if(strstr(pCC->Callsign,"-R")) {
3254 pCC->bRepeater = TRUE;
3255 }
3256 else if(pCC->Proto == PROTO_SF) {
3257 if(strncmp(pCC->Callsign,"stn",3) == 0) {
3258 pCC->bRepeater = TRUE;
3259 pCC->bIRLP = TRUE;
3260 }
3261 else if(strncmp(pCC->Callsign,"exp",3) == 0) {
3262 pCC->bRepeater = TRUE;
3263 pCC->bIRLP = TRUE;
3264 }
3265 else if(strncmp(pCC->Callsign,"ref",3) == 0) {
3266 pCC->bConf = TRUE;
3267 pCC->bIRLP = TRUE;
3268 }
3269 }
3270
3271 if(pCC->bLink || pCC->bRepeater || pCC->bConf) {
3272 // a Potential belcher
3273 pCC->bBelcher = TRUE;
3274 }
3275 }
3276
3277 if(TextData != NULL) {
3278 free(TextData);
3279 }
3280
3281 return Ret;
3282 }
3283
GetUserCountString()3284 char *GetUserCountString()
3285 {
3286 static char Temp[80];
3287
3288 Temp[0] = 0;
3289 if(UserCountEnable) {
3290 if(MaxCountEnable) {
3291 snprintf(Temp,sizeof(Temp)," [%d/%d]",ConferenceClients,
3292 MaxConferenceClients);
3293 }
3294 else {
3295 snprintf(Temp,sizeof(Temp)," [%d]",ConferenceClients);
3296 }
3297 }
3298
3299 return Temp;
3300 }
3301
3302
GenStationList(ClientInfo * p)3303 void GenStationList(ClientInfo *p)
3304 {
3305 ConfClient *pCC = StationList;
3306
3307 while(pCC != NULL) {
3308 if(pCC->bConnected && (!pCC->bLurking || bLurkDisabled)) {
3309 if(pCC == ClientTalking) {
3310 BufPrintf(p,"->%s\r",pCC->CallPlus);
3311 }
3312 else if(pCC->bMuted) {
3313 BufPrintf(p,"%s (Muted)\r",pCC->Callsign);
3314 }
3315 else if(pCC->bTimedOut) {
3316 BufPrintf(p,"%s (Timed Out)\r",pCC->Callsign);
3317 }
3318 else if(pCC->bMonitor && strstr(pCC->CallPlus,"(Confer") != NULL) {
3319 BufPrintf(p,"%s (Receive only)\r",pCC->Callsign);
3320 }
3321 else {
3322 BufPrintf(p,"%s\r",pCC->CallPlus);
3323 }
3324 }
3325 pCC = pCC->Link;
3326 }
3327 }
3328
SendStationList(ConfServer * pCS)3329 void SendStationList(ConfServer *pCS)
3330 {
3331 struct avl_traverser avl_trans;
3332 ConfClient *pCC;
3333 ClientInfo *p = pCS->pAudio;
3334 int bSendingInfo = FALSE;
3335
3336 p->Count = 0; // init for BufPrintf
3337 BufPrintf(p,"%c" NDATA,ILINK_DATA_PACKET);
3338
3339 if(ShowStationInfo &&
3340 ClientTalking != NULL &&
3341 ClientTalking->Info != NULL &&
3342 !ClientTalking->bSentInfo)
3343 { // Show new client's info
3344 ClientTalking->bSentInfo = TRUE;
3345 bSendingInfo = TRUE;
3346 BufPrintf(p,"\rAbout %s:%s",ClientTalking->Callsign,ClientTalking->Info);
3347 }
3348 else {
3349 BufPrintf(p,"CONF %s%s",ConferenceID,GetUserCountString());
3350 if(SB_Enable && ClientTalking == NULL) {
3351 BufPrintf(p," <SB>");
3352 }
3353
3354 BufPrintf(p,"\r\r");
3355 GenStationList(p);
3356
3357 if(Banner != NULL) {
3358 BufPrintf(p,"\r%s\r",Banner);
3359 }
3360 }
3361
3362 if(p->Count >= sizeof(StatusMsg)) {
3363 p->Count = sizeof(StatusMsg) - 1;
3364 }
3365 p->Buf[p->Count++] = 0;
3366 StatusMsgLen = p->Count;
3367 memcpy(StatusMsg,p->Buf,StatusMsgLen);
3368
3369 #if defined _WIN32GUI || defined _X11GUI
3370 UpdateGui(GUIUPDATETYPE_STN,StatusMsg,StatusMsgLen);
3371 #endif
3372
3373 pCC = (ConfClient *) avl_t_first(&avl_trans,pCS->ConfTree);
3374
3375 while(pCC != NULL) {
3376 if(ClientTalking == NULL) {
3377 // Reset everyone's warning flags
3378 pCC->bDoublingWarningSent = FALSE;
3379 pCC->bPauseWarningSent = FALSE;
3380 }
3381 else {
3382 // Reset everyone's welcome pending flags
3383 pCC->bWelcomePending = FALSE;
3384 }
3385
3386 if(pCC->bInConf && !pCC->bConf && pCC->Proto == PROTO_ILINK) {
3387 Send2(pCC,StatusMsg,StatusMsgLen,FALSE);
3388 }
3389 pCC = (ConfClient *) avl_t_next(&avl_trans);
3390 }
3391 }
3392
GenSDES(int rtp_version,int bTxtExtension)3393 void GenSDES(int rtp_version,int bTxtExtension)
3394 {
3395 char PadCount;
3396 char *pFirstData;
3397 char CharSave = 0;
3398 time_t timenow = (time_t) TimeNow.tv_sec;
3399 struct tm *tm = localtime(&timenow);
3400 char *cp, *cp1, *cp2;
3401 ConfClient *pCC;
3402 char *Talker = NULL;
3403 int bConferencing = ConfEnable;
3404 union {
3405 char *cp;
3406 rtcp_t *p;
3407 } u;
3408
3409 union {
3410 char *cp;
3411 rtcp_sdes_item_t *p;
3412 } Item;
3413 int i = rtp_version - 1;
3414
3415
3416 if(rtp_version != ILINK_RTP_VERSION && OurNodeID == 0) {
3417 // Don't generate an SDES for SF or RTP until we have a node number
3418 return;
3419 }
3420
3421 gProtoData[i].bTxtExtension = bTxtExtension;
3422 u.cp = gProtoData[i].OurSDES;
3423
3424 u.p->common.version = rtp_version;
3425 u.p->common.p = 0;
3426 u.p->common.count = 0;
3427 u.p->common.pt = RTCP_RR;
3428 u.p->common.length = htons(1);
3429 u.p->r.rr.ssrc = OurNodeID;
3430
3431 u.cp += sizeof(u.p->common) + sizeof(u.p->r.rr.ssrc);
3432
3433 if(rtp_version == SF_RTP_VERSION) {
3434 // Only the first RTCP packet is version 1 on Speek freely, the
3435 // remaining packets report the standard version
3436 rtp_version = RTP_VERSION;
3437 }
3438
3439 u.p->common.version = rtp_version;
3440 u.p->common.p = 1;
3441 u.p->common.count = 1;
3442 u.p->common.pt = RTCP_SDES;
3443
3444 u.p->r.sdes.src = OurNodeID;
3445 pFirstData = (char *) &u.p->r.sdes.src;
3446
3447 Item.p = &u.p->r.sdes.item[0];
3448 Item.p->type = RTCP_SDES_CNAME;
3449 Item.p->length = (u_int8) strlen(CallSignString);
3450 memcpy(Item.p->data,CallSignString,Item.p->length);
3451
3452 Item.cp = Item.cp + sizeof(rtcp_sdes_item_t) + Item.p->length - 1;
3453 Item.p->type = RTCP_SDES_NAME;
3454
3455 if(rtp_version == ILINK_RTP_VERSION) {
3456
3457 if((pCC = ClientTalking) != NULL) {
3458 cp1 = NULL;
3459 cp = strchr(pCC->CallPlus,' ');
3460
3461 if(cp != NULL && cp[1] == '(') {
3462 while((cp = strchr(&cp[1],'(')) != NULL) {
3463 cp1 = cp;
3464 }
3465 }
3466
3467 if(cp1 != NULL && (cp2 = strchr(cp1,')')) != NULL) {
3468 // CallPlus plus contains (<stuff>), just pass along the <stuff>
3469 CharSave = *cp2;
3470 *cp2 = 0;
3471 Talker = strdup(&cp1[1]);
3472 *cp2 = CharSave;
3473 }
3474 else {
3475 if((cp = strchr(ClientTalking->CallPlus,' ')) != NULL) {
3476 while(*cp == ' ') {
3477 cp++;
3478 }
3479 while(isalnum(*cp)) {
3480 cp++;
3481 }
3482
3483 // truncate name at after first non-alphanumeric character
3484 CharSave = *cp;
3485 *cp = 0;
3486 }
3487 Talker = strdup(ClientTalking->CallPlus);
3488
3489 // restore the original character
3490 if(cp != NULL) {
3491 *cp = CharSave;
3492 }
3493 }
3494 }
3495
3496 if(SF_Port != SF_ReplyPort) {
3497 // Configured for EchoIRLP
3498 if(ConferenceClients < 3) {
3499 // EchoIRLP has an internal connection so 2 connections isn't
3500 // really a conference
3501 bConferencing = FALSE;
3502 }
3503 }
3504 #ifdef LINK_BOX
3505 else {
3506 if(ConferenceClients < 2) {
3507 bConferencing = FALSE;
3508 }
3509 }
3510 #endif
3511
3512 if(!bConferencing) {
3513 if(UserName != NULL) {
3514 sprintf(Item.p->data,"%s %s",ConferenceCall,UserName);
3515 }
3516 else {
3517 strcpy(Item.p->data,ConferenceCall);
3518 }
3519 }
3520 else if(Talker != NULL) {
3521 sprintf(Item.p->data,"%s (%s) CONF",ConferenceCall,Talker);
3522 }
3523 else {
3524 sprintf(Item.p->data,"%s (Conference %s) CONF",ConferenceCall,
3525 GetUserCountString());
3526 }
3527
3528 Item.p->length = (u_int8) strlen(Item.p->data);
3529
3530 Item.cp = Item.cp + sizeof(rtcp_sdes_item_t) + Item.p->length - 1;
3531 Item.p->type = RTCP_SDES_EMAIL;
3532 Item.p->length = (u_int8) strlen(CallSignString);
3533 memcpy(Item.p->data,CallSignString,Item.p->length);
3534
3535 Item.cp = Item.cp + sizeof(rtcp_sdes_item_t) + Item.p->length - 1;
3536 Item.p->type = RTCP_SDES_PHONE;
3537 sprintf(Item.p->data,"%02d:%02d",tm->tm_hour,tm->tm_min);
3538 Item.p->length = (u_int8) strlen(Item.p->data);
3539 }
3540 else {
3541 // RTP / Speak freely
3542 if(FullName != NULL) {
3543 strcpy(Item.p->data,FullName);
3544 }
3545 else if(UserName != NULL) {
3546 sprintf(Item.p->data,"%s %s",ConferenceCall,UserName);
3547 }
3548 else {
3549 strcpy(Item.p->data,ConferenceCall);
3550 }
3551 Item.p->length = (u_int8) strlen(Item.p->data);
3552 }
3553
3554 // Set tool item so another conference can identify us as thebridge
3555 Item.cp = Item.cp + sizeof(rtcp_sdes_item_t) + Item.p->length - 1;
3556 Item.p->type = RTCP_SDES_TOOL;
3557 sprintf(Item.p->data,PACKAGE " V " VERSION);
3558
3559 Item.p->length = (u_int8) strlen(Item.p->data);
3560
3561 if(rtp_version == ILINK_RTP_VERSION && bTxtExtension && ConfEnable) {
3562 // Set
3563 Item.cp = Item.cp + sizeof(rtcp_sdes_item_t) + Item.p->length - 1;
3564 Item.p->type = RTCP_SDES_PRIV;
3565
3566 if(Talker != NULL) {
3567 sprintf(Item.p->data,"txt(%s)",Talker);
3568 }
3569 else {
3570 sprintf(Item.p->data,"txt(Conference%s)",GetUserCountString());
3571 }
3572 Item.p->length = (u_int8) strlen(Item.p->data);
3573 }
3574
3575 // enable remote DTMF pad
3576 if(EnableRemoteDTMF) {
3577 Item.cp = Item.cp + sizeof(rtcp_sdes_item_t) + Item.p->length - 1;
3578 Item.p->type = RTCP_SDES_PRIV;
3579 Item.p->length = 3;
3580 memcpy(Item.p->data,"\001D1",3);
3581 }
3582
3583 if(FullDuplex) {
3584 // We're capable of running full duplex
3585 Item.cp = Item.cp + sizeof(rtcp_sdes_item_t) + Item.p->length - 1;
3586 Item.p->type = RTCP_SDES_PRIV;
3587 Item.p->length = 5;
3588 memcpy(Item.p->data,"\003dpx1",5);
3589 }
3590
3591 Item.cp = Item.cp + sizeof(rtcp_sdes_item_t) + Item.p->length - 1;
3592 Item.p->type = RTCP_SDES_END ;
3593 Item.p->length = 0;
3594 Item.cp = Item.cp + sizeof(rtcp_sdes_item_t) + Item.p->length - 1;
3595
3596 PadCount = 4 - ((Item.cp - pFirstData) % 4);
3597
3598 if(PadCount > 0 && PadCount < 4) {
3599 // Padding is needed
3600 u.p->common.p = 1;
3601 if(PadCount > 1) {
3602 memset(Item.cp,0,PadCount-1);
3603 }
3604 Item.cp[PadCount-1] = PadCount;
3605 Item.cp += PadCount;
3606 }
3607 else {
3608 // No padding needed
3609 u.p->common.p = 0;
3610 }
3611 u.p->common.length = htons((u_short) ((Item.cp - pFirstData)/4));
3612 gProtoData[i].SDESLen = Item.cp - gProtoData[i].OurSDES;
3613
3614 if(Talker != NULL) {
3615 free(Talker);
3616 }
3617 }
3618
GenBye(ConfClient * pCC,char * Temp,char * Reason)3619 int GenBye(ConfClient *pCC,char *Temp,char *Reason)
3620 {
3621 char PadCount;
3622 char *pFirstData;
3623 char *cp;
3624
3625 union {
3626 char *cp;
3627 rtcp_t *p;
3628 } u;
3629
3630 u.cp = Temp;
3631
3632 u.p->common.version = pCC->Proto + 1;
3633 u.p->common.p = 0;
3634 u.p->common.count = 0;
3635 u.p->common.pt = RTCP_RR;
3636 u.p->common.length = htons(1);
3637 u.p->r.rr.ssrc = 0;
3638
3639 u.cp += sizeof(u.p->common) + sizeof(u.p->r.rr.ssrc);
3640
3641 // NB: SF has version 1 as the in the first chunk, but version 2 in
3642 // the remaining chunks.
3643 u.p->common.version = pCC->Proto == PROTO_ILINK ? 3 : 2;
3644 u.p->common.count = 1;
3645 u.p->common.pt = RTCP_BYE;
3646
3647 u.p->r.bye.src[0] = OurNodeID;
3648 pFirstData = (char *) &u.p->r.bye.src;
3649 cp = pFirstData + sizeof(u.p->r.bye.src);
3650
3651 strcpy(&cp[1],Reason);
3652
3653 *cp = (char) strlen(Reason);
3654 cp += *cp + 1;
3655
3656 PadCount = 4 - ((cp - pFirstData) % 4);
3657
3658 if(PadCount > 0 && PadCount < 4) {
3659 // Padding is needed
3660 u.p->common.p = 1;
3661 if(PadCount > 1) {
3662 memset(cp,0,PadCount-1);
3663 }
3664 cp[PadCount-1] = PadCount;
3665 cp += PadCount;
3666 }
3667 else {
3668 // No padding needed
3669 u.p->common.p = 0;
3670 }
3671 u.p->common.length = htons((u_short) ((cp - pFirstData)/4));
3672 return cp - Temp;
3673 }
3674
3675 // Convert string to upper case
Convert2Upper(char * cp)3676 void Convert2Upper(char *cp)
3677 {
3678 while(*cp) {
3679 *cp = toupper(*cp);
3680 cp++;
3681 }
3682 }
3683
SetLurkMode(ClientInfo * p,char * Arg,int bEnable)3684 void SetLurkMode(ClientInfo *p,char *Arg,int bEnable)
3685 {
3686 ConfClient *pCC;
3687 struct avl_traverser avl_trans;
3688 ConfServer *pCS = (ConfServer *) p->p;
3689
3690 // Try to find a station matching the specified callsign
3691 pCC = (ConfClient *) avl_t_first(&avl_trans,pCS->ConfTree);
3692 while(pCC != NULL) {
3693 if(STRCMP(Arg,pCC->Callsign) == 0) {
3694 if((pCC->bLurking && !bEnable) || (!pCC->bLurking && bEnable)) {
3695 pCC->bLurking = bEnable;
3696 pCC->bAutoLurk = bEnable;
3697 pCS->bSendStationList = TRUE;
3698 }
3699 break;
3700 }
3701 pCC = (ConfClient *) avl_t_next(&avl_trans);
3702 }
3703
3704 if(pCC == NULL) {
3705 BufPrintf(p,"Say what?\r");
3706 }
3707 }
3708
CmdLurk(ClientInfo * p,ConfClient * pCC,char * Arg)3709 void CmdLurk(ClientInfo *p,ConfClient *pCC,char *Arg)
3710 {
3711 ConfServer *pCS = (ConfServer *) p->p;
3712 int bLurkModeSet = FALSE;
3713
3714 if(pCC->bSysop && *Arg) {
3715 if(STRCMP(Arg,"enable") == 0) {
3716 bLurkDisabled = FALSE;
3717 BufPrintf(p,"Lurking enabled\r");
3718 }
3719 else if(STRCMP(Arg,"disable") == 0) {
3720 bLurkDisabled = TRUE;
3721 BufPrintf(p,"Lurking disabled\r");
3722 }
3723 else {
3724 SetLurkMode(p,Arg,TRUE);
3725 }
3726 bLurkModeSet = TRUE;
3727 }
3728
3729 if(!bLurkModeSet) {
3730 if(bLurkDisabled) {
3731 BufPrintf(p,"Sorry, the administrator of this conference has "
3732 "disabled the .lurk command\r");
3733 }
3734 else if(!pCC->bLurking) {
3735 pCC->bAutoLurk = TRUE;
3736 pCC->bLurking = TRUE;
3737 pCS->bSendStationList = TRUE;
3738 }
3739 }
3740 }
3741
CmdDelurk(ClientInfo * p,ConfClient * pCC,char * Arg)3742 void CmdDelurk(ClientInfo *p,ConfClient *pCC,char *Arg)
3743 {
3744 ConfServer *pCS = (ConfServer *) p->p;
3745
3746 if(pCC->bSysop && *Arg) {
3747 SetLurkMode(p,Arg,FALSE);
3748 }
3749 else {
3750 pCC->bLurking = FALSE;
3751 pCC->bAutoLurk = FALSE;
3752 if(pCC->bLurking) {
3753 pCS->bSendStationList = TRUE;
3754 }
3755 }
3756 }
3757
CmdLurkers(ClientInfo * p,ConfClient * pCC1,char * Arg)3758 void CmdLurkers(ClientInfo *p,ConfClient *pCC1,char *Arg)
3759 {
3760 ConfClient *pCC;
3761 struct avl_traverser avl_trans;
3762 int Lurkers = 0;
3763 ConfServer *pCS = (ConfServer *) p->p;
3764
3765
3766 pCC = (ConfClient *) avl_t_first(&avl_trans,pCS->ConfTree);
3767 while(pCC != NULL) {
3768 if(pCC->bInConf && pCC->bLurking) {
3769 if(Lurkers == 0) {
3770 BufPrintf(p,"Lurkers:\r");
3771 }
3772 BufPrintf(p,"%s\r",pCC->CallPlus);
3773 Lurkers++;
3774 }
3775 pCC = (ConfClient *) avl_t_next(&avl_trans);
3776 }
3777
3778 if(Lurkers == 0) {
3779 BufPrintf(p,"I see no lurkers here, try down below\r");
3780 }
3781 }
3782
CmdUpTime(ClientInfo * p,ConfClient * pCC1,char * Arg)3783 void CmdUpTime(ClientInfo *p,ConfClient *pCC1,char *Arg)
3784 {
3785 BufPrintf(p,"Uptime: %s\r",TimeLapse2String(BootTime,FALSE));
3786 }
3787
CmdShutdown(ClientInfo * p,ConfClient * pCC1,char * Arg)3788 void CmdShutdown(ClientInfo *p,ConfClient *pCC1,char *Arg)
3789 {
3790 BufPrintf(p,"Shutting down\r");
3791 bShutdown = TRUE;
3792 }
3793
CmdStats(ClientInfo * p,ConfClient * pCC1,char * Arg)3794 void CmdStats(ClientInfo *p,ConfClient *pCC1,char *Arg)
3795 {
3796 ConfServer *pCS = (ConfServer *) p->p;
3797 u_int TxBytes = pCS->TxBytes;
3798 u_int TxMBytes = pCS->TxMBytes;
3799 u_int RxBytes = pCS->RxBytes;
3800 u_int RxMBytes = pCS->RxMBytes;
3801
3802
3803 CalcBW(pCS,FALSE);
3804 CmdUpTime(p,pCC1,Arg);
3805
3806
3807 if(TxBytes > 1000000) {
3808 TxMBytes += TxBytes / 1000000;
3809 TxBytes %= 1000000;
3810 }
3811
3812 if(RxBytes > 1000000) {
3813 RxMBytes += RxBytes / 1000000;
3814 RxBytes %= 1000000;
3815 }
3816
3817 BufPrintf(p,"Tx packets: %d, ",pCS->TxCount);
3818 BufPrintf(p,"%d.%02d Mbytes\r",TxMBytes,TxBytes*100/1000000);
3819 BufPrintf(p,"Rx packets: %d, ",pCS->RxCount);
3820 BufPrintf(p,"%d.%02d Mbytes\r",RxMBytes,RxBytes*100/1000000);
3821 BufPrintf(p,"Connections: %d\r",ClientConnects);
3822
3823 BufPrintf(p,"Users: %d of %d Max\r",ConferenceClients,MaxConferenceClients);
3824 BufPrintf(p,"Bandwidth Tx: %d Kbps, ",pCS->TxBandWidth);
3825 BufPrintf(p,"Rx: %d Kbps\r",pCS->RxBandWidth);
3826
3827 BufPrintf(p,"Peak users: %d, %s\r",PeakClients,TimeT2String(PeakClientTime));
3828 BufPrintf(p,"Peak Tx bandwidth: %d Kbps @ %s\r",pCS->PeakTxBandWidth,
3829 TimeT2String(pCS->PeakTxBandWidthTime));
3830 BufPrintf(p,"Peak Rx bandwidth: %d Kbps @ %s\r",pCS->PeakRxBandWidth,
3831 TimeT2String(pCS->PeakRxBandWidthTime));
3832 }
3833
CmdVersion(ClientInfo * p,ConfClient * pCC1,char * Arg)3834 void CmdVersion(ClientInfo *p,ConfClient *pCC1,char *Arg)
3835 {
3836 BufPrintf(p,PACKAGE " V " VERSION " on " OUR_HOST "\r");
3837 }
3838
3839 #ifndef _WIN32
3840 extern int CallBacksRegistered;
3841 extern int SigChilds;
3842 extern int ChildExits;
3843 #endif
3844
CmdDebug(ClientInfo * p,ConfClient * pCC,char * Arg)3845 void CmdDebug(ClientInfo *p,ConfClient *pCC,char *Arg)
3846 {
3847 int i;
3848 ConfServer *pCS = (ConfServer *) p->p;
3849
3850 #ifndef _WIN32
3851 BufPrintf(p,"CallBacksRegistered: %d\r",CallBacksRegistered);
3852 BufPrintf(p,"SigChilds: %d\r",SigChilds);
3853 BufPrintf(p,"ChildExits: %d\r",ChildExits);
3854 #endif
3855 BufPrintf(p,"OpenSockets: %d\r",OpenSockets);
3856 BufPrintf(p,"ClientTree entries: %d\r",ClientTree->avl_count);
3857 BufPrintf(p,"ConfTree entries: %d\r",pCS->ConfTree->avl_count);
3858 BufPrintf(p,"DuplicateClientsDeleted: %d\r",DuplicateClientsDeleted);
3859 BufPrintf(p,"Rx errors: %d\r",pCS->RxErrs);
3860 BufPrintf(p,"Tx errors: %d\r",pCS->TxErrs);
3861 BufPrintf(p,"Bad RTCP packets Rxed: %d\r",BadRTCPPacketCount);
3862 BufPrintf(p,"Node #: %d\r",ntohl(OurNodeID));
3863 BufPrintf(p,"EchoLink Auth Failures: %d\r",EchoAuthenticationFailures);
3864 BufPrintf(p,"EchoLink Auth IP compare failures: %d\r",EchoLinkIPCompareFailures);
3865 BufPrintf(p,"Other Auth Failures: %d\r",AuthenticationFailures);
3866 BufPrintf(p,"Active Dir entries: %d\r",ActiveDirEntries);
3867 BufPrintf(p,"Inactive Dir entries: %d\r",InactiveDirEntries);
3868 BufPrintf(p,"BelchTime: %d\r",BelchTime);
3869 BufPrintf(p,"PauseTime: %d\r",PauseTime);
3870 BufPrintf(p,"BlabOffTimer: %d\r",BlabOffTimer);
3871 BufPrintf(p,"ZeroSSRC: %d\r",ZeroSSRC);
3872 BufPrintf(p,"EventProcessRunning: %d\r",EventProcessRunning);
3873 BufPrintf(p,"DupsReceived: %d\r",pCS->DupsReceived);
3874 BufPrintf(p,"DupDisconnects: %d\r",pCS->DupDisconnects);
3875
3876 BufPrintf(p,"Server: Requested, Succeed, Failed\r");
3877 for(i = 0; i < NUM_DIRECTORY_SERVERS; i++) {
3878 if(DirServerHost[i] == NULL || ServerStats[i].Requests == 0) {
3879 break;
3880 }
3881 BufPrintf(p,"%s: %d, %d, %d\r",DirServerHost[i],
3882 ServerStats[i].Requests,ServerStats[i].Success,
3883 ServerStats[i].Failure);
3884 }
3885 }
3886
CmdAbout(ClientInfo * p,ConfClient * pCC1,char * Arg)3887 void CmdAbout(ClientInfo *p,ConfClient *pCC1,char *Arg)
3888 {
3889 ConfClient *pCC;
3890 struct avl_traverser avl_trans;
3891 ConfServer *pCS = (ConfServer *) p->p;
3892
3893 pCC = (ConfClient *) avl_t_first(&avl_trans,pCS->ConfTree);
3894 while(pCC != NULL) {
3895 if(STRCMP(Arg,pCC->Callsign) == 0) {
3896 if(pCC->Info != NULL) {
3897 BufPrintf(p,"\rAbout %s:%s",pCC->Callsign,pCC->Info);
3898 }
3899 else {
3900 BufPrintf(p,"Sorry I don't know anything about %s.\r",Arg);
3901 Rcode = TBD_STATION_NO_INFO;
3902 }
3903 break;
3904 }
3905 pCC = (ConfClient *) avl_t_next(&avl_trans);
3906 }
3907
3908 if(pCC == NULL) {
3909 BufPrintf(p,"%s not found\r",Arg);
3910 Rcode = TBD_STATION_NOT_FOUND;
3911 }
3912 }
3913
CmdAdmin(ClientInfo * p,ConfClient * pCC,char * Arg)3914 void CmdAdmin(ClientInfo *p,ConfClient *pCC,char *Arg)
3915 {
3916 if(AdminPass != NULL && STRCMP(AdminPass,Arg) == 0) {
3917 pCC->bAdmin = TRUE;
3918 pCC->bSysop = TRUE;
3919 pCC->bMuted = FALSE;
3920 BufPrintf(p,"Your wish is my command.\r");
3921 LOG_NORM(("Admin %s logged in\n",pCC->Callsign));
3922 }
3923 }
3924
CmdSysop(ClientInfo * p,ConfClient * pCC,char * Arg)3925 void CmdSysop(ClientInfo *p,ConfClient *pCC,char *Arg)
3926 {
3927 if(SysopPass != NULL && strcmp(SysopPass,Arg) == 0) {
3928 pCC->bSysop = TRUE;
3929 pCC->bMuted = FALSE;
3930 BufPrintf(p,"By your command.\r");
3931 LOG_NORM(("Sysop %s logged in\n",pCC->Callsign));
3932 }
3933 }
3934
CmdAdmins(ClientInfo * p,ConfClient * pCC1,char * Arg)3935 void CmdAdmins(ClientInfo *p,ConfClient *pCC1,char *Arg)
3936 {
3937 ConfClient *pCC;
3938 struct avl_traverser avl_trans;
3939 ConfServer *pCS = (ConfServer *) p->p;
3940
3941 BufPrintf(p,"Admins:\r");
3942 pCC = (ConfClient *) avl_t_first(&avl_trans,pCS->ConfTree);
3943 while(pCC != NULL) {
3944 if(pCC->bAdmin) {
3945 BufPrintf(p,"%s\r",pCC->CallPlus);
3946 }
3947 pCC = (ConfClient *) avl_t_next(&avl_trans);
3948 }
3949 BufPrintf(p,"Sysops:\r");
3950 pCC = (ConfClient *) avl_t_first(&avl_trans,pCS->ConfTree);
3951 while(pCC != NULL) {
3952 if(!pCC->bAdmin && pCC->bSysop) {
3953 BufPrintf(p,"%s\r",pCC->CallPlus);
3954 }
3955 pCC = (ConfClient *) avl_t_next(&avl_trans);
3956 }
3957 }
3958
SortCClientsByTalkTime(const void * p,const void * p1)3959 int SortCClientsByTalkTime(const void *p, const void *p1)
3960 {
3961 return (*(ConfClient **)p1)->LastAudioIn.tv_sec -
3962 (*(ConfClient **)p)->LastAudioIn.tv_sec;
3963 }
3964
SortCClientsBySN(const void * p,const void * p1)3965 int SortCClientsBySN(const void *p, const void *p1)
3966 {
3967 return (*(ConfClient **)p)->SN - (*(ConfClient **)p1)->SN;
3968 }
3969
CmdUsers(ClientInfo * p,ConfClient * pCC1,char * Args)3970 void CmdUsers(ClientInfo *p,ConfClient *pCC1,char *Args)
3971 {
3972 struct avl_traverser avl_trans;
3973 size_t i = 0;
3974 ConfServer *pCS = (ConfServer *) p->p;
3975 int CClientsSize = pCS->ConfTree->avl_count * sizeof(ConfClient *);
3976 ConfClient **CClients = NULL;
3977 ConfClient *pCC;
3978 char Line[80];
3979 char *cp;
3980 char *Arg = Args;
3981 int Option = 0;
3982 int bDisplayConnectTime = FALSE;
3983 int bDisplayTalkTime = FALSE;
3984 int bDisplayAttributes = TRUE;
3985 int bDisplayTool = FALSE;
3986 int bStationList = FALSE;
3987 int bSortByTalkTime = FALSE;
3988 int BytesLeft;
3989 int Len;
3990
3991 while((Option = GetCmdOptions(&Arg,"bcqstTv")) != -1) {
3992 switch(Option) {
3993 case 'b': // bare
3994 bDisplayAttributes = FALSE;
3995 break;
3996
3997 case 'c': // display connection time
3998 bDisplayConnectTime = TRUE;
3999 break;
4000
4001 case 'q': // Quiet - for CGI polling, suppress logging.
4002 bLogCmd = FALSE;
4003 break;
4004
4005 case 't': // display time since last transmission
4006 bDisplayTalkTime = TRUE;
4007 break;
4008
4009 case 'T': // sort by time since last transmission
4010 bSortByTalkTime = TRUE;
4011 break;
4012
4013 case 'v': // display client's "tool" and version number
4014 bDisplayTool = TRUE;
4015 break;
4016
4017 case 's': // display station list
4018 bStationList = TRUE;
4019 break;
4020
4021 case '?': // invalid switch
4022 BufPrintf(p,"Usage:\r");
4023 BufPrintf(p,"users [-b] [-c] [-t] [-T] [-v] [?]\r");
4024 BufPrintf(p," -b - bare (suppress user attributes)\r");
4025 BufPrintf(p," -c - display time user has been connected\r");
4026 BufPrintf(p," -q - Quiet (don't log command)\r");
4027 BufPrintf(p," -t - display time since user last transmitted\r");
4028 BufPrintf(p," -T - sort by time since last transmission\r");
4029 BufPrintf(p," -s - display in station list format\r");
4030 BufPrintf(p," -v - display version number of user's client\r");
4031 BufPrintf(p," ? - display User attribute characters\r");
4032 break;
4033 }
4034 }
4035
4036 do {
4037 if(*Arg == '?') {
4038 BufPrintf(p,"User attributes:\r");
4039 BufPrintf(p,"0 Speak Freely protocol\r");
4040 BufPrintf(p,"1 RTP protocol\r");
4041 BufPrintf(p,"7 G.726 codec\r");
4042 BufPrintf(p,"A Admin\r");
4043 BufPrintf(p,"a ADCPM codec\r");
4044 BufPrintf(p,"B theBridge conference\r");
4045 BufPrintf(p,"C other conference\r");
4046 BufPrintf(p,"c Chat text suppressed\r");
4047 BufPrintf(p,"F playing File\r");
4048 BufPrintf(p,"f Full duplex\r");
4049 BufPrintf(p,"I Isolated (not In conference)\r");
4050 BufPrintf(p,"K Kicked\r");
4051 BufPrintf(p,"L Lurker\r");
4052 BufPrintf(p,"m audio Muted\r");
4053 BufPrintf(p,"M audio and text Muted\r");
4054 BufPrintf(p,"n Nailed up connection\r");
4055 BufPrintf(p,"P Permanent connection\r");
4056 BufPrintf(p,"R Receive only (monitored)\r");
4057 BufPrintf(p,"S Sysop\r");
4058 BufPrintf(p,"t Text muted\r");
4059 BufPrintf(p,"T Talking\r");
4060 BufPrintf(p,"u uLaw codec\r");
4061 BufPrintf(p,"x inactive\r");
4062 break;
4063 }
4064
4065 if(bStationList) {
4066 GenStationList(p);
4067 }
4068 else {
4069 if((CClients = malloc(CClientsSize)) == NULL) {
4070 LOG_ERROR(("CmdUsers: malloc failed\n"));
4071 break;
4072 }
4073 pCC = (ConfClient *) avl_t_first(&avl_trans,pCS->ConfTree);
4074 while(pCC != NULL) {
4075 CClients[i++] = pCC;
4076 pCC = (ConfClient *) avl_t_next(&avl_trans);
4077 }
4078
4079 qsort(CClients,pCS->ConfTree->avl_count,sizeof(ConfClient *),
4080 bSortByTalkTime? SortCClientsByTalkTime : SortCClientsBySN);
4081
4082 for(i = 0; i < pCS->ConfTree->avl_count; i++) {
4083 pCC = CClients[i];
4084 cp = Line;
4085 *cp = 0;
4086
4087 if(bDisplayAttributes) {
4088 if(pCC->bAdmin) {
4089 *cp++ = 'A';
4090 }
4091 else if(pCC->bSysop) {
4092 *cp++ = 'S';
4093 }
4094
4095 if(!pCC->bInConf) {
4096 *cp++ = 'I';
4097 }
4098
4099 if(pCC->bLurking) {
4100 *cp++ = 'L';
4101 }
4102 else if(pCC->bAutoLurk) {
4103 *cp++ = 'l';
4104 }
4105
4106 if(pCC->bMuted) {
4107 *cp++ = 'm';
4108 }
4109
4110 if(pCC->bSWL) {
4111 *cp++ = 'M';
4112 }
4113
4114 if(pCC->bKicked) {
4115 *cp++ = 'K';
4116 }
4117
4118 if(pCC->bTBD) {
4119 *cp++ = 'B';
4120 }
4121 else if(pCC->bConf) {
4122 *cp++ = 'C';
4123 }
4124
4125 if(pCC->bFileIOActive) {
4126 *cp++ = 'F';
4127 }
4128 else if(!pCC->bConnected) {
4129 *cp++ = 'x';
4130 }
4131
4132 if(pCC->bPermanent) {
4133 *cp++ = 'P';
4134 }
4135
4136 if(pCC->bTalking) {
4137 *cp++ = 'T';
4138 }
4139
4140 if(pCC->bMuteChat) {
4141 *cp++ = 't';
4142 }
4143
4144 else if(pCC->Proto != PROTO_ILINK) {
4145 *cp++ = (char) ('0' + pCC->Proto);
4146 }
4147
4148 if(pCC->bNoChat) {
4149 *cp++ = 'c';
4150 }
4151
4152 if(pCC->bMonitor) {
4153 *cp++ = 'R';
4154 }
4155
4156 if(pCC->bTxtExtension) {
4157 *cp++ = '!';
4158 }
4159
4160 if(pCC->CompressionType == RTP_PT_DVI4_8K) {
4161 *cp++ = 'a';
4162 }
4163
4164 if(pCC->CompressionType == RTP_PT_PCMU) {
4165 *cp++ = 'u';
4166 }
4167
4168 if(pCC->CompressionType == RTP_PT_G726) {
4169 *cp++ = '7';
4170 }
4171
4172 if(pCC->bFullDuplex) {
4173 *cp++ = 'f';
4174 }
4175
4176 if(pCC->bNailed) {
4177 *cp++ = 'n';
4178 }
4179 }
4180 else if(!pCC->bConnected) {
4181 continue;
4182 }
4183
4184 if(bDisplayConnectTime) {
4185 BytesLeft = sizeof(Line)-(cp-Line);
4186 Len = snprintf(cp,BytesLeft,"%s%s%s",
4187 (bDisplayAttributes && Line[0] != 0) ? ", " : "",
4188 bDisplayAttributes ? "connected: " : "\t",
4189 TimeLapse2String(pCC->LoginTime,TRUE));
4190 if(Len != -1 && Len < BytesLeft) {
4191 cp += Len;
4192 }
4193 }
4194
4195 if(bDisplayTalkTime) {
4196 BytesLeft = sizeof(Line)-(cp-Line);
4197 if(pCC->FirstAudioIn.tv_sec != 0) {
4198 Len = snprintf(cp,BytesLeft,"%s%s%s",
4199 (bDisplayAttributes && Line[0] != 0) ? ", " : "",
4200 bDisplayAttributes ? "last tx: " : "\t",
4201 TimeLapse2String(pCC->LastAudioIn.tv_sec,TRUE));
4202 if(Len != -1 && Len < BytesLeft) {
4203 cp += Len;
4204 }
4205 }
4206 }
4207
4208 if(bDisplayTool && pCC->Tool != NULL) {
4209 BytesLeft = sizeof(Line)-(cp-Line);
4210 Len = snprintf(cp,BytesLeft,"%s%s%s",
4211 (bDisplayAttributes && Line[0] != 0) ? ", " : "",
4212 bDisplayAttributes ? "ver: " : "\t",
4213 pCC->Tool);
4214 if(Len != -1 && Len < BytesLeft) {
4215 cp += Len;
4216 }
4217 }
4218 *cp = 0;
4219
4220 BufPrintf(p,"%d. %s %s\r",i+1,pCC->Callsign,Line);
4221 }
4222 free(CClients);
4223 } while(FALSE);
4224 } while(FALSE);
4225 }
4226
CmdMessage(ClientInfo * p,ConfClient * pCC1,char * Arg)4227 void CmdMessage(ClientInfo *p,ConfClient *pCC1,char *Arg)
4228 {
4229 ConfServer *pCS = (ConfServer *) p->p;
4230 struct avl_traverser avl_trans;
4231 ConfClient *pCC = avl_t_first(&avl_trans,pCS->ConfTree);
4232
4233 BufPrintf(p,"%s\r",Arg);
4234 p->Count++;
4235
4236 while(pCC != NULL) {
4237 SendBuf2(p,pCC,FALSE);
4238 pCC = (ConfClient *) avl_t_next(&avl_trans);
4239 }
4240 SendChatEvent("sent_chat",p->Buf);
4241
4242 p->Count = 0;
4243 BufPrintf(p,"%c" NDATA "Message sent\r",ILINK_DATA_PACKET);
4244 }
4245
CmdMonitor(ClientInfo * p,ConfClient * pCC1,char * Arg)4246 void CmdMonitor(ClientInfo *p,ConfClient *pCC1,char *Arg)
4247 {
4248 ConfClient *pCC;
4249 struct avl_traverser avl_trans;
4250 ConfServer *pCS = (ConfServer *) p->p;
4251 int bMonitor = TRUE;
4252
4253 if(strstr(Arg,"disable") == Arg) {
4254 bMonitor = FALSE;
4255 while(*Arg && *Arg != ' ') {
4256 Arg++;
4257 }
4258
4259 while(*Arg && *Arg == ' ') {
4260 Arg++;
4261 }
4262 }
4263
4264 pCC = (ConfClient *) avl_t_first(&avl_trans,pCS->ConfTree);
4265 while(pCC != NULL) {
4266 if(STRCMP(Arg,pCC->Callsign) == 0) {
4267 pCC->bMonitor = bMonitor;
4268 if(bMonitor) {
4269 BufPrintf(p,"Monitoring %s\r",Arg);
4270 }
4271 else {
4272 BufPrintf(p,"transmit enabled to %s\r",Arg);
4273 }
4274 break;
4275 }
4276 pCC = (ConfClient *) avl_t_next(&avl_trans);
4277 }
4278
4279 if(*Arg == 0) {
4280 // No argument
4281 BufPrintf(p,"Usage:\r");
4282 BufPrintf(p,"monitor <call>\r");
4283 BufPrintf(p,"monitor disable <call>\r");
4284 }
4285 else if(pCC == NULL) {
4286 BufPrintf(p,"%s not found\r",Arg);
4287 Rcode = TBD_STATION_NOT_FOUND;
4288 }
4289 }
4290
4291 // "lookup" <callsign>
CmdLookUp(ClientInfo * p,ConfClient * pCC,char * Arg)4292 void CmdLookUp(ClientInfo *p,ConfClient *pCC,char *Arg)
4293 {
4294 UserInfo UserLookup;
4295 UserInfo *pUser = NULL;
4296
4297 char *Callsign = NULL;
4298 char *cp = Arg;
4299 char *WhiteSpace = "\t\n ";
4300
4301 if((Callsign = strtok(cp,WhiteSpace)) == NULL) {
4302 BufPrintf(p,"Usage: lookup <callsign>\r");
4303 Rcode = TBD_ERR_INVALID_ARG;
4304 }
4305
4306 if(Rcode == 0) {
4307 Convert2Upper(Callsign);
4308 UserLookup.Callsign = Callsign;
4309 if((pUser = avl_find(UserTree,&UserLookup)) != NULL) {
4310 BufPrintf(p,"Callsign:%s \r"
4311 "NodeID:%d \r"
4312 "Qth:%s \r"
4313 "%s \r",
4314 pUser->Callsign,
4315 pUser->NodeID,
4316 pUser->Qth,
4317 pUser->bBusy?"Busy":"Not Busy");
4318 }
4319 else {
4320 BufPrintf(p,"Station \"%s\" not found.\r",Callsign);
4321 Rcode = TBD_STATION_NOT_FOUND;
4322 }
4323 }
4324 }
4325
4326 // "mute" | "unmute" [-u][-c][-t][-a][-p] [.] [-x][<callsign> ...] [chat]
CmdMuteCommon(ClientInfo * p,ConfClient * pCC1,char * Arg,int bMute)4327 void CmdMuteCommon(ClientInfo *p,ConfClient *pCC1,char *Arg,int bMute)
4328 {
4329 #define MAX_MUTE_CALLS 32
4330 ConfClient *pCC = NULL;
4331 struct avl_traverser avl_trans;
4332 ConfServer *pCS = (ConfServer *) p->p;
4333 int bDoConf = FALSE;
4334 int bDoTBD = FALSE;
4335 int bDoUsers = FALSE;
4336 int bDoSyops = FALSE;
4337 int bDoRF = FALSE;
4338 int bDoTalker = FALSE;
4339 int bDidTalker = FALSE;
4340 int bDoList = FALSE;
4341 int bDidit = FALSE;
4342 int bDoUsage = FALSE;
4343 int bDidChat = FALSE;
4344 int bMuteChat = FALSE;
4345 int bPersistent = FALSE;
4346 int PersistentCalls = 0;
4347 int bByCall = FALSE;
4348 int bDoIt;
4349 int NumCalls = 0;
4350 int CallsFound = 0;
4351 char *Calls[MAX_MUTE_CALLS];
4352 int CallFound[MAX_MUTE_CALLS];
4353 int OptionsFound = 0;
4354 char *Sep = "";
4355 char *SetSep;
4356 char *MsgStart = &p->Buf[p->Count];
4357 int i;
4358 UserInfo UserLookup;
4359 UserInfo *pUser;
4360
4361 memset(CallFound,0,sizeof(CallFound));
4362
4363 if(*Arg == 0) {
4364 // No argument
4365 bDoList = TRUE;
4366 }
4367 else while(*Arg) {
4368 while(*Arg == ' ') {
4369 // Skip spaces between arguments
4370 Arg++;
4371 }
4372
4373 if(*Arg == '-') {
4374 // switch
4375 Arg++;
4376 OptionsFound++;
4377 while(*Arg && *Arg != ' ') {
4378 switch(tolower(*Arg)) {
4379 case 'a':
4380 bDoTBD = bDoConf = bDoUsers = bDoSyops = bDoRF = TRUE;
4381 bDoTalker = TRUE;
4382 pCS->bMuteTBD = pCS->bMuteRF = pCS->bMuteConf = bMute;
4383 pCS->bMuteUsers = bMute;
4384 bDidit = TRUE;
4385 break;
4386
4387 case 'c':
4388 pCS->bMuteTBD = bMute;
4389 bDoTBD = TRUE;
4390 bDidit = TRUE;
4391 break;
4392
4393 case 'e':
4394 pCS->bMuteConf = bMute;
4395 bDoConf = TRUE;
4396 bDidit = TRUE;
4397 break;
4398
4399 case 'p':
4400 bPersistent = TRUE;
4401 break;
4402
4403 case 'r':
4404 pCS->bMuteRF = bMute;
4405 bDoRF = TRUE;
4406 bDidit = TRUE;
4407 break;
4408
4409 case 's':
4410 bDoSyops = TRUE;
4411 break;
4412
4413 case 'u':
4414 pCS->bMuteUsers = bMute;
4415 bDoUsers = TRUE;
4416 bDidit = TRUE;
4417 break;
4418
4419 case 'x': // inbound Chat
4420 bMuteChat = TRUE;
4421 break;
4422
4423 case 't':
4424 case '.':
4425 if(bMute) {
4426 bDoTalker = TRUE;
4427 break;
4428 }
4429 // intentional fall thru to default case if unmute command
4430
4431 default:
4432 // Unknown switch
4433 BufPrintf(p,"Error: unknown option \"%c\".\r",*Arg);
4434 // intentional fall thru to 'h' case
4435
4436 case 'h':
4437 Arg = " ";
4438 bDoUsage = TRUE;
4439 break;
4440 }
4441 Arg++;
4442 }
4443 }
4444 else if(*Arg == '?') {
4445 bDoUsage = TRUE;
4446 break;
4447 }
4448 else if(strncmp(Arg,"chat",4) == 0) {
4449 // Mute/UnMute chat command
4450 OptionsFound++;
4451 pCC1->bNoChat = bMute;
4452 bDidit = TRUE;
4453 bDidChat = TRUE;
4454 Arg += 4;
4455 }
4456 else if(*Arg == '.') {
4457 if(bMute) {
4458 OptionsFound++;
4459 bDoTalker = TRUE;
4460 Arg++;
4461 }
4462 else {
4463 // No '.' option to the unmute command
4464 bDoUsage = TRUE;
4465 break;
4466 }
4467 }
4468 else {
4469 // Assume it's a callsign
4470 OptionsFound++;
4471 Calls[NumCalls++] = Arg;
4472 // Skip to end of callsign
4473 while(*Arg && *Arg != ' ') {
4474 Arg++;
4475 }
4476 if(*Arg) {
4477 *Arg++ = 0;
4478 }
4479 if(NumCalls == MAX_MUTE_CALLS) {
4480 // Callsign list full (!)
4481 break;
4482 }
4483 }
4484 }
4485
4486 pCC = (ConfClient *) avl_t_first(&avl_trans,pCS->ConfTree);
4487 while(!bDoUsage && pCC != NULL) {
4488 bDoIt = bByCall = FALSE;
4489 if(bDoList && bMute) {
4490 // No argument to mute command - list muted stations
4491 if(pCC->bMuted) {
4492 bDidit = TRUE;
4493 BufPrintf(p,"%s\r",pCC->Callsign);
4494 }
4495 }
4496
4497 if(bDoTalker && pCC == ClientTalking && !bDoConf) {
4498 // Mute station current talking
4499 if(ClientTalking->bConf) {
4500 // The guy talking is on another conference, send a .mute
4501 // command to that conference
4502 BufPrintf(p,"%s>%s\r",ConferenceCall,".mute %s.",
4503 bMuteChat ? "-x " : "");
4504 Send2(ClientTalking,p->Buf,p->Count,FALSE);
4505 p->Count = 0; // init for BufPrintf
4506 BufPrintf(p,"%c" NDATA,ILINK_DATA_PACKET);
4507 BufPrintf(p,"Mute command sent to %s\r",ClientTalking->Callsign);
4508 bDidit = TRUE; // message sent
4509 }
4510 else if(pCC1 == ClientTalking) {
4511 // Don't allow someone mute himself by accident
4512 if(!pCC1->bConf) {
4513 BufPrintf(p,"Just stop talking to mute yourself!\r");
4514 }
4515 }
4516 else {
4517 bDidTalker = bDoIt = TRUE;
4518 ClientTalking = NULL;
4519 }
4520 }
4521
4522 if((!pCC->bSysop || bDoSyops || !bMute) &&
4523 (pCC != pCC1 || !bMute) &&
4524 (pCC != ClientTalking || bDoTalker) &&
4525 (
4526 (bDoTBD && pCC->bTBD) ||
4527 (bDoConf && pCC->bConf && !pCC->bTBD) ||
4528 (bDoUsers && !(pCC->bLink || pCC->bRepeater || pCC->bConf)) ||
4529 (bDoSyops && pCC->bSysop) ||
4530 (bDoRF && (pCC->bLink || pCC->bRepeater)) ||
4531 (bDoTalker && pCC == ClientTalking)
4532 ))
4533 {
4534 bDoIt = TRUE;
4535 }
4536
4537 for(i = 0; i < NumCalls; i++) {
4538 if(STRCMP(Calls[i],pCC->Callsign) == 0) {
4539 CallFound[i] = TRUE;
4540 bDoIt = TRUE;
4541 bByCall = TRUE;
4542 CallsFound++;
4543 break;
4544 }
4545 }
4546
4547 if(bDoIt) {
4548 bDidit = TRUE;
4549 if(bMuteChat) {
4550 pCC->bMuteChat = bMute;
4551 }
4552 else {
4553 pCC->bMuted = bMute;
4554 }
4555 }
4556 pCC = (ConfClient *) avl_t_next(&avl_trans);
4557 }
4558
4559 if(NumCalls > 0 && bPersistent) {
4560 // Persistent request
4561 for(i = 0; i < NumCalls; i++) {
4562 UserLookup.Callsign = Calls[i];
4563 if((pUser = avl_find(UserTree,&UserLookup)) != NULL) {
4564 // user is in directory, save his mute flag
4565 if(bMuteChat) {
4566 pUser->bChatMuted = bMute;
4567 pUser->bChatUnMuted = !bMute;
4568 }
4569 else {
4570 pUser->bMuted = bMute;
4571 pUser->bUnMuted = !bMute;
4572 }
4573 }
4574 else {
4575 LOG_WARN(("CmdMuteCommon(): %s not in directory.\n",
4576 pCC->Callsign));
4577 }
4578 }
4579 }
4580
4581 if(bDoUsage) {
4582 Rcode = TBD_ERR_INVALID_CMD;
4583 if(bMute) {
4584 BufPrintf(p,"Usage: mute -[aceprstuX] [<call> ...] [.] [chat]\r");
4585 }
4586 else {
4587 BufPrintf(p,"Usage: unmute -[aceprsuX] [<call> ...] [chat]\r");
4588 }
4589 BufPrintf(p,
4590 " -a: All users\r"
4591 " -c: tbd conferences\r"
4592 " -e: Echolink conferences\r"
4593 " -p: Persistent\r"
4594 " -r: RF users (-R and -L stations)\r"
4595 " -s: Sysops and Admins\r"
4596 " -t: Station talking\r"
4597 " -u: PC users\r"
4598 " -x: chat text from user\r"
4599 "<call>: specific user\r");
4600 }
4601 else if(!bDidit && bDoTalker && OptionsFound == 1 && ClientTalking == NULL) {
4602 // Simple attempt to mute current talker and no one is talking.
4603 Rcode = TBD_ERR_NO_TALKER;
4604 if(!pCC1->bConf) {
4605 // Command received from a local sysop, not via a quoted command
4606 // Forward the command
4607 BufPrintf(p,"Error: no one is talking.\r");
4608 }
4609 }
4610 else if(bDoList) {
4611 // Now list persistently muted/unmuted text
4612 pUser = (UserInfo *) avl_t_first(&avl_trans,UserTree);
4613 while(pUser != NULL) {
4614 if((bMute && pUser->bChatMuted) || (!bMute && pUser->bChatUnMuted)) {
4615 if(PersistentCalls++ == 0) {
4616 BufPrintf(p,"Stations with persistently %s text:\r",
4617 bMute ? "muted" : "unmuted");
4618 }
4619 BufPrintf(p,"%s\r",pUser->Callsign);
4620 }
4621 pUser = (UserInfo *) avl_t_next(&avl_trans);
4622 }
4623
4624 // Now list persistently muted/unmuted stations
4625 PersistentCalls = 0;
4626 pUser = (UserInfo *) avl_t_first(&avl_trans,UserTree);
4627 while(pUser != NULL) {
4628 if((bMute && pUser->bMuted) || (!bMute && pUser->bUnMuted)) {
4629 if(PersistentCalls++ == 0) {
4630 BufPrintf(p,"Stations persistently %s:\r",
4631 bMute ? "muted" : "unmuted");
4632 }
4633 BufPrintf(p,"%s\r",pUser->Callsign);
4634 }
4635 pUser = (UserInfo *) avl_t_next(&avl_trans);
4636 }
4637
4638 if(!bDidit) {
4639 BufPrintf(p,"No stations are currently muted.\r",Arg);
4640 }
4641
4642 if(PersistentCalls == 0 && !bMute) {
4643 BufPrintf(p,"No stations are unmuted persistently.\r",Arg);
4644 }
4645
4646 if(pCS->bMuteConf || pCS->bMuteUsers || pCS->bMuteRF || pCS->bMuteTBD) {
4647 BufPrintf(p,"New connections from ");
4648 SetSep = " and ";
4649 if(pCS->bMuteConf) {
4650 BufPrintf(p,"Echolink conferences");
4651 Sep = SetSep;
4652 }
4653
4654 if(pCS->bMuteTBD) {
4655 BufPrintf(p,"%stbd conferences",Sep);
4656 Sep = SetSep;
4657 }
4658
4659 if(pCS->bMuteUsers) {
4660 BufPrintf(p,"%sPC users",Sep);
4661 Sep = SetSep;
4662 }
4663
4664 if(pCS->bMuteRF) {
4665 BufPrintf(p,"%sRF users",Sep);
4666 Sep = SetSep;
4667 }
4668 BufPrintf(p," will be muted.\r");
4669 }
4670
4671 if(pCS->bMuteChat) {
4672 BufPrintf(p,"Chat text from new connections will be muted.\r");
4673 }
4674
4675 if(pCC1->bNoChat) {
4676 BufPrintf(p,"Your text chat is muted.\r");
4677 }
4678 }
4679 else {
4680 if(bDidit) {
4681 // Did something
4682 SetSep = ", ";
4683 }
4684 else {
4685 // Didn't do anything
4686 BufPrintf(p,"No ");
4687 SetSep = " or ";
4688 }
4689
4690 if(bDoConf) {
4691 BufPrintf(p,"EchoLink conferences");
4692 Sep = SetSep;
4693 }
4694
4695 if(bDoTBD) {
4696 BufPrintf(p,"%stbd conferences",Sep);
4697 Sep = SetSep;
4698 }
4699
4700 if(bDoUsers) {
4701 BufPrintf(p,"%sPC users",Sep);
4702 Sep = SetSep;
4703 }
4704
4705 if(bDoSyops) {
4706 BufPrintf(p,"%ssysops",Sep);
4707 Sep = SetSep;
4708 }
4709
4710 if(bDoRF) {
4711 BufPrintf(p,"%sRF users",Sep);
4712 Sep = SetSep;
4713 }
4714
4715 if(bDoTalker && (bDidTalker || !bDidit)) {
4716 BufPrintf(p,"%sstation talking",Sep);
4717 Sep = SetSep;
4718 }
4719
4720 if(bDidChat) {
4721 BufPrintf(p,"%schat text",Sep);
4722 Sep = SetSep;
4723 }
4724
4725 for(i = 0; i < NumCalls; i++) {
4726 if((bDidit && CallFound[i]) || (!bDidit && !CallFound[i])) {
4727 BufPrintf(p,"%s%s",Sep,Calls[i]);
4728 Sep = SetSep;
4729 }
4730 }
4731
4732 if(bDidit) {
4733 BufPrintf(p,"%s%s%s",
4734 bMuteChat ? " text" : "",
4735 bPersistent ? " persistently" : "",
4736 bMute ? " muted" : " unmuted");
4737 LOG_NORM(("%s by %s\n",MsgStart,pCC1->Callsign));
4738 BufPrintf(p,".\r");
4739
4740 if(NumCalls != CallsFound) {
4741 Sep = "";
4742 for(i = 0; i < NumCalls; i++) {
4743 if(!CallFound[i]) {
4744 BufPrintf(p,"%s%s",Sep,Calls[i]);
4745 Sep = ", ";
4746 }
4747 }
4748 BufPrintf(p," not found.\r");
4749 Rcode = TBD_STATION_NOT_FOUND;
4750 }
4751 }
4752 else {
4753 BufPrintf(p," found.\r");
4754 Rcode = TBD_STATION_NOT_FOUND;
4755 }
4756 }
4757 #undef MAX_MUTE_CALLS
4758 }
4759
CmdUnMute(ClientInfo * p,ConfClient * pCC1,char * Arg)4760 void CmdUnMute(ClientInfo *p,ConfClient *pCC1,char *Arg)
4761 {
4762 CmdMuteCommon(p,pCC1,Arg,FALSE);
4763 }
4764
CmdMute(ClientInfo * p,ConfClient * pCC1,char * Arg)4765 void CmdMute(ClientInfo *p,ConfClient *pCC1,char *Arg)
4766 {
4767 CmdMuteCommon(p,pCC1,Arg,TRUE);
4768 }
4769
StopRecording(ClientInfo * p,const char * Arg)4770 void StopRecording(ClientInfo *p,const char *Arg)
4771 {
4772 FilePacketHdr PktHdr;
4773 ConfServer *pCS = ((ConfServer *) p->p)->pMasterCS;
4774
4775 if(pCS->fp != NULL) {
4776 // Write end of stream record
4777 PktHdr.Len = 0;
4778 PktHdr.Flags = 0;
4779
4780 if(fwrite(&PktHdr,sizeof(PktHdr),1,pCS->fp) != 1) {
4781 // Write error
4782 LOG_ERROR(("StopRecording(): fwrite failed, %s",Err2String(errno)));
4783 }
4784
4785 fclose(pCS->fp);
4786 pCS->fp = NULL;
4787 pCS->bRecording = FALSE;
4788 BufPrintf(p,"Recording completed.\r");
4789 }
4790 else if(STRCMP(Arg,"stop") == 0) {
4791 BufPrintf(p,"Can't stop, we're not recording.\r");
4792 }
4793 }
4794
4795 // Arg = filename
CmdRecord(ClientInfo * p,ConfClient * pCC1,char * Arg)4796 void CmdRecord(ClientInfo *p,ConfClient *pCC1,char *Arg)
4797 {
4798 char *cp;
4799 ConfServer *pCS = ((ConfServer *) p->p)->pMasterCS;
4800
4801 StopRecording(p,Arg);
4802
4803 // Point Arg to the a bare filename to prevent "accidents"
4804 if((cp = strrchr(Arg,'\\')) != NULL) {
4805 Arg = cp + 1;
4806 }
4807
4808 if((cp = strrchr(Arg,'/')) != NULL) {
4809 Arg = cp + 1;
4810 }
4811
4812 if(STRCMP(Arg,"stop") != 0) {
4813 if((pCS->fp = FOPEN(Arg,MODE_RDWR_BIN)) == NULL) {
4814 pCS->bRecording = FALSE;
4815 BufPrintf(p,"Error: Couldn't open %s.",Arg);
4816 Rcode = TBD_ERR_FILE_OPEN;
4817 }
4818 else {
4819 pCS->bRecording = TRUE;
4820 BufPrintf(p,"Recording to %s.\r",Arg);
4821 }
4822 }
4823 }
4824
CmdRefresh(ClientInfo * p,ConfClient * pCC1,char * Arg)4825 void CmdRefresh(ClientInfo *p,ConfClient *pCC1,char *Arg)
4826 {
4827 NextStationListTime = TimeNow.tv_sec + StationListInterval;
4828 ServerRequest(SERV_REQ_LOGIN_AND_LIST,0,NULL);
4829 }
4830
CmdRehash(ClientInfo * p,ConfClient * pCC1,char * Arg)4831 void CmdRehash(ClientInfo *p,ConfClient *pCC1,char *Arg)
4832 {
4833 bRefreshConfig = TRUE;
4834 BufPrintf(p,"Reloading configuration file.\r");
4835 }
4836
CmdSet(ClientInfo * p,ConfClient * pCC1,char * Arg)4837 void CmdSet(ClientInfo *p,ConfClient *pCC1,char *Arg)
4838 {
4839 struct config_entry *pVars = ConfigVars;
4840 char *cp;
4841 int x;
4842 float f;
4843 int bSetVariable = FALSE;
4844 void *var_ptr;
4845 char Line[80];
4846 int bFirstLoop = TRUE;
4847 int Err;
4848
4849 do {
4850 if(*Arg == 0) {
4851 Rcode = TBD_ERR_INVALID_CMD;
4852 BufPrintf(p,"Usage:\n");
4853 BufPrintf(p,"set variable\r");
4854 BufPrintf(p,"set variable = value\r");
4855 break;
4856 }
4857
4858 if(strchr(Arg,'=') != NULL) {
4859 Err = ParseConfigLine(Arg,pVars,MANUAL_SET);
4860 #ifdef LINK_BOX
4861 if(Err != 0 && (pVars = GetConfigVarsPtr()) != ConfigVars) {
4862 // Try again
4863 Err = ParseConfigLine(Arg,pVars,MANUAL_SET);
4864 bFirstLoop = FALSE;
4865 }
4866 #endif
4867 if(Err != 0) {
4868 Rcode = TBD_ERR_INVALID_ARG;
4869 BufPrintf(p,"Error: Unable to set variable.\r");
4870 break;
4871 }
4872 else {
4873 bSetVariable = TRUE;
4874 }
4875 }
4876
4877 if(Rcode == TBD_OK) {
4878 // display the current value
4879 if((cp = strchr(Arg,' ')) != NULL) {
4880 // Remove trailing spaces
4881 *cp = 0;
4882 }
4883 if((cp = strchr(Arg,'=')) != NULL) {
4884 // Remove '='
4885 *cp = 0;
4886 }
4887
4888 while(pVars->var_name[0] != 0) {
4889 if(!STRCMP(Arg,pVars->var_name)) {
4890 // found our guy
4891 if(pVars->Flags & CON_FLG_BASED_VAR) {
4892 if((var_ptr = ConfigGetVarPtr(pVars)) == NULL) {
4893 BufPrintf(p,"Error: Unable to set variable.\r");
4894 break;
4895 }
4896 }
4897 else {
4898 var_ptr = pVars->var_ptr;
4899 }
4900
4901 switch(pVars->var_type[1]) {
4902 case 's': // String value
4903 // string value, make sure it's set
4904 cp = *((char **) var_ptr);
4905 if(cp == NULL) {
4906 BufPrintf(p,"variable %s is not set\r",pVars->var_name);
4907 }
4908 else {
4909 BufPrintf(p,"%s = \"%s\"\r",pVars->var_name,cp);
4910 }
4911 break;
4912
4913 case 'd': // int
4914 case 'x':
4915 x = *((int *) var_ptr);
4916 BufPrintf(p,"%s = ",pVars->var_name);
4917 BufPrintf(p,pVars->var_type,x);
4918 BufPrintf(p,"\r");
4919 break;
4920
4921 case 'f': // float
4922 f = *((float *) var_ptr);
4923 BufPrintf(p,"%s = ",pVars->var_name);
4924 BufPrintf(p,pVars->var_type,f);
4925 BufPrintf(p,"\r");
4926 break;
4927
4928 case 'F':
4929 // value is set via function call.
4930 Line[0] = 0;
4931 pVars->AccessFunc(pVars,Line,AF_DISPLAY_VAR,sizeof(Line));
4932 if(Line[0] == 0) {
4933
4934 BufPrintf(p,"Sorry, variable %s can not be displayed\r",
4935 pVars->var_name);
4936 }
4937 else {
4938 BufPrintf(p,"%s = %s\r",pVars->var_name,Line);
4939 }
4940 break;
4941
4942 default:
4943 if(!bSetVariable) {
4944 BufPrintf(p,"Sorry, variable %s can not be displayed\r",
4945 pVars->var_name);
4946 }
4947 break;
4948 }
4949 break;
4950 }
4951 pVars++;
4952 }
4953
4954 if(pVars->var_name[0] != 0) {
4955 // Found it
4956 break;
4957 }
4958
4959 #ifdef LINK_BOX
4960 if((pVars = GetConfigVarsPtr()) == ConfigVars)
4961 #endif
4962 bFirstLoop = FALSE;
4963
4964 if(!bFirstLoop) {
4965 BufPrintf(p,"variable %s not found\r",Arg);
4966 Rcode = TBD_ERR_INVALID_ARG;
4967 break;
4968 }
4969 bFirstLoop = FALSE;
4970 }
4971 } while(TRUE);
4972 }
4973
CmdShowIP(ClientInfo * p,ConfClient * pCC1,char * Arg)4974 void CmdShowIP(ClientInfo *p,ConfClient *pCC1,char *Arg)
4975 {
4976 struct avl_traverser avl_trans;
4977 ConfServer *pCS = (ConfServer *) p->p;
4978 ConfClient *pCC;
4979 char TypeChar;
4980
4981 pCC = (ConfClient *) avl_t_first(&avl_trans,pCS->ConfTree);
4982 while(pCC != NULL) {
4983 switch(pCC->Proto) {
4984 case PROTO_SF:
4985 TypeChar = 'S';
4986 break;
4987
4988 case PROTO_RTP:
4989 TypeChar = 'R';
4990 break;
4991
4992 case PROTO_ILINK:
4993 TypeChar = 'E';
4994 break;
4995
4996 default:
4997 TypeChar = '?';
4998 break;
4999 }
5000 BufPrintf(p,"%s\t%s\t%c\r",pCC->Callsign,
5001 inet_ntoa(pCC->HisAdr.i.sin_addr),TypeChar);
5002 pCC = (ConfClient *) avl_t_next(&avl_trans);
5003 }
5004 BufPrintf(p,"\r");
5005 }
5006
CmdDNS(ClientInfo * p,ConfClient * pCC1,char * Arg)5007 void CmdDNS(ClientInfo *p,ConfClient *pCC1,char *Arg)
5008 {
5009 if(STRCMP(Arg,"refresh") == 0) {
5010 UpdateDNSCache(TRUE);
5011 BufPrintf(p,"Refreshing DNS cache\r");
5012 }
5013 else if(STRCMP(Arg,"list") == 0) {
5014 DumpDNSCache(p);
5015 }
5016 else {
5017 BufPrintf(p,"Usage:\r"
5018 "dns refresh\r"
5019 "dns list\r");
5020 Rcode = TBD_ERR_INVALID_CMD;
5021 }
5022 }
5023
CmdKick(ClientInfo * p,ConfClient * pCC1,char * Arg)5024 void CmdKick(ClientInfo *p,ConfClient *pCC1,char *Arg)
5025 {
5026 ConfClient *pCC;
5027 struct avl_traverser avl_trans;
5028 ConfServer *pCS = (ConfServer *) p->p;
5029
5030 pCC = (ConfClient *) avl_t_first(&avl_trans,pCS->ConfTree);
5031 while(pCC != NULL) {
5032
5033 if(STRCMP(Arg,pCC->Callsign) == 0) {
5034 LOG_NORM(("%s kicked by %s\n",pCC->Callsign,pCC1->Callsign));
5035 BufPrintf(p,"%s kicked\r",Arg);
5036 pCC->bKicked = TRUE;
5037 if(pCC->bConnected) {
5038 pCC->bConnected = FALSE;
5039 ConferenceClients--;
5040 }
5041 pCC->bSWL = TRUE;
5042 break;
5043 }
5044 pCC = (ConfClient *) avl_t_next(&avl_trans);
5045 }
5046
5047 if(pCC == NULL) {
5048 BufPrintf(p,"%s not found\r",Arg);
5049 Rcode = TBD_STATION_NOT_FOUND;
5050 }
5051 }
5052
ACLAdd(ClientInfo * p,ConfClient * pCC1,char * Arg,int bAllowed)5053 void ACLAdd(ClientInfo *p,ConfClient *pCC1,char *Arg,int bAllowed)
5054 {
5055 ACL_User ACL_New;
5056 char *CmdString = bAllowed ? "allowed" : "banned";
5057 char *WhiteSpace = "\t\n ";
5058 int Count = p->Count;
5059
5060 if(!bAllowed) {
5061 // Kick the guy if he's on currently
5062 CmdKick(p,pCC1,Arg);
5063 p->Count = Count; // remove message generated by kick command
5064 }
5065
5066 memset(&ACL_New,0,sizeof(ACL_New));
5067 ACL_New.bAuthorized = bAllowed;
5068
5069 ACL_New.Callsign = strtok(Arg,WhiteSpace);
5070 Convert2Upper(ACL_New.Callsign);
5071
5072 ACL_New.HostName = strtok(NULL,WhiteSpace);
5073
5074 if(ACL_New.HostName == NULL) {
5075 ACL_New.HostName = "-";
5076 }
5077 else {
5078 ACL_New.Password = strtok(NULL,WhiteSpace);
5079 }
5080
5081 if(ACL_New.Password == NULL) {
5082 ACL_New.Password = "-";
5083 }
5084 else {
5085 // Take the rest of the string
5086 ACL_New.CallPlus = strtok(NULL,"");
5087 if(ACL_New.CallPlus != NULL) {
5088 // Skip leading white space
5089 while(*ACL_New.CallPlus && isspace(*ACL_New.CallPlus)) {
5090 ACL_New.CallPlus++;
5091 }
5092 }
5093 }
5094
5095 if(!ACLAddUser(&ACL_New)) {
5096 BufPrintf(p,"Error unable to resolve hostname \"%s\".",
5097 ACL_New.HostName);
5098 }
5099 else {
5100 SaveACL();
5101 BufPrintf(p,"%s %s\r",Arg,CmdString);
5102 LOG_NORM(("%s %s by %s\n",ACL_New.Callsign,CmdString,pCC1->Callsign));
5103 }
5104 }
5105
ACLDelete(ClientInfo * p,ConfClient * pCC1,char * Arg,int bAllowed)5106 void ACLDelete(ClientInfo *p,ConfClient *pCC1,char *Arg,int bAllowed)
5107 {
5108 ACL_User *pACL = NULL;
5109 ACL_User ACL_Lookup;
5110 char *cp;
5111 char *CmdString = bAllowed ? "allowed" : "banned";
5112
5113 // Remove possible -L or -R from the argument, we ban the base call
5114
5115 if((cp = strchr(Arg,'-')) != NULL) {
5116 *cp = 0;
5117 }
5118
5119 ACL_Lookup.Callsign = Arg;
5120 Convert2Upper(ACL_Lookup.Callsign);
5121
5122 if((pACL = avl_find(ACL_Call_Tree,&ACL_Lookup)) != NULL) {
5123 if((bAllowed && pACL->bAuthorized) || (!bAllowed && !pACL->bAuthorized)) {
5124 DeleteACLUser(pACL);
5125 BufPrintf(p,"%s removed from the %s list.\r",Arg,CmdString);
5126 SaveACL();
5127 }
5128 else {
5129 pACL = NULL;
5130 }
5131 }
5132
5133 if(pACL == NULL) {
5134 BufPrintf(p,"%s was not found on the %s list.\r",Arg,CmdString);
5135 }
5136 }
5137
ACLList(ClientInfo * p,ConfClient * pCC1,const char * Arg,int bAllowed)5138 void ACLList(ClientInfo *p,ConfClient *pCC1,const char *Arg,int bAllowed)
5139 {
5140 ACL_User *pACL = NULL;
5141 struct avl_traverser avl_trans;
5142 int Count = 0;
5143 char *CmdString = bAllowed ? "allowed" : "banned";
5144 ACL_User *pNextACL;
5145
5146 pNextACL = (ACL_User *) avl_t_first(&avl_trans,ACL_Call_Tree);
5147 while((pACL = pNextACL) != NULL) {
5148 // NB: Get the next user now in case we deleted the current user
5149 pNextACL = (ACL_User *) avl_t_next(&avl_trans);
5150 if((bAllowed && pACL->bAuthorized) || (!bAllowed && !pACL->bAuthorized)) {
5151 if(Count++ == 0) {
5152 BufPrintf(p,"%s users:\r",CmdString);
5153 }
5154 BufPrintf(p,"%s %s\r",pACL->CallPlus,pACL->HostName);
5155 }
5156 }
5157
5158 if(Count == 0) {
5159 BufPrintf(p,"The %s list is empty.\r",CmdString);
5160 }
5161 }
5162
5163 // [allow | deny] add <callsign> [<hostname> [<user name>]]
CmdACL(ClientInfo * p,ConfClient * pCC1,char * Arg,int bAllowed)5164 void CmdACL(ClientInfo *p,ConfClient *pCC1,char *Arg,int bAllowed)
5165 {
5166 char *cp;
5167
5168 if((cp = strchr(Arg,' ')) != NULL) {
5169 *cp++ = 0;
5170 while(*cp && *cp == ' ') {
5171 cp++;
5172 }
5173 }
5174
5175 if(STRCMP(Arg,"add") == 0) {
5176 if(cp != NULL && *cp) {
5177 ACLAdd(p,pCC1,cp,bAllowed);
5178 }
5179 else {
5180 BufPrintf(p,"Add who ?\r");
5181 Rcode = TBD_ERR_ARG_COUNT;
5182 }
5183 }
5184 else if(STRCMP(Arg,"delete") == 0) {
5185 if(cp != NULL && *cp) {
5186 ACLDelete(p,pCC1,cp,bAllowed);
5187 }
5188 else {
5189 BufPrintf(p,"Delete who ?\r");
5190 Rcode = TBD_ERR_ARG_COUNT;
5191 }
5192 }
5193 else if(STRCMP(Arg,"list") == 0) {
5194 ACLList(p,pCC1,Arg,bAllowed);
5195 }
5196 else {
5197 Rcode = TBD_ERR_INVALID_CMD;
5198 if(bAllowed) {
5199 BufPrintf(p,"Usage:\r"
5200 "allow add <callsign> [<hostname/IP Address> [password [<User's name>]]]\r"
5201 "allow delete <callsign>\r"
5202 "allow list\r");
5203 }
5204 else {
5205 BufPrintf(p,"Usage:\r"
5206 "ban add <callsign> [<hostname/IP Address>]\r"
5207 "ban delete <callsign>\r"
5208 "ban list\r");
5209 }
5210 }
5211 }
5212
CmdAllow(ClientInfo * p,ConfClient * pCC1,char * Arg)5213 void CmdAllow(ClientInfo *p,ConfClient *pCC1,char *Arg)
5214 {
5215 CmdACL(p,pCC1,Arg,TRUE);
5216 }
5217
CmdBan(ClientInfo * p,ConfClient * pCC1,char * Arg)5218 void CmdBan(ClientInfo *p,ConfClient *pCC1,char *Arg)
5219 {
5220 CmdACL(p,pCC1,Arg,FALSE);
5221 }
5222
5223
SaveBulletinList()5224 void SaveBulletinList()
5225 {
5226 FILE *fp;
5227 Bulletin *pBulletin = BulletinList;
5228
5229 if((fp = fopen("bulletin.lst","w")) != NULL) {
5230 while(pBulletin != NULL) {
5231 fprintf(fp,"%s\t%s\n",pBulletin->Filename,pBulletin->Description);
5232 pBulletin = pBulletin->Link;
5233 }
5234 fclose(fp);
5235 }
5236 }
5237
FreeBulletinList()5238 void FreeBulletinList()
5239 {
5240 Bulletin *pBulletin;
5241
5242 while(BulletinList != NULL) {
5243 pBulletin = BulletinList;
5244 BulletinList = pBulletin->Link;
5245 free(pBulletin->Filename);
5246 free(pBulletin->Description);
5247 free(pBulletin->RunTime);
5248 free(pBulletin);
5249 }
5250 }
5251
CmdListAdd(char * Filename,char * Desc)5252 int CmdListAdd(char *Filename,char *Desc)
5253 {
5254 struct stat FileStats;
5255 int AudioBlocks;
5256 int RunningTime;
5257 Bulletin *pBulletin;
5258 int Ret = FALSE;
5259
5260 // check that the file exists and calculate approximate length
5261 if(stat(Filename,&FileStats) == 0) {
5262 // Each audio packet encodes 80 milliseconds of audio. It consists
5263 // of a file header plus a RTP header plus 4 GSM 6.10 encoded audio frames.
5264 // Each GSM frame contains 33 bytes. This calculation is approximate
5265 // since it ignores text messages and timestamps, but it should be close
5266 // enough for our purposes.
5267
5268 AudioBlocks = FileStats.st_size /
5269 ((4 * 33) +
5270 sizeof(rtp_hdr_t) -
5271 sizeof(u_int32) /* optional csrc field not sent */ +
5272 sizeof(FilePacketHdr));
5273
5274 RunningTime = AudioBlocks * 2 / 25; // 12.5 packets/second of audio
5275
5276 pBulletin = (Bulletin *) malloc(sizeof(Bulletin));
5277 if(pBulletin != NULL) {
5278 pBulletin->Filename = strdup(Filename);
5279 pBulletin->Description = strdup(Desc);
5280 pBulletin->RunTime = strdup(DeltaTime2String(0,RunningTime,TRUE));
5281 pBulletin->Link = NULL;
5282 if(BulletinList == NULL) {
5283 BulletinList = pBulletin;
5284 }
5285 else {
5286 BulletinListTail->Link = pBulletin;
5287 }
5288 BulletinListTail = pBulletin;
5289
5290 Ret = TRUE;
5291 }
5292 }
5293
5294 return Ret;
5295 }
5296
LoadBulletinList()5297 void LoadBulletinList()
5298 {
5299 FILE *fp;
5300 char Line[80];
5301 char *cp;
5302
5303 FreeBulletinList();
5304 if((fp = fopen("bulletin.lst","r")) != NULL) {
5305 while(fgets(Line,sizeof(Line),fp) != NULL) {
5306 if((cp = strchr(Line,'\n')) != NULL) {
5307 *cp = 0;
5308 }
5309
5310 if((cp = strchr(Line,'\t')) != NULL) {
5311 *cp++ = 0;
5312 CmdListAdd(Line,cp);
5313 }
5314 }
5315 fclose(fp);
5316 }
5317 }
5318
CmdBulletinDelete(ClientInfo * p,ConfClient * pCC,char * Filename)5319 void CmdBulletinDelete(ClientInfo *p,ConfClient *pCC,char *Filename)
5320 {
5321 Bulletin *pLast = (Bulletin *) &BulletinList;
5322 Bulletin *pBulletin = BulletinList;
5323
5324 while(pBulletin != NULL) {
5325 if(STRCMP(pBulletin->Filename,Filename) == 0) {
5326 BufPrintf(p,"%s deleted\r",Filename);
5327 pLast->Link = pBulletin->Link;
5328 free(pBulletin->Filename);
5329 free(pBulletin->Description);
5330 free(pBulletin);
5331 SaveBulletinList();
5332 break;
5333 }
5334 pLast = pBulletin;
5335 pBulletin = pBulletin->Link;
5336 }
5337
5338 if(pBulletin == NULL) {
5339 BufPrintf(p,"%s not found\r",Filename);
5340 }
5341 }
5342
CmdXyzzy(ClientInfo * p,ConfClient * pCC,char * Arg)5343 void CmdXyzzy(ClientInfo *p,ConfClient *pCC,char *Arg)
5344 {
5345 BufPrintf(p,"Nothing happens\r");
5346 }
5347
CmdList(ClientInfo * p,ConfClient * pCC,char * Arg)5348 void CmdList(ClientInfo *p,ConfClient *pCC,char *Arg)
5349 {
5350 char *cp;
5351 char *cp1 = NULL;
5352 int i;
5353 Bulletin *pBulletin = BulletinList;
5354
5355 if((cp = strchr(Arg,' ')) != NULL) {
5356 *cp++ = 0;
5357 while(*cp && *cp == ' ') {
5358 cp++;
5359 }
5360
5361 if((cp1 = strchr(cp,' ')) != NULL) {
5362 *cp1++ = 0;
5363 while(*cp1 && *cp1 == ' ') {
5364 cp1++;
5365 }
5366 }
5367 }
5368
5369 if(pCC->bAdmin && cp != NULL && *cp) {
5370 if(STRCMP(Arg,"add") == 0) {
5371 if(cp != NULL && cp1 != NULL) {
5372 if(CmdListAdd(cp,cp1)) {
5373 SaveBulletinList();
5374 BufPrintf(p,"Added: %s as %s\r",cp,cp1);
5375 }
5376 else {
5377 BufPrintf(p,"File \"%s\" not found.\r",cp);
5378 }
5379 }
5380 else {
5381 BufPrintf(p,"usage: list add <filename> <description>\r");
5382 Rcode = TBD_ERR_INVALID_CMD;
5383 }
5384 }
5385 else if(STRCMP(Arg,"delete") == 0) {
5386 if(cp != NULL) {
5387 CmdBulletinDelete(p,pCC,cp);
5388 }
5389 else {
5390 BufPrintf(p,"usage: list delete <filename>\r");
5391 Rcode = TBD_ERR_INVALID_CMD;
5392 }
5393 }
5394 else {
5395 BufPrintf(p,"Usage:\r"
5396 "list add <filename> <description>\r"
5397 "list delete <filename>\r");
5398 Rcode = TBD_ERR_INVALID_CMD;
5399 }
5400 }
5401 else {
5402 // Normal client or just bare list command
5403
5404 i = 1;
5405 if(pBulletin == NULL) {
5406 BufPrintf(p,"Sorry, no bulletins are available at this time.\r");
5407 }
5408 else {
5409 BufPrintf(p,"Bulletins:\r");
5410 while(pBulletin != NULL) {
5411 if(pCC->bAdmin) {
5412 BufPrintf(p,"%d. (%s) %s (%s)\r",i++,pBulletin->Filename,
5413 pBulletin->Description,pBulletin->RunTime);
5414 }
5415 else {
5416 BufPrintf(p,"%d. %s (%s)\r",i++,pBulletin->Description,
5417 pBulletin->RunTime);
5418 }
5419 pBulletin = pBulletin->Link;
5420 }
5421 }
5422 }
5423 }
5424
PlayBackComplete(ConfClient * pCC,char * Reason)5425 void PlayBackComplete(ConfClient *pCC,char *Reason)
5426 {
5427 ClientFileIO *pFIO = (ClientFileIO *) pCC->p;
5428 ConfServer *pCS = pCC->pCS;
5429
5430 if(pFIO != NULL) {
5431 EventHook("playbackcomplete %s %s",pCC->Callsign,Reason);
5432 LOG_NORM(("PlayBackComplete() for %s, %s.\n",pCC->Callsign,Reason));
5433
5434 FileCleanup(pCC);
5435 }
5436 else {
5437 LOG_NORM(("PlayBackComplete() Error, pCC->p == NULL for %s, %s.\n",
5438 pCC->Callsign,Reason));
5439 }
5440
5441 if(AudioTestConf && FileRecord(NULL,pCC,EVENT_INIT)) {
5442 pCC->bInConf = FALSE;
5443 pCC->State = FileRecord;
5444 }
5445 else if(pCC->bFilePlayer) {
5446 pCC->pCS->bPlayWhenFree = FALSE;
5447 pCC->pCS->pFilePlayer = NULL;
5448 if(ClientTalking == pCC) {
5449 ClientTalking = NULL;
5450 }
5451 else {
5452 LOG_ERROR(("PlayBackComplete(): ClientTalking is not us.\n"));
5453 }
5454 DeleteCClient(pCC);
5455 SendStationList(pCS);
5456 pCS->bSendStationList = FALSE;
5457 }
5458 else {
5459 pCC->bInConf = TRUE;
5460 pCC->State = NULL;
5461 }
5462 }
5463
StartPlayback(ClientInfo * p,ConfClient * pCC)5464 void StartPlayback(ClientInfo *p,ConfClient *pCC)
5465 {
5466 ClientInfo *pPB;
5467 ClientFileIO *pFIO = (ClientFileIO *) pCC->p;
5468
5469 // Create a mainloop client to playback the file
5470 pPB = CreateNewClient();
5471 if(pPB == NULL) {
5472 LOG_ERROR(("FileRecord(): CreateNewClient failed.\n"));
5473 PlayBackComplete(pCC,"CreateNewClient() failed");
5474 }
5475 else {
5476 // Wait a second and then start playing it back
5477 pFIO->TimeFirstTx.tv_sec = 0;
5478 pFIO->pClient = pPB;
5479 pPB->p = pFIO;
5480 pPB->HisAdr = pCC->HisAdr;
5481 pPB->Socket = p->Socket;
5482 pPB->State = FilePlayBack;
5483 pPB->BufSize = CONF_BUF_SIZE;
5484 pPB->Buf = malloc(pPB->BufSize);
5485 if(pCC->bFilePlayer) {
5486 SetTimeout(pPB,1);
5487 }
5488 else {
5489 SetTimeout(pPB,1000);
5490 }
5491 avl_insert(ClientTree,pPB);
5492 pPB->bInClientTree = TRUE;
5493 }
5494 }
5495
OpenAudioFile(ConfClient * pCC,char * Filename,char * Mode)5496 void OpenAudioFile(ConfClient *pCC,char *Filename, char *Mode)
5497 {
5498 ClientFileIO *pFIO;
5499 FILE *fp;
5500
5501 if(pCC->bFileIOActive) {
5502 // A file is currently playing for this client, cancel it
5503 PlayBackComplete(pCC,"opening a new file");
5504 }
5505
5506 if((fp = FOPEN(Filename,Mode)) != NULL) {
5507 pFIO = (ClientFileIO *) (pCC->p = malloc(sizeof(ClientFileIO)));
5508 if(pFIO != NULL) {
5509 pCC->bFileIOActive = TRUE;
5510 pCC->bInConf = FALSE;
5511 memset(pFIO,0,sizeof(*pFIO));
5512 pFIO->fp = fp;
5513 pFIO->pCC = pCC;
5514 pFIO->Filename = strdup(Filename);
5515 pFIO->Timeout = 80; // Assume a perfect delay at first
5516 }
5517 else {
5518 LOG_ERROR(("OpenAudioFile(): malloc failed.\n"));
5519 }
5520 }
5521 else {
5522 LOG_ERROR(("OpenAudioFile: Unable to open \"%s\", %s.\n",
5523 Filename,Err2String(errno)));
5524 }
5525 }
5526
5527
5528 // play4 [-f] [-i] [-u callsign] filename [displayed name]
5529 // -f = force file to play now even if someone is talking
5530 // -i = play when conference becomes idle (IdleTimeout seconds of inactivity)
5531 // -u = play for specified user only
CmdPlayFor(ClientInfo * p,ConfClient * pCC1,char * Arg)5532 void CmdPlayFor(ClientInfo *p,ConfClient *pCC1,char *Arg)
5533 {
5534 int bForced = FALSE;
5535 int bWhenIdle = FALSE;
5536 int bIdle = FALSE;
5537 int bFileFoundErr = FALSE;
5538 ConfClient *pCC = NULL;
5539 ConfServer *pCS = (ConfServer *) p->p;
5540 char *cp = Arg;
5541 char *cp1;
5542 char *Filename = NULL;
5543 char CharSave;
5544 struct avl_traverser avl_trans;
5545
5546 while(*cp == '-') {
5547 switch(cp[1]) {
5548 case 'f':
5549 bForced = TRUE;
5550 break;
5551
5552 case 'i':
5553 bWhenIdle = TRUE;
5554 break;
5555
5556 case 'u':
5557 cp += 2;
5558 while(*cp && *cp == ' ') {
5559 cp++;
5560 }
5561 cp1 = cp;
5562 while(*cp1 && *cp1 != ' ') {
5563 cp1++;
5564 }
5565 CharSave = *cp1;
5566 *cp1 = 0;
5567
5568 pCC = (ConfClient *) avl_t_first(&avl_trans,pCS->ConfTree);
5569 while(pCC!= NULL) {
5570 if(STRCMP(cp,pCC->Callsign) == 0) {
5571 break;
5572 }
5573 pCC = (ConfClient *) avl_t_next(&avl_trans);
5574 }
5575 *cp1 = CharSave;
5576 cp = cp1 - 2;
5577
5578 if(pCC == NULL) {
5579 BufPrintf(p,"%s not found\r",cp);
5580 Rcode = TBD_STATION_NOT_FOUND;
5581 cp = NULL;
5582 }
5583 break;
5584
5585 default:
5586 BufPrintf(p,"Error: Unknown option '%c'\r",cp[1]);
5587 cp = NULL;
5588 Rcode = TBD_ERR_INVALID_ARG;
5589 break;
5590 }
5591
5592 if(cp == NULL) {
5593 break;
5594 }
5595 else {
5596 cp += 2;
5597 while(*cp && *cp == ' ') {
5598 cp++;
5599 }
5600 }
5601 }
5602
5603 if(cp != NULL) {
5604 // No error yet
5605 Filename = cp;
5606 if((cp = strchr(cp,' ')) != NULL) {
5607 while(*cp == ' ') {
5608 *cp++ = 0;
5609 }
5610 }
5611
5612 if(pCC != NULL) {
5613 // Playing for a specified user
5614 OpenAudioFile(pCC,Filename,MODE_RD_BIN);
5615 if(pCC->bFileIOActive) {
5616 // Playing for one station
5617 StartPlayback(p,pCC);
5618 BufPrintf(p,"Playing %s.\r",Filename);
5619 }
5620 else {
5621 bFileFoundErr = TRUE;
5622 }
5623 }
5624 else {
5625 // Playing for everyone
5626 pCC = CreateNewConfClient();
5627 pCC->bFilePlayer = TRUE;
5628 pCC->pCS = pCS;
5629 pCC->Proto = PROTO_ILINK;
5630 pCC->CompressionType = RTP_PT_GSM;
5631
5632 OpenAudioFile(pCC,Filename,MODE_RD_BIN);
5633 if(pCC->bFileIOActive) {
5634 pCC->bInConf = TRUE;
5635 if(cp == NULL) {
5636 cp = "QST";
5637 }
5638 pCC->CallPlus = strdup(cp);
5639 pCC->Callsign = strdup(cp);
5640 pCC->HisAdr.ADDR = inet_addr("127.0.0.1");
5641 if(pCS->pFilePlayer != NULL) {
5642 PlayBackComplete(pCS->pFilePlayer,"starting new playback 1");
5643 }
5644 pCS->pFilePlayer = pCC;
5645
5646 if(ClientTalking == NULL) {
5647 if(TimeNow.tv_sec - pCS->LastAudioIn.tv_sec > IdleTimeout) {
5648 bIdle = TRUE;
5649 }
5650 }
5651
5652 EndPoint(p,pCC,EVENT_INIT);
5653 if(bForced || (!bWhenIdle && ClientTalking == NULL) ||
5654 (bWhenIdle && bIdle))
5655 {
5656 ClientTalking = pCC;
5657 pCC->FirstAudioIn = TimeNow;
5658 StartPlayback(p,pCC);
5659 BufPrintf(p,"Playing %s.\r",Filename);
5660 }
5661 else if(bWhenIdle) {
5662 pCS->bPlayWhenIdle = TRUE;
5663 BufPrintf(p,"%s set to play when the conference becomes idle.\r",
5664 Filename);
5665 }
5666 else {
5667 pCS->bPlayWhenFree = TRUE;
5668 BufPrintf(p,"%s set to play when the conference becomes free.\r",
5669 Filename);
5670 }
5671 }
5672 else {
5673 bFileFoundErr = TRUE;
5674 }
5675 }
5676 }
5677
5678 if(bFileFoundErr) {
5679 BufPrintf(p,"%s not found.\r",Filename);
5680 Rcode = TBD_ERR_FILE_OPEN;
5681 }
5682 }
5683
CmdPlay(ClientInfo * p,ConfClient * pCC,char * Arg)5684 void CmdPlay(ClientInfo *p,ConfClient *pCC,char *Arg)
5685 {
5686 int Selection;
5687 Bulletin *pBulletin = BulletinList;
5688
5689 if(pCC->bFileIOActive) {
5690 PlayBackComplete(pCC,"starting new playback");
5691 }
5692
5693 if(sscanf(Arg,"%d",&Selection) == 1) {
5694 while(Selection != 1 && pBulletin != NULL) {
5695 Selection--;
5696 pBulletin = pBulletin->Link;
5697 }
5698
5699 if(Selection == 1 && pBulletin != NULL) {
5700 OpenAudioFile(pCC,pBulletin->Filename,MODE_RD_BIN);
5701 if(pCC->bFileIOActive) {
5702 StartPlayback(p,pCC);
5703 }
5704 }
5705 }
5706
5707 if(pCC->bFileIOActive) {
5708 BufPrintf(p,"Playing bulletin %s.\r"
5709 "Enter .stop to end, or just disconnect.\r",Arg);
5710 }
5711 else {
5712 BufPrintf(p,"Usage: .play <bulletin number>\r"
5713 " hint: use the list command to find the bulletins.\r");
5714 }
5715 }
5716
CmdQuit(ClientInfo * p,ConfClient * pCC,char * Arg)5717 void CmdQuit(ClientInfo *p,ConfClient *pCC,char *Arg)
5718 {
5719 BufPrintf(p,"Exiting\r");
5720 bRunning = FALSE;
5721 }
5722
CmdQuote(ClientInfo * p,ConfClient * pCC1,char * Arg)5723 void CmdQuote(ClientInfo *p,ConfClient *pCC1,char *Arg)
5724 {
5725 ConfClient *pCC;
5726 struct avl_traverser avl_trans;
5727 ConfServer *pCS = (ConfServer *) p->p;
5728
5729 BufPrintf(p,"%s>%s\r",ConferenceCall,Arg);
5730
5731 pCC = (ConfClient *) avl_t_first(&avl_trans,pCS->ConfTree);
5732 while(pCC != NULL) {
5733 if(pCC->bTBD || pCC->bSysop) {
5734 Send2(pCC,p->Buf,p->Count,FALSE);
5735 }
5736 pCC = (ConfClient *) avl_t_next(&avl_trans);
5737 }
5738 pCC1->bSentQuotedCmd = TRUE;
5739 pCC1->FirstAudioIn = TimeNow;
5740
5741 p->Count = 0; // reinit for BufPrintf
5742 BufPrintf(p,"%c" NDATA,ILINK_DATA_PACKET);
5743 }
5744
CmdStop(ClientInfo * p,ConfClient * pCC,char * Arg)5745 void CmdStop(ClientInfo *p,ConfClient *pCC,char *Arg)
5746 {
5747 ConfServer *pCS = (ConfServer *) p->p;
5748
5749 if(pCC->bFileIOActive) {
5750 BufPrintf(p,"Playback stopped.\r");
5751 PlayBackComplete(pCC,"stop command");
5752 }
5753 else if(pCS->pFilePlayer != NULL &&
5754 ((ClientFileIO *) pCS->pFilePlayer->p)->pClient != NULL)
5755 {
5756 PlayBackComplete(pCS->pFilePlayer,"stop command");
5757 }
5758 else {
5759 BufPrintf(p,"Error: Nothing is playing.\r");
5760 }
5761 }
5762
SendAudioFromFile(ClientInfo * p,ClientFileIO * pFIO,int NumPackets)5763 int SendAudioFromFile(ClientInfo *p,ClientFileIO *pFIO,int NumPackets)
5764 {
5765 FilePacketHdr PktHdr;
5766 int Ret = 0;
5767 time_t TimeStamp;
5768 int PlayBackPause = 0;
5769 ConfClient *pCC = pFIO->pCC;
5770 ConfServer *pCS = pCC->pCS;
5771 int TotalDelta = 0;
5772 rtp_hdr_t *pRTP;
5773
5774 if(MaxPlayWithoutPause > 0 && pFIO->TimeFirstTx.tv_sec != 0) {
5775 TotalDelta = ((TimeNow.tv_sec - pFIO->TimeFirstTx.tv_sec) * 1000) +
5776 ((TimeNow.tv_usec - pFIO->TimeFirstTx.tv_usec) / 1000);
5777
5778 if(TotalDelta > (MaxPlayWithoutPause * 1000)) {
5779 // time to pause for a while
5780 if(MinPlayBackPause > 0) {
5781 PlayBackPause = MinPlayBackPause;
5782 }
5783 else {
5784 PlayBackPause = 5;
5785 }
5786 Ret = 2;
5787 }
5788 }
5789
5790 while(Ret != 2 && NumPackets--) {
5791 if(fread(&PktHdr,sizeof(PktHdr),1,pFIO->fp) != 1) {
5792 if(feof(pFIO->fp)) {
5793 LOG_ERROR(("SendAudioFromFile(): unexpected end of file\n"));
5794 }
5795 else {
5796 LOG_ERROR(("SendAudioFromFile(): fread failed, %s",
5797 Err2String(errno)));
5798 }
5799 D3PRINTF(("Reading header\n"));
5800 break;
5801 }
5802
5803 PktHdr.Flags = ntohl(PktHdr.Flags);
5804 PktHdr.Len = ntohl(PktHdr.Len);
5805
5806 D3PRINTF(("SendAudioFromFile(): PktHdr.Len = %d, PktHdr.Flags = 0x%x\n",
5807 PktHdr.Len,PktHdr.Flags));
5808
5809 if(PktHdr.Flags & FILE_FLAG_TIME_STAMP) {
5810 // A timestamp header, pause playback
5811 TimeStamp = (time_t) PktHdr.Len;
5812 if(pFIO->FirstTimeStamp == 0) {
5813 pFIO->FirstTimeStamp = TimeStamp;
5814 }
5815
5816 if(pFIO->LastTimeStamp != 0) {
5817 PlayBackPause = TimeStamp - pFIO->LastTimeStamp;
5818 if(PlayBackPause > MaxPlayBackPause) {
5819 PlayBackPause = MaxPlayBackPause;
5820 }
5821 else if(PlayBackPause < MinPlayBackPause) {
5822 PlayBackPause = MinPlayBackPause;
5823 }
5824 }
5825 else {
5826 PlayBackPause = MinPlayBackPause;
5827 }
5828 pFIO->LastTimeStamp = TimeStamp;
5829 Ret = 2;
5830 break;
5831 }
5832
5833 if(PktHdr.Len > CONF_BUF_SIZE || PktHdr.Len < 0) {
5834 LOG_ERROR(("SendAudioFromFile(): Len > CONF_BUF_SIZE (%d).\n",
5835 PktHdr.Len));
5836 break;
5837 }
5838
5839 if(PktHdr.Len == 0) {
5840 D2PRINTF(("SendAudioFromFile(): playback complete.\n"));
5841 break;
5842 }
5843
5844 if(fread(p->Buf,PktHdr.Len,1,pFIO->fp) != 1) {
5845 if(feof(pFIO->fp)) {
5846 LOG_ERROR(("SendAudioFromFile(): unexpected end of file\n"));
5847 }
5848 else {
5849 LOG_ERROR(("SendAudioFromFile(): fread failed, %s",
5850 Err2String(errno)));
5851 }
5852 break;
5853 }
5854 pRTP = (rtp_hdr_t *) p->Buf;
5855
5856 if(!pCC->bFilePlayer) {
5857 if(pCC->Proto != PROTO_ILINK) {
5858 ProtoData *pPDat = pFIO->pPDat;
5859 int Len;
5860
5861 // playing for a non-EchoLink client
5862 if(pPDat == NULL) {
5863 if((pPDat = malloc(sizeof(ProtoData)*NUM_PROTOCOLS)) == NULL) {
5864 LOG_ERROR(("SendAudioFromFile: malloc failed.\n"));
5865 }
5866 else {
5867 pFIO->pPDat = pPDat;
5868 memset(pPDat,0,sizeof(ProtoData)*NUM_PROTOCOLS);
5869 pPDat->Callsign = pCC->Callsign;
5870 pPDat[PROTO_ILINK].Type = PKT_TYPE_AUDIO;
5871 pPDat[PROTO_ILINK].bDataValid = TRUE;
5872 pPDat[PROTO_ILINK].Data[0] = p->Buf;
5873 }
5874 }
5875
5876 if(pPDat != NULL && !(PktHdr.Flags & FILE_FLAG_DATA_PKT)) {
5877 pPDat[pCC->Proto].DataLen[0] = 0;
5878 pPDat[pCC->Proto].bDataValid = FALSE;
5879 pPDat[PROTO_ILINK].DataLen[0] = PktHdr.Len;
5880 ConvertProtocol(pPDat,PROTO_ILINK,pCC->Proto,FALSE);
5881
5882 if((Len = pPDat[pCC->Proto].DataLen[0]) != 0) {
5883 Send2(pCC,pPDat[pCC->Proto].Data[0],Len,FALSE);
5884 }
5885 }
5886 }
5887 else {
5888 if(!(PktHdr.Flags & FILE_FLAG_DATA_PKT) && !pCC->bSendSSRC) {
5889 // It's an audio packet make sure the ssrc is zero, this
5890 // client can't deal with a non-zero ssrc !
5891 pRTP->ssrc = 0;
5892 }
5893 Send2(pCC,p->Buf,PktHdr.Len,FALSE);
5894 }
5895 }
5896 else {
5897 p->Count = PktHdr.Len;
5898 RTP_Data(p,pCS,pCC);
5899 if(pCS->bSendStationList) {
5900 pCS->bSendStationList = FALSE;
5901 SendStationList(pCS);
5902 }
5903 }
5904
5905 if(!(PktHdr.Flags & FILE_FLAG_RATE_LIMIT)) {
5906 // Don't count this packet against rate limit
5907 NumPackets++;
5908 }
5909 else {
5910 pFIO->x++;
5911 }
5912 Ret = 1;
5913 }
5914
5915 if(Ret == 1) {
5916 SetTimeout(p,pFIO->Timeout);
5917 pFIO->TimeLastTx = TimeNow;
5918 D3PRINTF(("SendAudioFromFile(): Next timeout in %d milliseconds.\n",
5919 pFIO->Timeout));
5920 }
5921 else if(Ret == 2) {
5922 EndPoint(p,pFIO->pCC,EVENT_RTP_TO);
5923 pFIO->TimeFirstTx.tv_sec = 0;
5924 pFIO->x = 0;
5925 SetTimeout(p,PlayBackPause*1000);
5926 if(ClientTalking == pCC) {
5927 ClientTalking = NULL;
5928 pCS->bSendStationList = TRUE;
5929 }
5930 D3PRINTF(("SendAudioFromFile(): Pausing %d seconds.\n",PlayBackPause));
5931 }
5932 return Ret;
5933 }
5934
5935
FilePlayBack(ClientInfo * p)5936 int FilePlayBack(ClientInfo *p)
5937 {
5938 int TotalDelta = 0;
5939 int ThisDelta;
5940 int NewDelay;
5941 int Packets2Send = 0;
5942 ClientFileIO *pFIO = (ClientFileIO *) p->p;
5943 ConfClient *pCC = pFIO->pCC;
5944
5945 if(pFIO->TimeFirstTx.tv_sec != 0) {
5946 TotalDelta = ((TimeNow.tv_sec - pFIO->TimeFirstTx.tv_sec) * 1000) +
5947 ((TimeNow.tv_usec - pFIO->TimeFirstTx.tv_usec) / 1000);
5948
5949 // send one packet every 80 milliseconds
5950 // pFIO->x = packets sent so far
5951 Packets2Send = (TotalDelta / 80) - pFIO->x;
5952
5953 // Adjust the next Timeout
5954 ThisDelta = ((TimeNow.tv_sec - pFIO->TimeLastTx.tv_sec) * 1000) +
5955 ((TimeNow.tv_usec - pFIO->TimeLastTx.tv_usec) / 1000);
5956
5957 D3PRINTF(("FilePlayBack(): Last delta %d milliseconds.\n",ThisDelta));
5958
5959 pFIO->Timeout -= ((ThisDelta - 80)/2);
5960 if(pFIO->Timeout <= 0) {
5961 // Hmmm not keeping up... but we can't make time go backwards!
5962 pFIO->Timeout = 1;
5963 if(!pFIO->bTooSlowLogged) {
5964 pFIO->bTooSlowLogged = TRUE;
5965 LOG_ERROR(("FilePlayBack(): Desired timeout reached zero after %d"
5966 " packets.\n",pFIO->x));
5967 }
5968 }
5969 }
5970 else {
5971 // First transmission
5972 Packets2Send = 1;
5973 pFIO->TimeFirstTx = TimeNow;
5974 }
5975
5976 if(Packets2Send <= 0) {
5977 Packets2Send = 0;
5978 NewDelay = ((pFIO->x + 1) * 80) - TotalDelta;
5979 if(NewDelay <= 0) {
5980 // Hmmm again !
5981 NewDelay = 1;
5982 }
5983
5984 D3PRINTF(("FilePlayBack(): Too soon to send, waiting %d milliseconds.\n",
5985 NewDelay));
5986 SetTimeout(p,NewDelay);
5987 }
5988 else {
5989 D3PRINTF(("FilePlayBack(): Send %d packets this time.\n",Packets2Send));
5990 if(!SendAudioFromFile(p,pFIO,Packets2Send)) {
5991 PlayBackComplete(pCC,"playback complete");
5992 }
5993 }
5994
5995 return FALSE;
5996 }
5997
5998
FileRecord(ClientInfo * p,ConfClient * pCC,EventType Event)5999 int FileRecord(ClientInfo *p,ConfClient *pCC,EventType Event)
6000 {
6001 char *Filename;
6002 FilePacketHdr PktHdr;
6003 ClientFileIO *pFIO = (ClientFileIO *) pCC->p;
6004 int Ret = TRUE;
6005
6006 switch(Event) {
6007 case EVENT_INIT:
6008 // Open the file
6009 Filename = malloc(strlen(pCC->Callsign)+6);
6010 if(Filename != NULL) {
6011 strcpy(Filename,pCC->Callsign);
6012 strcat(Filename,".test");
6013 D2PRINTF(("FileRecord(): Opening file \"%s\".\n",Filename));
6014 OpenAudioFile(pCC,Filename,MODE_RDWR_BIN);
6015 free(Filename);
6016 pFIO = (ClientFileIO *) pCC->p;
6017 if(pFIO != NULL) {
6018 pFIO->bDeleteOnClose = TRUE;
6019 }
6020 }
6021
6022 if(!pCC->bFileIOActive) {
6023 // file open failed
6024 LOG_ERROR(("FileRecord(): fopen for \"%s\" failed, %s",Filename,
6025 Err2String(errno)));
6026 Ret = FALSE;
6027 }
6028 break;
6029
6030 case EVENT_RTP_RX:
6031 if(pFIO->pClient == NULL && p->Buf[0] != ILINK_DATA_PACKET) {
6032 if(!pCC->bTalking) {
6033 D2PRINTF(("FileRecord(): First RTP data received.\n"));
6034 pCC->bTalking = TRUE;
6035 }
6036 pCC->LastAudioIn = TimeNow;
6037 PktHdr.Len = htonl(p->Count);
6038 PktHdr.Flags = htonl(FILE_FLAG_RATE_LIMIT);
6039
6040 if(fwrite(&PktHdr,sizeof(PktHdr),1,pFIO->fp) != 1 ||
6041 fwrite(p->Buf,p->Count,1,pFIO->fp) != 1)
6042 { // Write error
6043 LOG_ERROR(("FileRecord(): fwrite failed, %s",Err2String(errno)));
6044 PlayBackComplete(pCC,"write error 1");
6045 }
6046 }
6047 break;
6048
6049 case EVENT_RTP_TO:
6050 // timeout, recording is complete, play it back
6051 D2PRINTF(("FileRecord(): RTP timeout, rewinding file.\n"));
6052 PktHdr.Len = 0;
6053 PktHdr.Flags = 0;
6054 if(fwrite(&PktHdr,sizeof(PktHdr),1,pFIO->fp) != 1) {
6055 // Write error
6056 LOG_ERROR(("FileRecord(): fwrite of eof header failed, %s",
6057 Err2String(errno)));
6058 PlayBackComplete(pCC,"write error 2");
6059 }
6060 else {
6061 fseek(pFIO->fp,0,SEEK_SET);
6062 StartPlayback(p,pCC);
6063 }
6064 break;
6065
6066 case EVENT_RTCP_TO:
6067 // He's gone
6068 PlayBackComplete(pCC,"rtcp timeout");
6069 break;
6070
6071 default:
6072 break;
6073 }
6074
6075 return Ret;
6076 }
6077
CmdLevelTest(ClientInfo * p,ConfClient * pCC,char * Arg)6078 void CmdLevelTest(ClientInfo *p,ConfClient *pCC,char *Arg)
6079 {
6080 if(!AudioTestConf && FileRecord(p,pCC,EVENT_INIT)) {
6081 pCC->bInConf = FALSE;
6082 pCC->State = FileRecord;
6083 BufPrintf(p,"Your next transmission will be recorded and played back.\r");
6084 }
6085 }
6086
6087 // .connect [-a] [-p <port#>] [-f] [-m] [-n] [-s | -r] [-7] <callign/ip adr> [description]
6088 // options:
6089 // -a - use ADPCM codec (RTP and Speak Freely protocols only)
6090 // -f - full duplex
6091 // -m - monitor only
6092 // -n - nailed up connection (not disconnected by disconnect all)
6093 // -p - use specified port
6094 // -r - use RTP protocol
6095 // -s - use Speak Freely protocol
6096 // -u - use uLaw codec (RTP and Speak Freely protocols only)
6097 // -7 - use G.726 codec (RTP protocols only)
CmdConnect(ClientInfo * p,ConfClient * pCC2,char * Arg)6098 void CmdConnect(ClientInfo *p,ConfClient *pCC2,char *Arg)
6099 {
6100 UserInfo OtherConf;
6101 ConfClient Lookup;
6102 UserInfo *pUser;
6103 struct hostent *pTemp;
6104 ConfClient *pCC = NULL;
6105 ConfClient *pCC1;
6106 struct avl_traverser avl_trans;
6107 ConfServer *pCS = (ConfServer *) p->p;
6108 ConfServer *pHisCS = pCS;
6109 ConfServer *pDynamicCS = NULL;
6110 ConfServer ConfLookup;
6111 int NodeID;
6112 int Codecs = 0;
6113 int bSpeakFreely = FALSE;
6114 int bMonitor = FALSE;
6115 int bNailed = FALSE;
6116 int bRTP = FALSE;
6117 int bADPCM = FALSE;
6118 int bULaw = FALSE;
6119 int bG726 = FALSE;
6120 int bFullDuplex = FALSE;
6121 int Port = SF_ReplyPort;
6122 int Err;
6123 int bEchoLinkNode = FALSE;
6124 char *cp = Arg;
6125 char *DispCall = NULL;
6126 char *Callsign = NULL;
6127 char *WhiteSpace = "\t\n ";
6128 char Option;
6129
6130 while(*cp == '-') {
6131 cp++;
6132 switch(Option = *cp++) {
6133 case 'p':
6134 if(sscanf(cp,"%d",&Port) != 1) {
6135 BufPrintf(p,"Error: Invalid port number\r");
6136 cp = NULL;
6137 Rcode = TBD_ERR_INVALID_ARG;
6138 break;
6139 }
6140 else if(Port & 1) {
6141 BufPrintf(p,"Error: Invalid port number, must be even\r");
6142 cp = NULL;
6143 Rcode = TBD_ERR_INVALID_ARG;
6144 break;
6145 }
6146 else {
6147 // Skip past port number
6148 while(*cp == ' ') {
6149 cp++;
6150 }
6151
6152 while(*cp && *cp != ' ') {
6153 cp++;
6154 }
6155 }
6156 break;
6157
6158 case 's':
6159 bSpeakFreely = TRUE;
6160 bRTP = FALSE;
6161 break;
6162
6163 case 'r':
6164 bRTP = TRUE;
6165 bSpeakFreely = FALSE;
6166 break;
6167
6168 case 'm':
6169 bMonitor = TRUE;
6170 break;
6171
6172 case 'n':
6173 bNailed = TRUE;
6174 break;
6175
6176 #ifdef LINK_BOX
6177 case 'a':
6178 bADPCM = TRUE;
6179 Codecs++;
6180 break;
6181
6182 case 'u':
6183 bULaw = TRUE;
6184 Codecs++;
6185 break;
6186
6187 case '7':
6188 bG726 = TRUE;
6189 Codecs++;
6190 break;
6191 #endif
6192 case 'f':
6193 bFullDuplex = TRUE;
6194 break;
6195
6196 default:
6197 BufPrintf(p,"Error: Unknown option '%c'\r",Option);
6198 cp = NULL;
6199 Rcode = TBD_ERR_INVALID_ARG;
6200 break;
6201 }
6202
6203 if(cp == NULL) {
6204 break;
6205 }
6206 else {
6207 while(*cp && *cp == ' ') {
6208 cp++;
6209 }
6210 }
6211 }
6212
6213 if(Rcode == 0) do {
6214 if(bADPCM && !bSpeakFreely && !bRTP) {
6215 // No ADPCM on Echolink
6216 BufPrintf(p,"Error: ADPCM codec is not supported by EchoLink.\r");
6217 Rcode = TBD_ERR_INVALID_ARG;
6218 break;
6219 }
6220
6221 if(bULaw && !bSpeakFreely && !bRTP) {
6222 // No uLaw on Echolink
6223 BufPrintf(p,"Error: uLaw codec is not supported by EchoLink.\r");
6224 Rcode = TBD_ERR_INVALID_ARG;
6225 break;
6226 }
6227
6228 if(bG726 && !bRTP) {
6229 // No G726 on SF or Echolink
6230 BufPrintf(p,"Error: G.726 codec is not supported by EchoLink or "
6231 "Speak Freely.\r");
6232 Rcode = TBD_ERR_INVALID_ARG;
6233 break;
6234 }
6235
6236 if(Codecs > 1) {
6237 BufPrintf(p,"Error: pick just one, uLaw, ADPCM or G.726.\r");
6238 Rcode = TBD_ERR_INVALID_ARG;
6239 break;
6240 }
6241
6242 if((Callsign= strtok(cp,WhiteSpace)) == NULL) {
6243 BufPrintf(p,"Usage:\r");
6244 BufPrintf(p,"connect [-a] [-u] [-7] [-p <port#>] [-m] [-s | -r] [-f] "
6245 "<callign/ip adr> [description]\r");
6246 Rcode = TBD_ERR_INVALID_ARG;
6247 break;
6248 }
6249
6250 if(bSpeakFreely || bRTP || bG726) {
6251 // Look for a "conference" using this port number
6252 ConfLookup.AudioPort = Port;
6253 if((pHisCS = avl_find(Conferences,&ConfLookup)) == NULL) {
6254 // need to create a dynamic conference
6255 Err = CreateConference(Port,piLinkConf,&pDynamicCS);
6256 if(Err != 0) {
6257 BufPrintf(p,"Error: Couldn't open port (%d)\r",Err);
6258 Rcode = TBD_ERR_FILE_OPEN;
6259 break;
6260 }
6261 else {
6262 LOG_NORM(("Conference for port %d created\n",Port));
6263 pDynamicCS->bDynamicConf = TRUE;
6264 if(bSpeakFreely) {
6265 pDynamicCS->bSFConf = TRUE;
6266 }
6267 else {
6268 pDynamicCS->bRTPConf = TRUE;
6269 }
6270 pDynamicCS->pAudio->State = RTP_Handler;
6271 pDynamicCS->pControl->State = RTCP_Handler;
6272 pDynamicCS->TimeNextSDES = TimeNow.tv_sec + SDES_Interval;
6273 pHisCS = pDynamicCS;
6274 }
6275 }
6276 }
6277
6278
6279 DispCall = strtok(NULL,"");
6280 Convert2Upper(Callsign);
6281 OtherConf.Callsign = Callsign;
6282 pCC = (ConfClient *) avl_t_first(&avl_trans,pCS->ConfTree);
6283 while(pCC != NULL) {
6284 if(STRCMP(Arg,pCC->Callsign) == 0) {
6285 if(pCC->bKicked) {
6286 pCC->bKicked = FALSE;
6287 pCC->bSWL = FALSE;
6288 pCC->bInConf = TRUE;
6289 pCC->bConnected = TRUE;
6290 pCC->bPermanent = TRUE;
6291 IncrementClientCount(pCS,pCC);
6292 BufPrintf(p,"Reconnecting \"%s\".\r",Arg);
6293 }
6294 else if(pCC->bPermanent) {
6295 BufPrintf(p,"Already connected to \"%s\".\r",Arg);
6296 Rcode = TBD_ERR_CONNECTED;
6297 pCC = NULL;
6298 }
6299 else {
6300 pCC->bPermanent = TRUE;
6301 BufPrintf(p,"Connection to \"%s\" set to permanent.\r",Arg);
6302 }
6303 break;
6304 }
6305 pCC = (ConfClient *) avl_t_next(&avl_trans);
6306 }
6307
6308 if(Rcode != 0 || pCC != NULL) {
6309 break;
6310 }
6311
6312 // Didn't find an existing conference client, create a new one
6313 if((pCC = CreateNewConfClient()) == NULL) {
6314 break;
6315 }
6316
6317 pCC->pCS = pHisCS;
6318 pCC->HisAdr.ADDR = INADDR_NONE;
6319 // pCC->HisAdr.PORT = htons((uint16_t) Port);
6320 pCC->HisAdr.i.sin_family = AF_INET;
6321 pCC->bNailed = bNailed;
6322 if(bSpeakFreely) {
6323 pCC->Proto = PROTO_SF;
6324 }
6325 else if(bRTP) {
6326 pCC->Proto = PROTO_RTP;
6327 }
6328 else {
6329 pCC->Proto = PROTO_ILINK;
6330 }
6331
6332 if(bFullDuplex) {
6333 pCC->bFullDuplex = TRUE;
6334 }
6335
6336 if(bADPCM) {
6337 pCC->CompressionType = RTP_PT_DVI4_8K;
6338 }
6339 else if(bULaw) {
6340 pCC->CompressionType = RTP_PT_PCMU;
6341 }
6342 else if(bG726) {
6343 pCC->CompressionType = RTP_PT_G726;
6344 }
6345 else {
6346 pCC->CompressionType = RTP_PT_GSM;
6347 }
6348
6349 if(pDynamicCS != NULL) {
6350 pDynamicCS->CompressionType = pCC->CompressionType;
6351 }
6352 pCC->bMonitor = bMonitor;
6353
6354 if(DispCall != NULL) {
6355 pCC->bCallsignOverride = TRUE;
6356 pCC->CallPlus = strdup(DispCall);
6357 if((cp = strchr(DispCall,' ')) != NULL) {
6358 // truncate string after callsign
6359 *cp = 0;
6360 }
6361 pCC->Callsign = strdup(DispCall);
6362 }
6363 else {
6364 pCC->Callsign = strdup(Callsign);
6365 pCC->CallPlus = strdup(Callsign);
6366 }
6367 pCC->bPermanent = TRUE;
6368 pCC->bInConf = FALSE; // Not until we get a response from him
6369
6370 if((pUser = avl_find(UserTree,&OtherConf)) != NULL) {
6371 pCC->HisAdr.ADDR = pUser->HisAdr.ADDR;
6372 bEchoLinkNode = TRUE;
6373 }
6374 else if(strchr(Callsign,'.') == NULL &&
6375 sscanf(Callsign,"%d",&NodeID) == 1)
6376 { // Numeric argument that doesn't contain a '.',
6377 // looking up argument as a EchoLink node ID
6378 pUser = (UserInfo *) avl_t_first(&avl_trans,UserTree);
6379 while(pUser != NULL) {
6380 if(pUser->NodeID == NodeID) {
6381 free(pCC->Callsign);
6382 free(pCC->CallPlus);
6383 pCC->Callsign = strdup(pUser->Callsign);
6384 pCC->CallPlus = strdup(pUser->Callsign);
6385 pCC->HisAdr.ADDR = pUser->HisAdr.ADDR;
6386 bEchoLinkNode = TRUE;
6387 break;
6388 }
6389 pUser = (UserInfo *) avl_t_next(&avl_trans);
6390 }
6391 }
6392 else {
6393 // Didn't find a currently logged in station by that call
6394 // or Node ID, try an IP address
6395
6396 pCC->HisAdr.ADDR = inet_addr(Callsign);
6397
6398 if(pCC->HisAdr.ADDR == INADDR_NONE) {
6399 // Try looking up the argument as a hostname
6400 pTemp = GetHostByName(Callsign);
6401 if(pTemp != NULL) {
6402 pCC->HisAdr.ADDR = IP_FROM_HOSTENT(pTemp,0);
6403 }
6404 }
6405 }
6406
6407 if(pCC->HisAdr.ADDR != INADDR_NONE) {
6408 pCC->HisAdr.PORT = pHisCS->AudioPort;
6409 Lookup.HisAdr = pCC->HisAdr;
6410
6411 if((pCC1 = avl_find(pCS->ConfTree,&Lookup)) == NULL) {
6412 AddClient2Conf(pHisCS,pCC);
6413 SendSDES(pHisCS,pCC);
6414 if(bEchoLinkNode) {
6415 // K1RFD 1/30/08
6416 SendFirewallOpenRequest(pHisCS,pCC);
6417 }
6418 BufPrintf(p,"Connecting to \"%s\".\r",pCC->Callsign);
6419 }
6420 else {
6421 pCC->HisAdr.ADDR = INADDR_NONE;
6422 if(pCC1->bKicked) {
6423 pCC1->bKicked = FALSE;
6424 pCC1->bSWL = FALSE;
6425 pCC1->bInConf = TRUE;
6426 pCC1->bPermanent = TRUE;
6427 pCC1->bConnected = TRUE;
6428 IncrementClientCount(pCS,pCC);
6429 BufPrintf(p,"Reconnecting \"%s\".\r",pCC->Callsign);
6430 }
6431 else if(pCC1->bPermanent) {
6432 BufPrintf(p,"Already connected to \"%s\".\r",pCC->Callsign);
6433 Rcode = TBD_ERR_CONNECTED;
6434 }
6435 else {
6436 pCC1->bPermanent = TRUE;
6437 BufPrintf(p,"Connection to \"%s\" set to permanent.\r",
6438 pCC->Callsign);
6439 }
6440 }
6441 }
6442 else {
6443 BufPrintf(p,"Station \"%s\" not found.\r",Callsign);
6444 Rcode = TBD_STATION_NOT_FOUND;
6445 }
6446
6447 if(pCC->HisAdr.ADDR == INADDR_NONE) {
6448 free(pCC->Callsign);
6449 free(pCC->CallPlus);
6450 free(pCC);
6451 }
6452 } while(FALSE);
6453
6454 if(pDynamicCS != NULL) {
6455 if(Rcode != 0) {
6456 // Clean up dynamically created conference on failure
6457 DeleteConf(pDynamicCS);
6458 }
6459 }
6460 }
6461
CmdDisconnect(ClientInfo * p,ConfClient * pCC1,char * Arg)6462 void CmdDisconnect(ClientInfo *p,ConfClient *pCC1,char *Arg)
6463 {
6464 ConfClient *pCC;
6465 struct avl_traverser avl_trans;
6466 ConfServer *pCS = (ConfServer *) p->p;
6467 int bStationFound = FALSE;
6468 char *Call = Arg;
6469 int bAllStations = FALSE;
6470
6471 if(STRCMP(Arg,"all") == 0) {
6472 bAllStations = TRUE;
6473 }
6474 else if(STRCMP(Arg,"last") == 0) {
6475 // Find the last station that connected
6476 int LargestSN = 0;
6477 ConfClient *pCC1 = NULL;
6478
6479 pCC = (ConfClient *) avl_t_first(&avl_trans,pCS->ConfTree);
6480 while(pCC != NULL) {
6481 if(pCC->SN > LargestSN) {
6482 LargestSN = pCC->SN;
6483 pCC1 = pCC;
6484 }
6485 pCC = (ConfClient *) avl_t_next(&avl_trans);
6486 }
6487
6488 if(pCC1 != NULL) {
6489 // Set arg to the callsign of the selected station
6490 Call = pCC1->Callsign;
6491 }
6492 }
6493
6494 pCC = (ConfClient *) avl_t_first(&avl_trans,pCS->ConfTree);
6495 while(pCC != NULL) {
6496 if(STRCMP(Call,pCC->Callsign) == 0 ||
6497 (bAllStations && !pCC->bNailed) ||
6498 (*Arg == '.' && pCC == ClientTalking))
6499 {
6500 bStationFound = TRUE;
6501 LOG_NORM(("%s disconnected by %s\n",CallLog(pCC),pCC1->Callsign));
6502 DisconnectCC(p,pCC,"Bye bye !");
6503
6504 pCS->bSendStationList = TRUE;
6505 if(StationDisconnected != NULL) {
6506 free(StationDisconnected);
6507 }
6508 StationDisconnected = strdup(pCC->Callsign);
6509
6510 if(!bAllStations) {
6511 break;
6512 }
6513 }
6514 pCC = (ConfClient *) avl_t_next(&avl_trans);
6515 }
6516
6517 // We clobbered the buffer, reinitialize it for our response
6518 p->Count = 0; // init for BufPrintf
6519 BufPrintf(p,"%c" NDATA,ILINK_DATA_PACKET);
6520
6521 if(!bStationFound) {
6522 if(*Arg == '.') {
6523 BufPrintf(p,"No one is talking\r");
6524 }
6525 else if(bAllStations) {
6526 BufPrintf(p,"No stations found\r");
6527 }
6528 else {
6529 BufPrintf(p,"%s not found\r",Arg);
6530 }
6531 Rcode = TBD_STATION_NOT_FOUND;
6532 }
6533 else if(bAllStations) {
6534 BufPrintf(p,"All stations disconnected\r");
6535 }
6536 else {
6537 BufPrintf(p,"%s disconnected\r",StationDisconnected);
6538 }
6539 }
6540
CmdInfo(ClientInfo * p,ConfClient * pCC,char * Arg)6541 void CmdInfo(ClientInfo *p,ConfClient *pCC,char *Arg)
6542 {
6543 char *Temp = strdup(ConnectedStatus);
6544 UpdateConnectedStatus();
6545 if(*Arg) {
6546 if(ConferenceQth != NULL) {
6547 free(ConferenceQth);
6548 }
6549 ConferenceQth = strdup(Arg);
6550 }
6551
6552 if(LoginInterval > 0 && (*Arg || strcmp(ConnectedStatus,Temp) != 0)) {
6553 // Send update to directory server
6554 NextLoginTime = TimeNow.tv_sec + LoginInterval;
6555 ServerRequest(SERV_REQ_LOGIN,0,NULL);
6556 }
6557 free(Temp);
6558
6559 BufPrintf(p,"Current location string: %s\r",ConferenceQth);
6560 if(ShowStatusInInfo && ConnectedStatus[0] != 0) {
6561 BufPrintf(p,"Currently displaying: %s\r",ConnectedStatus);
6562 }
6563 }
6564
CmdPauseTime(ClientInfo * p,ConfClient * pCC,char * Arg)6565 void CmdPauseTime(ClientInfo *p,ConfClient *pCC,char *Arg)
6566 {
6567 if(*Arg) {
6568 if(sscanf(Arg,"%d",&PauseTime) != 1) {
6569 BufPrintf(p,"Error: \"%s\" is not a number.\r",Arg);
6570 Rcode = TBD_ERR_INVALID_ARG;
6571 }
6572 }
6573 BufPrintf(p,"The minimum pause time is %d milliseconds\r",PauseTime);
6574 }
6575
CmdBelchfilter(ClientInfo * p,ConfClient * pCC,char * Arg)6576 void CmdBelchfilter(ClientInfo *p,ConfClient *pCC,char *Arg)
6577 {
6578 if(*Arg) {
6579 if(sscanf(Arg,"%d",&BelchTime) != 1) {
6580 BufPrintf(p,"Error: \"%s\" is not a number.\r",Arg);
6581 }
6582 }
6583 BufPrintf(p,"The belchfilter is current set to %d milliseconds\r",BelchTime);
6584 }
6585
6586 // Busy [on] [off] [status]
CmdBusy(ClientInfo * p,ConfClient * pCC,char * Arg)6587 void CmdBusy(ClientInfo *p,ConfClient *pCC,char *Arg)
6588 {
6589 int bBusyStateChanged = FALSE;
6590
6591 if(*Arg) {
6592 if(STRCMP(Arg,"on") == 0) {
6593 bBusyStateChanged = !PullerStatusBusy;
6594 bConferenceBusy = TRUE;
6595 PullerStatusBusy = TRUE;
6596 }
6597 else if(STRCMP(Arg,"off") == 0) {
6598 bBusyStateChanged = PullerStatusBusy;
6599 bConferenceBusy = FALSE;
6600 PullerStatusBusy = FALSE;
6601 }
6602 else if(STRCMP(Arg,"status") == 0) {
6603 bBusyStateChanged = !PullerStatusBusy;
6604 bConferenceBusy = FALSE;
6605 PullerStatusBusy = TRUE;
6606 }
6607 else {
6608 BufPrintf(p,"Error: Invalid argument \"%s\"\r",Arg);
6609 BufPrintf(p,"Usage: Busy [on] [off] [status]\r");
6610 }
6611 }
6612 if(bBusyStateChanged) {
6613 if(LoginInterval > 0) {
6614 NextAVRSTime = TimeNow.tv_sec; // Force an AVRS update
6615 NextLoginTime = TimeNow.tv_sec + LoginInterval;
6616 ServerRequest(SERV_REQ_LOGIN,0,NULL);
6617 }
6618 }
6619 if(bConferenceBusy != PullerStatusBusy) {
6620 BufPrintf(p,"The indicated conference state is %s.\r",
6621 PullerStatusBusy ? "busy" : "available");
6622 }
6623 else {
6624 BufPrintf(p,"The conference is %s.\r",
6625 PullerStatusBusy ? "busy" : "available");
6626 }
6627 }
6628
CmdChat(ClientInfo * p,ConfClient * pCC,char * Arg)6629 void CmdChat(ClientInfo *p,ConfClient *pCC,char *Arg)
6630 {
6631 if(*Arg) {
6632 if(STRCMP(Arg,"on") == 0) {
6633 bCmdLineChatMode = TRUE;
6634 }
6635 else if(STRCMP(Arg,"off") == 0) {
6636 bCmdLineChatMode = FALSE;
6637 }
6638 else {
6639 BufPrintf(p,"Error: Invalid argument \"%s\"\r",Arg);
6640 BufPrintf(p,"Usage: Chat [on] [off]\r");
6641 }
6642 }
6643 BufPrintf(p,"Chat mode is %s.\r",bCmdLineChatMode ? "on" : "off");
6644 }
6645
CmdCrash(ClientInfo * p,ConfClient * pCC,char * Arg)6646 void CmdCrash(ClientInfo *p,ConfClient *pCC,char *Arg)
6647 {
6648 DumpMe();
6649 }
6650
6651
CmdHelpNoDot(ClientInfo * p,ConfClient * pCC,char * Arg)6652 void CmdHelpNoDot(ClientInfo *p,ConfClient *pCC,char *Arg)
6653 {
6654 CmdHelp(p,pCC,Arg);
6655
6656 BufPrintf(p,"PLEASE NOTE: All commands MUST start with a PERIOD or DOT.\r");
6657 BufPrintf(p,"The HELP command you just entered is the only exception.\r");
6658 }
6659
6660 // CmdLine
6661 // 0 = command doesn't match
6662 // 1 = command matches
6663 // 2 = exact command match
MatchCmd(const char * CmdLine,const char * Command)6664 int MatchCmd(const char *CmdLine, const char *Command)
6665 {
6666 while(*CmdLine == ' ') {
6667 CmdLine++;
6668 }
6669
6670 if(!*CmdLine) {
6671 return 0;
6672 }
6673
6674 while(*CmdLine && *CmdLine != ' ' && *CmdLine != '\r' && *CmdLine != '\n') {
6675 if(tolower(*CmdLine) != tolower(*Command)) {
6676 return FALSE;
6677 }
6678 CmdLine++;
6679 Command++;
6680 }
6681 if(*Command == 0) {
6682 // Exact match
6683 return 2;
6684 }
6685 return 1;
6686 }
6687
6688 struct COMMAND_TABLE {
6689 char *CmdString;
6690 char *HelpString;
6691 void (*CmdHandler)(ClientInfo *p,ConfClient *pCC,char *Arg);
6692 int Flags;
6693 #define CMD_FLAG_NOLIST 1 // suppress listing in help commands
6694 #define CMD_FLAG_ADMIN 2 // commands available to admins only
6695 #define CMD_FLAG_NEED_DISK 4 // commands that access the disk
6696 #define CMD_FLAG_SYSOP 8 // commands available to sysops & admins
6697 #define CMD_FLAG_LURK 0x10 // lurking command
6698 #define CMD_FLAG_NOSCRIPT 0x20 // command not available to scripting
6699 #define CMD_FLAG_SCRIPT 0x40 // command only available to scripting
6700 #define CMD_FLAG_RFONLY 0x80 // command only available to rf users
6701 };
6702
6703 #define CMD_ADMIN_DISK (CMD_FLAG_ADMIN | CMD_FLAG_NEED_DISK)
6704
6705 struct COMMAND_TABLE commandtable[] = {
6706 { "about", "Show information about specified station", CmdAbout, 0},
6707 { "admin", "", CmdAdmin, CMD_FLAG_NOLIST},
6708 { "admins", "List logged in admins", CmdAdmins, CMD_FLAG_SYSOP},
6709 { "allow", "Allow a user to use the conference", CmdAllow, CMD_FLAG_ADMIN},
6710 { "ban", "Ban a user from the conference", CmdBan, CMD_FLAG_ADMIN},
6711 { "belchfilter", "Set minimum TX time for recognition", CmdBelchfilter,
6712 CMD_FLAG_SYSOP},
6713 { "busy", "List conference as busy", CmdBusy, CMD_FLAG_SYSOP},
6714 { "chat", "enable or disable chat traffic", CmdChat, CMD_FLAG_SCRIPT},
6715 { "crash", "", CmdCrash, CMD_FLAG_ADMIN | CMD_FLAG_NOLIST },
6716 { "connect", "Connect to another conference", CmdConnect, CMD_FLAG_SYSOP},
6717 { "debug", "", CmdDebug, CMD_FLAG_NOLIST },
6718 #ifdef LINK_BOX
6719 { "dtmfdecode", "", CmdDtmfDecode, CMD_FLAG_NOLIST },
6720 #endif
6721 { "dns", "DNS [refresh] [list]", CmdDNS, CMD_FLAG_ADMIN},
6722 { "disconnect", "Disconnect from another conference", CmdDisconnect,
6723 CMD_FLAG_SYSOP},
6724 { "delurk", "Leave monitor mode", CmdDelurk, CMD_FLAG_LURK },
6725 { "help", "", CmdHelp, CMD_FLAG_NOLIST},
6726 { "elp", "", CmdHelpNoDot, CMD_FLAG_NOLIST},
6727 #ifdef LINK_BOX
6728 { "id", "", CmdForceID, CMD_FLAG_NOLIST | CMD_FLAG_ADMIN},
6729 { "frequency", "set RF ports operating parameters", CmdFrequency,
6730 CMD_FLAG_ADMIN | CMD_FLAG_NOLIST},
6731 #endif
6732 { "info", "Set directory server location string", CmdInfo, CMD_FLAG_ADMIN},
6733 { "kick", "kick a user off conference", CmdKick, CMD_FLAG_ADMIN},
6734 { "list", "List available prerecorded nets and bulletins", CmdList,
6735 CMD_FLAG_NEED_DISK },
6736 #ifdef LINK_BOX
6737 { "link", "Link on port to another", CmdLink, CMD_FLAG_NOLIST | CMD_FLAG_ADMIN},
6738 #endif
6739 { "lookup","Lookup a user by Callsign and display Node,Qth", CmdLookUp,0},
6740 { "lurk", "Enter monitor mode", CmdLurk, CMD_FLAG_LURK },
6741 { "lurkers", "", CmdLurkers, CMD_FLAG_NOLIST},
6742 { "message", "send text message to all users", CmdMessage,
6743 CMD_FLAG_SYSOP | CMD_FLAG_SCRIPT},
6744 { "monitor", "don't send to specified station", CmdMonitor, CMD_FLAG_SYSOP},
6745 { "mute", "put a user into receive only mode", CmdMute, CMD_FLAG_SYSOP},
6746 #ifdef LINK_BOX
6747 { "script", "", CmdScript, CMD_FLAG_RFONLY | CMD_FLAG_NOLIST},
6748 #endif
6749 { "sysop", "", CmdSysop, CMD_FLAG_NOLIST},
6750 { "pausetime", "Set minimum pause time", CmdPauseTime, CMD_FLAG_SYSOP},
6751
6752 #ifdef LINK_BOX
6753 { "pcm", "raw pcm record/playback for debuggging", CmdPCM,
6754 CMD_FLAG_ADMIN | CMD_FLAG_NOLIST},
6755 #endif
6756
6757 { "play", "play are prerecorded net or bulletin", CmdPlay,
6758 CMD_FLAG_NEED_DISK | CMD_FLAG_NOSCRIPT},
6759 { "play4", "play file for specified user(s)", CmdPlayFor,
6760 CMD_FLAG_SYSOP | CMD_FLAG_NEED_DISK },
6761 #ifdef LINK_BOX
6762 { "port", "",CmdSetPort, CMD_FLAG_ADMIN | CMD_FLAG_NOLIST },
6763 #endif
6764 { "quickexit", "", CmdQuit, CMD_FLAG_ADMIN | CMD_FLAG_NOLIST },
6765 { "quote", "send a command to linked conferences", CmdQuote, CMD_FLAG_SYSOP},
6766 { "record", "record traffic for later playback", CmdRecord, CMD_ADMIN_DISK},
6767 { "refresh", "refresh station list", CmdRefresh, CMD_FLAG_ADMIN},
6768 { "rehash", "re-read the configuration file", CmdRehash, CMD_FLAG_ADMIN},
6769 #ifdef LINK_BOX
6770 { "txoffset", "set transmitter offset", CmdTxOffset,
6771 CMD_FLAG_ADMIN | CMD_FLAG_NOLIST},
6772 { "txpower", "set transmitter power", CmdTxPower,
6773 CMD_FLAG_ADMIN | CMD_FLAG_NOLIST},
6774 { "rxfrequency", "set ports receive frequency", CmdRxFrequency,
6775 CMD_FLAG_ADMIN | CMD_FLAG_NOLIST},
6776 { "rxtone", "set CTCSS or DCS decoder tone", CmdRxTone,
6777 CMD_FLAG_ADMIN | CMD_FLAG_NOLIST},
6778 { "rxlevel", "display receiver levels", CmdRxLevel,
6779 CMD_FLAG_ADMIN | CMD_FLAG_NOLIST},
6780 #endif
6781 { "set", "set a configuration variable", CmdSet, CMD_FLAG_ADMIN},
6782 { "showip", "show IP address of current users", CmdShowIP, CMD_FLAG_ADMIN },
6783 { "shutdown", "", CmdShutdown, CMD_FLAG_ADMIN | CMD_FLAG_NOLIST},
6784 #ifdef LINK_BOX
6785 { "shutup", "kill queued announcements", CmdShutup,
6786 CMD_FLAG_ADMIN | CMD_FLAG_NOLIST},
6787 { "say", "Speak a phrase to RF port", CmdSay,
6788 CMD_FLAG_ADMIN | CMD_FLAG_NOLIST},
6789 { "sayresult", "Say results of previous command", CmdSayResult,
6790 CMD_FLAG_RFONLY | CMD_FLAG_NOLIST},
6791 { "saystatus", "Announce system status", CmdSayStatus,
6792 CMD_FLAG_RFONLY | CMD_FLAG_NOLIST},
6793 { "sendbeacon", "send AX25 beacon", CmdSendAx25Beacon,
6794 CMD_FLAG_RFONLY | CMD_FLAG_NOLIST},
6795 #endif
6796 { "stats", "Display statistics", CmdStats, 0},
6797 #ifdef LINK_BOX
6798 { "sweep", "Generate test tone", CmdSweep, CMD_FLAG_ADMIN | CMD_FLAG_NOLIST},
6799 #endif
6800 { "stop", "end playback of recorded bulletin", CmdStop,
6801 CMD_FLAG_NEED_DISK | CMD_FLAG_NOSCRIPT},
6802 #ifdef LINK_BOX
6803 { "tonegen", "Generate telemeter tones on for current port", CmdToneGen,
6804 CMD_FLAG_ADMIN | CMD_FLAG_NOLIST},
6805 { "txtone", "set CTCSS or DCS encode tone", CmdTxTone,
6806 CMD_FLAG_ADMIN | CMD_FLAG_NOLIST},
6807 #endif
6808 { "test", "Record and playback test transmission", CmdLevelTest,
6809 CMD_FLAG_NEED_DISK | CMD_FLAG_NOSCRIPT},
6810 { "unmute", "restore a user to transceive mode", CmdUnMute, CMD_FLAG_SYSOP},
6811 { "users", "show user list", CmdUsers, CMD_FLAG_SYSOP},
6812
6813 #ifdef LINK_BOX
6814 { "unlink", "Unlink ports", CmdUnLink, CMD_FLAG_NOLIST | CMD_FLAG_ADMIN},
6815 #endif
6816 { "uptime", "Display system uptime", CmdUpTime, 0},
6817 { "version", "Display version info", CmdVersion, 0},
6818 { "xyzzy", "", CmdXyzzy, CMD_FLAG_NOLIST},
6819 { "?", "", CmdHelp, CMD_FLAG_NOLIST},
6820 { NULL}
6821 };
6822
CmdHelp(ClientInfo * p,ConfClient * pCC,char * Arg)6823 void CmdHelp(ClientInfo *p,ConfClient *pCC,char *Arg)
6824 {
6825 struct COMMAND_TABLE *pTbl = commandtable;
6826
6827 if(CustomHelp != NULL) {
6828 BufPrintf(p,"%s",CustomHelp);
6829 }
6830 else {
6831 BufPrintf(p,"Commands:\r");
6832
6833 pTbl = commandtable;
6834 while(pTbl->CmdString != NULL) {
6835 if(!(pTbl->Flags & CMD_FLAG_NOLIST) &&
6836 (!(pTbl->Flags & CMD_FLAG_ADMIN) || pCC->bAdmin) &&
6837 (!(pTbl->Flags & CMD_FLAG_SYSOP) || pCC->bSysop) &&
6838 (!(pTbl->Flags & CMD_FLAG_SCRIPT) || pCC->bCmdLine) &&
6839 (!(pTbl->Flags & CMD_FLAG_NOSCRIPT) || !pCC->bCmdLine) &&
6840 (!(pTbl->Flags & CMD_FLAG_NEED_DISK) || EnableDiskCommands) &&
6841 (!(pTbl->Flags & CMD_FLAG_LURK) || !bLurkDisabled))
6842 {
6843 BufPrintf(p,".%s:\r %s\r",pTbl->CmdString,pTbl->HelpString);
6844 }
6845 pTbl++;
6846 }
6847 }
6848 }
6849
6850 // Process conference command
ConferenceCmd(ClientInfo * p,ConfClient * pCC,char * Cmd)6851 void ConferenceCmd(ClientInfo *p,ConfClient *pCC,char *Cmd)
6852 {
6853 int i = -1 ;
6854 int Result = -1;
6855 int InitialCount = -1;
6856 int CurrentCmd = -1;
6857 int CmdsMatched = -1;
6858 int bFirstLoop = TRUE;
6859 char *cp = NULL;
6860 char *Arg = NULL;
6861 struct COMMAND_TABLE *pTbl = commandtable;
6862 char *CmdLine = strdup(Cmd);
6863 char *NextCommand = CmdLine;
6864 char *Command = NULL;
6865 int QuoteCount = 0;
6866 ClientInfo DummyClient;
6867
6868 DummyClient.Buf = NULL;
6869 Rcode = TBD_OK;
6870
6871 for( ; ; ) {
6872 bLogCmd = TRUE; // assume we'll log this command
6873 if(bFirstLoop) {
6874 bFirstLoop = FALSE;
6875 if(p == NULL) {
6876 // no ClientInfo, use a the dummy
6877 DummyClient.BufSize = CONF_BUF_SIZE;
6878 if((DummyClient.Buf = malloc(DummyClient.BufSize)) == NULL) {
6879 LOG_ERROR(("ConferenceCmd: Error: malloc failed\n"));
6880 break;
6881 }
6882 DummyClient.p = piLinkConf;
6883 p = &DummyClient;
6884 }
6885
6886 if(CmdLine == NULL) {
6887 LOG_ERROR(("ConferenceCmd: Error: malloc failed\n"));
6888 break;
6889 }
6890 p->Count = 0; // init for BufPrintf
6891 BufPrintf(p,"%c" NDATA,ILINK_DATA_PACKET);
6892 InitialCount = p->Count;
6893 }
6894
6895 CmdsMatched = 0;
6896 if((cp = NextCommand) == NULL) {
6897 break;
6898 }
6899 NextCommand = NULL;
6900
6901 while(*cp == ' ') {
6902 cp++;
6903 }
6904
6905 if(*cp == '.') {
6906 cp++;
6907 }
6908
6909 if(*cp == 0) {
6910 break;
6911 }
6912
6913 // Find next command
6914
6915 Command = cp;
6916 while(*cp) {
6917 if(*cp == ';') {
6918 if((QuoteCount & 1) == 0) {
6919 *cp++ = 0;
6920 NextCommand = cp;
6921 break;
6922 }
6923 }
6924 else if(*cp == '"') {
6925 QuoteCount++;
6926 }
6927 cp++;
6928 }
6929
6930 pTbl = commandtable;
6931 for(i = 0; pTbl->CmdString != NULL; i++) {
6932 if((Result = MatchCmd(Command,pTbl->CmdString))) {
6933 CmdsMatched++;
6934 CurrentCmd = i;
6935 if(Result == 2) {
6936 // Exact match found
6937 CmdsMatched = 1;
6938 break;
6939 }
6940 }
6941 pTbl++;
6942 }
6943
6944 if(CmdsMatched > 1) {
6945 // Ambiguous
6946 BufPrintf(p,"Error: Command is ambiguous.\r");
6947 Rcode = TBD_ERR_INVALID_CMD;
6948 break;
6949 }
6950
6951 if(CmdsMatched == 0) {
6952 // not found
6953 EventHook("command %s %s",pCC->Callsign,CmdLine);
6954 Rcode = TBD_ERR_INVALID_CMD;
6955 break;
6956 }
6957
6958 cp = Command;
6959 while(*cp && *cp != ' ') {
6960 cp++;
6961 }
6962 while(*cp && *cp == ' ') {
6963 cp++;
6964 }
6965
6966 pTbl = &commandtable[CurrentCmd];
6967 if(pTbl->Flags & CMD_FLAG_NEED_DISK && !EnableDiskCommands) {
6968 // Command needs disk access and disk is disabled
6969 Rcode = TBD_ERR_NO_DISK;
6970 break;
6971 }
6972
6973 if(pTbl->Flags & CMD_FLAG_NOSCRIPT && pCC->bCmdLine) {
6974 Rcode = TBD_ERR_INVALID_CMD;
6975 break;
6976 }
6977
6978 if(pTbl->Flags & CMD_FLAG_SCRIPT && !pCC->bCmdLine) {
6979 Rcode = TBD_ERR_INVALID_CMD;
6980 break;
6981 }
6982
6983 if((!(pTbl->Flags & CMD_FLAG_ADMIN) || pCC->bAdmin) &&
6984 (!(pTbl->Flags & CMD_FLAG_SYSOP) || pCC->bSysop))
6985 {
6986 Arg = strdup(cp);
6987 pTbl->CmdHandler(p,pCC,Arg);
6988 free(Arg);
6989 }
6990 if(bLogCmd) {
6991 LOG_NORM(("%s: \"%s\" (%d).\n",pCC->Callsign,Command,Rcode));
6992 }
6993 if(Rcode != TBD_OK) {
6994 break;
6995 }
6996 }
6997
6998 if(p->Count > InitialCount && !pCC->bCmdLine) {
6999 // send response
7000 p->Count++; // include terminating null
7001 SendBuf2(p,pCC,FALSE);
7002 }
7003
7004 // Save the result code and text for possible use by SayResult
7005 if(LastCmdText != NULL) {
7006 free(LastCmdText);
7007 LastCmdText = NULL;
7008 }
7009
7010 LastRcode = Rcode;
7011 if(p->Count > InitialCount) {
7012 LastCmdText = strdup(&p->Buf[strlen(NDATA)+1]);
7013 }
7014 LastCmd = pTbl->CmdString;
7015
7016 if(CmdLine != NULL) {
7017 free(CmdLine);
7018 }
7019
7020 if(DummyClient.Buf != NULL) {
7021 free(DummyClient.Buf);
7022 }
7023 }
7024
7025
7026 // Return number of milliseconds of time since p
TimeLapse(struct timeval * p)7027 int TimeLapse(struct timeval *p)
7028 {
7029 int DeltaSeconds = TimeNow.tv_sec - p->tv_sec;
7030
7031 if(DeltaSeconds > (0x7fffffff / 1000)) {
7032 // TimeLapse is too big to express in milliseconds, just return
7033 // the max we can.
7034 return 0x7fffffff;
7035 }
7036 return (DeltaSeconds * 1000) +
7037 ((TimeNow.tv_usec - p->tv_usec) / 1000);
7038 }
7039
Send2(ConfClient * pCC,void * Data,int Count,int bControlPort)7040 void Send2(ConfClient *pCC,void *Data,int Count,int bControlPort)
7041 {
7042 ConfServer *pCS = pCC->pCS;
7043 int TxLen;
7044 IPAdrUnion HisAdr;
7045 int Socket;
7046
7047 if(bEchoLinkEnabled || !pCS->biLinkConf) {
7048 HisAdr.ADDR = pCC->HisAdr.ADDR;
7049 HisAdr.i.sin_family = AF_INET;
7050 if(bControlPort) {
7051 // rtcp (control)
7052 Socket = pCS->pControl->Socket;
7053 HisAdr.PORT = htons((unsigned short) (pCS->AudioPort + 1));
7054 }
7055 else {
7056 // Even = audio port
7057 Socket = pCS->pAudio->Socket;
7058 HisAdr.PORT = htons((unsigned short) pCS->AudioPort);
7059 }
7060
7061 TxLen = sendto(Socket,Data,Count,0,&HisAdr.s,sizeof(HisAdr));
7062
7063 if(Count != TxLen) {
7064 pCS->TxErrs++;
7065 LOG_ERROR(("Send2(): sendto() failed, %s",Err2String(ERROR_CODE)));
7066 }
7067 else {
7068 pCS->TxCount++;
7069 pCC->TxBytes += Count + HEADER_OVERHEAD;
7070 pCS->TxBytes += Count + HEADER_OVERHEAD;
7071 TxBytesAllConf += Count + HEADER_OVERHEAD;
7072 }
7073 }
7074 }
7075
SendBuf2(ClientInfo * p,ConfClient * pCC,int bControLPort)7076 void SendBuf2(ClientInfo *p,ConfClient *pCC,int bControLPort)
7077 {
7078 Send2(pCC,p->Buf,p->Count,bControLPort);
7079 }
7080
CalcBW(ConfServer * pCS,int bClear)7081 void CalcBW(ConfServer *pCS,int bClear)
7082 {
7083 int DeltaT = TimeLapse(&pCS->StartTime);
7084
7085 if(DeltaT > 5000) {
7086 // only calculate bandwidth based on samples >= 5 seconds
7087 if(pCS->StartTime.tv_sec != 0) {
7088 pCS->TxBandWidth = (pCS->TxBytes - pCS->TxBytesStart) * 8 / DeltaT;
7089 if(pCS->TxBandWidth > pCS->PeakTxBandWidth) {
7090 pCS->PeakTxBandWidth = pCS->TxBandWidth;
7091 pCS->PeakTxBandWidthTime = TimeNow.tv_sec;
7092 }
7093
7094 pCS->RxBandWidth = (pCS->RxBytes - pCS->RxBytesStart) * 8 / DeltaT;
7095 if(pCS->RxBandWidth > pCS->PeakRxBandWidth) {
7096 pCS->PeakRxBandWidth = pCS->RxBandWidth;
7097 pCS->PeakRxBandWidthTime = TimeNow.tv_sec;
7098 }
7099 }
7100 }
7101
7102 if(bClear) {
7103 if(pCS->TxBytes > 1000000) {
7104 pCS->TxMBytes += pCS->TxBytes / 1000000;
7105 pCS->TxBytes %= 1000000;
7106 }
7107
7108 if(pCS->RxBytes > 1000000) {
7109 pCS->RxMBytes += pCS->RxBytes / 1000000;
7110 pCS->RxBytes %= 1000000;
7111 }
7112
7113 if(TxBytesAllConf > 1000000) {
7114 TxMBytesAllConf += TxBytesAllConf / 1000000;
7115 TxBytesAllConf %= 1000000;
7116 }
7117
7118 if(RxBytesAllConf > 1000000) {
7119 RxMBytesAllConf += RxBytesAllConf / 1000000;
7120 RxBytesAllConf %= 1000000;
7121 }
7122
7123
7124 pCS->StartTime = TimeNow;
7125 pCS->TxBytesStart = pCS->TxBytes;
7126 pCS->RxBytesStart = pCS->RxBytes;
7127 }
7128 }
7129
SendToAllSysops(ClientInfo * p,ConfServer * pCS)7130 void SendToAllSysops(ClientInfo *p,ConfServer *pCS)
7131 {
7132 struct avl_traverser avl_trans;
7133 ConfClient *pCC = avl_t_first(&avl_trans,pCS->ConfTree);
7134
7135 while(pCC != NULL) {
7136 if(pCC->bSysop) {
7137 SendBuf2(p,pCC,FALSE);
7138 }
7139 pCC = (ConfClient *) avl_t_next(&avl_trans);
7140 }
7141 }
7142
SendSDES(ConfServer * pCS,ConfClient * pCC)7143 void SendSDES(ConfServer *pCS,ConfClient *pCC)
7144 {
7145 int i = pCC->Proto;
7146 if(gProtoData[i].SDESLen == 0 ||
7147 pCC->bTxtExtension != gProtoData[i].bTxtExtension)
7148 {
7149 GenSDES(i+1,pCC->bTxtExtension);
7150 }
7151
7152 if(gProtoData[i].SDESLen > 0) {
7153 Send2(pCC,gProtoData[i].OurSDES,gProtoData[i].SDESLen,TRUE);
7154 }
7155 }
7156
7157 /*
7158 For now we assume that RTP clients are running GSM so all we need to
7159 do is fudge the version numbers
7160 */
ConvertRTP2iLink(ProtoData * pPDat,char * inBuf,int inLen,int bNewStream)7161 void ConvertRTP2iLink(ProtoData *pPDat, char *inBuf,int inLen,int bNewStream)
7162 {
7163 ProtoData *pPD = &pPDat[ILINK_RTP_VERSION - 1];
7164 rtp_hdr_t *pRTP = (rtp_hdr_t *) (pPD->Temp);
7165
7166 pPD->Data[0]= pPD->Temp;
7167 memcpy(pPD->Data[0],inBuf,inLen);
7168 pPD->DataLen[0] = inLen;
7169
7170 pRTP->ssrc = 0;
7171 pRTP->version = ILINK_RTP_VERSION;
7172 }
7173
ConvertiLink2RTP(ProtoData * pPDat,char * inBuf,int inLen,int bNewStream)7174 void ConvertiLink2RTP(ProtoData *pPDat,char *inBuf,int inLen,int bNewStream)
7175 {
7176 ProtoData *pPD = &pPDat[RTP_VERSION -1];
7177 rtp_hdr_t *pRTP = (rtp_hdr_t *) (pPD->Temp);
7178
7179 if(pPDat[PROTO_ILINK].Type == PKT_TYPE_AUDIO) {
7180 pPD->Data[0]= pPD->Temp;
7181 memcpy(pPD->Data[0],inBuf,inLen);
7182 pPD->DataLen[0] = inLen;
7183
7184 pRTP->ssrc = OurNodeID;
7185 pRTP->version = RTP_VERSION;
7186 }
7187 }
7188
7189 // Speak Freely sends 10 GSM frames per UDP packet, EchoLink
7190 // can't deal with that so we break the first Speak Freely packet into
7191 // 2 UDP packets of 4 GSM frames plus 2 left over.
7192 // The next time we'll use the 2 left over plus the 10 new frames to
7193 // create 3 EchoLink UDP packets.
7194 //
7195 // ProtoData.Temp after even Speak Freely Packet:
7196 // <iLink Frame n> <iLink Frame n+1> <iLink Frame n+2 (partial)>
7197 //
7198 // ProtoData.Temp after old Speak Freely Packet:
7199 // <iLink Frame n+3> <iLink Frame n+4> <iLink Frame n+2>
7200 //
ConvertSF2iLink(ProtoData * pPDat,char * inBuf,int inLen,int bNewStream)7201 void ConvertSF2iLink(ProtoData *pPDat,char *inBuf,int inLen,int bNewStream)
7202 {
7203 static u_int16 Sequence = 1;
7204 ProtoData *pPD = &pPDat[ILINK_RTP_VERSION-1];
7205 static int bEvenPacket = TRUE;
7206 soundbuf *pSFHdr = (soundbuf *) inBuf;
7207 int DataLen = ntohl(pSFHdr->buffer.buffer_len) - sizeof(u_int16);
7208 char *pGSM_In = pSFHdr->buffer.buffer_val + sizeof(u_int16);
7209 int Count = 0;
7210 union {
7211 char *cp;
7212 rtp_hdr_t *pRTP;
7213 } u;
7214
7215 u.cp = pPD->Temp;
7216
7217
7218 if(bNewStream) {
7219 // Toss any left over data, this is a new talker
7220 bEvenPacket = TRUE;
7221 }
7222
7223 if(bEvenPacket) {
7224 bEvenPacket = FALSE;
7225 pPD->Data[0] = pPD->Temp;
7226 pPD->DataLen[0] = ILINK_RTP_FRAME_LEN;
7227
7228 pPD->Data[1] = &pPD->Temp[ILINK_RTP_FRAME_LEN];
7229 pPD->DataLen[1] = ILINK_RTP_FRAME_LEN;
7230
7231 pPD->DataLen[2] = 0;
7232
7233 }
7234 else {
7235 // Odd packet, complete last partial packet
7236 bEvenPacket = TRUE;
7237 Count = 2 * GSM_FRAME_LEN;
7238 pPD->Data[0] = &pPD->Temp[ILINK_RTP_FRAME_LEN*2];
7239 pPD->DataLen[0] = ILINK_RTP_FRAME_LEN;
7240 memcpy(&pPD->Data[0][RTP_HDR_SIZE+Count],pGSM_In,Count);
7241 pGSM_In += Count;
7242 DataLen -= Count;
7243
7244 pPD->Data[1] = pPD->Temp;
7245 pPD->DataLen[1] = ILINK_RTP_FRAME_LEN;
7246
7247 pPD->Data[2] = &pPD->Temp[ILINK_RTP_FRAME_LEN];
7248 pPD->DataLen[2] = ILINK_RTP_FRAME_LEN;
7249 }
7250
7251 while(DataLen > 0) {
7252 u.pRTP->version = ILINK_RTP_VERSION;
7253 u.pRTP->p = 0;
7254 u.pRTP->x = 0;
7255 u.pRTP->cc = 0;
7256 u.pRTP->m = 0;
7257 u.pRTP->pt = RTP_PT_GSM;
7258 u.pRTP->seq = htons(Sequence++);
7259 u.pRTP->ts = 0;
7260 u.pRTP->ssrc = 0;
7261
7262 Count = DataLen;
7263 if(Count >= GSM_FRAME_LEN * 4) {
7264 Count = GSM_FRAME_LEN * 4;
7265 }
7266 memcpy(&u.pRTP->csrc,pGSM_In,Count);
7267
7268 pGSM_In += Count;
7269 DataLen -= Count;
7270 u.cp = u.cp + ILINK_RTP_FRAME_LEN;
7271 }
7272 }
7273
7274 // Echolink sends 4 GSM frames per UDP packet, Speak Freely sends 10.
7275 // We repack the EchoLink frame so they contain the same number of GSM
7276 // frames as sent by a Speak Freely client.
7277 //
7278 // iLinkCount =-1: Initialize header
7279 // iLinkCount = 0: copy first 4 GSM frames in to buffer, return DataLen = 0
7280 // iLinkCount = 1: copy next 4 GSM frames in to buffer, return DataLen = 0
7281 // iLinkCount = 2: copy last 4 GSM frames in to buffer, return
7282 // DataLen = size of Speak Freely packet with 10 GSM frames
7283 // iLinkCount = 3: copy 2 left over GSM frames to beginning of buffer,
7284 // copy 4 new GSM frames into buffer, return DataLen = 0
7285 // iLinkCount = 4: copy last 4 GSM frames into buffer, clear iLinkCount, return
7286 // DataLen = size of Speak Freely packet with 10 GSM frames
7287 //
ConvertiLink2SF(ProtoData * pPDat,char * inBuf,int inLen,int bNewStream)7288 void ConvertiLink2SF(ProtoData *pPDat,char *inBuf,int inLen,int bNewStream)
7289 {
7290 int HdrSize = sizeof(rtp_hdr_t) - sizeof(u_int32);
7291 int DataLen = inLen - HdrSize;
7292 ProtoData *pPD = &pPDat[PROTO_SF];
7293 soundbuf *pSFHdr = (soundbuf *) pPD->Temp;
7294 char *pGSM_data = pSFHdr->buffer.buffer_val + sizeof(u_int16);
7295 u_int16 *pOrigDataLen = (u_int16 *) pSFHdr->buffer.buffer_val;
7296
7297 if(pPDat[PROTO_ILINK].Type == PKT_TYPE_AUDIO) {
7298 if(bNewStream) {
7299 // Toss any left over data, this is a new talker
7300 pPDat->iLinkCount = 0;
7301 }
7302
7303 switch(pPDat->iLinkCount++) {
7304 case 0:
7305 // Initialize Speak Freely Header
7306 pSFHdr->compression = htonl(fCompGSM | fProtocol);
7307 pSFHdr->buffer.buffer_len = htonl(GSM_FRAME_LEN*10+sizeof(u_int16));
7308 *pOrigDataLen = htons(SF_AUDIO_SAMPLES_PER_PACKET);
7309 memset(pSFHdr->sendinghost,0,sizeof(pSFHdr->sendinghost));
7310 if(pPDat == gProtoData) {
7311 strncpy(pSFHdr->sendinghost,ClientTalking->Callsign,
7312 sizeof(pSFHdr->sendinghost));
7313 }
7314 else {
7315 strncpy(pSFHdr->sendinghost,pPDat->Callsign,
7316 sizeof(pSFHdr->sendinghost));
7317 }
7318 break;
7319
7320 case 1:
7321 pGSM_data += (4 * GSM_FRAME_LEN);
7322 break;
7323
7324 case 2:
7325 pGSM_data += (8 * GSM_FRAME_LEN);
7326 pPD->Data[0] = pPD->Temp;
7327 pPD->DataLen[0] = SF_GSM_FRAME_LEN;
7328 break;
7329
7330 case 3:
7331 // Move the 2 extra GSM frames down to the head of the buffer
7332 memcpy(pGSM_data,pGSM_data + (10 * GSM_FRAME_LEN),GSM_FRAME_LEN*2);
7333 pGSM_data += (2 * GSM_FRAME_LEN);
7334 break;
7335
7336 case 4:
7337 pGSM_data += (6 * GSM_FRAME_LEN);
7338 pPDat->iLinkCount = 0;
7339 pPD->Data[0] = pPD->Temp;
7340 pPD->DataLen[0] = SF_GSM_FRAME_LEN;
7341 break;
7342 }
7343 memcpy(pGSM_data,&inBuf[HdrSize],DataLen);
7344 }
7345 }
7346
7347
7348 // The iLink protocol is used as a common basis, i.e. either the InputProto
7349 // or OutputProto must be PROTO_ILINK.
ConvertProtocol(ProtoData * pProtoDat,Protocol InputProto,Protocol OutputProto,int bNewStream)7350 void ConvertProtocol(
7351 ProtoData *pProtoDat,
7352 Protocol InputProto,
7353 Protocol OutputProto,
7354 int bNewStream)
7355 {
7356 char *Buf;
7357 int Len;
7358 int i;
7359
7360 if(ProtoConvert[InputProto][OutputProto] != NULL) {
7361 for(i = 0; i < MAX_CONV_PACKETS; i++) {
7362 if((Len = pProtoDat[InputProto].DataLen[i]) > 0) {
7363 Buf = pProtoDat[InputProto].Data[i];
7364 if(i == 0 && bNewStream) {
7365 ProtoConvert[InputProto][OutputProto](pProtoDat,Buf,Len,TRUE);
7366 }
7367 else {
7368 ProtoConvert[InputProto][OutputProto](pProtoDat,Buf,Len,FALSE);
7369 }
7370 }
7371 }
7372 }
7373 pProtoDat[OutputProto].bDataValid = TRUE;
7374 }
7375
OurUser(ConfServer * pCS,char * Callsign)7376 int OurUser(ConfServer *pCS,char *Callsign)
7377 {
7378 ConfClient *pCC;
7379 struct avl_traverser avl_trans;
7380 int Ret = FALSE;
7381
7382 pCC = (ConfClient *) avl_t_first(&avl_trans,pCS->ConfTree);
7383 while(pCC != NULL) {
7384 if(strcmp(pCC->Callsign,Callsign) == 0) {
7385 // Yup he's one of ours
7386 Ret = TRUE;
7387 break;
7388 }
7389 pCC = (ConfClient *) avl_t_next(&avl_trans);
7390 }
7391
7392 return Ret;
7393 }
7394
ForwardCommandResponse(ClientInfo * p,ConfClient * pCC1)7395 void ForwardCommandResponse(ClientInfo *p,ConfClient *pCC1)
7396 {
7397 ConfClient *pCC;
7398 struct avl_traverser avl_trans;
7399 ConfServer *pCS = pCC1->pCS;
7400 char *cp = strdup(&p->Buf[sizeof(NDATA)]);
7401
7402 if(cp != NULL) {
7403 p->Count = 0; // init for BufPrintf
7404 BufPrintf(p,"%c" NDATA "%s>%s",ILINK_DATA_PACKET,pCC1->Callsign,cp);
7405 free(cp);
7406
7407 // Forward all (remote) command responses to the command line client
7408 // if he's in chat mode since he didn't need to use the .quote command
7409 // to issue a command
7410
7411 if(bCmdLineChatMode) {
7412 FwdTextCmdLine(p,p->Buf,&CmdLineCC);
7413 }
7414
7415 if(ChatPort != 0) {
7416 FwdTextCmdLine(p,p->Buf,&ChatCC);
7417 }
7418
7419 pCC = &CmdLineCC;
7420 while(pCC != NULL) {
7421 if(pCC->bSentQuotedCmd) {
7422 if(pCC == &CmdLineCC) {
7423 if(!bCmdLineChatMode) {
7424 FwdTextCmdLine(p,p->Buf,&CmdLineCC);
7425 }
7426 }
7427 else if(TimeLapse(&pCC->FirstAudioIn) > 30000) {
7428 // Stop forwarding possible responses after 30 seconds
7429 pCC->bSentQuotedCmd = FALSE;
7430 }
7431 else {
7432 Send2(pCC,p->Buf,p->Count,FALSE);
7433 break;
7434 }
7435 }
7436
7437 if(pCC == &CmdLineCC) {
7438 pCC = (ConfClient *) avl_t_first(&avl_trans,pCS->ConfTree);
7439 }
7440 else {
7441 pCC = (ConfClient *) avl_t_next(&avl_trans);
7442 }
7443 }
7444 }
7445 }
7446
DuplicateTextMsg(ClientInfo * p,ConfClient * pCC)7447 int DuplicateTextMsg(ClientInfo *p,ConfClient *pCC)
7448 {
7449 int i;
7450 int j;
7451 unsigned short crc = 0;
7452 int Ret = FALSE;
7453 time_t Oldest = (time_t) -1;
7454 int OldestEntry = 0;
7455 DupTrackingEntry *pDup;
7456 char *DupMessage;
7457 char *cp;
7458 ConfServer *pCS = pCC->pCS;
7459 int TextLen = strlen(p->Buf);
7460
7461 // Calculate a CRC of the text message
7462 // NB: exclude any SSRC appended to the message, there's a bug
7463 // in some versions of EchoLink that caused the SSRC to be corrupted
7464 // when text messages are forwarded.
7465
7466 for(i = 0; i < TextLen; i++) {
7467 crc ^= p->Buf[i] << 8;
7468 for(j = 0; j < 8; j++) {
7469 if(crc & 0x8000) {
7470 crc = crc << 1 ^ 0x1021;
7471 }
7472 else {
7473 crc <<= 1;
7474 }
7475 }
7476 }
7477
7478 pDup = DupTracking;
7479 for(i = 0; i < MAX_DUP_TRACKING; i++) {
7480 if(Oldest == (time_t) -1 || pDup->TimeStamp < Oldest) {
7481 Oldest = pDup->TimeStamp;
7482 OldestEntry = i;
7483 }
7484
7485 if((TimeNow.tv_sec - pDup->TimeStamp) <= DUP_TRACKING_TIMEOUT &&
7486 crc == pDup->crc)
7487 { // Duplicate!
7488 pDup->TimeStamp = TimeNow.tv_sec;
7489 if(!pDup->bLogged) {
7490 pDup->bLogged = TRUE;
7491 DupMessage = &p->Buf[sizeof(NDATA)];
7492 if((cp = strchr(DupMessage,'\r')) != NULL) {
7493 // remove CR
7494 *cp = 0;
7495 }
7496 LOG_WARN(("Dup from %s, %s dropped (0x%x):\n",pDup->FirstFrom,
7497 pCC->Callsign,pDup->crc));
7498 LOG_WARN(("%s\n",DupMessage));
7499
7500 p->Count = 0; // init for BufPrintf
7501 BufPrintf(p,"%c" NDATA,ILINK_DATA_PACKET);
7502 BufPrintf(p,"%s>Dup from %s, %s dropped.\r",ConferenceCall,
7503 pDup->FirstFrom,pCC->Callsign);
7504 SendToAllSysops(p,pCS);
7505 }
7506 Ret = TRUE;
7507 pCC->DupsReceived++;
7508 pCS->DupsReceived++;
7509 if(MaxDups != 0 && pCC->DupsReceived > MaxDups) {
7510 pCS->DupDisconnects++;
7511 LOG_ERROR(("Disconnecting %s too many dups.\n",pCC->Callsign));
7512 DisconnectCC(p,pCC,"Loop disconnect!");
7513
7514 // We clobbered the buffer, reinitialize it for our response
7515 p->Count = 0; // init for BufPrintf
7516 BufPrintf(p,"%c" NDATA,ILINK_DATA_PACKET);
7517 BufPrintf(p,"%s disconnected, too many dups.\r",pCC->Callsign);
7518 pCS->bSendStationList = TRUE;
7519 SendToAllSysops(p,pCS);
7520 }
7521 break;
7522 }
7523 pDup++;
7524 }
7525
7526 if(!Ret) {
7527 // Add new text message to the cache
7528 pDup = &DupTracking[OldestEntry];
7529 if(pDup->TimeStamp != 0 &&
7530 (TimeNow.tv_sec - pDup->TimeStamp) <= DUP_TRACKING_TIMEOUT)
7531 {
7532 // Play it safe and assume this is a duplicate
7533 Ret = TRUE;
7534 LOG_WARN(("DupTracking cache full!\n"));
7535 LOG_WARN(("Oldest: %d\n",OldestEntry));
7536 for(i = 0; i < MAX_DUP_TRACKING; i++) {
7537 LOG_WARN(("%d: TimeStamp: %d, From:%s, CRC: %d\n",i,
7538 DupTracking[i].TimeStamp,DupTracking[i].FirstFrom,
7539 DupTracking[i].crc));
7540 }
7541 }
7542 pDup->crc = crc;
7543 pDup->TimeStamp = TimeNow.tv_sec;
7544 pDup->bLogged = FALSE;
7545 strncpy(pDup->FirstFrom,pCC->Callsign,MAX_CALL_LEN);
7546 }
7547
7548 return Ret;
7549 }
7550
GetPacketType(ClientInfo * p,ConfClient * pCC)7551 PacketType GetPacketType(ClientInfo *p,ConfClient *pCC)
7552 {
7553 char *cp;
7554 char *cp1;
7555 int Len;
7556 soundbuf *pSFHdr = (soundbuf *) p->Buf;
7557 rtp_hdr_t *pRTP = (rtp_hdr_t *) p->Buf;
7558 ConfServer *pCS = pCC->pCS;
7559 PacketType Ret = PKT_TYPE_UNKNOWN;
7560 FILE *fp;
7561 int CallLen;
7562 int Compression;
7563
7564 switch(pCC->Proto) {
7565 case PROTO_SF:
7566 // Speak Freely sends text via the RTCP port, so if it's on the RTP
7567 // port it must be audio
7568 Compression = (int) ntohl(pSFHdr->compression);
7569 if(Compression == (fCompGSM | fProtocol)) {
7570 pCC->CompressionType = RTP_PT_GSM;
7571 Ret = PKT_TYPE_AUDIO;
7572 }
7573 else if(Compression == (fCompADPCM | fProtocol)) {
7574 pCC->CompressionType = RTP_PT_DVI4_8K;
7575 Ret = PKT_TYPE_AUDIO;
7576 }
7577 else if(Compression == fProtocol) {
7578 // No compression (uLaw)
7579 pCC->CompressionType = RTP_PT_PCMU;
7580 Ret = PKT_TYPE_AUDIO;
7581 }
7582 else {
7583 Ret = PKT_TYPE_IGNORE;
7584 DLOG(DLOG_CODEC_TYPE,
7585 ("Ignoring SF CompressionType 0x%x from %s.\n",
7586 ntohl(pSFHdr->compression),CallLog(pCC)));
7587 }
7588 break;
7589
7590 case PROTO_RTP:
7591 // RTP doesn't send text so it must be audio
7592 pCC->bSendSSRC = TRUE; // always send ssrc for RTP
7593 pCC->CompressionType = pRTP->pt;
7594 switch(pRTP->pt) {
7595 case RTP_PT_GSM:
7596 case RTP_PT_DVI4_8K: // aka adpcm
7597 case RTP_PT_PCMU:
7598 case RTP_PT_G726:
7599 Ret = PKT_TYPE_AUDIO;
7600 break;
7601
7602 default:
7603 Ret = PKT_TYPE_IGNORE;
7604 DLOG(DLOG_CODEC_TYPE,
7605 ("Ignoring RTP packet type 0x%x from %s.\n",pRTP->pt,
7606 CallLog(pCC)));
7607 break;
7608 }
7609 break;
7610
7611 case PROTO_ILINK:
7612 switch(p->Buf[0]) {
7613 case '/':
7614 Ret = PKT_TYPE_CMD;
7615 break;
7616
7617 case ILINK_DATA_PACKET:
7618 // Data packet. We only want to forward message line traffic,
7619 // not the general station info that we get when a new station
7620 // joins the conference. The general form of message line traffic
7621 // is "callsign>data ..." Check for "callsign>" at the begining of
7622 // the line.
7623
7624 // Make sure this packet has an SSRC
7625 Ret = PKT_TYPE_IGNORE;
7626 cp = &p->Buf[sizeof(NDATA)]; // first character of data
7627 if(*cp != '\r' && (cp1 = strchr(cp,'>')) != NULL) {
7628 *cp1 = 0;
7629 // Make sure there are no spaces from the beginning of the line
7630 // to the '>' and that the length of the callsign is reasonable
7631 Len = strlen(cp);
7632 if(Len <= MAX_CALL_LEN && strchr(cp,' ') == NULL) {
7633 // Yup, it's a message line
7634 Ret = PKT_TYPE_DATA;
7635 if(pCC->bCmdLine || strcmp(cp,pCC->Callsign) == 0) {
7636 if(!pCC->bCmdLine &&
7637 (cp1[1] == '.' ||
7638 strncmp(&cp1[1],"help\r",5) == 0 ||
7639 strncmp(&cp1[1],"HELP\r",5) == 0))
7640 {
7641 Ret = PKT_TYPE_CMD;
7642 }
7643 }
7644 else {
7645 // Callsign doesn't match. Assume the client is a
7646 // conference bridge because he's sending us text
7647 // messages from other stations.
7648 pCC->bConf = TRUE;
7649
7650 if(*cp == '*' || OurUser(pCS,cp)) {
7651 // Don't forward messages from our users or messages
7652 // from conference bridges to break loops.
7653 // Just ignore the packet
7654 Ret = PKT_TYPE_IGNORE;
7655 }
7656 }
7657 }
7658 *cp1 = '>';
7659 if(Ret != PKT_TYPE_CMD && !pCC->bCmdLine &&
7660 DuplicateTextMsg(p,pCC))
7661 {
7662 Ret = PKT_TYPE_IGNORE;
7663 }
7664 }
7665
7666 if(Ret == PKT_TYPE_IGNORE && !pCC->bFilePlayer) {
7667 if(pCC->bConf && *cp != '\r') {
7668 // Forward possible command response anyone who has
7669 // entered a quoted command
7670 ForwardCommandResponse(p,pCC);
7671 }
7672 else if(*cp == '\r') {
7673 // Must be the station's info text or station list
7674 Len = p->Count - strlen(NDATA) - 1;
7675 if(!pCC->bTBD) {
7676 // Only save brag sheet info if it's not relayed from
7677 // another other conference
7678 if(pCC->Info != NULL) {
7679 free(pCC->Info);
7680 }
7681 pCC->Info = malloc(Len);
7682 if(pCC->Info != NULL) {
7683 memcpy(pCC->Info,cp,Len);
7684 }
7685 }
7686
7687 if(SaveInfoFiles) {
7688 // Convert \r to \n
7689 cp1 = cp;
7690 while((cp1 = strchr(cp1,'\r')) != NULL) {
7691 *cp1++ = '\n';
7692 }
7693
7694 CallLen = strlen(pCC->Callsign) + 1; // include null
7695 cp1 = malloc(CallLen+strlen(INFO_EXTENSION));
7696 if(cp1 != NULL) {
7697 strcpy(cp1,pCC->Callsign);
7698 strcat(cp1,INFO_EXTENSION);
7699 Len = strlen(&cp[1]);
7700 if((fp = fopen(cp1,"w")) != NULL) {
7701 if(fwrite(&cp[1],Len,1,fp) != 1) {
7702 // Write error
7703 LOG_ERROR(("GetPacketType(): fwrite failed, %s"
7704 ,Err2String(errno)));
7705 }
7706 fclose(fp);
7707 }
7708 free(cp1);
7709 }
7710 }
7711 }
7712 }
7713 break;
7714
7715 case ILINK_UNKNOWN_PACKET:
7716 Ret = PKT_TYPE_IGNORE;
7717 break;
7718
7719 default:
7720 if(pRTP->ssrc != 0) {
7721 // Assume that he can handle a nonzero ssrc if he sends one
7722 pCC->bSendSSRC = TRUE;
7723 }
7724 Ret = PKT_TYPE_AUDIO;
7725 break;
7726 }
7727 break;
7728 }
7729
7730 if(Ret == PKT_TYPE_AUDIO && pCC->CompressionType != pCS->CompressionType) {
7731 // It's audio, but not the compression type supported by this conference
7732 DLOG(DLOG_CODEC_TYPE,("Ignoring CompressionType %d from %s.\n",
7733 pCC->CompressionType,CallLog(pCC)));
7734 Ret = PKT_TYPE_OTHER_AUDIO;
7735 }
7736
7737 return Ret;
7738 }
7739
FromUs(ClientInfo * p,ConfClient * pCC)7740 int FromUs(ClientInfo *p,ConfClient *pCC)
7741 {
7742 rtp_hdr_t *pRTP = (rtp_hdr_t *) p->Buf;
7743 int Ret = FALSE;
7744
7745 if(pCC->Proto == PROTO_ILINK || pCC->Proto == PROTO_RTP) {
7746 if(pRTP->ssrc == OurNodeID && OurNodeID != 0) {
7747 Ret = TRUE;
7748 }
7749 }
7750
7751 return Ret;
7752 }
7753
CheckPassword(ConfClient * pCC,ACL_User * pACL)7754 int CheckPassword(ConfClient *pCC,ACL_User *pACL)
7755 {
7756 int Ret = FALSE;
7757
7758 if(pACL != NULL && *pACL->Password == '-') {
7759 // User doesn't require a password
7760 Ret = TRUE;
7761 }
7762 else if(pCC->Password != NULL && pACL != NULL) {
7763 // User password set in ACL
7764 if(strcmp(pACL->Password,pCC->Password) == 0) {
7765 // User's password matches
7766 Ret = TRUE;
7767 }
7768 }
7769 else if(RTP_Pass != NULL) {
7770 // Conference password set
7771 if(strcmp(RTP_Pass,"-") == 0) {
7772 // You asked for it, you got it ... no security for RTP / Speak Freely
7773 Ret = TRUE;
7774 }
7775 else if(pCC->Password != NULL && strcmp(pCC->Password,RTP_Pass) == 0) {
7776 Ret = TRUE;
7777 }
7778 }
7779
7780 return Ret;
7781 }
7782
7783 // return:
7784 // 0 - Client is not authorized
7785 // 1 - Client is authorized
7786 // 2 - Don't know yet. (Unknown EchoLink client, checking with
7787 // directory server).
AuthorizedClient(ClientInfo * p,ConfClient * pCC)7788 int AuthorizedClient(ClientInfo *p,ConfClient *pCC)
7789 {
7790 UserInfo UserLookup;
7791 UserInfo *pUser;
7792 ACL_User *pACL;
7793 ACL_User *pACL1;
7794 ACL_User ACL_Lookup;
7795 char *Dash;
7796 int Authorized = 0;
7797 int bAllowUnknown = FALSE;
7798 char Temp[32];
7799 char *BaseCall = strdup(pCC->Callsign);
7800
7801 UserLookup.Callsign = pCC->Callsign;
7802 ACL_Lookup.Callsign = BaseCall;
7803 ACL_Lookup.HisAdr.ADDR = pCC->HisAdr.ADDR;
7804
7805 if((Dash = strchr(BaseCall,'-')) != NULL) {
7806 *Dash = 0;
7807 }
7808
7809 if((pUser = avl_find(UserTree,&UserLookup)) != NULL) {
7810 // user is in directory, set his mute flag
7811 if(pUser->bMuted) {
7812 pCC->bMuted = TRUE;
7813 }
7814
7815 if(pUser->bUnMuted) {
7816 pCC->bMuted = FALSE;
7817 }
7818
7819 if(pUser->bChatMuted) {
7820 pCC->bMuteChat = TRUE;
7821 }
7822
7823 if(pUser->bChatUnMuted) {
7824 pCC->bMuteChat = FALSE;
7825 }
7826 }
7827
7828 if(RTP_Pass != NULL && strcmp(RTP_Pass,"!") == 0) {
7829 // You asked for it, you got it ... hardly any security at all !
7830 bAllowUnknown = TRUE;
7831 }
7832
7833 if(pCC->Proto == PROTO_ILINK) {
7834 // Lookup EchoLink clients by callsign and check the IP address
7835 pACL = avl_find(ACL_Call_Tree,&ACL_Lookup);
7836
7837 if(pACL != NULL && !pACL->bAuthorized) {
7838 // Banned client, ignore the turkey
7839 }
7840 else if((pACL1 = avl_find(ACL_IP_Tree,&ACL_Lookup)) != NULL) {
7841 // Client's IP address is in ACL
7842 if(pACL1->bAuthorized) {
7843 // Client Allowed by IP address
7844 Authorized = 1;
7845 LOG_NORM(("%s allowed by IP/hostname address %s/%s.\n",
7846 pCC->Callsign,inet_ntoa(pACL1->HisAdr.i.sin_addr),
7847 pACL1->HostName));
7848 }
7849 else {
7850 // Client Banned by IP address, ignore the turkey
7851 LOG_NORM(("%s banned IP/hostname address %s/%s.\n",
7852 pCC->Callsign,inet_ntoa(pACL1->HisAdr.i.sin_addr),
7853 pACL1->HostName));
7854 }
7855 }
7856 else if(bAllowUnknown || (bQuickStart && ActiveDirEntries == 0)) {
7857 // Allow anyone that's not specifically banned
7858 Authorized = 1;
7859 }
7860 else if(pUser != NULL) {
7861 // client found in directory
7862 if(pUser->HisAdr.ADDR == pCC->HisAdr.ADDR) {
7863 // IP address matches
7864 if(PrivateConference) {
7865 if(pACL != NULL) {
7866 Authorized = pACL->bAuthorized ? 1 : 0;
7867 }
7868 }
7869 else {
7870 Authorized = pUser->bAuthorized ? 1 : 0;
7871 }
7872 }
7873 else {
7874 // IP address mismatch
7875 strcpy(Temp,inet_ntoa(pCC->HisAdr.i.sin_addr));
7876 LOG_NORM(("Rejecting %s, dir IP %s != IP %s.\n",
7877 pCC->Callsign,inet_ntoa(pUser->HisAdr.i.sin_addr),Temp));
7878 // Perhaps he has a dynamic IP address and has a difference IP
7879 // address than he did when we last refreshed the list, check
7880 // with the directory server and see.
7881 if(LoginInterval > 0) {
7882 ValidateCallsign(pCC->Callsign,&pCC->HisAdr);
7883 }
7884 EchoLinkIPCompareFailures++;
7885 }
7886 }
7887 else if(pACL != NULL) {
7888 // client not in directory, but in ACL and not banned
7889 if(*pACL->HostName == '-' ) {
7890 // allowed client with any IP address
7891 Authorized = 1;
7892 LOG_WARN(("Accepted %s %s without IP address check.\n",
7893 pCC->Callsign,inet_ntoa(pCC->HisAdr.i.sin_addr)));
7894 }
7895 else if(pCC->HisAdr.ADDR == pACL->HisAdr.ADDR) {
7896 Authorized = 1;
7897 }
7898 }
7899 else {
7900 // User is not in our ACL or in the directory server's list currently,
7901 // ask server about him specifically.
7902 LOG_NORM(("%s not found in directory.\n",pCC->Callsign));
7903 if(iLinkDirServer) {
7904 // The iLink server doesn't support individual callsign validation,
7905 // just refresh the entire directory list
7906 NextLoginTime = TimeNow.tv_sec + LoginInterval;
7907 NextStationListTime = TimeNow.tv_sec + StationListInterval;
7908 ServerRequest(SERV_REQ_LOGIN_AND_LIST,0,NULL);
7909 }
7910 else if(LoginInterval > 0) {
7911 Authorized = 2; // Don't know yet
7912 ValidateCallsign(pCC->Callsign,&pCC->HisAdr);
7913 }
7914 }
7915
7916 if(Authorized == 0) {
7917 EchoAuthenticationFailures++;
7918 }
7919 }
7920 else {
7921 // RTP / Speak Freely client
7922 if((pACL = avl_find(ACL_IP_Tree,&ACL_Lookup)) != NULL) {
7923 // Found IP address or hostname match
7924 if(pACL->bAuthorized && CheckPassword(pCC,pACL)) {
7925 Authorized = 1;
7926 if(avl_find(ACL_Call_Tree,&ACL_Lookup) == NULL) {
7927 // Didn't find a match on the callsign so RTCP didn't have
7928 // callsign info Get it from the ACL list
7929
7930 if(pCC->Callsign != NULL) {
7931 free(pCC->Callsign);
7932 }
7933 if(pCC->CallPlus != NULL) {
7934 free(pCC->CallPlus);
7935 }
7936
7937 pCC->CallPlus = strdup(pACL->CallPlus);
7938 pCC->Callsign = strdup(pACL->Callsign);
7939 }
7940 }
7941 }
7942 else if((pACL = avl_find(ACL_Call_Tree,&ACL_Lookup)) != NULL) {
7943 // Found callsign match
7944 if(pACL->bAuthorized && *pACL->HostName == '-' &&
7945 CheckPassword(pCC,pACL))
7946 {
7947 Authorized = 1;
7948 }
7949 }
7950 else if(CheckPassword(pCC,NULL)) {
7951 // Conference bridge password match
7952 Authorized = 1;
7953 }
7954 else if(bAllowUnknown) {
7955 // Allow anyone that's not specifically banned
7956 Authorized = 1;
7957 }
7958 else {
7959 // No thanks !
7960 p->Count = 0; // init for BufPrintf
7961 BufPrintf(p,"%c" NDATA,ILINK_DATA_PACKET);
7962 BufPrintf(p,"Unauthorized connect request from %s.",
7963 inet_ntoa(pCC->HisAdr.i.sin_addr));
7964 SendToAllSysops(p,pCC->pCS);
7965 }
7966
7967 if(Authorized == 0) {
7968 AuthenticationFailures++;
7969 }
7970 }
7971
7972 if(BaseCall != NULL) {
7973 free(BaseCall);
7974 }
7975
7976 return Authorized;
7977 }
7978
SaveBadPacket(ClientInfo * p,char * Filename)7979 void SaveBadPacket(ClientInfo *p,char *Filename)
7980 {
7981 FILE *fp = fopen(Filename,"w");
7982
7983 if(fp != NULL) {
7984 if(fwrite(p->Buf,p->Count,1,fp) != 1) {
7985 LOG_ERROR(("SaveBadPacket(): fwrite failed, %s",Err2String(errno)));
7986 }
7987 fclose(fp);
7988 }
7989 else {
7990 LOG_ERROR(("SaveBadPacket(): fopen failed, %s",Err2String(errno)));
7991 }
7992 }
7993
SendAvrsUpdate(AVRS_State State,char * Callsign)7994 void SendAvrsUpdate(AVRS_State State,char *Callsign)
7995 {
7996 char TempSDES[512];
7997 int Len = 0;
7998 IPAdrUnion HisAdr;
7999 struct hostent *pHost;
8000 int Sent;
8001
8002 if(AvrsEnable && LoginInterval > 0) {
8003 Len = CreateAvrsPacket(State,Callsign,TempSDES,sizeof(TempSDES));
8004 }
8005
8006 if(Len > 0) {
8007 pHost = GetHostByName("aprs.echolink.org");
8008 if(pHost != NULL) {
8009 HisAdr.i.sin_addr.s_addr = IP_FROM_HOSTENT(pHost,0);
8010 HisAdr.i.sin_family = AF_INET;
8011 HisAdr.PORT = htons(ILINK_RTCP_PORT);
8012 Sent = sendto(piLinkConf->pControl->Socket,TempSDES,Len,0,&HisAdr.s,
8013 sizeof(HisAdr));
8014 if(Sent != Len) {
8015 LOG_ERROR(("SendAvrsUpdate: sendto failed, %s",Err2String(errno)));
8016 }
8017 }
8018 }
8019 }
8020
8021 // Begin K1RFD 1/30/08
SendFirewallOpenRequest(ConfServer * pCS,ConfClient * pCC)8022 void SendFirewallOpenRequest(ConfServer *pCS, ConfClient *pCC)
8023 {
8024 // Send an OPEN request to the specified peer via the addressing
8025 // server. We do this each time we want to establish a connection,
8026 // in case the recipient of the connection request is behind an
8027 // unconfigured NAT firewall. His software has already established
8028 // a "flow" with the addressing server, so the addressing server
8029 // should be able to relay this request to him successfully. When
8030 // he receives it, he responds by sending us a set of packets through
8031 // his firewall, which establishes the necessary flow in his firewall
8032 // for a QSO between us to take place.
8033
8034 // The OPEN request is in the SDES portion of an RTCP packet.
8035 // The fields are as follows:
8036 // CNAME: callsign of the originating node (us)
8037 // LOC: "OPEN"
8038 // EMAIL: dotted ip address of the destination node (him)
8039
8040 int rtp_version = ILINK_RTP_VERSION;
8041 char *pFirstData;
8042 int PadCount;
8043 ConfClient cc;
8044 int Server = 0; // for now
8045 char *DestNodeDottedIP;
8046 struct hostent *pTemp;
8047 char OurSDES[512];
8048 int SDESLen;
8049
8050 union {
8051 char *cp;
8052 rtcp_t *p;
8053 } u;
8054
8055 union {
8056 char *cp;
8057 rtcp_sdes_item_t *p;
8058 } Item;
8059
8060
8061 DestNodeDottedIP = inet_ntoa(pCC->HisAdr.i.sin_addr);
8062
8063 u.cp = OurSDES;
8064
8065 u.p->common.version = rtp_version;
8066 u.p->common.p = 0;
8067 u.p->common.count = 0;
8068 u.p->common.pt = RTCP_RR;
8069 u.p->common.length = htons(1);
8070 u.p->r.rr.ssrc = OurNodeID;
8071
8072 u.cp += sizeof(u.p->common) + sizeof(u.p->r.rr.ssrc);
8073 u.p->common.version = rtp_version;
8074 u.p->common.p = 1;
8075 u.p->common.count = 1;
8076 u.p->common.pt = RTCP_SDES;
8077
8078 u.p->r.sdes.src = OurNodeID;
8079 pFirstData = (char *) &u.p->r.sdes.src;
8080
8081 Item.p = &u.p->r.sdes.item[0];
8082 Item.p->type = RTCP_SDES_CNAME;
8083 Item.p->length = (u_int8) strlen(ConferenceCall);
8084 memcpy(Item.p->data,ConferenceCall,Item.p->length);
8085
8086 Item.cp = Item.cp + sizeof(rtcp_sdes_item_t) + Item.p->length - 1;
8087 Item.p->type = RTCP_SDES_LOC;
8088 Item.p->length = (u_int8) strlen("OPEN");
8089 memcpy(Item.p->data,"OPEN",Item.p->length);
8090
8091 Item.cp = Item.cp + sizeof(rtcp_sdes_item_t) + Item.p->length - 1;
8092 Item.p->type = RTCP_SDES_EMAIL;
8093 Item.p->length = (u_int8) strlen(DestNodeDottedIP);
8094 memcpy(Item.p->data,DestNodeDottedIP,Item.p->length);
8095
8096 Item.cp = Item.cp + sizeof(rtcp_sdes_item_t) + Item.p->length - 1;
8097 Item.p->type = RTCP_SDES_END;
8098 Item.p->length = 0;
8099 Item.cp = Item.cp + sizeof(rtcp_sdes_item_t) + Item.p->length - 1;
8100
8101 PadCount = 4 - ((Item.cp - pFirstData) % 4);
8102
8103 if(PadCount > 0 && PadCount < 4) {
8104 // Padding is needed
8105 u.p->common.p = 1;
8106 if(PadCount > 1) {
8107 memset(Item.cp,0,PadCount-1);
8108 }
8109 Item.cp[PadCount-1] = PadCount;
8110 Item.cp += PadCount;
8111 } else {
8112 // No padding needed
8113 u.p->common.p = 0;
8114 }
8115 u.p->common.length = htons((u_short) ((Item.cp - pFirstData)/4));
8116 SDESLen = Item.cp - OurSDES;
8117
8118 // Construct a "client" structure for the addressing server.
8119 // Note that Send2() needs the pCS member.
8120 memset(&cc, 0, sizeof(cc));
8121 cc.pCS = pCS;
8122 cc.HisAdr.i.sin_family = AF_INET;
8123 cc.HisAdr.i.sin_port = ILINK_RTCP_PORT;
8124
8125 // TODO: We really should send this packet to the same addressing
8126 // server we are currently using, rather than the first one in
8127 // the list, in case the first one in the list is offline. However,
8128 // aside from that, it makes no difference which one we send it to
8129 // since they all relay these packets to one another.
8130 if (Server >= NUM_DIRECTORY_SERVERS || DirServerHost[Server] == NULL)
8131 Server = 0;
8132
8133 // Apropos of the note above, if we keep track of the current addressing
8134 // server's Internet address, we can avoid this DNS lookup here.
8135 pTemp = GetHostByName(DirServerHost[Server]);
8136 if(pTemp != NULL) {
8137 cc.HisAdr.i.sin_addr.s_addr = IP_FROM_HOSTENT(pTemp,0);
8138 } else {
8139 cc.HisAdr.i.sin_addr.s_addr = inet_addr(DirServerHost[Server]);
8140 }
8141
8142 Send2(&cc,OurSDES,SDESLen,TRUE);
8143 D2PRINTF(("Sent firewall OPEN request to %s via %s\n", DestNodeDottedIP,
8144 DirServerHost[Server]));
8145 }
8146 // End K1RFD 1/30/08
8147
8148
8149 // Update ConnectedStatus to reflect current connection status.
8150 // "real" conferences take precedence over "normal" nodes running as
8151 // conferences which take precedence over "normal" nodes.
UpdateConnectedStatus()8152 void UpdateConnectedStatus()
8153 {
8154 struct avl_traverser avl_trans;
8155 ConfClient *pCC = NULL;
8156 char *TempRealConf = NULL;
8157 char *TempConf = NULL;
8158 char *TempStation = NULL;
8159 int bCurrentTopStation;
8160
8161 pCC = (ConfClient *) avl_t_first(&avl_trans,piLinkConf->ConfTree);
8162 while(pCC != NULL) {
8163 if(pCC->bInConf) {
8164 // This is an active connection
8165 bCurrentTopStation = strcmp(ConnectedStatus,pCC->Callsign) == 0;
8166 if(pCC->bConf) {
8167 // This client is a conference
8168 if(pCC->Callsign[0] == '*') {
8169 // And it's a "real" conference
8170 if(bCurrentTopStation || TempRealConf == NULL) {
8171 TempRealConf = pCC->Callsign;
8172 }
8173 }
8174 else {
8175 // Not a "real" conference but better than nothing
8176 if(bCurrentTopStation || TempConf == NULL) {
8177 TempConf = pCC->Callsign;
8178 }
8179 }
8180 }
8181 else if(ShowStatusInInfo == 2) {
8182 if(bCurrentTopStation || TempStation == NULL) {
8183 TempStation = pCC->Callsign;
8184 }
8185 }
8186 }
8187 pCC = (ConfClient *) avl_t_next(&avl_trans);
8188 }
8189
8190 if(TempRealConf != NULL) {
8191 TempConf = TempRealConf;
8192 }
8193
8194 if(TempConf != NULL) {
8195 snprintf(ConnectedStatus,sizeof(ConnectedStatus),"In Conference %s",
8196 TempConf);
8197 }
8198 else if(TempStation != NULL) {
8199 snprintf(ConnectedStatus,sizeof(ConnectedStatus),"Connected to %s",
8200 TempStation);
8201 }
8202 else {
8203 ConnectedStatus[0] = 0;
8204 }
8205 }
8206
8207 // Return pointer to first character of the user's chat text
8208 // Follows CALLSIGN>
FirstChatChar(char * Buf)8209 char *FirstChatChar(char *Buf)
8210 {
8211 char *cp;
8212 char *cp1;
8213 char *Ret = NULL;
8214 int Len;
8215
8216 cp = &Buf[sizeof(NDATA)]; // first character of data
8217 if(*cp != '\r' && (cp1 = strchr(cp,'>')) != NULL) {
8218 *cp1 = 0;
8219 // Make sure there are no spaces from the beginning of the line
8220 // to the '>' and that the length of the callsign is reasonable
8221 Len = strlen(cp);
8222 if(Len <= MAX_CALL_LEN && strchr(cp,' ') == NULL) {
8223 // Yup, looks like a chat line all right
8224 Ret = &cp1[1];
8225 }
8226 *cp1 = '>';
8227 }
8228 return Ret;
8229 }
8230
ConferenceCleanup()8231 void ConferenceCleanup()
8232 {
8233 struct avl_traverser avl_trans;
8234 ConfServer *pCS;
8235 ConfServer *pNextCS;
8236
8237 pNextCS = (ConfServer *) avl_t_first(&avl_trans,Conferences);
8238 while((pCS = pNextCS) != NULL) {
8239 pNextCS = (ConfServer *) avl_t_next(&avl_trans);
8240 DeleteConf(pCS);
8241 }
8242 avl_destroy(Conferences,NULL);
8243
8244 #if 0 // !!! find out why this blows chow someday
8245 if(CmdClient != NULL) {
8246 DeleteClient(CmdClient);
8247 }
8248
8249 if(ChatClient != NULL) {
8250 DeleteClient(ChatClient);
8251 }
8252 #endif
8253 }
8254
SendChatEvent(char * Type,char * Buf)8255 void SendChatEvent(char *Type,char *Buf)
8256 {
8257 char *Text = &Buf[sizeof(NDATA)];
8258 char *cp;
8259
8260 // Remove carrage return from text
8261 if((cp = strchr(Text,'\r')) != NULL) {
8262 *cp = 0;
8263 }
8264
8265 if(strlen(Text) > 0) {
8266 EventHook("%s %s",Type,Text);
8267 }
8268
8269 if(cp != NULL) {
8270 *cp = '\r';
8271 }
8272 }
8273
GetCmdOptions(char ** args,const char * opts)8274 int GetCmdOptions(char **args,const char *opts)
8275 {
8276 int Ret = -1;
8277 char *cp = *args;
8278 char *cp1;
8279 int bQuoted = FALSE;
8280
8281 CmdArg = NULL;
8282 do {
8283 while(isspace(*cp)) {
8284 cp++;
8285 }
8286 if(*cp != '-') {
8287 // not a switch we're done
8288 break;
8289 }
8290 cp++;
8291 if((cp1 = strchr(opts,*cp)) == NULL) {
8292 // undefined option
8293 Ret = '?';
8294 break;
8295 }
8296
8297 Ret = *cp++; // Valid option found
8298
8299 if(cp1[1] == ':') {
8300 // option has an argument
8301 while(isspace(*cp)) {
8302 cp++;
8303 }
8304 if(!isprint(*cp) || *cp == '-') {
8305 // invalid or no argument specified
8306 break;
8307 }
8308
8309 if(*cp == '"') {
8310 cp++;
8311 bQuoted = TRUE;
8312 }
8313 // Save pointer to argument
8314 CmdArg = cp;
8315 // Skip to the end of argument
8316
8317 if(bQuoted) {
8318 while(isprint(*cp) && *cp != '"') {
8319 cp++;
8320 }
8321 if(*cp != '"') {
8322 // improper termination of quoted argument
8323 CmdArg = NULL;
8324 }
8325 }
8326 else {
8327 while(!isspace(*cp) && isprint(*cp) && *cp != '-') {
8328 cp++;
8329 }
8330 }
8331 *cp++ = 0; // terminate it
8332 }
8333 } while(FALSE);
8334
8335 *args = cp;
8336 return Ret;
8337 }
8338
CallLog(ConfClient * pCC)8339 char *CallLog(ConfClient *pCC)
8340 {
8341 static char IPAddress[1024];
8342 char *Ret = pCC->Callsign;
8343
8344 if(LogIPAddresses) {
8345 snprintf(IPAddress,sizeof(IPAddress),"%s (%s)",pCC->Callsign,
8346 inet_ntoa(pCC->HisAdr.i.sin_addr));
8347 Ret = IPAddress;
8348 }
8349
8350 return Ret;
8351 }
8352