Asterisk, and other worldly endeavours.

A blog by Leif Madsen

Migrating from AgentCallbackLogin() to Standard Dialplan Methods (part 1)


The purpose of this article is to ease the migration from AgentCallbackLogin() to using standard dialplan applications to solve the problem of calling back queue members (agents) who are logged into a queue.

We will look at some of the common uses of AgentCallbackLogin(), and explore how to perform the same functionality using the commonly available dialplan applications in Asterisk.

The version of Asterisk used in the following examples is based on a recent version of Asterisk 1.4, namely any version from 1.4.25 and beyond. This is because in 1.4.25, a backport of the state interface features in 1.6.x were put into Asterisk 1.4 in order to solve some issues with incorrect device state on Local channels. We’ll be using these state interface features in the examples that follow.

All agents (queue members) will be utilizing the SIP channel driver as it is the only channel driver that contains code for true device state monitoring.

Starting Simple

Lets start with a simple example of how to use AgentCallbackLogin(), and then look at how we can accomplish the same thing with some dialplan logic. First, we need to setup a queue and add some agents to the system. We’ll also setup the SIP devices, and configure the dialplan.

While it may seem excessive in the next few sections to setup something that we’re going to replace anyways, the majority of the configuration is going to be reused in the sections that follow.

Setting up the SIP devices

First we need to configure sip.conf for a couple of devices. One of these will be used for calling the Queue(), while the other will be the agent that will be logged into the queue.

Under the [authentication] header, we will create a template called [phones] that will be used for all the phones. We’re going to use some MAC addresses for the device names, which is how they will authenticate. This is a best practice in general, as we typically want to abstract the device name away from any sort of identifier, such as extension numbers and agent names.

; Create a template we'll use for our phones
[phones](!)
type=friend
host=dynamic
context=from-devices
canreinvite=no
dtmfmode=rfc2833
disallow=all
allow=ulaw
call-limit=5

Note that we have the ‘call-limit’ option enabled. This is necessary for Asterisk to determine whether the device is in use or not. You can set this number to anything you want, as long as it is set.

; Define local devices here
[000A8A2150CB](phones)
username=000A8A2150CB
secret=welcome

[00085D193AB5](phones)
username=00085D193AB5
secret=welcome

[00085D182ACF](phones)
username=00085D182ACF
secret=welcome

The [general] section was left pretty much default, with only a couple of minor tweaks related to the local system configuration. One of the tweaks that should be mentioned however is the ‘limitonpeers’ option. By enabling this, you will get better (correct) InUse status on devices, as outgoing calls will have the call limits applied to the peer structure, thereby giving you the correct device status.

From the sip.conf configuration file:

limitonpeers = yes             ; Apply call limits on peers only. This will improve 
                                ; status notification when you are using type=friend
                                ; Inbound calls, that really apply to the user part
                                ; of a friend will now be added to and compared with
                                ; the peer limit instead of applying two call limits,
                                ; one for the peer and one for the user.
                                ; "sip show inuse" will only show active calls on 
                                ; the peer side of a "type=friend" object if this
                                ; setting is turned on.

From the Asterisk CLI, we need to reload chan_sip, and get our phones registered.

*CLI> module reload chan_sip.so
Reloading SIP


*CLI> sip show peers
Name/username Host Dyn Nat ACL Port Status
00085D182ACF/00085D182ACF 192.168.128.138 D 5501 Unmonitored
00085D193AB5/00085D193AB5 192.168.128.145 D 5430 Unmonitored
000A8A2150CB/000A8A2150CB (Unspecified) D 0 Unmonitored
3 sip peers [Monitored: 0 online, 0 offline Unmonitored: 2 online, 1 offline]

Configuring Agents

Next we want to configure a couple of agents which we’ll use from the AgentCallbackLogin() test we’ll develop shortly.

In agents.conf, configure the following at the bottom of the sample configuration file. Be sure to comment out any sample agents that may not be commented in the sample configuration.

agent => 100,1234,Leif Madsen
agent => 101,1234,Russell Bryant
agent => 102,1234,Mark Michelson

Save the file, and reload the chan_agent module.

*CLI> module reload chan_agent.so
-- Reloading module 'chan_agent.so' (Agent Proxy Channel)

Then verify the agents have been loaded into memory.


