Can't execute python script via AGI - file does not exist

Good day.
I’m trying to start voice acting.

[hello]
exten = s,1,Answer()
same = n,Wait(1)
same = n,Set(RHV_FILE=${CALLERID(num)}_${EPOCH})
same = n,Set(RHV_TEXT=The company welcomes you, ${CALLERID(Name)})
same = n,AGI(/var/lib/asterisk/agi-bin/Robovoice.py ${RHV_FILE} ${RHV_TEXT})
same = n,Playback(/var/lib/asterisk/sounds/rhv_playback/${RHV_FILE}&silence/1)
same = n,System(rm -f /var/lib/asterisk/sounds/rhv_playback/${RHV_FILE}.wav)
same = n,System(rm -f /var/lib/asterisk/sounds/rhv_playback/${RHV_FILE}_t.wav)
exten = s,n,Return

I’m trying to select AGI or EAGI - I get an error:

Executing [dest-2@customdests:1] NoOp("SIP/9999-00000012", "Entering Custom Destination Greetings by Name") in new stack
    -- Executing [dest-2@customdests:2] Gosub("SIP/9999-00000012", "hello,s,1()") in new stack
    -- Executing [s@civility-hi:1] Answer("SIP/9999-00000012", "") in new stack
    -- Executing [s@civility-hi:2] Wait("SIP/9999-00000012", "1") in new stack
    -- Executing [s@civility-hi:3] Set("SIP/9999-00000012", "RHV_FILE=9999_1691415275") in new stack
    -- Executing [s@civility-hi:4] Set("SIP/1514-00000014", "RHV_TEXT="The company welcomes you, John"") in new stack
    -- Executing [s@civility-hi:5] AGI("SIP/1514-00000014", "/var/lib/asterisk/agi-bin/Robovoice.py 9999_1691415533 "The company welcomes you, John"") in new stack
[2023-08-07 16:38:53] WARNING[23382][C-00000015]: res_agi.c:2218 launch_script: Failed to execute ' /var/lib/asterisk/agi-bin/Robovoice.py 9999_1691415533 The company welcomes you, John': File does not exist.
    -- Executing [s@civility-hi:6] Playback("SIP/1514-00000014", "/var/lib/asterisk/sounds/rhv_playback/1514_1691415533&silence/1") in new stack
[2023-08-07 16:34:35] WARNING[22369][C-00000013]: file.c:824 ast_openstream_full: File /var/lib/asterisk/sounds/rhv_playback/9999_1691415275 does not exist in any format
[2023-08-07 16:34:35] WARNING[22369][C-00000013]: file.c:1303 ast_streamfile: Unable to open /var/lib/asterisk/sounds/rhv_playback/9999_1691415275 (format (ulaw)): No such file or directory
[2023-08-07 16:34:35] WARNING[22369][C-00000013]: app_playback.c:512 playback_exec: Playback failed on SIP/1514-00000012 for /var/lib/asterisk/sounds/rhv_playback/9999_1691415275&silence/1

That is, the script cannot create a file and, accordingly, play it.

If I just execute /var/lib/asterisk/agi-bin/Robovoice.py on the command line 9999_1691415533 The company welcomes you, John - the script works, but takes only the word company
That is, to record the entire phrase, the request should look like this: /var/lib/asterisk/agi-bin/Robovoice.py 9999_1691415533 “The company welcomes you, John”

The script itself is in the directory:

[root@freepbx agi-bin]# ls -la
total 1722656
-rwxrwxr-x   1 asterisk asterisk        915 Aug  7 22:44 Robovoice.py

If I run it manually, everything works.

If I run, as it is written in the log (‘/var/lib/asterisk/agi-bin/Robovoice.py 1514_1691416909 The company welcomes you, John’ :), with apostrophes, I expectedly get No such file or directory

