2

Background

We have a handful of Cisco C40s that I'm trying to automate via bash (although I'd be open to alternatives). I need to log into it, dial an IP, get back the call ID that's returned, and then use that CallID to send a DTMF tone to the far end. I'm able to get about 90% of the way there but for some reason using SSH isn't returning all the text that's returned when using an interactive session.

Interactive Shell Sample:

login as: admin
Using keyboard-interactive authentication.
Password:
Welcome to XXX
TANDBERG Codec Release TC7.1.1.168aadf
SW Release Date: 2014-04-11
*r Login successful

OK

xConfiguration Audio Volume: 0
** end

OK
xCommand Dial Number: FAR_END_IP

OK
*r DialResult (status=OK):
    CallId: 73
    ConferenceId: 44
** end

Non-Interactive Shell Samples

Without ssh -T or ssh -t -t Options

This occurs where call-init-step1.txt contains the xConfiguration Audio Volume: 0 and xCommand Dial Number: FAR_END_IP in addition to a bye to hang up.

[user@controlserver C40]$ cat call-init-step1.txt | ssh admin@cisco_codec
Pseudo-terminal will not be allocated because stdin is not a terminal.
Welcome to XXX
TANDBERG Codec Release TC7.1.1.168aadf
SW Release Date: 2014-04-11
*r Login successful

OK

** end

OK

OK

What I'm missing here is the block with

*r DialResult (status=OK):
    CallId: 73
    ConferenceId: 44
** end

so that I can parse it for the CallID and then use it to send the next commands.

With ssh -T or ssh -t -t Options

A few threads had suggested using ssh -T or ssh -t -t which in this case doesn't seem to help, below is the output.

[user@controlserver C40]$ cat call-init-step1.txt | ssh -T admin@cisco_codec
Welcome to XXX
TANDBERG Codec Release TC7.1.1.168aadf
SW Release Date: 2014-04-11
*r Login successful

OK

** end

OK

OK

And

[user@controlserver C40]$ cat call-init-step1.txt | ssh -t -t admin@cisco_codec
Welcome to XXX
TANDBERG Codec Release TC7.1.1.168aadf
SW Release Date: 2014-04-11
*r Login successful

OK

** end

OK

OK

The Question

Any insight in how to get the missing DialResult block would be greatly appreciated.

Edit: I should also mention that ultimately the command cat call-init-step1.txt | ssh admin@cisco_codec would get redirected to a file to be parsed further on in my script meaning it would probably look something like cat call-init-step1.txt | ssh -t -t admin@cisco_codec > results.txt and then be parsed.

Edit 2: To frame what I'm working with the full API guide is found here

Edit 2.5: An attempt with expect

Per a suggestion from @MarkSetchell we went ahead and wrote a semi-functional expect script that looks like the following:

#!/usr/bin/expect
spawn ssh admin@cisco_codec
expect "*r Login successful"
send "xConfiguration Audio Volume: 0"
expect "OK"
send "xCommand Dial Number: FAR_END_IP"
expect "** end"

That resulted in the following:

[user@controlserver C40]$ expect expect-call
spawn ssh admin@cisco_codec
Welcome to XXX
TANDBERG Codec Release TC7.1.1.168aadf
SW Release Date: 2014-04-11
*r Login successful

OK