*CLI> agent show
100 (Leif Madsen) not logged in (musiconhold is 'default')
101 (Russell Bryant) not logged in (musiconhold is 'default')
102 (Mark Michelson) not logged in (musiconhold is 'default')
3 agents configured [0 online , 3 offline]

Configuring Queues

We now want to configure a queue, and add the agents to that queue. We’ll use the previously added agents from the agents.conf file in queues.conf. We’re going to add a new queue called ‘primary’ with just the bare minimum configuration settings to get the queue working. Asterisk will then use default values for anything else we haven’t specifically configured.

In queues.conf, we can add the primary queue like so.

[primary]
strategy=ringall
timeout=15
ringinuse=no
autopause=yes

member => Agent/100
member => Agent/101
member => Agent/102

We’re using the ring all strategy which will ring all available queue agents. Additionally, we’ve setup the timeout (number of seconds to ring the agents) to 15 seconds, configured Asterisk to mark the agent as in use when ringing the agent (so they don’t get multiple calls), and automatically pause any agents who do not answer within the allotted time out period.

We’ve added our three agents, 100, 101, and 102 with the member lines.

Next we need to reload the app_queue module to load the queue configuration into memory.


*CLI> module reload app_queue.so
-- Reloading module 'app_queue.so' (True Call Queueing)


*CLI> queue show
primary has 0 calls (max unlimited) in 'ringall' strategy (0s holdtime)
Members:
Agent/102 (Unavailable) has taken no calls yet
Agent/101 (Unavailable) has taken no calls yet
Agent/100 (Unavailable) has taken no calls yet
No Callers

Configuring the Dialplan

Next, we need to add some dialplan logic which allows use to login the agents to the queue, and then to test that we can get calls to the agents by calling into the queue.

The following dialplan will accomplish what we want.

[general]
static=yes
writeprotect=no
clearglobalvars=no

[globals]

[default]

[from-devices]
; simple usage of AgentCallbackLogin()
;
exten => 999,1,Verbose(2,Logging in agent)
exten => 999,n,Playback(silence/1)
exten => 999,n,AgentCallbackLogin(,,${CUT(CUT(CHANNEL,/,2),-,1)}@agent_callback)
exten => 999,n,Hangup()

; calling 'primary' queue
;
exten => 555,1,Verbose(2,Calling into the primary queue)
exten => 555,n,Playback(silence/1)
exten => 555,n,Queue(primary)
exten => 555,n,Hangup()

[agent_callback]
exten => _[A-Za-z0-9].,1,Set(EXTENSION=${EXTEN})
exten => _[A-Za-z0-9].,n,Goto(start,1)

exten => start,1,Dial(SIP/${EXTENSION})

What we’re doing in the [from-devices] context at extension 999 is allow the agent to login by dialing 999, which will then prompt for their agent ID and password. These are the extensions and passwords we configured in agents.conf.

The format of the AgentCallbackLogin() application is as follows:


AgentCallbackLogin([AgentNo][,[options][,[exten]@context]])

We are only passing the third option, which is exten@context, where exten is the extension we will call, and the context is where the extension will be called.

The part of the AgentCallbackLogin() application which may be slightly confusing is this:


${CUT(CUT(CHANNEL,/,2),-,1)}@agent_callback

What we’re doing is using the CHANNEL variable, which is automatically populated by Asterisk when a channel is created. It will look something like:

SIP/00085D182ACF-017b24f0

*** Note:
The CUT() function works by providing the field separator, and then the field we want. We are nesting two CUT() functions together to first look for the front slash (/) separator, and return the 2nd field (00085D182ACF-017b24f0) and that is then given to the outside CUT() function, which looks for the hyphen (-) separator, and returns the first field (00085D182ACF).
***

The part on the left side of the front slash is the technology (SIP), and to the right is the device identifier we’ve configured in sip.conf, but with a hyphen and unique identifier tacked onto the end. What we’re doing with the CUT() function, is to strip off the SIP/ part of the CHANNEL application, along with the unique identifier, including the hyphen. This will then leave the following:

00085D182ACF

When we login, we then are telling the Queue() application to call Agent/100 by dialing 00085D182ACF in the [agent_callback] context. This context then will dial SIP/00085D182ACF, which rings the device.

The reason we do it this way is because we then create what is effectively a hot-desking application, where an agent can login from any device using their agent ID.