I have other scripts in this directory, they are run from under system but do not work through AGI.
Specifically, this script does not work from under the system( It seems to me that it simply does not have time to execute when I launch it from system.

Please tell me how to solve this issue?

just a guess, you may need to add “” when you call AGI and not on Set

same = n,AGI(/var/lib/asterisk/agi-bin/Robovoice.py "${RHV_FILE}" "${RHV_TEXT}")

I tried as you wrote.
The error persists. Now, however, cut off JOHN

[2023-08-08 09:58:22] WARNING[21413][C-0000001d]: res_agi.c:2218 launch_script: Failed to execute '/var/lib/asterisk/agi-bin/Robovoice.py 1514_1691477902 The company welcomes you': File does not exist.

I thought it might be related to quotes and apostrophes, but I can’t figure out how to put them in the right place

-- Executing [s@civility-hi:2] Wait("SIP/1514-0000001b", "1") in new stack
    -- Executing [s@civility-hi:3] Set("SIP/1514-0000001b", "RHV_FILE=1514_1691477902") in new stack
    -- Executing [s@civility-hi:4] Set("SIP/1514-0000001b", "RHV_TEXT="The company welcomes you, John"") in new stack
    -- Executing [s@civility-hi:5] AGI("SIP/1514-0000001b", "/var/lib/asterisk/agi-bin/Robovoice.py "1514_1691477902" ""The company welcomes you, John""") in new stack
[2023-08-08 09:58:22] WARNING[21413][C-0000001d]: res_agi.c:2218 launch_script: Failed to execute '/var/lib/asterisk/agi-bin/Robovoice.py 1514_1691477902 The company welcomes you': File does not exist.
    -- Executing [s@civility-hi:6] Playback("SIP/1514-0000001b", "/var/lib/asterisk/sounds/rhv_playback/1514_1691477902&silence/1") in new stack
[2023-08-08 09:58:22] WARNING[21413][C-0000001d]: file.c:824 ast_openstream_full: File /var/lib/asterisk/sounds/rhv_playback/1514_1691477902 does not exist in any format
[2023-08-08 09:58:22] WARNING[21413][C-0000001d]: file.c:1303 ast_streamfile: Unable to open /var/lib/asterisk/sounds/rhv_playback/1514_1691477902 (format (ulaw)): No such file or directory
[2023-08-08 09:58:22] WARNING[21413][C-0000001d]: app_playback.c:512 playback_exec: Playback failed on SIP/1514-0000001b for /var/lib/asterisk/sounds/rhv_playback/1514_1691477902&silence/1

Does the first line in the python file, point to a correct python interpreter?

Eg.

#!/bin/python3

And does that python interpreter actually exist?

Also if you have astagidir configured correctly in your asterisk.conf file, like

astagidir=/var/lib/asterisk/agi-bin

You should be able to just call it like

same = n,AGI(Robovoice.py "${RHV_FILE}" "${RHV_TEXT}")

Also you COULD try to skip the parameters, like so

same = n,AGI(Robovoice.py)

To see if it’s the parameters or the script itself, that trigger the error. If you still get the “File not found” when only specifying the file, try renaming it, eg. use only lowercase letters.

[root@freepbx agi-bin]# cat Robovoice.py
#!/usr/bin/python3
[root@freepbx bin]# ls -la
lrwxrwxrwx.   1 root     root            7 Aug  1 09:28 python -> python2
lrwxrwxrwx.   1 root     root            9 Aug  1 09:28 python2 -> python2.7
-rwxr-xr-x.   1 root     root         7144 Apr  2  2020 python2.7
lrwxrwxrwx    1 root     root           24 Aug  2 09:31 python3 -> /usr/local/bin/python3.8
[root@freepbx bin]# cat /etc/asterisk/asterisk.conf
[directories]
astetcdir=/etc/asterisk
astmoddir=/usr/lib64/asterisk/modules
astvarlibdir=/var/lib/asterisk
astagidir=/var/lib/asterisk/agi-bin
astspooldir=/var/spool/asterisk
astrundir=/var/run/asterisk
astlogdir=/var/log/asterisk
[options]
transmit_silence_during_record=yes
languageprefix=yes
execincludes=yes
dontwarn=yes
[files]
astctlpermissions=775

I tried your option, without specifying the full path

same = n,AGI(Robovoice.py "${RHV_FILE}" "${RHV_TEXT}")

Executing [s@civility-hi:5] AGI("SIP/1514-00000021", "Robovoice.py "1514_1691479698" ""The company welcomes you, John""") in new stack
[2023-08-08 10:28:18] WARNING[31861][C-00000023]: res_agi.c:2218 launch_script: Failed to execute '/var/lib/asterisk/agi-bin/Robovoice.py 1514_1691479698 The company welcomes you': File does not exist.

I tried to run without parameters, as I understand it - everything worked.
The question is really in quotes, which should be transmitted. How to do it?

Executing [s@civility-hi:3] Set("SIP/1514-00000022", "RHV_FILE=1514_1691479901") in new stack
    -- Executing [s@civility-hi:4] Set("SIP/1514-00000022", "RHV_TEXT="The company welcomes you, John"") in new stack
    -- Executing [s@civility-hi:5] AGI("SIP/1514-00000022", "Robovoice.py") in new stack
    -- Launched AGI Script /var/lib/asterisk/agi-bin/Robovoice.py
<SIP/1514-00000022>AGI Tx >> agi_request: Robovoice.py
<SIP/1514-00000022>AGI Tx >> agi_channel: SIP/1514-00000022
<SIP/1514-00000022>AGI Tx >> agi_language: en
<SIP/1514-00000022>AGI Tx >> agi_type: SIP
<SIP/1514-00000022>AGI Tx >> agi_uniqueid: 1691479895.67
<SIP/1514-00000022>AGI Tx >> agi_version: 18.19.0
<SIP/1514-00000022>AGI Tx >> agi_callerid: 1514
<SIP/1514-00000022>AGI Tx >> agi_calleridname: John
<SIP/1514-00000022>AGI Tx >> agi_callingpres: 0
<SIP/1514-00000022>AGI Tx >> agi_callingani2: 0
<SIP/1514-00000022>AGI Tx >> agi_callington: 0
<SIP/1514-00000022>AGI Tx >> agi_callingtns: 0
<SIP/1514-00000022>AGI Tx >> agi_dnid: 400
<SIP/1514-00000022>AGI Tx >> agi_rdnis: unknown
<SIP/1514-00000022>AGI Tx >> agi_context: civility-hi
<SIP/1514-00000022>AGI Tx >> agi_extension: s
<SIP/1514-00000022>AGI Tx >> agi_priority: 5
<SIP/1514-00000022>AGI Tx >> agi_enhanced: 0.0
<SIP/1514-00000022>AGI Tx >> agi_accountcode:
<SIP/1514-00000022>AGI Tx >> agi_threadid: 139637879105280
<SIP/1514-00000022>AGI Tx >>
    -- <SIP/1514-00000022>AGI Script Robovoice.py completed, returning 0
    -- Executing [s@civility-hi:6] Playback("SIP/1514-00000022", "/var/lib/asterisk/sounds/rhv_playback/1514_1691479901&silence/1") in new stack
[2023-08-08 10:31:41] WARNING[656][C-00000024]: file.c:824 ast_openstream_full: File /var/lib/asterisk/sounds/rhv_playback/1514_1691479901 does not exist in any format
[2023-08-08 10:31:41] WARNING[656][C-00000024]: file.c:1303 ast_streamfile: Unable to open /var/lib/asterisk/sounds/rhv_playback/1514_1691479901 (format (ulaw)): No such file or directory

Let me see if I get this right… Your python script creates an audio file, that Asterisk is supposed to playback?

As I see it, the script is running, and exits without error.

You’ll need this to be true

  1. The directory /var/lib/asterisk/sounds/rhv_playback must be writable by the user running Asterisk.
  2. The file must be in a format Asterisk understands, and have the correct extension, as the Extension seems to be what Asterisk uses to decide on the correct file for the current channel.

If your script does not generate the audio file in the correct format, your can convert it with programs like sox or ffmpeg. I would suggest converting the files to alaw or ulaw depending on the codecs you use. In my case we talk to the PSTN and use alaw there, and as such converts our files to alaw, to avoid realtime transcoding on every call.

If you have a low call volume, or “unlimited” CPU resources, transcoding is not a problem, but if you want to decrease load, avoiding transcoding is one way of doing it. Conversion to alaw can be handled by Asterisk, I can not find an AGI or AMI command for the job, but in the CLI file convert <source>.wav <destination>.alaw will do the job, and there’s an AMI command for running CLI commands.

As for the quotes, you’re adding quotes to the text when you assign it to the variable, and you add another set of quotes to the text when you call the AGI script.

To avoid all that “crap” you could forget about using the parameters, assign the data to Asterisk variables, then read the variables from you AGI script instead.

The audio format is correct, it is recorded in wav and before that it was converted with sox up to 8000.
The asterisk user has write permissions.

As for the quotes, you’re adding quotes to the text when you assign it to the variable, and you add another set of quotes to the text when you call the AGI script.

Okay, I tried escaping quotes with a slash.

same = n,Set(RHV_TEXT=\"The company welcomes you, ${CALLERID(Name)}\")
same = n,AGI(Robovoice.py "${RHV_FILE}" "${RHV_TEXT}")

Now the error looks correct, that is, this is exactly how the command should be executed

[2023-08-08 11:48:46] WARNING[18011][C-00000016]: res_agi.c:2218 launch_script: Failed to execute '/var/lib/asterisk/agi-bin/Robovoice.py 1514_1691484526 "The company welcomes you, John"': File does not exist.

If I copy it and just execute /var/lib/asterisk/agi-bin/Robovoice.py 1514_1691484526 “The company welcomes you, John”, it works.
Including from under the user an asterisk.

Why does the asterisk refuse to execute this command?

assign the data to Asterisk variables, then read the variables from you AGI script instead

Sorry, but I have absolutely no idea how to do this (

Don’t add the quotes at all, at this point…

This would result in Asterisk calling the script like this:

Robovoice.py "1514_1691484526" ""The company welcomes you, John""

Which can cause all kinds of problems.

The python script you’re using, is that written to be used as an AGI script in Asterisk? If not, executing it with AGI could have unexpected side effects, try using System() instead, as with the call to AGI there are security issues to be aware of, some of them are listed in the manual. The reason being that AGI is an interface directly between Asterisk and whatever application you start with the AGI call. It allows the AGI application to receive data from Asterisk on stdin, and will send data to Asterisk on stdout. If the script you run, uses either stdin or stdout for anything, it could get confused by the initial information sent from Asterisk when the script starts up.

Also, if you try changing the text from a long string, to a single word, without the quotes, does that work? It’s always a good idea to try the bare minimum if things ain’t working as expected.

In summary:

  • If the Robovoice.py script is NOT an Asterisk AGI script, use System instead of AGI for execution.

I just so happened to check the manual for AGI() after writing everything above. It turns out AGI does not work as you think.

The arguments for the script needs to be separated from the filename by a comma. Like so AGI(Robovoice.py,1514_1691479901,John) That would most likely eliminate the need for quotes, but also require special handling of comma in the parameters.

But again, unless Robovoice.py is specifically written as an AGI script, which everything you’ve pasted above indicates it is not, you should NOT execute it with AGI, but use System instead. System(), which takes the entire command as a single value, and quotes are needed, but you should only add the quotes ONCE, either when you assign them to the variable, or when calling the system application. Not both.

My guess, Asterisk is waiting to read a file, not a text.

No, it’s just a python script.

same = n,Set(RHV_TEXT=“The company welcomes you, ${CALLERID(Name)}”)
same = n,System(/var/lib/asterisk/agi-bin/Robovoice.py ${RHV_FILE} ${RHV_TEXT})

The system works with this value, but nothing happens. That is, it just runs and that’s it. I started with this, but then I decided that this would not work in principle.
Unfortunately, as I understand it, the system does not wait for the script to be executed, but it is quite large and can be different every time.

I just have no other explanation, because if I again just copy the request /var/lib/asterisk/agi-bin/Robovoice.py 1514_1691492360 “The company welcomes you, JOHN” and execute it, the script works. I even tried to change to the home/asterisk folder to eliminate rights problems

Executing [s@civility-hi:3] Set("SIP/1514-0000001d", "RHV_FILE=1514_1691492360") in new stack
    -- Executing [s@civility-hi:4] Set("SIP/1514-0000001d", "RHV_TEXT="The company welcomes you, JOHN"") in new stack
    -- Executing [s@civility-hi:5] System("SIP/1514-0000001d", "/var/lib/asterisk/agi-bin/Robovoice.py 1514_1691492360 "The company welcomes you, JOHN"") in new stack
    -- Executing [s@civility-hi:6] Playback("SIP/1514-0000001d", "/home/asterisk/sounds/1514_1691492360&silence/1") in new stack
[2023-08-08 13:59:20] WARNING[11617][C-00000017]: file.c:824 ast_openstream_full: File /home/asterisk/sounds/1514_1691492360 does not exist in any format
[2023-08-08 13:59:20] WARNING[11617][C-00000017]: file.c:1303 ast_streamfile: Unable to open /home/asterisk/sounds/1514_1691492360 (format (ulaw)): No such file or directory
[2023-08-08 13:59:20] WARNING[11617][C-00000017]: app_playback.c:512 playback_exec: Playback failed on SIP/1514-0000001d for /home/asterisk/sounds/1514_1691492360&silence/1

What filename is actually generated by the python script? What extension does the file have? If the filename has no extension, it will not work.

The script takes the number of the caller + epoch and the text of the message.
Inside itself, the script reads the text, writes it to a temporary file _t, re-encodes it and creates the file caller number + epoch.wav as output.
I made a test run of the script and substituted the values for it myself (1234 for number and “Hello, breakfast” as text)

[root@freepbx sounds]# ls
1234_t.wav  1234.wav
[root@freepbx sounds]# soxi 1234.wav

Input File     : '1234.wav'
Channels       : 1
Sample Rate    : 8000
Precision      : 16-bit
Duration       : 00:00:00.93 = 7430 samples ~ 69.6562 CDDA sectors
File Size      : 14.9k
Bit Rate       : 128k
Sample Encoding: 16-bit Signed Integer PCM

[root@freepbx sounds]# soxi 1234_t.wav

Input File     : '1234_t.wav'
Channels       : 1
Sample Rate    : 22050
Precision      : 16-bit
Duration       : 00:00:00.93 = 20480 samples ~ 69.6599 CDDA sectors
File Size      : 41.0k
Bit Rate       : 353k
Sample Encoding: 16-bit Signed Integer PCM

The problem seems to be the parameters then. If you change the text you assign to the variable to just be “John” no more, nor less.

Like this:

same = n,Set(RHV_TEXT=John)

Let’s make the example as simple as possible, to remove EVERYTHING that might cause problems. Like spaces, and the comma.

Also in your dialplan snippet, the quotes are not double quotes, they are some sort of typographical citation marks, this may be due to the copy paste, but if they are NOT the double quote character in your dialplan file, you need to change them from “ and ” to ".

You also specify the filename as 1514_1691492360, with no extension, does the script add the extension? If it does not, you need to make sure it gets added.

Your response to kwemheuer does not make that completely clear for me.

I just made a test using /bin/sleep 10 and Asterisk does indeed wait the 10 sec for sleep to be done. So it seems it DOES wait for the command to complete, which is also what I would expect.

These lines from the log, suggests that the output file, is NOT written where you expect, or does not have the expected filename. Try verifying the file does indeed exist, after calling the script, if it does not exist, something goes wrong when running the script, usually information would be available on stderr, however, I have no clue how to get what’s outputted to stderr when running from Asterisk, perhaps checking the system logs might give a hint? (Like /var/log/syslog or /var/log/messages)

asterisk> core show application agi
.
.
.
[Syntax]
AGI(command[,arg1[,arg2[,...]]])

[Arguments]
command
    How AGI should be invoked on the channel.
args
    Arguments to pass to the AGI script or server.

The argument separator is comma, not space.

‘agi set debug on’ will yield meaningful clues when figuring out AGI.

With a dialplan like:

; spaces in arguments and channel variables
        same = n,                       set(test=this is a test)
        same = n,                       agi(null-agi.php,this is arg1,this is arg2)
        same = n,                       hangup()

And an AGI (in PHP) like:

// spaces in arguments
        $test = $agi->get_variable('test');
        $agi->verbose('test = ' . strlen($test['data']) . ', test = ' . $test['data']);
        $agi->verbose('argv0 = ' . strlen($argv[0]) . ', argv0 = ' . $argv[0]);
        $agi->verbose('argv1 = ' . strlen($argv[1]) . ', argv1 = ' . $argv[1]);
        $agi->verbose('argv2 = ' . strlen($argv[2]) . ', argv2 = ' . $argv[2]);
        exit(0);

You can see output like:

    -- Launched AGI Script /var/lib/asterisk/agi-bin/null-agi.php
<SIP/poly-77a1-0000002e>AGI Tx >> agi_request: null-agi.php
<SIP/poly-77a1-0000002e>AGI Tx >> agi_channel: SIP/poly-77a1-0000002e
<SIP/poly-77a1-0000002e>AGI Tx >> agi_language: en
<SIP/poly-77a1-0000002e>AGI Tx >> agi_type: SIP
<SIP/poly-77a1-0000002e>AGI Tx >> agi_uniqueid: 1691510324.127
<SIP/poly-77a1-0000002e>AGI Tx >> agi_version: 19.2.0
<SIP/poly-77a1-0000002e>AGI Tx >> agi_callerid: 5555555555
<SIP/poly-77a1-0000002e>AGI Tx >> agi_calleridname: Steve Edwards
<SIP/poly-77a1-0000002e>AGI Tx >> agi_callingpres: 0
<SIP/poly-77a1-0000002e>AGI Tx >> agi_callingani2: 0
<SIP/poly-77a1-0000002e>AGI Tx >> agi_callington: 0
<SIP/poly-77a1-0000002e>AGI Tx >> agi_callingtns: 0
<SIP/poly-77a1-0000002e>AGI Tx >> agi_dnid: *
<SIP/poly-77a1-0000002e>AGI Tx >> agi_rdnis: unknown
<SIP/poly-77a1-0000002e>AGI Tx >> agi_context: newline
<SIP/poly-77a1-0000002e>AGI Tx >> agi_extension: *
<SIP/poly-77a1-0000002e>AGI Tx >> agi_priority: 7
<SIP/poly-77a1-0000002e>AGI Tx >> agi_enhanced: 0.0
<SIP/poly-77a1-0000002e>AGI Tx >> agi_accountcode: 
<SIP/poly-77a1-0000002e>AGI Tx >> agi_threadid: 140033160656640
<SIP/poly-77a1-0000002e>AGI Tx >> agi_arg_1: this is arg1
<SIP/poly-77a1-0000002e>AGI Tx >> agi_arg_2: this is arg2
<SIP/poly-77a1-0000002e>AGI Tx >> 
<SIP/poly-77a1-0000002e>AGI Rx << GET VARIABLE test
<SIP/poly-77a1-0000002e>AGI Tx >> 200 result=1 (this is a test)
<SIP/poly-77a1-0000002e>AGI Rx << VERBOSE "test = 14, test = this is a test" 1
 null-agi.php,this is arg1,this is arg2: test = 14, test = this is a test
<SIP/poly-77a1-0000002e>AGI Tx >> 200 result=1
<SIP/poly-77a1-0000002e>AGI Rx << VERBOSE "argv0 = 38, argv0 = /var/lib/asterisk/agi-bin/null-agi.php" 1
 null-agi.php,this is arg1,this is arg2: argv0 = 38, argv0 = /var/lib/asterisk/agi-bin/null-agi.php
<SIP/poly-77a1-0000002e>AGI Tx >> 200 result=1
<SIP/poly-77a1-0000002e>AGI Rx << VERBOSE "argv1 = 12, argv1 = this is arg1" 1
 null-agi.php,this is arg1,this is arg2: argv1 = 12, argv1 = this is arg1
<SIP/poly-77a1-0000002e>AGI Tx >> 200 result=1
<SIP/poly-77a1-0000002e>AGI Rx << VERBOSE "argv2 = 12, argv2 = this is arg2" 1
 null-agi.php,this is arg1,this is arg2: argv2 = 12, argv2 = this is arg2
<SIP/poly-77a1-0000002e>AGI Tx >> 200 result=1
    -- <SIP/poly-77a1-0000002e>AGI Script null-agi.php completed, returning 0

Which shows:

  1. Arguments are separated by comma
  2. No quotes are needed.
  3. Arguments are available as environment variables ($argv) and as AGI variables (agi_arg_x).

I checked everything you wrote and yes, in the end it was the quotes that turned out to be problematic. I ended up copying yours from the comment and it worked. Apparently, one of my editors changes them to this.
What a stupid mistake - I should have checked it myself but didn’t even think about it. Thank you very much for your time and sorry for bothering you.

Well, quotes, character sets, missing spaces, = instead of == and not catching them when you look at it yourself, is typical mistakes to make, done it myself LOTS of times.

As for editors I can recommend BBEdit for mac (Free version will do just fine for most stuff) or Notepad++ for Windows. They are a bit more featured than the built-in default editors.

If editing directly on the server, over SSH, nano seems to be a good choice. And neither of these will cause your quotes to be changed. :slight_smile:

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.