xConfiguration Audio Volume: 0xCommand Dial Number: FAR_END_IPxConfiguration Audio Volume: 0xCommand Dial Number: FAR_END_IP
jak119
  • 165
  • 2
  • 11
  • 2
    Maybe it's a timing thing, and maybe you need to use `expect`. Just a thought. – Mark Setchell Sep 29 '15 at 20:44
  • @MarkSetchell Hmm worth looking into. I'll give it a shot, might take me some time to re-work and test it. – jak119 Sep 29 '15 at 21:22
  • 2
    Excellent question! You've really tried to solve this on your own. Did you try running with a redirect as you indicate `> results.txt` ? Just to dbl-check. I've seen cases where redirected text gets saved, whereas text to the screen has troubles. use `cat -vet results.txt` and look for `^M` chars at the end of line. **Then** you might have something to work with ;-) (Just an idea). Good luck! – shellter Sep 29 '15 at 21:58
  • [ansible](http://packetpushers.net/ansible-cisco-snmp/) might give you an easier way to do this – chicks Sep 29 '15 at 22:09
  • @shellter I've certainly been banging away at this for a few days. The script initially did a redirect and it didn't appear in when I did plain old `cat results.txt` but I just went back and tried with `cat -vet` and unfortunately that didn't do the trick. Thanks for the suggestion though! – jak119 Sep 29 '15 at 23:51
  • @chicks Boy did I get excited when you mentioned ansible. Unfortunately the OS that runs on these video conference codecs is IOS flavored but so far I haven't found anything that will play nice with IOS _and_ these codecs. Not to say I've found anything written for these codecs... – jak119 Sep 29 '15 at 23:54
  • @MarkSetchell Had an opportunity to try `expect` to no avail. I've edited the original question with the results of our attempt. – jak119 Sep 30 '15 at 00:03
  • hm... maybe use more extreme filtering to see what is coming back? Maybe `.... | od -c > result_od.txt` may bring light. Any chance that the output is non-ascii, and encoded in something like UTF-8, or UCS-2 BE BOM? If so, then use `iconv` to convert to ascii. Good luck again! – shellter Sep 30 '15 at 02:17
  • @shellter `od -c` returned lots of "stuff" but I still didn't see the block I'm missing. I also tried `iconv` with similar results. Thanks for the ideas though! – jak119 Sep 30 '15 at 02:40

1 Answers1

1

It seemed there were a few approaches to this, if I had to stay with bash it seemed that using a properly written except script was going to be the way to go. That said someone on a Reddit post I'd made suggested XML. Using XML had crossed my mind but neither I nor the folks I immediately had to help me were super well versed with how to go about this but with an incredibly helpful post I was well on my way to using Python and XML.

The final product ended up looking like something like this:

#!/usr/bin/env python
import time
import requests
from lxml.etree import fromstring, Element, tostring


def putxml_request(xml, **kwargs):
    return requests.post(
        'http://HOSTNAME/putxml',
        auth=('USER', 'PASSWORD'),
        data=xml.format(**kwargs)).content


def xconfiguration_request(*path, **keys_and_values):
    root = Element('Configuration')
    parent = root
    for level in path:
        current = Element(level)
        parent.append(current)
        parent = current

    for k, v in keys_and_values.iteritems():
        node = Element(k)
        node.text = str(v)
        current.append(node)

    xml = tostring(root, pretty_print=True)
    return putxml_request(xml)

xconfiguration_request('Audio', Volume=0)

DIAL = '''\
<Command>
  <Dial>
    <Number>{number}</Number>
    <Protocol>{protocol}</Protocol>
  </Dial>
</Command>'''

outcome = putxml_request(
    DIAL, number='XXX', protocol='Sip')
callid = fromstring(outcome).xpath('//CallId')[0].text

# this gives it some time for the call to connect
time.sleep(10)

DTMFSEND = '''\
<Command>
  <DTMFSend>
    <CallId>{callid}</CallId>
    <DTMFString>{dtmf}</DTMFString>
  </DTMFSend>
</Command>'''
outcome = putxml_request(DTMFSEND, callid=callid, dtmf='1234')
status = fromstring(outcome).xpath('//DTMFSendResult')[0].attrib['status']

if status != 'OK':
    print('bad')
else:
    print('sent dtmf')

Ultimately I ended up scheduling this script to initiate a call (via cron) and then wrote a very similar script to hang up the call using DisconnectAll.

I hope this helps someone and thanks to /u/omgdave on Reddit (who I'd offered an opportunity to respond to this with an answer but wasn't taken up on it) for the help with this.

jak119
  • 165
  • 2
  • 11