Lets reload the dialplan and then we can test our application.

*CLI> dialplan reload

Logging in the Agent

Now that we’ve configured our devices, agents, queues, and dialplan, we can now go about testing it. We’ll start by logging in an agent to the ‘primary’ queue, and call it from another device in order to show how the AgentCallbackLogin() application works now.

First, start by dialing extension 999 which will then prompt you for your Agent ID (100) followed by the password (1234). After each prompt be sure to press the # key. After you’ve logged in you can verify your status from the Asterisk CLI.


*CLI> agent show
100 (Leif Madsen) available at '00085D182ACF@agent_callback'
101 (Russell Bryant) not logged in (musiconhold is 'default')
102 (Mark Michelson) not logged in (musiconhold is 'default')
3 agents configured [1 online , 2 offline]

And then verify that our agent is available in the queue.


*CLI> show queues
primary has 0 calls (max unlimited) in 'ringall' strategy (0s holdtime)
Members:
Agent/102 (Unavailable) has taken no calls yet
Agent/101 (Unavailable) has taken no calls yet
Agent/100 (Not in use) has taken no calls yet
No Callers

Testing the Queue

We can now test our queue by dialing extension 555 from our second phone, which will then cause the queue to dial our agent.

Lets step through the following console output to determine what happened when we dialed 555.

*CLI>
-- Executing [555@from-devices:1] Verbose("SIP/00085D193AB5-017b24f0", "2|Calling into the primary queue") in new stack
== Calling into the primary queue
-- Executing [555@from-devices:2] Playback("SIP/00085D193AB5-017b24f0", "silence/1") in new stack
--  Playing 'silence/1' (language 'en')

Here we enter the Queue() application.

-- Executing [555@from-devices:3] Queue("SIP/00085D193AB5-017b24f0", "primary") in new stack

And now the music on hold starts for the incoming caller while we attempt to dial an agent.

-- Started music on hold, class 'default', on SIP/00085D193AB5-017b24f0

Then the Queue() application attempts to call agent 100, which is called via the Local channel.

*** Note:
The Local channel in Asterisk is a pseudo channel that permits applications to execute dialplan logic in place of calling a standard channel, like a SIP channel.
***

The Local channel is dialing extension 00085D182ACF within the [agent_callback] context. When we logged in agent 100, we told the AgentCallbackLogin() application that agent 100 can be reached by executing extension 00085D182ACF inside the [agent_callback] context.

-- outgoing agentcall, to agent '100', on 'Local/00085D182ACF@agent_callback-dabf,1'

The first priority of the [agent_callback] context saves the dialed extension to a channel variable. The reason we do this is because we’re using a complex pattern match in the dialplan (see Configuring the Dialplan earlier) which could be error prone to type several times. So we save the value of ${EXTEN} to the EXTENSION channel variable, and use that in an easier to type extension name (i.e. start)

-- Executing [00085D182ACF@agent_callback:1] Set("Local/00085D182ACF@agent_callback-dabf,2", "EXTENSION=00085D182ACF") in new stack

Now that we’ve saved the extension matched by our pattern, we can Goto() the ‘start’ extension.

-- Executing [00085D182ACF@agent_callback:2] Goto("Local/00085D182ACF@agent_callback-dabf,2", "start|1") in new stack
-- Goto (agent_callback,start,1)

The ‘start’ extension simply performs the Dial() over a SIP channel to the device we want to call, which is device 00085D182ACF (as configured in sip.conf).

-- Executing [start@agent_callback:1] Dial("Local/00085D182ACF@agent_callback-dabf,2", "SIP/00085D182ACF") in new stack
-- Called 00085D182ACF

The agent then hears ringing at their device.

-- SIP/00085D182ACF-017b9510 is ringing
-- Agent/100 is ringing

After the agents phone rings, they answer it, and music on hold is stopped.

-- SIP/00085D182ACF-017b9510 answered Local/00085D182ACF@agent_callback-dabf,2
-- Agent/100 answered SIP/00085D193AB5-017b24f0
-- Stopped music on hold on SIP/00085D193AB5-017b24f0

Logging Out the Agent

Prior to updating our dialplan to stop using the AgentCallbackLogin(), we should logout our agent. We can do this simply from the dialplan like so.


*CLI> agent logoff Agent/100
Logging out 100

