Added SIP redirect example

This commit is contained in:
Trial97
2020-06-24 16:28:36 +03:00
parent aec2096010
commit 85704c65d1
11 changed files with 613 additions and 5 deletions

1
.gitignore vendored
View File

@@ -12,6 +12,7 @@ dean*
data/vagrant/.vagrant
data/vagrant/vagrant_ansible_inventory_default
data/tutorials/fs_evsock/freeswitch/etc/freeswitch/
data/tutorials/sip_redirect/freeswitch/etc/freeswitch/
data/tutorial_tests/fs_evsock/freeswitch/etc/freeswitch/
vendor
*.test

View File

@@ -119,6 +119,9 @@ func (sa *SIPAgent) serveUDP(stop chan struct{}) (err error) {
wg.Done()
return
}
if "ACK" == sipMessage.MethodFrom("Request") {
return
}
var sipAnswer sipingo.Message
var err error
if sipAnswer, err = sa.handleMessage(sipMessage, saddr.String()); err != nil {
@@ -204,6 +207,9 @@ func (sa *SIPAgent) serveTCP(stop chan struct{}) (err error) {
wg.Done()
continue
}
if "ACK" == sipMessage.MethodFrom("Request") {
continue
}
var sipAnswer sipingo.Message
if sipAnswer, err = sa.handleMessage(sipMessage, conn.LocalAddr().String()); err != nil {
continue

View File

@@ -1,2 +1,3 @@
#Tenant,Account,ActionPlanId,ActionTriggersId,AllowNegative,Disabled
cgrates.org,1001,AP_PACKAGE_10,,,
cgrates.org,1001,AP_PACKAGE_10,,,
cgrates.org,1002,AP_PACKAGE_10,,,
1 #Tenant Account ActionPlanId ActionTriggersId AllowNegative Disabled
2 cgrates.org 1001 AP_PACKAGE_10
3 cgrates.org 1002 AP_PACKAGE_10

View File

@@ -0,0 +1,5 @@
#Tenant,ID,FilterIDs,ActivationInterval,RunID,AttributeIDs,Weight
cgrates.org,DEFAULT,,,*default,*none,0
cgrates.org,route1,,,*raw,*constant:*req.RequestType:*none;*constant:*req.Destination:1003,0
cgrates.org,route2,,,*raw,*constant:*req.RequestType:*none;*constant:*req.Destination:1004,0
cgrates.org,route3,,,*raw,*constant:*req.RequestType:*none;*constant:*req.Destination:1005,0
1 #Tenant ID FilterIDs ActivationInterval RunID AttributeIDs Weight
2 cgrates.org DEFAULT *default *none 0
3 cgrates.org route1 *raw *constant:*req.RequestType:*none;*constant:*req.Destination:1003 0
4 cgrates.org route2 *raw *constant:*req.RequestType:*none;*constant:*req.Destination:1004 0
5 cgrates.org route3 *raw *constant:*req.RequestType:*none;*constant:*req.Destination:1005 0

View File

@@ -1,5 +1,7 @@
#Tenant,ID,FilterIDs,ActivationInterval,Sorting,SortingParameters,RouteID,RouteFilterIDs,RouteAccountIDs,RouteRatingPlanIDs,RouteResourceIDs,RouteStatIDs,RouteWeight,RouteBlocker,RouteParameters,Weight
cgrates.org,SPL_ACNT_1001,*string:~*req.Account:1001,2017-11-27T00:00:00Z,*weight,,supplier1,,1001,RP_10CNT,,,20,,cgrates.org,20
cgrates.org,SPL_ACNT_1001,,,,,supplier2,,1001,RP_20CNT,,,10,,cgrates.net,10
cgrates.org,SPL_ACNT_1001,,,,,supplier3,,1001,RP_1CNT,,,5,,cgrates.com,5
cgrates.org,SPL_ACNT_1001,*string:~*req.Account:1001,2017-11-27T00:00:00Z,*weight,,route1,,1001,RP_10CNT,,,20,,cgrates.org,20
cgrates.org,SPL_ACNT_1001,,,,,route2,,1001,RP_20CNT,,,10,,cgrates.net,10
cgrates.org,SPL_ACNT_1001,,,,,route3,,1001,RP_1CNT,,,5,,cgrates.com,5
cgrates.org,SPL_ACNT_1002,*string:~*req.Account:1002,2017-11-27T00:00:00Z,*weight,,route1,,1002,RP_10CNT,,,20,,1003@192.168.56.203,20
cgrates.org,SPL_ACNT_1002,,,,,route2,,1002,RP_20CNT,,,10,,1004@192.168.57.203,10
cgrates.org,SPL_ACNT_1002,,,,,route3,,1002,RP_1CNT,,,5,,1005@192.168.58.203,5
1 #Tenant ID FilterIDs ActivationInterval Sorting SortingParameters RouteID RouteFilterIDs RouteAccountIDs RouteRatingPlanIDs RouteResourceIDs RouteStatIDs RouteWeight RouteBlocker RouteParameters Weight
2 cgrates.org SPL_ACNT_1001 *string:~*req.Account:1001 2017-11-27T00:00:00Z *weight supplier1 route1 1001 RP_10CNT 20 cgrates.org 20
3 cgrates.org SPL_ACNT_1001 supplier2 route2 1001 RP_20CNT 10 cgrates.net 10
4 cgrates.org SPL_ACNT_1001 supplier3 route3 1001 RP_1CNT 5 cgrates.com 5
5 cgrates.org SPL_ACNT_1002 *string:~*req.Account:1002 2017-11-27T00:00:00Z *weight route1 1002 RP_10CNT 20 1003@192.168.56.203 20
6 cgrates.org SPL_ACNT_1002 route2 1002 RP_20CNT 10 1004@192.168.57.203 10
7 cgrates.org SPL_ACNT_1002 route3 1002 RP_1CNT 5 1005@192.168.58.203 5

View File

@@ -0,0 +1,18 @@
Tutorial SIP Redirect
================
Scenario:
---------
- FreeSWITCH with minimal custom configuration.
- Added following users (with configs in *etc/freeswitch/directory/default*): 1001-prepaid, 1002-postpaid, 1003-pseudoprepaid, 1004-rated, 1006-prepaid, 1007-prepaid.
- Have added inside default dialplan a redirect for the destinatin 1001 to the CGRateS SIPAgent that will populate the Contact header and add X-Identity header
- **CGRateS** with following components:
- RALs to calculate the maxusage
- RouteS to get the rounting information
- ChargerS to change the destination for each route
- SIPAgent to comunicate with FreeSWITCH
- SessionS to comunicate with the agent

View File

@@ -0,0 +1,105 @@
{
// Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
// Copyright (C) ITsysCOM GmbH
"general": {
"log_level": 7,
"node_id":"CGRSIP",
},
"listen": {
"rpc_json": ":2012",
"rpc_gob": ":2013",
"http": ":2080",
},
"stor_db": {
"db_password": "CGRateS.org",
},
"schedulers": {
"enabled": true,
},
"rals": {
"enabled": true,
"thresholds_conns": ["*localhost"],
"stats_conns": ["*localhost"],
},
"cdrs": {
"enabled": true,
"stats_conns": ["*localhost"],
"chargers_conns": ["*internal"],
"rals_conns": ["*localhost"],
"sessions_cost_retries": 5,
},
"chargers": {
"enabled": true,
"attributes_conns": ["*internal"],
},
"sessions": {
"enabled": true,
"rals_conns": ["*localhost"],
"cdrs_conns": ["*localhost"],
"resources_conns": ["*localhost"],
"routes_conns": ["*localhost"],
"attributes_conns": ["*localhost"],
"stats_conns": ["*localhost"],
"thresholds_conns": ["*localhost"],
"chargers_conns": ["*internal"],
"debit_interval": "5s",
"channel_sync_interval":"7s",
},
"attributes": {
"enabled": true,
"string_indexed_fields": ["Account"],
},
"resources": {
"enabled": true,
"string_indexed_fields": ["Account"],
},
"stats": {
"enabled": true,
"string_indexed_fields": ["Account","RunID","Destination"],
},
"thresholds": {
"enabled": true,
"string_indexed_fields": ["Account"],
},
"routes": {
"enabled": true,
"resources_conns": ["*internal"],
"stats_conns": ["*internal"],
"rals_conns": ["*internal"],
"string_indexed_fields": ["Account"],
},
"apiers": {
"enabled": true,
"scheduler_conns": ["*internal"],
},
}

View File

@@ -0,0 +1,141 @@
{
"sip_agent": {
"enabled": true, // enables the SIP agent: <true|false>
"listen": "192.168.56.203:6060", // address where to listen for SIP requests <x.y.z.y:1234>
"listen_net": "udp", // network to listen on <udp|tcp|tcp-tls>
"request_processors": [
{
"id": "Register",
"filters": ["*notstring:~*vars.Method:INVITE"],
"flags": ["*none"],
"request_fields":[],
"reply_fields":[
{"tag": "Request", "path": "*rep.Request", "type": "*constant",
"value": "SIP/2.0 405 Method Not Allowed"}
]
},
{
"id": "RoutesQuery",
"filters": ["*string:~*vars.Method:INVITE"],
"flags": ["*event", "*routes","*stir_initiate","*continue"],
"request_fields":[
{"tag": "Account", "path": "*cgreq.Account", "type": "*variable",
"value": "~*req.From{*sipuri_user}", "mandatory": true},
{"tag": "Destination", "path": "*cgreq.Destination", "type": "*variable",
"value": "~*req.To{*sipuri_user}", "mandatory": true},
{"tag": "SetupTime", "path": "*cgreq.SetupTime", "type": "*variable",
"value": "*now", "mandatory": true},
{"tag": "Category", "path": "*cgreq.Category", "type": "*variable",
"value": "call", "mandatory": true},
{"tag": "STIRPublicKeyPath", "path": "*opts.STIRPublicKeyPath", "type": "*constant",
"value": "/usr/share/cgrates/stir/stir_pubkey.pem", "mandatory": true},
{"tag": "STIRPrivateKeyPath", "path": "*opts.STIRPrivateKeyPath", "type": "*constant",
"value": "/usr/share/cgrates/stir/stir_privatekey.pem", "mandatory": true},
],
"reply_fields":[
]
},
{
"id": "RoutesQueryOneRoute",
"filters": ["*string:~*vars.Method:INVITE",
"*gte:~*cgrep.Routes.Count:1",
],
"flags": ["*none","*continue"], // do not send request to CGRateS
"reply_fields":[
{"tag": "Request", "path": "*rep.Request", "type": "*constant",
"value": "SIP/2.0 302 Moved Temporarily"},
{"tag": "Contact", "path": "*rep.Contact", "type": "*composed",
"value":"\"1003\" <sip:"},
{"tag": "Contact", "path": "*rep.Contact", "type": "*composed",
"value": "~*cgrep.Routes.SortedRoutes[0].RouteParameters"},
{"tag": "Contact", "path": "*rep.Contact", "type": "*composed",
"value":"*constant:;q=0.7; expires=3600;cgr_cost="},
{"tag": "Contact", "path": "*rep.Contact", "type": "*composed",
"value":"~*cgrep.Routes.SortedRoutes[0].SortingData.Cost"},
{"tag": "Contact", "path": "*rep.Contact", "type": "*composed",
"value":"*constant:;cgr_maxusage="},
{"tag": "Contact", "path": "*rep.Contact", "type": "*composed",
"value":"~*cgrep.Routes.SortedRoutes[0].SortingData.MaxUsage"},
{"tag": "Contact", "path": "*rep.Contact", "type": "*composed",
"value":"*constant:;cgr_route="},
{"tag": "Contact", "path": "*rep.Contact", "type": "*composed",
"value":"~*cgrep.Routes.SortedRoutes[0].RouteID"},
{"tag": "Contact", "path": "*rep.Contact", "type": "*composed",
"value":"*constant:>"},
{"tag": "X-Identity", "path": "*rep.X-Identity", "type": "*variable",
"value":"~*cgrep.STIRIdentity[~*cgrep.Routes.SortedRoutes[0].RouteID]"},
]
},
{
"id": "RoutesQueryTwoRoute",
"filters": ["*string:~*vars.Method:INVITE",
"*gte:~*cgrep.Routes.Count:2",
],
"flags": ["*none","*continue"], // do not send request to CGRateS
"reply_fields":[
{"tag": "Contact", "path": "*rep.Contact", "type": "*composed",
"value":",\"1004\" <sip:"},
{"tag": "Contact", "path": "*rep.Contact", "type": "*composed",
"value": "~*cgrep.Routes.SortedRoutes[1].RouteParameters"},
{"tag": "Contact", "path": "*rep.Contact", "type": "*composed",
"value":"*constant:;q=0.7; expires=3600;cgr_cost="},
{"tag": "Contact", "path": "*rep.Contact", "type": "*composed",
"value":"~*cgrep.Routes.SortedRoutes[1].SortingData.Cost"},
{"tag": "Contact", "path": "*rep.Contact", "type": "*composed",
"value":"*constant:;cgr_maxusage="},
{"tag": "Contact", "path": "*rep.Contact", "type": "*composed",
"value":"~*cgrep.Routes.SortedRoutes[1].SortingData.MaxUsage"},
{"tag": "Contact", "path": "*rep.Contact", "type": "*composed",
"value":"*constant:;cgr_route="},
{"tag": "Contact", "path": "*rep.Contact", "type": "*composed",
"value":"~*cgrep.Routes.SortedRoutes[1].RouteID"},
{"tag": "Contact", "path": "*rep.Contact", "type": "*composed",
"value":"*constant:>"},
]
},
{
"id": "RoutesQueryThreeRoute",
"filters": ["*string:~*vars.Method:INVITE",
"*gte:~*cgrep.Routes.Count:2",
],
"flags": ["*none","*continue"], // do not send request to CGRateS
"reply_fields":[
{"tag": "Contact", "path": "*rep.Contact", "type": "*composed",
"value":",\"1005\" <sip:"},
{"tag": "Contact", "path": "*rep.Contact", "type": "*composed",
"value": "~*cgrep.Routes.SortedRoutes[2].RouteParameters"},
{"tag": "Contact", "path": "*rep.Contact", "type": "*composed",
"value":"*constant:;q=0.7; expires=3600;cgr_cost="},
{"tag": "Contact", "path": "*rep.Contact", "type": "*composed",
"value":"~*cgrep.Routes.SortedRoutes[2].SortingData.Cost"},
{"tag": "Contact", "path": "*rep.Contact", "type": "*composed",
"value":"*constant:;cgr_maxusage="},
{"tag": "Contact", "path": "*rep.Contact", "type": "*composed",
"value":"~*cgrep.Routes.SortedRoutes[2].SortingData.MaxUsage"},
{"tag": "Contact", "path": "*rep.Contact", "type": "*composed",
"value":"*constant:;cgr_route="},
{"tag": "Contact", "path": "*rep.Contact", "type": "*composed",
"value":"~*cgrep.Routes.SortedRoutes[2].RouteID"},
{"tag": "Contact", "path": "*rep.Contact", "type": "*composed",
"value":"*constant:>"},
]
},
]
}
}

View File

@@ -0,0 +1,184 @@
#! /bin/sh
### BEGIN INIT INFO
# Provides: cgrates
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: CGRateS real-time charging system
# Description: Control CGRateS - carrier grade real-time charging system
### END INIT INFO
# Author: DanB <danb@cgrates.org>
#
# Do NOT "set -e"
# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="CGRateS real-time charging system"
NAME=cgrates
DAEMON=/usr/bin/cgr-engine
USER=cgrates
GROUP=cgrates
DAEMON_OPTS=""
TUTFOLDER=/usr/share/cgrates/tutorials/sip_redirect/cgrates
TMP_DIR=/tmp/cgr_fsevsock/cgrates
SCRIPTNAME=$TUTFOLDER/etc/init.d/$NAME
RUNDIR=$TMP_DIR/run
PIDFILE=$RUNDIR/cgr-engine.pid
STACKTRACE=$RUNDIR/$NAME.strace
ENABLE=true
DAEMON_OPTS="-config_path=$TUTFOLDER/etc/cgrates"
CDREDIR=$TMP_DIR/cdre
ERSINDIR=$TMP_DIR/ers/in
ERSOUTDIR=$TMP_DIR/ers/out
# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0
# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh
# Define LSB log_* functions.
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. /lib/lsb/init-functions
if [ "$ENABLE" != "true" ]; then
echo "$DESC not yet configured. Edit $DEFAULTS first."
exit 0
fi
# Install the run folder
if [ ! -d $RUNDIR ]; then
mkdir -p $RUNDIR
touch $STACKTRACE
chown -R $USER:$GROUP $RUNDIR
fi
# Install the ers folder
if [ ! -d $ERSINDIR ]; then
mkdir -p $ERSINDIR
chown $USER:$GROUP $ERSINDIR
mkdir -p $ERSOUTDIR
chown $USER:$GROUP $ERSOUTDIR
fi
# Install the cdre folder
if [ ! -d $CDREDIR ]; then
mkdir -p $CDREDIR
chown $USER:$GROUP $CDREDIR
fi
#
# Function that starts the daemon/service
#
do_start()
{
# Return
# 0 if daemon has been started
# 1 if daemon was already running
# 2 if daemon could not be started
echo "\n### Started at:" `date`>>$STACKTRACE
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test\
|| return 1
start-stop-daemon --start --quiet --chuid $USER:$GROUP --make-pidfile --pidfile $PIDFILE --background\
--startas /bin/bash -- -c "exec $DAEMON $DAEMON_OPTS >> $STACKTRACE 2>&1" \
|| return 2
}
#
# Function that stops the daemon/service
#
do_stop()
{
# Return
# 0 if daemon has been stopped
# 1 if daemon was already stopped
# 2 if daemon could not be stopped
# other if a failure occurred
start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE
RETVAL="$?"
[ "$RETVAL" = 2 ] && return 2
# Wait for children to finish too if this is a daemon that forks
# and if the daemon is only ever run from this initscript.
# If the above conditions are not satisfied then add some other code
# that waits for the process to drop all resources that could be
# needed by services started subsequently. A last resort is to
# sleep for some time.
start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
[ "$?" = 2 ] && return 2
# Many daemons don't delete their pidfiles when they exit.
rm -f $PIDFILE
return "$RETVAL"
}
#
# Function that sends a SIGHUP to the daemon/service
#
do_reload() {
#
# If the daemon can reload its configuration without
# restarting (for example, when it is sent a SIGHUP),
# then implement that here.
#
start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
return 0
}
case "$1" in
start)
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
do_start
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
stop)
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
do_stop
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
status)
status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
;;
#reload|force-reload)
#
# If do_reload() is not implemented then leave this commented out
# and leave 'force-reload' as an alias for 'restart'.
#
#log_daemon_msg "Reloading $DESC" "$NAME"
#do_reload
#log_end_msg $?
#;;
restart|force-reload)
#
# If the "reload" option is implemented then remove the
# 'force-reload' alias
#
log_daemon_msg "Restarting $DESC" "$NAME"
do_stop
case "$?" in
0|1)
do_start
case "$?" in
0) log_end_msg 0 ;;
1) log_end_msg 1 ;; # Old process is still running
*) log_end_msg 1 ;; # Failed to start
esac
;;
*)
# Failed to stop
log_end_msg 1
;;
esac
;;
*)
echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
exit 3
;;
esac
:

