first commit
This commit is contained in:
		
						commit
						b7ff98764b
					
				
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
config.json
 | 
			
		||||
							
								
								
									
										19
									
								
								License.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								License.txt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
Copyright (c) 2021 Mr. Gecko's Media (James Coleman). http://mrgeckosmedia.com/
 | 
			
		||||
 | 
			
		||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
in the Software without restriction, including without limitation the rights
 | 
			
		||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
furnished to do so, subject to the following conditions:
 | 
			
		||||
 | 
			
		||||
The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
copies or substantial portions of the Software.
 | 
			
		||||
 | 
			
		||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
SOFTWARE.
 | 
			
		||||
							
								
								
									
										21
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
# Asterisk Outgoing Call API
 | 
			
		||||
 | 
			
		||||
Starts an outgoing call based on provided paramters according to the https://wiki.asterisk.org/wiki/display/AST/Asterisk+Call+Files call file format. View https://www.voip-info.org/asterisk-auto-dial-out/ for more details about the call file format. I created this to make my phone ring when my cell phone rings via a tasker profile. Should be useful for many additional things though.
 | 
			
		||||
 | 
			
		||||
# Accepted parameters
 | 
			
		||||
 | 
			
		||||
- token: The API token to authenticate with the server.
 | 
			
		||||
- channel: Channel to use for the call.
 | 
			
		||||
- caller_id: Caller ID, Please note: It may not work if you do not respect the format: CallerID: “Some Name” <1234>
 | 
			
		||||
- wait_time: Seconds to wait for an answer. Default is 45.
 | 
			
		||||
- max_retries: Number of retries before failing (not including the initial attempt, e.g. 0 = total of 1 attempt to make the call). Default is 0.
 | 
			
		||||
- retry_time: Seconds between retries, Don’t hammer an unavailable phone. The default is 300 (5 min).
 | 
			
		||||
- account: Set the account code to use.
 | 
			
		||||
- application: Asterisk Application to run (use instead of specifying context, extension and priority).
 | 
			
		||||
- data: The options to be passed to application.
 | 
			
		||||
- context: Context in extensions.conf
 | 
			
		||||
- extension: Extension definition in extensions.conf
 | 
			
		||||
- priority: Priority of extension to start with.
 | 
			
		||||
- set_var: Set of variables to set in url query format.
 | 
			
		||||
- archive: Yes/No – Move to subdir “outgoing_done” with “Status: value”, where value can be Completed, Expired or Failed.
 | 
			
		||||
- schedule: Schedule call for a later date/time. Can be natrual language input as parsed by https://github.com/olebedev/when 
 | 
			
		||||
							
								
								
									
										87
									
								
								config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								config.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,87 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"log"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/user"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Reference from:
 | 
			
		||||
// https://wiki.asterisk.org/wiki/display/AST/Asterisk+Call+Files
 | 
			
		||||
 | 
			
		||||
// Config all configurations for this application.
 | 
			
		||||