Then we can verify the agent is logged out, and no longer available in the queue.


*CLI> agent show
100 (Leif Madsen) not logged in (musiconhold is 'default')
101 (Russell Bryant) not logged in (musiconhold is 'default')
102 (Mark Michelson) not logged in (musiconhold is 'default')
3 agents configured [0 online , 3 offline]


*CLI> queue show
primary has 0 calls (max unlimited) in 'ringall' strategy (1s holdtime)
Members:
Agent/102 (Unavailable) has taken no calls yet
Agent/101 (Unavailable) has taken no calls yet
Agent/100 (Unavailable) has taken 1 calls (last was 1543 secs ago)
No Callers

Using AddQueueMember()

In order to stop using the AgentCallbackLogin(), we need to replace it with something. The something is the AddQueueMember() application. With this dialplan application, we can add channels to dial from the queue dynamically.

Lets create a simple dialplan to replace our existing dialplan which uses the AddQueueMember() dialplan application. The following dialplan will completely replace our existing dialplan. You can rename the existing extensions.conf and create a new file, and filling it with the following dialplan.

[general]
static=yes
writeprotect=no
clearglobalvars=no

[globals]
MY_QUEUES=primary

[default]

[from-devices]
; A replaced version of AgentCallbackLogin() using a GoSub()
;
exten => 999,1,Verbose(2,Logging in agent)
exten => 999,n,GoSub(AgentCallbackLogin,start,1)
exten => 999,n,Hangup()

; calling 'primary' queue
;
exten => 555,1,Verbose(2,Calling into the primary queue)
exten => 555,n,Playback(silence/1)
exten => 555,n,Queue(primary)
exten => 555,n,Hangup()

[agent_callback]
exten => _[A-Za-z0-9].,1,Set(EXTENSION=${EXTEN})
exten => _[A-Za-z0-9].,Goto(start,1)

exten => start,1,Dial(SIP/${EXTENSION})

[AgentCallbackLogin]
; conversion of AgentCallbackLogin() to using AddQueueMember()
;
exten => start,1,Verbose(2,Logging in agent)
exten => start,n,Playback(silence/1)
exten => start,n,Read(AGENT_USERID,agent-user)
exten => start,n,VMauthenticate(${AGENT_USERID}@default)
exten => start,n,AddQueueMember(${MY_QUEUES},Local/${CUT(CUT(CHANNEL,-,1),/,2)}@agent_callback,,,,${CUT(CHANNEL,-,1)})
exten => start,n,Playback(agent-loginok)
exten => start,n,Return()

At extension 999, we’re using a GoSub() (which is similar to a Macro(), but without the restrictions of Macro(), and is recommended where possible). This GoSub() executes the dialplan located at the ‘start’ extension of the [AgentCallbackLogin] context.

After playing 1 second of silence (which is used to answer the channel so we don’t have the first few milliseconds of our prompts cut off), we then prompt the agent to enter their agent ID (100). After pressing #, the value is saved to the AGENT_USERID channel variable. Asterisk then executes the VMauthenticate() application to authenticate the agent (more on configuring
this in a moment).

After we’ve successfully authenticated the agent, we then make them available in the queue by using the AddQueueMember() application. You’ll notice that we’ve added the MY_QUEUES global variable which contains the name of the queue we are logging our agent into.

In more advanced dialplans, we may have multiple queues listed, separated by something like hyphen (-), and use the CUT() application, along with a While() loop to login the agent to multiple queues.

The second option to AddQueueMember() is the channel we want the Queue() application to call when it wishes to dial our agent. This is similar to how the AgentCallbackLogin() application works like we saw in Testing the Queue.

The nested CUT() statement is the same as we saw in Configuring the Dialplan, where the purpose is to take the value of the CHANNEL channel variable, and strip off the technology (SIP/) and the unique identifier (-017b24f0), leaving just the device name (00085D182ACF).

In the 6th position of the AddQueueMember() (the last option), we have another CUT() which strips off the unique identifier of the CHANNEL channel variable, leaving something like SIP/00085D182ACF. This option tells Asterisk to ignore the device state of the Local channel, and to look at the device state of the specified channel. Defining which channel to monitor for state is important because the Local channel is a pseudo channel, which can report an incorrect status, leading to agents who are unavailable when they should be.