View File

@@ -0,0 +1,145 @@
#!/bin/sh
### -*- mode:shell-script; indent-tabs-mode:nil; sh-basic-offset:2 -*-
### BEGIN INIT INFO
# Provides: freeswitch
# Required-Start: $network $remote_fs $local_fs
# Required-Stop: $network $remote_fs $local_fs
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: FreeSWITCH Softswitch
# Description: FreeSWITCH Softswitch
### END INIT INFO
# Author: Travis Cross <tc@traviscross.com>
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC=freeswitch
NAME=freeswitch
DAEMON=/usr/bin/freeswitch
USER=freeswitch
GROUP=freeswitch
TUTDIR=/usr/share/cgrates/tutorials/sip_redirect/freeswitch
SCRIPTNAME=$TUTDIR/etc/init.d/$NAME
TMP_DIR=/tmp/cgr_fsevsock/freeswitch
CONFDIR=$TUTDIR/etc/$NAME
RUNDIR=$TMP_DIR/run
LOGDIR=$TMP_DIR/log
PIDFILE=$RUNDIR/$NAME.pid
WORKDIR=$TMP_DIR/lib
DBDIR=$WORKDIR/db/
DAEMON_ARGS="-rp -conf $CONFDIR -db $DBDIR -log $LOGDIR -u $USER -g $GROUP -nonat -nc"
[ -x $DAEMON ] || exit 0
. /lib/init/vars.sh
. /lib/lsb/init-functions
if [ ! -d $RUNDIR ]; then
mkdir -p $RUNDIR
chown -R $USER:$GROUP $RUNDIR
chmod -R ug=rwX,o= $RUNDIR
fi
if [ ! -d $LOGDIR ]; then
mkdir -p $LOGDIR
chown -R $USER:$GROUP $LOGDIR
fi
if [ ! -d $DBDIR ]; then
mkdir -p $DBDIR
chown -R $USER:$GROUP $DBDIR
fi
do_start() {
if ! [ -f $CONFDIR/freeswitch.xml ]; then
echo "$NAME is not configured so not starting.">&2
echo "Please review /usr/share/doc/$NAME/README.Debian">&2
return 3
fi
echo $DAEMON_ARGS
start-stop-daemon --start --quiet \
--pidfile $PIDFILE --exec $DAEMON --name $NAME --user $USER \
--test > /dev/null \
|| return 1
ulimit -s 240
start-stop-daemon --start --quiet \
--pidfile $PIDFILE --exec $DAEMON --name $NAME --user $USER \
--chdir $WORKDIR -- $DAEMON_ARGS $DAEMON_OPTS \
|| return 2
return 0
}
stop_fs() {
start-stop-daemon --stop --quiet \
--pidfile $PIDFILE --name $NAME --user $USER \
--retry=TERM/30/KILL/5
}
stop_fs_children() {
start-stop-daemon --stop --quiet \
--exec $DAEMON \
--oknodo --retry=0/30/KILL/5
}
do_stop() {
stop_fs
RETVAL="$?"
[ "$RETVAL" -eq 2 ] && return 2
stop_fs_children
[ "$?" -eq 2 ] && return 2
rm -f $PIDFILE
return "$RETVAL"
}
do_reload() {
start-stop-daemon --stop --quiet \
--pidfile $PIDFILE --name $NAME --user $USER \
--signal HUP
}
case "$1" in
start)
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC " "$NAME"
do_start
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
stop)
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
do_stop
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
status)
status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
;;
reload|force-reload)
log_daemon_msg "Reloading $DESC" "$NAME"
do_reload
log_end_msg $?
;;
restart)
log_daemon_msg "Restarting $DESC" "$NAME"
do_stop
case "$?" in
0|1)
do_start
case "$?" in
0) log_end_msg 0 ;;
1|*) log_end_msg 1 ;;
esac
;;
*) log_end_msg 1 ;;
esac
;;
*)
echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
exit 3
;;
esac
exit 0