type Config struct {
 | 
			
		||||
	HTTPBind              string            `json:"http_bind"`
 | 
			
		||||
	HTTPPort              uint              `json:"http_port"`
 | 
			
		||||
	HTTPDebug             bool              `json:"http_debug"`
 | 
			
		||||
	HTTPSystemDSocket     bool              `json:"http_systemd_socket"`
 | 
			
		||||
	AsteriskSpoolDir      string            `json:"asterisk_spool_dir"`
 | 
			
		||||
	DefaultChannel        string            `json:"default_channel"`
 | 
			
		||||
	DefaultCallerId       string            `json:"default_caller_id"`
 | 
			
		||||
	DefaultWaitTime       uint64            `json:"default_wait_time"` // 5 seconds per ring.
 | 
			
		||||
	DefaultMaxRetries     uint64            `json:"default_max_retries"`
 | 
			
		||||
	DefaultRetryTime      uint64            `json:"default_retry_time"`
 | 
			
		||||
	DefaultAccount        string            `json:"default_account"`
 | 
			
		||||
	DefaultApplication    string            `json:"default_application"`
 | 
			
		||||
	DefaultData           string            `json:"default_data"`
 | 
			
		||||
	PreventAPIApplication bool              `json:"prevent_api_application"` // For security, prevent applications from being executed via API call.
 | 
			
		||||
	DefaultContext        string            `json:"default_context"`
 | 
			
		||||
	DefaultExtension      string            `json:"default_extension"`
 | 
			
		||||
	DefaultPriority       string            `json:"default_priority"`
 | 
			
		||||
	DefaultSetVar         map[string]string `json:"default_set_var"`
 | 
			
		||||
	DefaultArchive        bool              `json:"default_archive"`
 | 
			
		||||
	APIToken              string            `json:"api_token"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReadConfig read the configuration file into the config structure of the app.
 | 
			
		||||
func (a *App) ReadConfig() {
 | 
			
		||||
	// Get our current user for use in determining the home path.
 | 
			
		||||
	usr, err := user.Current()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Different configuration file paths.
 | 
			
		||||
	localConfig, _ := filepath.Abs("./config.json")
 | 
			
		||||
	homeDirConfig := usr.HomeDir + "/.config/asterisk-outgoing-call-api/config.json"
 | 
			
		||||
	etcConfig := "/etc/asterisk/outgoing-call-api.json"
 | 
			
		||||
 | 
			
		||||
	// Store defaults first.
 | 
			
		||||
	app.config = Config{
 | 
			
		||||
		HTTPPort:              9747,
 | 
			
		||||
		HTTPDebug:             false,
 | 
			
		||||
		HTTPSystemDSocket:     false,
 | 
			
		||||
		AsteriskSpoolDir:      "/var/spool/asterisk",
 | 
			
		||||
		PreventAPIApplication: true,
 | 
			
		||||
		DefaultArchive:        false,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Determine which config file to use.
 | 
			
		||||
	var configFile string
 | 
			
		||||
	if _, err := os.Stat(app.flags.ConfigPath); err == nil && app.flags.ConfigPath != "" {
 | 
			
		||||
		configFile = app.flags.ConfigPath
 | 
			
		||||
	} else if _, err := os.Stat(localConfig); err == nil {
 | 
			
		||||
		configFile = localConfig
 | 
			
		||||
	} else if _, err := os.Stat(homeDirConfig); err == nil {
 | 
			
		||||
		configFile = homeDirConfig
 | 
			
		||||
	} else if _, err := os.Stat(etcConfig); err == nil {
 | 
			
		||||
		configFile = etcConfig
 | 
			
		||||
	} else {
 | 
			
		||||
		log.Fatal("Unable to find a configuration file.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Read the config file.
 | 
			
		||||
	jsonFile, err := ioutil.ReadFile(configFile)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatalf("Error reading JSON file: %v\n", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Parse the config file into the configuration structure.
 | 
			
		||||
	err = json.Unmarshal(jsonFile, &app.config)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatalf("Error parsing JSON file: %v\n", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										6
									
								
								example.call
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								example.call
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
			
		||||
Channel: pjsip/103
 | 
			
		||||
WaitTime: 15
 | 
			
		||||
Context: Phone-Ring-Dummy-Answer
 | 
			
		||||
Extension: talk
 | 
			
		||||
Priority: 1
 | 
			
		||||
Archive: no
 | 
			
		||||
							
								
								
									
										4
									
								
								extension.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								extension.conf
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,4 @@
 | 
			
		||||
[Phone-Ring-Dummy-Answer]
 | 
			
		||||
exten => talk,1,Answer()
 | 
			
		||||
 same => n,Playback(all-your-base)
 | 
			
		||||
 same => n,Hangup()
 | 
			
		||||
							
								
								
									
										45
									
								
								flags.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								flags.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,45 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Flags are command line tick options.
 | 
			
		||||
type Flags struct {
 | 
			
		||||
	ConfigPath string
 | 
			
		||||
	HTTPBind   string
 | 
			
		||||
	HTTPPort   uint
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Init configures the golang flags and parses the command line provided options.
 | 
			
		||||
func (f *Flags) Init() {
 | 
			
		||||
	flag.Usage = func() {
 | 
			
		||||
		fmt.Printf("asterisk-outgoing-call-api: Make an outgoing call via an API call.\n\nUsage:\n")
 | 
			
		||||
		flag.PrintDefaults()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var printVersion bool
 | 
			
		||||
	flag.BoolVar(&printVersion, "v", false, "Print version")
 | 
			
		||||
 | 
			
		||||
	var usage string
 | 
			
		||||
	usage = "Load configuration from file."
 | 
			
		||||
	flag.StringVar(&f.ConfigPath, "config", "", usage)
 | 
			
		||||
	flag.StringVar(&f.ConfigPath, "c", "", usage+" (shorthand)")
 | 
			
		||||
 | 
			
		||||
	usage = "Bind address for http server"
 | 
			
		||||
	flag.StringVar(&f.HTTPBind, "http-bind", "", usage)
 | 
			
		||||
	flag.StringVar(&f.HTTPBind, "b", "", usage+" (shorthand)")
 | 
			
		||||
 | 
			
		||||
	usage = "Bind port for http server"
 | 
			
		||||
	flag.UintVar(&f.HTTPPort, "http-port", 0, usage)
 | 
			
		||||
	flag.UintVar(&f.HTTPPort, "p", 0, usage+" (shorthand)")
 | 
			
		||||
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
 | 
			
		||||
	if printVersion {
 | 
			
		||||
		fmt.Println("asterisk-outgoing-call-api: 0.1")
 | 
			
		||||
		os.Exit(0)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
module github.com/GRMrGecko/asterisk-outgoing-call-api
 | 
			
		||||
 | 
			
		||||
go 1.16
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
 | 
			
		||||
	github.com/olebedev/when v0.0.0-20190311101825-c3b538a97254
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										16
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
			
		||||
github.com/AlekSi/pointer v1.0.0 h1:KWCWzsvFxNLcmM5XmiqHsGTTsuwZMsLFwWF9Y+//bNE=
 | 
			
		||||
github.com/AlekSi/pointer v1.0.0/go.mod h1:1kjywbfcPFCmncIxtk6fIEub6LKrfMz3gc5QKVOSOA8=
 | 
			
		||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
 | 
			
		||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/olebedev/when v0.0.0-20190311101825-c3b538a97254 h1:JYoQR67E1vv1WGoeW8DkdFs7vrIEe/5wP+qJItd5tUE=
 | 
			
		||||
github.com/olebedev/when v0.0.0-20190311101825-c3b538a97254/go.mod h1:DPucAeQGDPUzYUt+NaWw6qsF5SFapWWToxEiVDh2aV0=
 | 
			
		||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
 | 
			
		||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
			
		||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
			
		||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
 | 
			
		||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 | 
			
		||||
							
								
								
									
										357
									
								
								http.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										357
									
								
								http.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,357 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"math/rand"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/signal"
 | 
			
		||||
	"path"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"syscall"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/coreos/go-systemd/activation"
 | 
			
		||||
	"github.com/olebedev/when"
 | 
			
		||||
	"github.com/olebedev/when/rules/common"
 | 
			
		||||
	"github.com/olebedev/when/rules/en"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// HTTPServer the http server structure.
 | 
			
		||||
type HTTPServer struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Common strings.
 | 
			
		||||
const (
 | 
			
		||||
	APIOK  = "ok"
 | 
			
		||||
	APIERR = "error"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// APIGeneralResp General response to API requests.
 | 
			
		||||
type APIGeneralResp struct {
 | 
			
		||||
	Status string `json:"status"`
 | 
			
		||||
	Error  string `json:"error"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// JSONResponse Takes a golang structure and converts it to a JSON object for response.
 | 
			
		||||
func (s *HTTPServer) JSONResponse(w http.ResponseWriter, resp interface{}) {
 | 
			
		||||
	// Encode response as json.
 | 
			
		||||
	js, err := json.Marshal(resp)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		// Error should not happen normally...
 | 
			
		||||
		http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If no err, we can set content type header and send response.
 | 
			
		||||
	w.Header().Set("Content-Type", "application/json")
 | 
			
		||||
	w.Write(js)
 | 
			
		||||
	w.Write([]byte{'\n'})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// APISendGeneralResp Send a standard response.
 | 
			
		||||
func (s *HTTPServer) APISendGeneralResp(w http.ResponseWriter, status, err string) {
 | 
			
		||||
	resp := APIGeneralResp{}
 | 
			
		||||
	resp.Status = status
 | 
			
		||||
	resp.Error = err
 | 
			
		||||
	s.JSONResponse(w, resp)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// registerHandlers HTTP server handlers.
 | 
			
		||||
func (s *HTTPServer) registerHandlers(r *http.ServeMux) {
 | 
			
		||||
	// For this project, we only handle requests to /.
 | 
			
		||||
	r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		// Parse form data.
 | 
			
		||||
		err := r.ParseMultipartForm(32 << 20)
 | 
			
		||||
		if err == http.ErrNotMultipart {
 | 
			
		||||
			err = r.ParseForm()
 | 
			
		||||
		}
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Println(err)
 | 
			
		||||
			s.APISendGeneralResp(w, APIERR, "Bad request")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Verify we are authorized.
 | 
			
		||||
		if r.Form.Get("token") != app.config.APIToken {
 | 
			
		||||
			s.APISendGeneralResp(w, APIERR, "Unauthorized")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Get call details.
 | 
			
		||||
		channel := r.Form.Get("channel")
 | 
			
		||||
		if channel == "" {
 | 
			
		||||
			channel = app.config.DefaultChannel
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		callerId := r.Form.Get("caller_id")
 | 
			
		||||
		if callerId == "" {
 | 
			
		||||
			callerId = app.config.DefaultCallerId
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		waitTime, err := strconv.ParseUint(r.Form.Get("wait_time"), 10, 64)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			waitTime = app.config.DefaultWaitTime
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		maxRetries, err := strconv.ParseUint(r.Form.Get("max_retries"), 10, 64)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			maxRetries = app.config.DefaultMaxRetries
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		retryTime, err := strconv.ParseUint(r.Form.Get("retry_time"), 10, 64)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			retryTime = app.config.DefaultRetryTime
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		account := r.Form.Get("account")
 | 
			
		||||
		if account == "" {
 | 
			
		||||
			account = app.config.DefaultCallerId
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		application := r.Form.Get("application")
 | 
			
		||||
		if application == "" || app.config.PreventAPIApplication {
 | 
			
		||||
			application = app.config.DefaultApplication
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		data := r.Form.Get("data")
 | 
			
		||||
		if data == "" || app.config.PreventAPIApplication {
 | 
			
		||||
			data = app.config.DefaultData
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		context := r.Form.Get("context")
 | 
			
		||||
		if context == "" {
 | 
			
		||||
			context = app.config.DefaultContext
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		extension := r.Form.Get("extension")
 | 
			
		||||
		if context == "" {
 | 
			
		||||
			extension = app.config.DefaultExtension
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		priority := r.Form.Get("priority")
 | 
			
		||||
		if context == "" {
 | 
			
		||||
			priority = app.config.DefaultPriority
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		setVar := make(map[string]string)
 | 
			
		||||
		parsed, err := url.ParseQuery(r.Form.Get("set_var"))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			setVar = app.config.DefaultSetVar
 | 
			
		||||
		} else {
 | 
			
		||||
			for key, value := range parsed {
 | 
			
		||||
				setVar[key] = value[0]
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		archiveVal := strings.ToLower(r.Form.Get("archive"))
 | 
			
		||||
		archive := false
 | 
			
		||||
		if archiveVal == "true" || archiveVal == "yes" {
 | 
			
		||||
			archive = true
 | 
			
		||||
		} else if archiveVal != "false" && archiveVal != "no" {
 | 
			
		||||
			archive = app.config.DefaultArchive
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		schedule := r.Form.Get("schedule")
 | 
			
		||||
 | 
			
		||||
		if channel == "" || (application == "" && context == "") {
 | 
			
		||||
			s.APISendGeneralResp(w, APIERR, "Required options not set")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Setup call file details.
 | 
			
		||||
		outgoingCallName := "outgoing-call-" + strconv.Itoa(rand.Int())
 | 
			
		||||
		spoolFileName := path.Join(app.config.AsteriskSpoolDir, outgoingCallName)
 | 
			
		||||
		outgoingFileName := path.Join(app.config.AsteriskSpoolDir, "outgoing", outgoingCallName)
 | 
			
		||||
 | 
			
		||||
		callFile, err := os.Create(spoolFileName)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Println(err)
 | 
			
		||||
			s.APISendGeneralResp(w, APIERR, "Unable to create call file")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Write call details.
 | 
			
		||||
		callFile.WriteString("Channel: " + channel + "\n")
 | 
			
		||||
 | 
			
		||||
		if callerId != "" {
 | 
			
		||||
			callFile.WriteString("Callerid: " + callerId + "\n")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if waitTime != 0 {
 | 
			
		||||
			callFile.WriteString("WaitTime: " + strconv.FormatUint(waitTime, 10) + "\n")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if maxRetries != 0 {
 | 
			
		||||
			callFile.WriteString("MaxRetries: " + strconv.FormatUint(maxRetries, 10) + "\n")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if retryTime != 0 {
 | 
			
		||||
			callFile.WriteString("RetryTime: " + strconv.FormatUint(retryTime, 10) + "\n")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if account != "" {
 | 
			
		||||
			callFile.WriteString("Account: " + account + "\n")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if application != "" {
 | 
			
		||||
			callFile.WriteString("Application: " + application + "\n")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if data != "" {
 | 
			
		||||
			callFile.WriteString("Data: " + data + "\n")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if context != "" {
 | 
			
		||||
			callFile.WriteString("Context: " + context + "\n")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if extension != "" {
 | 
			
		||||
			callFile.WriteString("Extension: " + extension + "\n")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if priority != "" {
 | 
			
		||||
			callFile.WriteString("Priority: " + priority + "\n")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for key, value := range setVar {
 | 
			
		||||
			callFile.WriteString("Setvar: " + key + "=" + value + "\n")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if archive {
 | 
			
		||||
			callFile.WriteString("Archive: yes\n")
 | 
			
		||||
		} else {
 | 
			
		||||
			callFile.WriteString("Archive: no\n")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		callFile.Close()
 | 
			
		||||
 | 
			
		||||
		if schedule != "" {
 | 
			
		||||
			now := time.Now()
 | 
			
		||||
			w := when.New(nil)
 | 
			
		||||
			w.Add(en.All...)
 | 
			
		||||
			w.Add(common.All...)
 | 
			
		||||
			parsedTime, _ := w.Parse(schedule, now)
 | 
			
		||||
			if parsedTime == nil {
 | 
			
		||||
				parsedTime = new(when.Result)
 | 
			
		||||
				parsedTime.Time = now
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			os.Chtimes(spoolFileName, parsedTime.Time, parsedTime.Time)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Add call to the outgoing call queue.
 | 
			
		||||
		err = os.Rename(spoolFileName, outgoingFileName)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Println(err)
 | 
			
		||||
			s.APISendGeneralResp(w, APIERR, "Unable to move call file into outgoing directory")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Send final response.
 | 
			
		||||
		s.APISendGeneralResp(w, APIOK, "")
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func HTTPServe() {
 | 
			
		||||
	// Used to reset the app quit timeout for systemd sockets.
 | 
			
		||||
	var timeoutReset chan struct{}
 | 
			
		||||
 | 
			
		||||
	// Create the server.
 | 
			
		||||
	httpServer := new(HTTPServer)
 | 
			
		||||
	app.httpServer = httpServer
 | 
			
		||||
 | 
			
		||||
	// Setup the handlers.
 | 
			
		||||
	r := http.NewServeMux()
 | 
			
		||||
	httpServer.registerHandlers(r)
 | 
			
		||||
 | 
			
		||||
	// The http server handler will be the mux router by default.
 | 
			
		||||
	var handler http.Handler
 | 
			
		||||
	handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
 | 
			
		||||
		if app.config.HTTPSystemDSocket {
 | 
			
		||||
			timeoutReset <- struct{}{}
 | 
			
		||||
		}
 | 
			
		||||
		if app.config.HTTPDebug {
 | 
			
		||||
			log.Println(req.Method + " " + req.URL.String())
 | 
			
		||||
		}
 | 
			
		||||
		r.ServeHTTP(w, req)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// Determine if we're using a systemd socket activation or just a standard listen.
 | 
			
		||||
	if app.config.HTTPSystemDSocket {
 | 
			
		||||
		done := make(chan struct{})
 | 
			
		||||
		quit := make(chan os.Signal, 1)
 | 
			
		||||
		timeoutReset = make(chan struct{})
 | 
			
		||||
 | 
			
		||||
		// On signal, gracefully shut down the server and wait 5
 | 
			
		||||
		// seconds for current connection to stop.
 | 
			
		||||
		signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
 | 
			
		||||
 | 
			
		||||
		// Pull existing listener from systemd.
 | 
			
		||||
		listeners, err := activation.Listeners()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Panicf("Cannot retrieve listeners: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// If we already have a asterisk-outgoing-call-api running, then we shouldn't start...
 | 
			
		||||
		if len(listeners) != 1 {
 | 
			
		||||
			log.Panicf("Unexpected number of socket activation (%d != 1)", len(listeners))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		server := &http.Server{
 | 
			
		||||
			Handler: handler,
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Upon signal, close out existing connection and quit.
 | 
			
		||||
		go func() {
 | 
			
		||||
			<-quit
 | 
			
		||||
			log.Println("Server is shutting down")
 | 
			
		||||
			ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
 | 
			
		||||
			defer cancel()
 | 
			
		||||
			server.SetKeepAlivesEnabled(false)
 | 
			
		||||
			if err := server.Shutdown(ctx); err != nil {
 | 
			
		||||
				log.Panicf("Cannot gracefully shut down the server: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
			close(done)
 | 
			
		||||
		}()
 | 
			
		||||
 | 
			
		||||
		// 30 minute time out if no connection is received.
 | 
			
		||||
		go func() {
 | 
			
		||||
			for {
 | 
			
		||||
				select {
 | 
			
		||||
				case <-timeoutReset:
 | 
			
		||||
				case <-time.After(30 * time.Minute):
 | 
			
		||||
					close(quit)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}()
 | 
			
		||||
 | 
			
		||||
		// Listen on existing systemd socket.
 | 
			
		||||
		server.Serve(listeners[0])
 | 
			
		||||
 | 
			
		||||
		// Wait for existing connections befor exiting.
 | 
			
		||||
		<-done
 | 
			
		||||
	} else {
 | 
			
		||||
		// Get the configuration.
 | 
			
		||||
		httpBind := app.config.HTTPBind
 | 
			
		||||
		httpPort := app.config.HTTPPort
 | 
			
		||||
		if app.flags.HTTPBind != "" {
 | 
			
		||||
			httpBind = app.flags.HTTPBind
 | 
			
		||||
		}
 | 
			
		||||
		if app.flags.HTTPPort != 0 {
 | 
			
		||||
			httpPort = app.flags.HTTPPort
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Start the server.
 | 
			
		||||
		log.Println("Starting the http server on port", httpPort)
 | 
			
		||||
		err := http.ListenAndServe(fmt.Sprintf("%s:%d", httpBind, httpPort), handler)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"math/rand"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// App is the standard structure that allows different parts of the application to access common parameters/configuration.
 | 
			
		||||
type App struct {
 | 
			
		||||
	flags      *Flags
 | 
			
		||||
	httpServer *HTTPServer
 | 
			
		||||
	config     Config
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var app *App
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	// We use rand for file naming, best set seed at start.
 | 
			
		||||
	rand.Seed(time.Now().UnixNano())
 | 
			
		||||
 | 
			
		||||
	app = new(App)
 | 
			
		||||
	app.flags = new(Flags)
 | 
			
		||||
	app.flags.Init()
 | 
			
		||||
	app.ReadConfig()
 | 
			
		||||
 | 
			
		||||
	HTTPServe()
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user