After adding the member to the queue, we then playback a prompt that tells the agent they have been logged in successfully. We can verify our agent is now available by running the following command.


*CLI> queue show
primary has 0 calls (max unlimited) in 'ringall' strategy (1s holdtime)
Members:
Local/00085D182ACF@agent_callback (dynamic) (Not in use) has taken no calls yet
No Callers

Using voicemail.conf for Authentication

In the previous section, we used the VMauthenticate() application to perform the authentication of our queue member. In order to do this, we needed to add them to the voicemail.conf file, and give them a password. In many situations you will already have created a mailbox for your agents, and can simply use the existing voicemail configuration to perform the authentication. An advantage to this is that you no longer need to maintain a separate authentication file for your agents.

The voicemail.conf file we used was the default sample file installed when you run ‘make samples’ during Asterisk installation. We only changed the [default] voicemail context at the end to look like the following.


[default]
100 => 1234,Leif Madsen
101 => 1234,Russell Bryant
102 => 1234,Mark Michelson

Then we can reload app_voicemail to make our users available to the VMauthenticate() application for authentication.


*CLI> module reload app_voicemail.so

And verify our users exist.


*CLI> voicemail show users for default
Context Mbox User Zone NewMsg
default general New User 0
default 100 Leif Madsen 0
default 101 Russell Bryant 0
default 102 Mark Michelson 0

Conclusion

This article looked at what the AgentCallbackLogin() application gives us, how it is used, and how to perform the same functionality with the AddQueueMember() dialplan application. By doing this, you position yourself to utilize the preferred method of adding members to a queue, and using a 1.6.x compatible method.

Future articles will expand upon this most basic of functionality by providing dialplan for logging out queue members from the dialplan, pausing and unpausing members, and adding agents to more than one queue, and controlling their weight within those queues.

Questions and comments are welcomed and encouraged. Thanks for reading!

Written by Leif Madsen

2009/07/15 at 9:59 pm

14 Responses

Subscribe to comments with RSS.

  1. Great how-to article Leif! I’ve been hoping to find something like this instead of having to implement it in AEL2 like the examples in 1.6 where AgentCallBackLogin has been deprecated. I’m looking forward to Part 2.
    One thing I noticed is that in the queues.conf sample code and the extensions.conf sample code wherever there should be a => it appears as =&gt but it’s easy to figure out that it was some kind of tag or font rendering issue and the code examples are still easy to follow, even for slow old farts like me 🙂

    Jaytee

    2009/07/16 at 8:31 am

  2. Leif,

    Great article! However you have set ‘ringinuse=yes’ which *will* ring an agent if they are already on a queue call. This should be set to ‘no’ so that it does *not* ring the the agent when they are already on a call from the queue.

    Additionally you may want to mention the use of the option ‘limitonpeers’ in sip.conf to mark the agent as inuse if they make an outbound call.

    angler

    2009/07/16 at 11:00 am

  3. Edit: The comments by Jaytee re: the use of > in some of the examples has been resolved. Additionally, I’ve changed the ‘ringinuse’ option to the correct value per angler.

    I have also added a section mentioning and explaining the ‘limitonpeers’ option from sip.conf, also mentioned by angler.

    Thanks!

    Leif Madsen

    2009/07/18 at 11:04 am

  4. Hi Leif,

    Gonna add my 2 cents here, as I’ve had to deal with this from the larger deployment side of things with multiple queues.

    You mention in your MY_QUEUES variable explanation, that for larger sites, you might loop thru a hyphen delimited list of queues to log into. I just wanted to confirm, and maybe see if you had any ideas around my interpretation of a bigger change than simply using AgentCallBackLogin() as a function.

    Seemingly, with this new method, you completely lose the ability of leveraging the “Agent/” channel type, where in you define an Agent, then in queues.conf, specify which queues an Agent should be a part of, then simply logging in as that agent – somehow – you immediately become available in all queues you’ve been configured for in agents.conf.

    Now instead, seemingly agents.conf is generally not used at all, and using Agent/ types in queues.conf isnt either. For companies that have, say, a group of 5 people who join Queue1, 10 people who join Queue2, and a mix of 8 of those people where some join Queue1 AND Queue2, your dialplan begins to grow exponentially and require different extensions for logging agents in, depending on what queue(s) you intend them to join, rather than simply logging them in as an “agent” as was only necessary in the past.

    What are your thoughts on this issue?

    In summary, the big issue here is having a large group of people, and multiple queues, especially when some people join multiple queues, but not everyone does, etc, and managing to pull that off now in the dial plan alone.

    voxter

    2009/07/27 at 8:58 pm

    • Yes, you lose the Agent/ channel, which is really just an aliased Local/ channel, so you’re not really losing anything. I guess it depends from which perspective you’re looking.

      So in queues.conf, instead of using Agent/foo, replace it with Local/foo@agents. You’ll get the same, or more, functionality this way. You just create the dialplan that determines how to call ‘foo’, and then it can check the local Asterisk database via the DB() function to determine if you’ve logged that agent into a queue or not.

      One of the methods I mentioned was to create the list of queues to login to from the dialplan, and then loop through and add those members to the queue dynamically. This is mostly so that you don’t have to modify queues.conf, but rather just modify the dialplan. Additionally, I wasn’t using the Local channel, but rather just adding the members device directly to the queue. The power with this method (versus using AgentCallbackLogin()) is that you can come up with many different ways to solve the same (or slightly altered) problem multiple ways.

      You are correct — agents.conf is no longer used as you’re not using Agent channels anymore.

      I think you’re misunderstanding the approach slightly, or perhaps more likely, I haven’t documented your solution (yet!). This was a very basic intro to get people thinking about ways of solving the same problem that AgentCallbackLogin() solves. So far I’ve only documented an extremely narrow and simple usage of the application, where the purpose is to make sure people are all on the same page, and not get too crazy in the first go-round.

      So to partially answer your question (where a full answer would be to show you how to do it — coming soon), the process is to remove Agent/ and replace with Local/ in your queues.conf file, and to then control the logging in and out from the queue by setting a DB() key in the AstDB in order to mark the user as logged in or not. If they aren’t logged in, you return Congestion() to the queue, which then triggers it to roll into the Local channel.

      Leif Madsen

      2009/07/30 at 2:44 pm

  5. Hi Leif

    Thanks for a great article!

    I have one question though – voicemail.conf and agent.conf both having to have passwords kept twice?

    It seems that this is what you have to do. In many call centres agents don’t have mailboxes so you will need to create and manage two files with the exact same data right?

    Maybe I’m wrong but this sounds like a nightmare 🙂 I’ve yet to upgrade to 1.6 over this and everywhere I see examples everybody is using VMauthenticate.

    Is there a way to use the password you set for the agent to authenticate rather than setting up two files with the same data and setting up voicemail for people who will never use it.

    🙂 Thanks
    Dave

    David

    2009/08/14 at 6:15 am

    • That’s the thing though — you’re not using the Agent channel anymore. You don’t have to worry about that agents.conf file at all. My method uses the password from voicemail.conf — which means my method *IS* only using a single password, stored in one location. Even if you don’t use the mailbox, you can still provide the mailbox configuration in order to setup the mailbox, then just block access to the agent logging into it (i.e. don’t call VoicemailMain()).

      It’s not a nightmare — you’re just presuming that you need to use chan_agent still. Just don’t. Avoid using it, and you’ll have more control, and less issues overall.

      Leif Madsen

      2009/09/01 at 7:15 am

  6. Dear Leif

    Thanks for your great article!
    I cant find the Second Part of this Article . Did you write it ?

    Regards,

    Omid Mohajerani

    2010/08/16 at 7:40 pm

  7. where is the advanced part of this agentcallbacklogin using addqueuemember ???

    please let me know so that i can also integrate it on my server.

    thanks in advance.

    Aamir Chougule

    Aamir Chougule

    2011/12/12 at 7:46 pm

  8. Executing [7000@agent_callback:1] Set(“Local/7000@agent_callback-ee06;2”, “EXTENSION=7000”) in new stack
    — Auto fallthrough, channel ‘Local/7000@agent_callback-ee06;2’ status is ‘UNKNOWN’

    Why status is UNKNOW ??
    My thomson is 7000 and my agent is 7001.

    Jimy

    2012/03/06 at 8:15 pm

    • This article is probably a little outdated now; a better article is available in Asterisk: The Definitive Guide (3rd edition). Additionally, support cannot be provided in this forum.

      Leif Madsen

      2012/03/07 at 8:25 am


Comments are closed.