diff options
-rw-r--r-- | INSTALL | 27 | ||||
-rw-r--r-- | Makefile | 86 | ||||
-rw-r--r-- | README | 229 | ||||
-rw-r--r-- | TODO | 15 | ||||
-rw-r--r-- | VERSIONS | 39 | ||||
-rw-r--r-- | configs/astmanproxy.conf (renamed from astmanproxy.conf) | 45 | ||||
-rw-r--r-- | configs/astmanproxy.users | 10 | ||||
-rw-r--r-- | configs/ssl.conf | 154 | ||||
-rw-r--r-- | doc/README.csv (renamed from README.csv) | 0 | ||||
-rw-r--r-- | doc/README.http (renamed from README.http) | 0 | ||||
-rw-r--r-- | doc/README.standard (renamed from README.standard) | 0 | ||||
-rw-r--r-- | doc/README.xml (renamed from README.xml) | 0 | ||||
-rw-r--r-- | http.c | 137 | ||||
-rw-r--r-- | samples/httpast2.html | 12 | ||||
-rw-r--r-- | src/astmanproxy.c (renamed from astmanproxy.c) | 121 | ||||
-rw-r--r-- | src/common.c (renamed from common.c) | 122 | ||||
-rw-r--r-- | src/config.c (renamed from config.c) | 49 | ||||
-rw-r--r-- | src/config_perms.c | 125 | ||||
-rw-r--r-- | src/csv.c (renamed from csv.c) | 7 | ||||
-rw-r--r-- | src/dlfcn.c (renamed from dlfcn.c) | 0 | ||||
-rw-r--r-- | src/http.c | 155 | ||||
-rw-r--r-- | src/include/astmanproxy.h (renamed from astmanproxy.h) | 54 | ||||
-rw-r--r-- | src/include/dlfcn-compat.h (renamed from dlfcn-compat.h) | 0 | ||||
-rw-r--r-- | src/include/endian.h | 60 | ||||
-rw-r--r-- | src/include/md5.h | 18 | ||||
-rw-r--r-- | src/include/poll-compat.h (renamed from poll-compat.h) | 0 | ||||
-rw-r--r-- | src/include/ssl.h | 89 | ||||
-rw-r--r-- | src/log.c (renamed from log.c) | 0 | ||||
-rw-r--r-- | src/md5.c | 260 | ||||
-rw-r--r-- | src/poll.c (renamed from poll.c) | 0 | ||||
-rw-r--r-- | src/proxyfunc.c (renamed from proxyfunc.c) | 160 | ||||
-rw-r--r-- | src/ssl.c | 438 | ||||
-rw-r--r-- | src/standard.c (renamed from standard.c) | 13 | ||||
-rw-r--r-- | src/xml.c (renamed from xml.c) | 11 |
34 files changed, 2045 insertions, 391 deletions
@@ -0,0 +1,27 @@ +Quick Install Instructions +-------------------------- + +Requirements: openssl, openssl-devel + +Installation is easy: + +1. make +2. make install +3. To run: + +astmanproxy -d (for debug mode) +astmanproxy -dddddd (for more debug info) +astmanproxy (for background mode) + +Other make actions: + +make cert Makes initial proxy server certificate +make certificate Forces remake of proxy server certificate) +make clean Remove all binary object files from source tree + +Please read README and docs/README.* for info on specific modules. + +Enjoy! + +-------------------------------------------- +(C) 2005-2006 David C. Troy, dave@popvox.com @@ -3,15 +3,20 @@ OSARCH=$(shell uname -s) OSREV=$(shell uname -r) -VERSION := 1.13 -DESTDIR := +VERSION := 1.20pre +DESTDIR ?= CONFDIR:=/etc/asterisk CONFDIR_REAL := $(DESTDIR)/etc/asterisk +PERMDIR:=/etc/asterisk +PERMDIR_REAL := $(DESTDIR)/etc/asterisk LIBDIR := $(DESTDIR)/usr/lib/astmanproxy CONFFILE := astmanproxy.conf +PERMFILE := astmanproxy.users DISTDIR := /var/www/html/astmanproxy - +CERTDIR := /var/lib/asterisk/certs +PROXYCERT := $(CERTDIR)/proxy-server.pem +PROXYSSLCONF := $(CONFDIR)/proxy-ssl.conf CC := gcc #LIBS := -lpthread @@ -20,25 +25,27 @@ PREFIX:= /usr/local BINDIR := $(DESTDIR)$(PREFIX)/sbin # For compilation dependencies -MODS := astmanproxy config common proxyfunc log +MODS := astmanproxy config config_perms common proxyfunc log ssl md5 HANDLERS := xml standard csv http SOBJS := $(HANDLERS:%=%.so) +LIBS := -lssl ifeq (${OSARCH},Darwin) - LIBS=-lresolv + LIBS+=-lresolv CFLAGS+=-D__Darwin_ -# -DFINK_BUILD BINDIR=/opt/sbin LIBDIR=/opt/lib/astmanproxy CONFDIR=/opt/etc/asterisk CONFDIR_REAL=/opt/etc/asterisk + PERMDIR=/opt/etc/asterisk + PERMDIR_REAL=/opt/etc/asterisk LOGDIR=/opt/log/asterisk OBJS+=dlfcn.o poll.o ASTLINK=-Wl,-force_flat_namespace,-dynamic SOLINK=-dynamic -bundle -undefined suppress -force_flat_namespace else #These are used for all but Darwin - LIBS=-ldl -pthread + LIBS+=-ldl -pthread ASTLINK=-Wl,-E SOLINK=-shared -Xlinker -x LOGDIR=/var/log/asterisk @@ -47,17 +54,19 @@ endif OBJS += $(MODS:%=%.o) MODDIR := $(LIBDIR)/modules CONF_TARGET:= $(CONFDIR_REAL)/$(CONFFILE) -DEFINES:='-DPROXY_VERSION="$(VERSION)"' '-DCDIR="$(CONFDIR)"' '-DCFILE="$(CONFFILE)"' '-DMDIR="$(MODDIR)"' +PERM_TARGET:= $(PERMDIR_REAL)/$(PERMFILE) +DEFINES:='-DPROXY_VERSION="$(VERSION)"' '-DCDIR="$(CONFDIR)"' '-DCFILE="$(CONFFILE)"' +DEFINES+='-DMDIR="$(MODDIR)"' '-DPDIR="$(PERMDIR)"' '-DPFILE="$(PERMFILE)"' +VPATH = src # Add -g below for debug/GDB symbols -#CFLAGS+= $(DEFINES) -Wall -O2 -dynamic -D_REENTRANT -fPIC -CFLAGS+= $(DEFINES) -Wall -O2 -D_REENTRANT -fPIC +CFLAGS+= $(DEFINES) -Wall -O2 -D_REENTRANT -fPIC -Isrc/include -I/usr/include/openssl -I- # For printing only SRCS := $(MODS:%=%.c) HDRS := astmanproxy.h -all: astmanproxy +all: astmanproxy cert astmanproxy: $(OBJS) $(SOBJS) $(CC) $(CFLAGS) -o $@ $(ASTLINK) $(OBJS) $(LIBS) @@ -68,6 +77,56 @@ $(OBJS): %.o: %.c $(SOBJS): %.so: %.o $(CC) $(SOLINK) $< -o $@ +SERIAL=`date "+%Y%m%d%H%M%S"` + +cert: + if [ ! -f $(PROXYCERT) ]; then \ + umask 77 ; \ + PEM1=`/bin/mktemp /tmp/openssl.XXXXXX` ; \ + PEM2=`/bin/mktemp /tmp/openssl.XXXXXX` ; \ + if [ ! -f $(PROXYSSLCONF) ]; then \ + install ./configs/ssl.conf $(PROXYSSLCONF); \ + fi; \ + /usr/bin/openssl req $(UTF8) -newkey rsa:1024 -keyout $$PEM1 -nodes -x509 -days 365 -out $$PEM2 -set_serial $(SERIAL) -config $(PROXYSSLCONF) ; \ + mkdir -p $(CERTDIR); \ + cat $$PEM1 > $(PROXYCERT) ; \ + echo "" >> $(PROXYCERT) ; \ + cat $$PEM2 >> $(PROXYCERT) ; \ + rm $$PEM1 $$PEM2; \ + fi + +certificate: + createcert="1"; \ + if [ -f $(PROXYCERT) ]; then \ + echo -n "The certificate already exists, Do you really want to create new one(yes/no)?"; \ + read answer; \ + if [ "$$answer" = "yes" ]; then \ + echo "I am creating a new certificate, Old one is copied as server.pem.old ";\ + sudo cp /var/lib/asterisk/certs/server.pem /var/lib/asterisk/certs/server.pem.old; \ + elif [ "$$answer" = "no" ]; then \ + echo "Certificate already exists, I am not creating a new certificate,";\ + createcert="0"; \ + else \ + echo "You need to enter either yes or no"; \ + createcert="0"; \ + fi; \ + fi; \ + if [ "$$createcert" = "1" ]; then \ + umask 77 ; \ + PEM1=`/bin/mktemp /tmp/openssl.XXXXXX` ; \ + PEM2=`/bin/mktemp /tmp/openssl.XXXXXX` ; \ + if [ ! -f $(PROXYSSLCONF) ]; then \ + install ./configs/ssl.conf $(PROXYSSLCONF); \ + fi; \ + /usr/bin/openssl req $(UTF8) -newkey rsa:1024 -keyout $$PEM1 -nodes -x509 -days 365 -out $$PEM2 -set_serial $(SERIAL) -config $(PROXYSSLCONF) ; \ + mkdir -p $(CERTDIR); \ + cat $$PEM1 > $(PROXYCERT) ; \ + echo "" >> $(PROXYCERT) ; \ + cat $$PEM2 >> $(PROXYCERT) ; \ + rm $$PEM1 $$PEM2; \ + fi + + install: uninstall all install -d $(BINDIR) install astmanproxy $(BINDIR) @@ -78,6 +137,9 @@ install: uninstall all if [ ! -f $(CONF_TARGET) ]; then \ install $(CONFFILE) $(CONF_TARGET); \ fi + if [ ! -f $(PERM_TARGET) ]; then \ + install $(PERMFILE) $(PERM_TARGET); \ + fi @echo "Installation Complete!" uninstall: @@ -92,7 +154,7 @@ dist: clean /usr/bin/ssh www.popvox.com "ln -sf $(DISTDIR)/astmanproxy-${VERSION}-*.tgz $(DISTDIR)/astmanproxy-latest.tgz" clean: - rm -f *.o *.so core *~ astmanproxy; + rm -f *.o *.so core *~ astmanproxy proxy-server.pem; print: more Makefile $(HDRS) $(SRCS) | enscript -Ec -2r -j; exit 0 @@ -1,9 +1,10 @@ -astmanproxy README (c) 2005 David C. Troy +astmanproxy README +(c) 2005-2006 David C. Troy, dave@popvox.com ------------------------------------------------------------------ FOREWORD & QUICK START The need for a proxy to Asterisk's manager interface has been -clear; almost all GUI's and other interfaces to asterisk implement a +clear; almost all GUIs and other interfaces to asterisk implement a proxy of some kind. Why? A proxy offers: - A single persistent connection to asterisk @@ -12,13 +13,15 @@ proxy of some kind. Why? A proxy offers: - Less connections and networking load for asterisk This proxy began as the the perl/select based "simpleproxy.pl" and -has since evolved into a full multithreaded quasi-stateful proxy -based in c/pthreads. It is now capable of serving as the basis for -an extensible application framework for multiple Asterisk servers. +has since evolved into a full multithreaded stateful proxy written +in c/pthreads. It can serve as the basis for an extensible +application framework for communication with multiple Asterisk +servers. -New features in version 1.1 include: +Features include: - Multiple Input/Output formats: HTTP, XML, CSV, and Standard + - SSL Support for clients & servers (including HTTPS clients) - API for addition of new, modular I/O formats - Ability to support communication with multiple Asterisk Servers - I/O Formats selectable on a per-client basis @@ -26,9 +29,10 @@ New features in version 1.1 include: For example, you can use Astmanproxy as a single point of contact to communicate with multiple Asterisk servers. -You can use Astmanproxy as the basis for a web-based application: -send it data using HTTP POST or HTTP GET, and receive XML output. -No web server required! +You can use Astmanproxy as the basis for a web-based application: +send it data using HTTP POST or HTTP GET, and receive XML output. +Or use HTTP POST and get Standard (text/plain) output back! +Astmanproxy speaks HTTP internally, so no web server is required! You can use Astmanproxy as an XML feed for a .NET program that keeps track of Asterisk's state. Or as an interface for injecting quick @@ -42,50 +46,37 @@ To get started quickly, simply: Edit the configuration file: vi /etc/asterisk/astmanproxy.conf +Optionally edit the other config files: + vi /etc/asterisk/astmanproxy.users + vi /etc/asterisk/ssl.conf + Start the program: astmanproxy To view debug output, start astmanproxy in debug mode: astmanproxy -d +For more debug output, add more -d's: + astmanproxy -ddddddd + You may want to start astmanproxy at boot. In that case, you might place it in /etc/rc.d/rc.local: /usr/local/sbin/astmanproxy Please send your feedback! We are looking for contributors to add -support for new I/O formats and to help debug the more complicated -HTTP and XML IO Handlers! +support for new I/O formats and add new features! Contributions: Paypal via dave@toad.net; beer accepted at Astricon events =================================================================== -INSTALLATION: - - make - make install - -The default configuration file location is -/etc/asterisk/astmanproxy.conf, and that file will be automatically -created upon installation if it does not already exist. - -TO RUN: - -Launch from /etc/rc.d/rc.local or startup script. - -/usr/local/sbin/astmanproxy (or whatever your path is) - -Launch with no options to run as daemon. To debug, use option "-d", -and to display version information, use option "-v". - -=================================================================== Additional Proxy Features In addition to exposing the entire Asterisk Manager API as a -pass-through, non-interpreting proxy, 'astmanproxy' can parse client -input where desired; this could be used in the future to add new -features that make sense to be in a proxy but that don't necessarily -need to be in Asterisk proper. +pass-through, non-interpreting proxy, 'astmanproxy' can parse client +input where desired; this could be used in the future to add new +features that should exist in a proxy but that don't +necessarily need to be in Asterisk proper. There are some proxy-specific headers that you can specify in your applications now: @@ -134,18 +125,34 @@ Server: (x.x.x.x|hostname) should exactly match the entry in your config host= section, or whatever name you used with ProxyAction: AddServer. +ProxyKey: secret +Action: Originate +... +ActionID: ... + You can use this as a simple authentication mechanism. + Rather than have to login with a username & password, + you can specify a ProxyKey that must be passed from + a client before requests are processed. This is helpful + in situations where you would like to authenticate and + execute an action in a single step. See the sample + config file for more information. + The proxy also intercepts the following Actions: Action: Login - Since the proxy logs in on behalf of your clients, and you are - using key-based or network-level security to secure the proxy, - logins should not be passed to asterisk. Ideally, your apps - will be written not to issue Logins in the first place, but - if you do, the proxy will intercept them and NOT pass them - to Asterisk. Instead, it will respond with an Authentication - Successful message, just as Asterisk would upon successful - authentication. Note it will do this regardless of information - supplied. + You can login to astmanproxy just as you would the Asterisk + Manager Interface. The user credentials are stored in + astmanproxy.users. + +Action: Challenge + Astmanproxy now supports the MD5 challenge authentication + mechanism. See section below for more information on + this authentication mechanism and how you can use it + in your applications to avoid having to send a password + over the internet, and instead use a MD5 challenge to + hash your password before sending. Note that this is + somewhat less of an issue with SSL support now enabled, + however, some apps require this mechanism, and we support it. Action: Logoff You don't want your applications logging the proxy off of @@ -160,23 +167,139 @@ Blank Commands blank command blocks. =================================================================== -Author Contact Info +On the 'Action: Challenge' Authentication Mechanism + +John Todd wrote this excellent summary of the Action: Challenge +Authentication Mechanism, and it accurately describes the +implementation included in astmanproxy: + +While the SSL encryption of the AMI is great, it's always a good +policy to never send passwords at all if you have an alternative. + + After connecting to the AMI port, send this message: + + Action: Challenge + AuthType: MD5 + + You should receive a challenge string: + + Response: Success + Challenge: 125065091 + +Then, assuming that the manager username is "joebob" and the +password is "yoyodyne11", perform this on a shell line of a handy +UNIX system (you programmers will figure out how to do this with a +library call, I'm sure): + + bash-3.00# md5 -s 125065091yoyodyne11 + MD5 ("125065091yoyodyne11") = e83a9e59e7c8d1bb6554982275d05016 + bash-3.00# + + Now use this key to log in, so type this to the AMI: + + Action: Login + AuthType: MD5 + Username: joebob + Key: e83a9e59e7c8d1bb6554982275d05016 + + ...and you'll get: + + Response: Success + Message: Authentication accepted + +=================================================================== +On Astmanproxy's SSL Support -This code is intended primarily as a foundation for others to add -new features and capabilities going forward. While I will attempt -to keep up with it and add to it, I make no guarantees that I'll be -able to do that. My hope is that the wonderful asterisk community -will start making changes, and that those changes can be managed -through Digium's great CVS process. +Support for SSL on the Asterisk Manager Interface has recently been +contributed to the Asterisk project (see Digium #6812). -However, you can concact me at dave@popvox.com, and I will endeavor -to post the latest code here: +This SSL implementation has been tested by several people and seems +to work fine. While it is not in a mainline Asterisk distribution +yet (in SVN Trunk only right now), it is likely that AMI will soon +support SSL natively. +I felt that it was important that Astmanproxy support the same SSL +mechanism as Asterisk; we have been talking about adding SSL/TLS +for some time. So, now it's been incorporated. + +This means you can implement scenarios like: + client <-> proxy <-> n*asterisk +with end-to-end SSL security. + +To make Astmanproxy talk to asterisk, turn on the 'usessl' option +in the server host specification (see astmanproxy.conf). + +To have Astmanproxy talk to clients via SSL, be sure to enable +'allowencryptedconnections' in the astmanproxy.conf file. + +To have Astmanproxy accept ONLY SSL connections, you should +enable 'allowencryptedconnections' and disable +'allowunencryptedconnections'. We've endeavored to use the same +configuration setting names as in manager.conf with the SSL +implementation in #6812. + +=================================================================== +Now Supports HTTPS Natively! + +One really interesting side effect of having both SSL and HTTP support +natively is that we in fact now support HTTPS! + +With the proxy configured on localhost:1234, you can do things +along these lines: + +https://localhost:1234/?Action=ShowChannels&ActionID=Foo + +This has been tested fairly extensively with good results. The +HTTP handler supports both GET and POST and can properly deal +with XML or Standard output formats. With Autofilter=on, +this paradigm is ideal for creating a simple REST-like interface +into Asterisk (even multiple boxes!) with no web servers needed. + +=================================================================== +Software Updates, Author Info, and How to Contribute + +Digium has graciously agreed to host the development of AstManProxy +on their Subversion Community Server. + +Latest releases can be found here: +http://svncommunity.digium.com/view/astmanproxy + +For development branches & experimental features: +http://svncommunity.digium.com/view/astmanproxy/branches + +For current development/stable snapshot: +http://svncommunity.digium.com/view/astmanproxy/trunk + +For stable release versions: +http://svncommunity.digium.com/view/astmanproxy/tags + +To download from these repositories: + + - Install Subversion (yum -y install subversion -- or equivalent) + - svn checkout http://svncommunity.digium.com/svn/astmanproxy/trunk + +Be sure to use the full URL path to the version you wish to check out; +for example, do not checkout the 'branches' tree, but instead choose +which branch to checkout, as in: + +http://svncommunity.digium.com/view/astmanproxy/branches/1.2x + +I will also try to post current tarballs here: http://www.popvox.com/astmanproxy -Donations are accepted via paypal to dave@toad.net. +Donations are accepted via paypal to dave@toad.net; beer is also +accepted at Astricon events. :) + +To contact me about contributing to the project, please email: +dave@popvox.com + +I acknowledge all contributions and encourage your experimentation! +AstManProxy would not be where it is without your support!! =================================================================== +AstManProxy Background Information +---------------------------------- + Developing web-based realtime applications for the asterisk open-source PBX often requires interacting with asterisk's Manager interface. The Asterisk Manager runs on port 5038 by default and @@ -1,15 +1,12 @@ -'connected' flag; do not set until we get "authentication accepted' back from server +write SOAP methods for http.c to use +clean up reconnect to lost asterisk server (socket reuse) +clean up debugmsg instances to add if (debug) +deal with http non-conformity better, and act on POST MIME-type inputs + Check for module versions; do not run without modules installed use a key? see loader.c in * State maintenance modules code formatting -tcpwrappers/access control/connect mask -tls? -better dir structure in source tree -subversion with digium +tcpwrappers/access control/connect mask? libtool/autoconf/automake support SetInputFormat proxyaction? -detect old config file fmt and puke - -kill http clients better? -snmp? @@ -1,17 +1,16 @@ -0.99a Initial Beta Release -0.99b Moved WriteXMLClient into xml.c - Fixed XML tag bug (Bryan Ballard) - Fixed ast_log instances -1.0 First major release -1.0a Added minor Makefile changes - Added Debian package support (Tzafrir Cohen) -1.0b Action: logoff (lowercase) was not intercepted properly (Steven Blatchford) - Fixed SetOutputFormat documentation error (Steven Blatchford) -1.0c Intercept empty command blocks; do not pass to Asterisk - Added call for proxyerror routine, previously unreferenced - Intercept Action: Login; always authenticates and does not pass to * -1.0d Properly intercept SIGPIPE (Brian Jones) +1.20pre Now using ast_carefulwrite for non-blocking writes to all sockets + Added asteriskwritetimeout config setting for asterisk write timeout + Added clientwritetimeout config setting for client write timeout + Added support for astmanproxy.users user authentication (Steve Davies) + Added support for Action: Challenge/AuthType: MD5 authentication + Added challenge field to mansession structure for use by Action: Challenge + Added writetimeout var to mansession structure for use by ast_carefulwrite + Added SSL Support (Remco Treffkorn, Mahesh Karoshi, John Todd; Tello Corp) + Added 'usessl' option for connecting to asterisk servers + Fixed proxykey pointer bug (Steve Davies) + Ditched autodisconnect handler property in favor of inputcomplete/outputcomplete +------------------------------------------------------------------------------------------------ 1.1pre2 Completely Modularized and Abstracted Input/Output formats Cleaned up session write mutex code (Peter Loeppky) Fixed message initialization bug -- using wrong sizeof @@ -42,3 +41,17 @@ Added support for Mac OS X (Tested on 10.3.9); BSD may also work Aborts on old config file format (detects incomplete server spec) +------------------------------------------------------------------------------------------------ +0.99a Initial Beta Release +0.99b Moved WriteXMLClient into xml.c + Fixed XML tag bug (Bryan Ballard) + Fixed ast_log instances +1.0 First major release +1.0a Added minor Makefile changes + Added Debian package support (Tzafrir Cohen) +1.0b Action: logoff (lowercase) was not intercepted properly (Steven Blatchford) + Fixed SetOutputFormat documentation error (Steven Blatchford) +1.0c Intercept empty command blocks; do not pass to Asterisk + Added call for proxyerror routine, previously unreferenced + Intercept Action: Login; always authenticates and does not pass to * +1.0d Properly intercept SIGPIPE (Brian Jones) diff --git a/astmanproxy.conf b/configs/astmanproxy.conf index ec19621..be28edf 100644 --- a/astmanproxy.conf +++ b/configs/astmanproxy.conf @@ -1,10 +1,12 @@ ; astmanproxy.conf ; Asterisk Manager Proxy Configuration Sample -; (C) 2005 David C. Troy +; (C) 2005-2006 David C. Troy - dave@popvox.com ; List of asterisk host(s) you want to proxy -; host = ip_addr, port, user, secret, events -host = localhost, 5038, dave, moo, on +; host = ip_addr, port, user, secret, events, use_ssl +host = localhost, 5038, dave, moo, on, off + +;host = 192.168.1.173, 5038, dave, moo, on, on ;host = 127.0.0.2, 5038, user, secret, on ;host = otherhost, 5038, user, secret, on ;host = newhost, 5030, user, secret, off @@ -19,6 +21,24 @@ retryinterval = 2 ; use 0 for infinitely, or some finite number maxretries = 10 +; How long do we wait on the manager port for an SSL session start? (ms) +sslclienthellotimeout = 200 + +; Do we accept encrypted SSL manager connections? +acceptencryptedconnection = yes + +; Do we accept unencrypted manager connections? +acceptunencryptedconnection = yes + +; Amount of time to wait before timing out on writes to asterisk +asteriskwritetimeout=100 + +; Amount of time to wait before timing out on writes to clients +clientwritetimeout=200 + +; Our server-side SSL certificate; what we use when answering clients +certfile = /var/lib/asterisk/certs/proxy-server.pem + ; Address for proxy to listen on, can be set to * or x.x.x.x format ; recommend that you listen only on 127.0.0.1 or on an interface that ; is otherwise locked down to a trusted host, since the proxy @@ -28,6 +48,10 @@ listenaddress = * ; Port for proxy to listen on listenport = 1234 +; Do we require authentication (either proxykey or astmanproxy.users entry)? +; See README and astmanproxy.users for more info +authrequired = no + ; Setting a proxy key requires proxy client connections to ; specify a ProxyKey: keyvalue header in the first incoming request ; to the proxy. Once this is done the client remains authenticated. @@ -37,7 +61,7 @@ listenport = 1234 ; in place and well understood. ; proxykey = foobar -; user and group for proxy to run as; will NOT run as root! +; local user and group for proxy to run as; will NOT run as root! proc_user = nobody proc_group = nobody @@ -47,6 +71,15 @@ proc_group = nobody inputformat = standard outputformat = standard +; to enable REST/XMLRPC-like functionality, try this combo. +; this gives you http input (POST or GET) and either +; text/xml or text/plain output with NO webserver required! +; to access: http://[host]:1234/?Action=Ping&ActionID=Foo +; +; inputformat = http +; outputfomat = xml|standard +; autofilter = on + ; set autofilter to be on or off by default ; with autofilter on, you can automatically filter responses ; to include only messages related to a specific actionid, @@ -58,6 +91,6 @@ outputformat = standard autofilter = off ; location of logfile -- will be owned by proc_user/proc_group -;logfile = /var/log/asterisk/astmanproxy.log -logfile = /opt/log/asterisk/astmanproxy.log +;logfile = /opt/log/asterisk/astmanproxy.log +logfile = /var/log/asterisk/astmanproxy.log diff --git a/configs/astmanproxy.users b/configs/astmanproxy.users new file mode 100644 index 0000000..9e31056 --- /dev/null +++ b/configs/astmanproxy.users @@ -0,0 +1,10 @@ +; Astmanproxy user list +; +; Reload permissions by sending a SIGHUP +; +; "user" is the username, secret is the password, and the (optional) +; channel setting causes filtering of events only for the specified +; channel to be sent to this user. +; +; user=secret,channel,out_context (to Asterisk),in_context (From Asterisk) +steve=steve,SIP/snom190,local, diff --git a/configs/ssl.conf b/configs/ssl.conf new file mode 100644 index 0000000..d10d9a1 --- /dev/null +++ b/configs/ssl.conf @@ -0,0 +1,154 @@ +# Asterisk SSL configuration +# +# OpenSSL configuration file for custom Certificate Authority. Use a +# different openssl.cnf file to generate certificate signing requests; +# this one is for use only in Certificate Authority operations (csr -> +# cert, cert revocation, revocation list generation). +# +# Be sure to customize this file prior to use, e.g. the commonName and +# other options under the root_ca_distinguished_name section. + +HOME = . +RANDFILE = $ENV::HOME/.rnd + +[ ca ] +default_ca = MyAsteriskCA + +[ MyAsteriskCA ] +dir = . +# unsed at present, and my limited certs can be kept in current dir +#certs = $dir/certs +new_certs_dir = $dir/newcerts +crl_dir = $dir/crl +database = $dir/index + +certificate = $dir/ca-cert.pem +serial = $dir/serial +crl = $dir/ca-crl.pem +private_key = $dir/private/ca-key.pem +RANDFILE = $dir/private/.rand + +x509_extensions = usr_cert + +# Comment out the following two lines for the "traditional" +# (and highly broken) format. +name_opt = ca_default +cert_opt = ca_default + +default_crl_days= 30 +default_days = 7300 +# if need to be compatible with older software, use weaker md5 +default_md = sha1 +# MSIE may need following set to yes? +preserve = no + +# A few difference way of specifying how similar the request should look +# For type CA, the listed attributes must be the same, and the optional +# and supplied fields are just that :-) +policy = policy_match + +# For the CA policy +[ policy_match ] +countryName = US +stateOrProvinceName = CA +organizationName = XYZ +organizationalUnitName = XYZ +commonName = asterisk +emailAddress = root@localhost + +# For the 'anything' policy +# At this point in time, you must list all acceptable 'object' +# types. +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +#################################################################### +[ req ] +default_bits = 2048 +default_keyfile = ./private/ca-key.pem +default_md = sha1 + +prompt = no +distinguished_name = root_ca_distinguished_name + +x509_extensions = v3_ca + +# Passwords for private keys if not present they will be prompted for +# input_password = secret +# output_password = secret + +# This sets a mask for permitted string types. There are several options. +# default: PrintableString, T61String, BMPString. +# pkix : PrintableString, BMPString. +# utf8only: only UTF8Strings. +# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings). +# MASK:XXXX a literal mask value. +# WARNING: current versions of Netscape crash on BMPStrings or UTF8Strings +# so use this option with caution! +string_mask = nombstr + +# req_extensions = v3_req + +[ root_ca_distinguished_name ] +commonName = NoSuchCA CA +countryName = US +stateOrProvinceName = California +localityName = San Mateo +0.organizationName = domain.net +emailAddress = nobody@localhost + +[ usr_cert ] + +# These extensions are added when 'ca' signs a request. + +# This goes against PKIX guidelines but some CAs do it and some software +# requires this to avoid interpreting an end user certificate as a CA. + +basicConstraints=CA:FALSE + +# PKIX recommendations harmless if included in all certificates. +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer:always + +nsCaRevocationUrl = https://www.sial.org/ca-crl.pem +#nsBaseUrl +#nsRevocationUrl +#nsRenewalUrl +#nsCaPolicyUrl +#nsSslServerName + +[ v3_req ] + +# Extensions to add to a certificate request + +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +[ v3_ca ] + + +# Extensions for a typical CA + +# PKIX recommendation. +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid:always,issuer:always + +# This is what PKIX recommends but some broken software chokes on critical +# extensions. +#basicConstraints = critical,CA:true +# So we do this instead. +basicConstraints = CA:true + +[ crl_ext ] + +# CRL extensions. +# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. + +# issuerAltName=issuer:copy +authorityKeyIdentifier=keyid:always,issuer:always diff --git a/README.csv b/doc/README.csv index 5f77c09..5f77c09 100644 --- a/README.csv +++ b/doc/README.csv diff --git a/README.http b/doc/README.http index 63268a9..63268a9 100644 --- a/README.http +++ b/doc/README.http diff --git a/README.standard b/doc/README.standard index 40699d7..40699d7 100644 --- a/README.standard +++ b/doc/README.standard diff --git a/README.xml b/doc/README.xml index 0e34c97..0e34c97 100644 --- a/README.xml +++ b/doc/README.xml @@ -1,137 +0,0 @@ -/* Asterisk Manager Proxy - Copyright (c) 2005 David C. Troy <dave@popvox.com> - - This program is free software, distributed under the terms of - the GNU General Public License. - - HTTP Input Handler -*/ - -#include "astmanproxy.h" - -int ParseHTTPInput(char *buf, struct message *m) { - char *n, *v; - - /* just an empty block; go home - if ( !(*buf) ) - return 0; */ - - /* initialize message block */ - memset(m, 0, sizeof (struct message) ); - - n = buf; - while ( (v = strstr(n, "=")) ) { - v += 1; - debugmsg("n: %s, v: %s", n, v); - strncat(m->headers[m->hdrcount], n, v-n-1); - strcat(m->headers[m->hdrcount], ": "); - - if ( (n = strstr(v, "&")) ) { - n += 1; - } else { - n = (v + strlen(v) + 1); - } - strncat(m->headers[m->hdrcount], v, n-v-1); - debugmsg("got hdr: %s", m->headers[m->hdrcount]); - m->hdrcount++; - } - - return (m->hdrcount > 0); -} - -int BuildHTTPHeader(char *hdr) { - - - time_t t; - struct tm tm; - char date[80]; - - time(&t); - localtime_r(&t, &tm); - strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", &tm); - - sprintf(hdr, - "HTTP/1.1 200 OK\r\n" - "Date: %s\r\n" - "Content-Type: text/xml\r\n" - "Connection: close\r\n" - "Server: %s/%s\r\n\r\n", - date, PROXY_BANNER, PROXY_VERSION); - - return 0; -} - -int _read(struct mansession *s, struct message *m) { - - /* Note: No single line may be longer than MAX_LEN/s->inbuf, as per get_input */ - /* No HTTP Input may be longer than BUFSIZE */ - - char line[MAX_LEN], method[10], formdata[MAX_LEN], header[MAX_LEN]; - int res, clength = 0; - - if (s->inputcomplete) - return 0; - - memset(method, 0, sizeof method); - memset(formdata, 0, sizeof formdata); - - /* for http, don't do get_input forever */ - for (;;) { - memset(line, 0, sizeof line); - res = get_input(s, line); - - if (res > 0) { - if (*line == '\0' ) { - if (*method == '\0') - break; - else { - if ( !strcasecmp(method, "POST") ) { - pthread_mutex_lock(&s->lock); - strncpy(formdata, s->inbuf, clength); - /* Move remaining data back to the front */ - memmove(s->inbuf, s->inbuf + clength+1, s->inlen - clength); - s->inlen -= clength; - pthread_mutex_unlock(&s->lock); - } - debugmsg("method: %s", method); - debugmsg("clength: %d", clength); - debugmsg("formdata: %s", formdata); - debugmsg("s->inbuf: %s", s->inbuf); - debugmsg("s->inlen: %d", s->inlen); - - BuildHTTPHeader(header); - pthread_mutex_lock(&s->lock); - s->inputcomplete = 1; - write(s->fd, header, strlen(header)); - pthread_mutex_unlock(&s->lock); - debugmsg("header: %s", header); - - /* now, let's transform and copy into a standard message block */ - res = ParseHTTPInput(formdata, m); - return res; - } - } else { - debugmsg("Got http: %s", line); - /* Do meaningful things here */ - if ( !strncmp(line,"POST",4) ) { - strncpy(method, line, 4); - } else if ( !strncmp(line,"GET",3) ) { - /* GET /?Action=Ping&ActionID=Foo HTTP/1.1 */ - strncpy(method, line, 3); - memcpy(formdata, line+6, strstr(line, " HTTP")-line-6); - } else if ( !strncasecmp(line, "Content-Length: ", 16) ) { - clength = atoi(line+16); - } - } - } else if (res < 0) - return res; - } - - return -1; -} - -int _autodisconnect() { - return 1; -} - -/* We do not define a _write or _onconnect method */ diff --git a/samples/httpast2.html b/samples/httpast2.html new file mode 100644 index 0000000..66baa2e --- /dev/null +++ b/samples/httpast2.html @@ -0,0 +1,12 @@ +<HTML> +<h3>Sample Astmanproxy HTTP Input</h3> +This version uses the GET method with an HTTPS action. Be sure +you have acceptencryptedconnections=yes in astmnanproxy.conf. +<p> +<FORM ACTION="https://localhost:1234" METHOD=GET> +Server: <input type=text name=Server value="127.0.0.1"><br> +Action: <input name="Action" type="text" value="Ping"><br> +ActionID: <input name="ActionID" type="text" value="Foo"><br> +<input type=submit><br> +</FORM> +</HTML> diff --git a/astmanproxy.c b/src/astmanproxy.c index c0a8470..b84fdad 100644 --- a/astmanproxy.c +++ b/src/astmanproxy.c @@ -10,12 +10,14 @@ extern int LoadHandlers( void ); extern void ReadConfig( void ); +extern void ReadPerms( void ); extern FILE *OpenLogfile( void ); extern int SetProcUID( void ); extern void *proxyaction_do(char *proxyaction, struct message *m, struct mansession *s); -extern void *ProxyLogin(struct mansession *s); +extern void *ProxyLogin(struct mansession *s, struct message *m); extern void *ProxyLogoff(struct mansession *s); +extern int ValidateAction(struct message *m, struct mansession *s, int inbound); int ConnectAsterisk(struct mansession *s); @@ -25,6 +27,7 @@ struct iohandler *iohandlers = NULL; pthread_mutex_t sessionlock; pthread_mutex_t serverlock; +pthread_mutex_t userslock; pthread_mutex_t loglock; pthread_mutex_t debuglock; static int asock = -1; @@ -38,6 +41,8 @@ void hup(int sig) { } proxylog = OpenLogfile(); logmsg("Received HUP -- reopened log"); + ReadPerms(); + logmsg("Received HUP -- reread permissions"); } void leave(int sig) { @@ -73,7 +78,7 @@ void leave(int sig) { c->output->write(c, &cm); logmsg("Shutdown, closed client %s", ast_inet_ntoa(iabuf, sizeof(iabuf), c->sin.sin_addr)); } - close(c->fd); + close_sock(c->fd); /* close tcp & ssl socket */ pthread_mutex_destroy(&c->lock); free(c); } @@ -90,7 +95,7 @@ void leave(int sig) { if (debug) debugmsg("Closing listener socket"); - close(asock); + close_sock(asock); /* close tcp & ssl socket */ /* unload io handlers */ while (iohandlers) { @@ -115,7 +120,7 @@ void leave(int sig) { void Version( void ) { - printf("astmanproxy: Version %s, (C) David C. Troy\n", PROXY_VERSION); + printf("astmanproxy: Version %s, (C) David C. Troy 2005-2006\n", PROXY_VERSION); return; } @@ -148,8 +153,7 @@ void destroy_session(struct mansession *s) else sessions = cur->next; debugmsg("Connection closed: %s", ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr)); - if (s->fd > -1) - close(s->fd); + close_sock(s->fd); /* close tcp/ssl socket */ pthread_mutex_destroy(&s->lock); free(s); } else @@ -169,16 +173,19 @@ int WriteClients(struct message *m) { c = sessions; while (c) { - if ( !c->server && m->hdrcount>1 ) { + if ( !c->server && m->hdrcount>1 && ValidateAction(m, c, 1) ) { if (c->autofilter && c->actionid) { - actionid = astman_get_header(m, "ActionID"); + actionid = astman_get_header(m, ACTION_ID); if ( !strcmp(actionid, c->actionid) ) { c->output->write(c, m); } } else c->output->write(c, m); - if ( c->input->autodisconnect && c->input->autodisconnect() ) - close(c->fd); + if (c->inputcomplete) { + pthread_mutex_lock(&c->lock); + c->outputcomplete = 1; + pthread_mutex_unlock(&c->lock); + } } c = c->next; } @@ -220,10 +227,10 @@ int WriteAsterisk(struct message *m) { for (i=0; i<m->hdrcount; i++) { if (strcasecmp(m->headers[i], "Server:") ) { sprintf(outstring, "%s\r\n", m->headers[i]); - write(s->fd, outstring, strlen(outstring) ); + ast_carefulwrite(s->fd, outstring, strlen(outstring), s->writetimeout ); } } - write(s->fd, "\r\n", 2); + ast_carefulwrite(s->fd, "\r\n", 2, s->writetimeout); pthread_mutex_unlock(&s->lock); return 1; } @@ -250,12 +257,16 @@ void *session_do(struct mansession *s) for (;;) { /* Get a complete message block from input handler */ memset(&m, 0, sizeof(struct message) ); + if (debug > 3) + debugmsg("calling %s_read...", s->input->formatname); res = s->input->read(s, &m); + if (debug > 3) + debugmsg("%s_read result = %d", s->input->formatname, res); m.session = s; if (res > 0) { /* Check for anything that requires proxy-side processing */ - if (pc.key && !s->authenticated) { + if (pc.key[0] != '\0' && !s->authenticated) { key = astman_get_header(&m, "ProxyKey"); if (!strcmp(key, pc.key) ) { pthread_mutex_lock(&s->lock); @@ -266,19 +277,26 @@ void *session_do(struct mansession *s) } proxyaction = astman_get_header(&m, "ProxyAction"); - actionid = astman_get_header(&m, "ActionID"); + actionid = astman_get_header(&m, ACTION_ID); action = astman_get_header(&m, "Action"); if ( !strcasecmp(action, "Login") ) - ProxyLogin(s); + if (!s->authenticated) + ProxyLogin(s, &m); + else + break; else if ( !strcasecmp(action, "Logoff") ) ProxyLogoff(s); + else if ( !strcasecmp(action, "Challenge") ) + ProxyChallenge(s, &m); else if ( !(*proxyaction == '\0') ) proxyaction_do(proxyaction, &m, s); - else { + else if ( ValidateAction(&m, s, 0) ) { if ( !(*actionid == '\0') ) setactionid(actionid, &m, s); if ( !WriteAsterisk(&m) ) break; + } else { + SendError(s, "Action Filtered"); } } else if (res < 0) break; @@ -286,7 +304,7 @@ void *session_do(struct mansession *s) destroy_session(s); if (debug) - debugmsg("Exiting session_do thread"); + debugmsg("--- exiting session_do thread ---"); pthread_exit(NULL); return NULL; } @@ -329,10 +347,9 @@ void *HandleAsterisk(struct mansession *s) if (!WriteClients(&m)) break; - /* TODO: does it make any sense to abort * conn if we cannot write to clients? I don't think so. - Do we even get a return code back that means anything? I don't think so. */ } else if (res < 0) { - if (debug) + /* TODO: do we need to do more than this here? or something different? */ + if ( debug ) debugmsg("asterisk@%s: Not connected", ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr)); if ( ConnectAsterisk(s) ) break; @@ -359,8 +376,8 @@ int ConnectAsterisk(struct mansession *s) { s->connected = 0; if (debug) - debugmsg("asterisk@%s: Connecting (u=%s, p=%s)", ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr), - s->server->ast_user, s->server->ast_pass); + debugmsg("asterisk@%s: Connecting (u=%s, p=%s, ssl=%s)", ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr), + s->server->ast_user, s->server->ast_pass, s->server->use_ssl ? "on" : "off"); /* Construct auth message just once */ memset( &m, 0, sizeof(struct message) ); @@ -370,15 +387,10 @@ int ConnectAsterisk(struct mansession *s) { AddHeader(&m, "Events: %s", s->server->ast_events); for ( ;; ) { - if ( connect_nonb(s->fd, (struct sockaddr *) &s->sin, sizeof(s->sin), 2) < 0 ) { - if (errno == EISCONN) { - pthread_mutex_lock(&s->lock); - s->fd = socket(AF_INET, SOCK_STREAM, 0); - pthread_mutex_unlock(&s->lock); - } + if ( ast_connect(s) == -1 ) { if (debug) - debugmsg("asterisk@%s: Connect failed, Retrying (%d) %s", - ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr), r, strerror(errno) ); + debugmsg("asterisk@%s: Connect failed, Retrying (%d) %s [%d]", + ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr), r, strerror(errno), errno ); if (pc.maxretries && (++r>pc.maxretries) ) { res = 1; break; @@ -442,7 +454,6 @@ int StartServer(struct ast_server *srv) { debugmsg("Set %s output format to %s", ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr), s->output->formatname); } - if (pthread_create(&s->t, &attr, (void *)HandleAsterisk, s)) destroy_session(s); else @@ -481,14 +492,13 @@ int SetIOHandlers(struct mansession *s, char *ifmt, char *ofmt) io = io->next; } + /* set default handlers if non match was found */ if (!s->output) { - /* TODO: Output debug that default/first handler was used */ s->output = iohandlers; res = 1; } if (!s->input) { - /* TODO: Output debug that default/first handler was used */ s->input = iohandlers; res = 1; } @@ -508,6 +518,7 @@ static void *accept_thread() int flags; pthread_attr_t attr; char iabuf[INET_ADDRSTRLEN]; + int is_encrypted; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); @@ -525,6 +536,36 @@ static void *accept_thread() logmsg("Failed to set listener tcp connection to TCP_NODELAY mode: %s\n", strerror(errno)); } } + + /* SSL stuff below */ + is_encrypted = is_encrypt_request(pc.sslclhellotimeout, as); + debugmsg("is_encrypted: %d", is_encrypted); + if (is_encrypted > 0) { + if (!pc.acceptencryptedconnection) { + if( debug ) + debugmsg("Accepting encrypted connection disabled, closing the connection \n"); + close_sock(as); + continue; + } else { + if((as = saccept(as)) >= 0 ) { + if( debug ) + debugmsg("Can't accept the ssl connection, since SSL init has failed for certificate reason\n"); + close_sock(as); + continue; + } + } + } else if (is_encrypted == -1) { + logmsg("SSL version 2 is unsecure, we don't support it\n"); + close_sock(as); + continue; + } + if ( (! pc.acceptunencryptedconnection) && (as >= 0)) { + logmsg("Unencrypted connections are not accepted and we received an unencrypted connection request\n"); + close_sock(as); + continue; + } + /* SSL stuff end */ + s = malloc(sizeof(struct mansession)); if ( !s ) { logmsg("Failed to allocate listener session: %s\n", strerror(errno)); @@ -534,14 +575,13 @@ static void *accept_thread() memcpy(&s->sin, &sin, sizeof(sin)); /* For safety, make sure socket is non-blocking */ - flags = fcntl(as, F_GETFL); - fcntl(as, F_SETFL, flags | O_NONBLOCK); + flags = fcntl(get_real_fd(as), F_GETFL); + fcntl(get_real_fd(as), F_SETFL, flags | O_NONBLOCK); pthread_mutex_init(&s->lock, NULL); s->fd = as; SetIOHandlers(s, pc.inputformat, pc.outputformat); s->autofilter = pc.autofilter; - s->inputcomplete = 0; s->server = NULL; pthread_mutex_lock(&sessionlock); @@ -577,7 +617,7 @@ int main(int argc, char *argv[]) { switch( i ) { case 'd': - debug = 1; + debug++; break; case 'h': Usage(); @@ -617,9 +657,16 @@ int main(int argc, char *argv[]) /* Initialize global mutexes */ pthread_mutex_init(&sessionlock, NULL); + pthread_mutex_init(&userslock, NULL); pthread_mutex_init(&loglock, NULL); pthread_mutex_init(&debuglock, NULL); + /* Read initial state for user permissions */ + ReadPerms(); + + /* Initialize SSL Client-Side Context */ + client_init_secure(); + /* Initialize global client/server list */ sessions = NULL; LaunchAsteriskThreads(); @@ -11,8 +11,6 @@ int get_input(struct mansession *s, char *output) struct pollfd fds[1]; char iabuf[INET_ADDRSTRLEN]; - debugmsg("in get_input"); - /* Look for \r\n from the front, our preferred end of line */ for (x=0;x<s->inlen;x++) { int xtra = 0; @@ -36,25 +34,37 @@ int get_input(struct mansession *s, char *output) debugmsg("Warning: Got long line with no end from %s: %s\n", ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr), s->inbuf); s->inlen = 0; } - debugmsg("attempting poll operation"); - fds[0].fd = s->fd; + /* get actual fd, even if a negative SSL fd */ + fds[0].fd = get_real_fd(s->fd); + fds[0].events = POLLIN; - res = poll(fds, 1, -1); - debugmsg("returned from poll op"); - if (res < 0 && debug) { - debugmsg("Select returned error"); - } else if (res > 0) { - pthread_mutex_lock(&s->lock); - debugmsg("attempting socket read in get_input..."); - res = read(s->fd, s->inbuf + s->inlen, sizeof(s->inbuf) - 1 - s->inlen); - pthread_mutex_unlock(&s->lock); - if (res < 1) - return -1; - } + do { + res = poll(fds, 1, -1); + if (res < 0) { + if (errno == EINTR) { + if (s->dead) + return -1; + continue; + } + if (debug) + debugmsg("Select returned error"); + return -1; + } else if (res > 0) { + pthread_mutex_lock(&s->lock); + /* read from socket; SSL or otherwise */ + res = m_recv(s->fd, s->inbuf + s->inlen, sizeof(s->inbuf) - 1 - s->inlen, 0); + pthread_mutex_unlock(&s->lock); + if (res < 1) + return -1; + break; + + } + } while(1); + + /* We have some input, but it's not ready for processing */ s->inlen += res; s->inbuf[s->inlen] = '\0'; return 0; - /* We have some input, but it's not ready for processing */ } char *astman_get_header(struct message *m, char *var) @@ -92,56 +102,34 @@ const char *ast_inet_ntoa(char *buf, int bufsiz, struct in_addr ia) } -int connect_nonb(int sockfd, const struct sockaddr *saptr, socklen_t salen, int nsec) +/*! If you are calling ast_carefulwrite, it is assumed that you are calling + it on a file descriptor that _DOES_ have NONBLOCK set. This way, + there is only one system call made to do a write, unless we actually + have a need to wait. This way, we get better performance. */ +int ast_carefulwrite(int fd, char *s, int len, int timeoutms) { - int flags, n, error; - socklen_t len; - fd_set rset, wset; - struct timeval tval; - - flags = fcntl(sockfd, F_GETFL, 0); - fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); - - error = 0; - if ( (n = connect(sockfd, (struct sockaddr *) saptr, salen)) < 0) - if (errno != EINPROGRESS) - return(-1); - - /* Do whatever we want while the connect is taking place. */ - - if (n == 0) - goto done; /* connect completed immediately */ - - FD_ZERO(&rset); - FD_SET(sockfd, &rset); - wset = rset; - tval.tv_sec = nsec; - tval.tv_usec = 0; - - if ( (n = select(sockfd+1, &rset, &wset, NULL, - nsec ? &tval : NULL)) == 0) { - /*close(sockfd);*/ /* we want to retry */ - errno = ETIMEDOUT; - return(-1); - } - - if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) { - len = sizeof(error); - if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) - return(-1); /* Solaris pending error */ - } else { - /*err_quit("select error: sockfd not set");*/ - logmsg("select error: sockfd not set"); - return(-1); + /* Try to write string, but wait no more than ms milliseconds + before timing out */ + int res=0; + struct pollfd fds[1]; + while(len) { + res = m_send(fd, s, len); + if ((res < 0) && (errno != EAGAIN)) { + return -1; + } + if (res < 0) res = 0; + len -= res; + s += res; + res = 0; + if (len) { + fds[0].fd = get_real_fd(fd); + fds[0].events = POLLOUT; + /* Wait until writable again */ + res = poll(fds, 1, timeoutms); + if (res < 1) + return -1; + } } - -done: - fcntl(sockfd, F_SETFL, flags); /* restore file status flags */ - - if (error) { - /* close(sockfd); */ /* disable for now, we want to retry... */ - errno = error; - return(-1); - } - return(0); + return res; } + @@ -9,6 +9,7 @@ void *add_server(char *srvspec) { int ccount = 0; struct ast_server *srv; char *s; + char usessl[10]; /* malloc ourselves a server credentials structure */ srv = malloc(sizeof(struct ast_server)); @@ -17,6 +18,7 @@ void *add_server(char *srvspec) { exit(1); } memset(srv, 0, sizeof (struct ast_server) ); + memset(usessl, 0, sizeof (usessl) ); s = srvspec; do { @@ -41,20 +43,24 @@ void *add_server(char *srvspec) { case 4: strncat(srv->ast_events, s, 1); break; + case 5: + strncat(usessl, s, 1); + break; } } while (*(s++)); - if (!srv->ast_host || !srv->ast_port || !srv->ast_user || !srv->ast_pass || !srv->ast_events) { - fprintf(stderr, "Aborting! Server spec incomplete: %s!\n", srvspec); + + if (!*srv->ast_host || !*srv->ast_port || !*srv->ast_user || !*srv->ast_pass || !*srv->ast_events || !*usessl) { + fprintf(stderr, "Aborting: server spec incomplete: %s\n", srvspec); free(srv); exit(1); - } else { - srv->next = pc.serverlist; - pc.serverlist = srv; } + srv->use_ssl = (!strcmp(usessl,"on")); + srv->next = pc.serverlist; + pc.serverlist = srv; + return 0; - /* TODO: make sure server credentials are freed at leave */ } void *processline(char *s) { @@ -95,6 +101,20 @@ void *processline(char *s) { strcpy(pc.listen_addr, value); else if (!strcmp(name,"listenport") ) pc.listen_port = atoi(value); + else if (!strcmp(name,"asteriskwritetimeout") ) + pc.asteriskwritetimeout = atoi(value); + else if (!strcmp(name,"clientwritetimeout") ) + pc.clientwritetimeout = atoi(value); + else if (!strcmp(name,"sslclienthellotimeout") ) + pc.sslclhellotimeout = atoi(value); + else if (!strcmp(name,"authrequired") ) + pc.authrequired = strcmp(value,"yes") ? 0 : 1; + else if (!strcmp(name,"acceptencryptedconnection") ) + pc.acceptencryptedconnection = strcmp(value,"yes") ? 0 : 1; + else if (!strcmp(name,"acceptunencryptedconnection") ) + pc.acceptunencryptedconnection = strcmp(value,"yes") ? 0 : 1; + else if (!strcmp(name,"certfile") ) + strcpy(pc.certfile, value); else if (!strcmp(name,"proxykey") ) strcpy(pc.key, value); else if (!strcmp(name,"proc_user") ) @@ -172,11 +192,7 @@ int LoadHandlers() { io->write = wh; if (och) io->onconnect = och; - io->autodisconnect = dlsym(dlhandle, "_autodisconnect"); - if ((error = dlerror()) != NULL) { - if (debug) - debugmsg("loading: note, %s_autodisconnect not defined; ignoring", fmt); - } + io->dlhandle = dlhandle; io->next = iohandlers; iohandlers = io; @@ -203,6 +219,12 @@ int ReadConfig() { memset( &pc, 0, sizeof pc ); + + /* Set nonzero config defaults */ + pc.asteriskwritetimeout = 100; + pc.clientwritetimeout = 100; + pc.sslclhellotimeout = 500; + sprintf(cfn, "%s/%s", CDIR, CFILE); FP = fopen( cfn, "r" ); @@ -222,6 +244,9 @@ int ReadConfig() { fclose(FP); + /* initialize SSL layer with our server certfile */ + init_secure(pc.certfile); + return 0; } @@ -229,7 +254,7 @@ FILE *OpenLogfile() { FILE *FP; FP = fopen( pc.logfile, "a" ); if ( !FP ) { - fprintf(stderr, "Unable to open logfile: %s!", pc.logfile); + fprintf(stderr, "Unable to open logfile: %s!\n", pc.logfile); exit( 1 ); } diff --git a/src/config_perms.c b/src/config_perms.c new file mode 100644 index 0000000..4dbeeb0 --- /dev/null +++ b/src/config_perms.c @@ -0,0 +1,125 @@ +#include "astmanproxy.h" + +extern pthread_mutex_t userslock; + +void *free_userperm(struct proxy_user *pu) { + struct proxy_user *next_pu; + + while( pu ) { + next_pu = pu->next; + free( pu ); + pu = next_pu; + } + return 0; +} + +void *add_userperm(char* username, char *userspec, struct proxy_user **pu) { + + int ccount = 0; + struct proxy_user *user; + char *s; + + /* malloc ourselves a server credentials structure */ + user = malloc(sizeof(struct proxy_user)); + if ( !user ) { + fprintf(stderr, "Failed to allocate user credentials: %s\n", strerror(errno)); + exit(1); + } + memset(user, 0, sizeof (struct proxy_user) ); + + s = userspec; + strncpy(user->username, username, sizeof(user->username)-1 ); + do { + if ( *s == ',' ) { + ccount++; + continue; + } + switch(ccount) { + case 0: + strncat(user->secret, s, 1); + break; + case 1: + strncat(user->channel, s, 1); + break; + case 2: + strncat(user->ocontext, s, 1); + break; + case 3: + strncat(user->icontext, s, 1); + break; + } + } while (*(s++)); + + user->next = *pu; + *pu = user; + + return 0; +} + +void *processperm(char *s, struct proxy_user **pu) { + char name[80],value[80]; + int nvstate = 0; + + + memset (name,0,sizeof name); + memset (value,0,sizeof value); + + do { + *s = tolower(*s); + + if ( *s == ' ' || *s == '\t') + continue; + if ( *s == ';' || *s == '#' || *s == '\r' || *s == '\n' ) + break; + if ( *s == '=' ) { + nvstate = 1; + continue; + } + if (!nvstate) + strncat(name, s, 1); + else + strncat(value, s, 1); + } while (*(s++)); + + if (debug) + debugmsg("perm: %s, %s", name, value); + + add_userperm(name,value,pu); + + return 0; +} + +int ReadPerms() { + FILE *FP; + char buf[1024]; + char cfn[80]; + struct proxy_user *pu; + + pu=0; + sprintf(cfn, "%s/%s", PDIR, PFILE); + FP = fopen( cfn, "r" ); + + if ( !FP ) + { + fprintf(stderr, "Unable to open permissions file: %s/%s!\n", PDIR, PFILE); + exit( 1 ); + } + + if (debug) + debugmsg("config: parsing configuration file: %s", cfn); + + while ( fgets( buf, sizeof buf, FP ) ) { + if (*buf == ';' || *buf == '\r' || *buf == '\n' || *buf == '#') continue; + processperm(buf,&pu); + } + + fclose(FP); + + pthread_mutex_lock(&userslock); + free_userperm(pc.userlist); + pc.userlist=pu; + pthread_mutex_unlock(&userslock); + + return 0; +} + @@ -20,14 +20,11 @@ int _write(struct mansession *s, struct message *m) { sprintf(outstring, "\"%s\"", m->headers[i]); if (i<m->hdrcount-1) strcat(outstring, ", "); - write(s->fd, outstring, strlen(outstring)); + ast_carefulwrite(s->fd, outstring, strlen(outstring), s->writetimeout); } - write(s->fd, "\r\n\r\n", 4); + ast_carefulwrite(s->fd, "\r\n\r\n", 4, s->writetimeout); pthread_mutex_unlock(&s->lock); return 0; } -int _autodisconnect() { - return 0; -} diff --git a/src/http.c b/src/http.c new file mode 100644 index 0000000..4f107db --- /dev/null +++ b/src/http.c @@ -0,0 +1,155 @@ +/* Asterisk Manager Proxy + Copyright (c) 2005 David C. Troy <dave@popvox.com> + + This program is free software, distributed under the terms of + the GNU General Public License. + + HTTP Input Handler +*/ + +#include "astmanproxy.h" + +int ParseHTTPInput(char *buf, struct message *m) { + char *n, *v; + + n = buf; + while ( (v = strstr(n, "=")) ) { + v += 1; + debugmsg("n: %s, v: %s", n, v); + strncat(m->headers[m->hdrcount], n, v-n-1); + strcat(m->headers[m->hdrcount], ": "); + + if ( (n = strstr(v, "&")) ) { + n += 1; + } else { + n = (v + strlen(v) + 1); + } + strncat(m->headers[m->hdrcount], v, n-v-1); + debugmsg("got hdr: %s", m->headers[m->hdrcount]); + m->hdrcount++; + } + + return (m->hdrcount > 0); +} + +int HTTPHeader(struct mansession *s, char *status) { + + + time_t t; + struct tm tm; + char date[80]; + char ctype[15], hdr[MAX_LEN]; + + time(&t); + localtime_r(&t, &tm); + strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", &tm); + + if ( !strcasecmp("xml", s->output->formatname) ) + sprintf(ctype, "text/xml"); + else + sprintf(ctype, "text/plain"); + + if (!strcmp("200 OK", status) ) + sprintf(hdr, + "HTTP/1.1 %s\r\n" + "Date: %s\r\n" + "Content-Type: %s\r\n" + "Connection: close\r\n" + "Server: %s/%s\r\n\r\n", status, + date, ctype, PROXY_BANNER, PROXY_VERSION); + else + sprintf(hdr, + "HTTP/1.1 %s\r\n" + "Date: %s\r\n" + "Status: %s\r\n" + "Server: %s/%s\r\n\r\n", status, date, status, PROXY_BANNER, PROXY_VERSION); + + pthread_mutex_lock(&s->lock); + s->inputcomplete = 1; + ast_carefulwrite(s->fd, hdr, strlen(hdr), s->writetimeout); + pthread_mutex_unlock(&s->lock); + debugmsg("http header: %s", hdr); + + return 0; +} + +int _read(struct mansession *s, struct message *m) { + + /* Note: No single line may be longer than MAX_LEN/s->inbuf, as per get_input */ + /* No HTTP Input may be longer than BUFSIZE */ + + char line[MAX_LEN], method[10], formdata[MAX_LEN], status[15]; + int res, clength = 0; + + memset(method, 0, sizeof method); + memset(formdata, 0, sizeof formdata); + memset(status, 0, sizeof status); + + /* for http, don't do get_input forever */ + for (;;) { + + if (s->inputcomplete && !s->outputcomplete) + continue; + else if (s->inputcomplete && s->outputcomplete) + return -1; + + memset(line, 0, sizeof line); + res = get_input(s, line); + debugmsg("res=%d, line: %s",res, line); + + if (res > 0) { + debugmsg("Got http: %s", line); + + if ( !clength && !strncasecmp(line, "Content-Length: ", 16) ) + clength = atoi(line+16); + + if (!*method) { + if ( !strncmp(line,"POST",4) ) { + strncpy(method, line, 4); + } else if ( !strncmp(line,"GET",3)) { + if ( strlen(line) > 14 ) { + /* GET / HTTP/1.1 ---- this is bad */ + /* GET /?Action=Ping&ActionID=Foo HTTP/1.1 */ + strncpy(method, line, 3); + memcpy(formdata, line+6, strstr(line, " HTTP")-line-6); + sprintf(status, "200 OK"); + } else + sprintf(status, "501 Not Implemented"); + } + } + } else if (res == 0) { + /* x-www-form-urlencoded handler */ + /* Content-Type: application/x-www-form-urlencoded */ + if (*method && !*formdata) { + if ( !strcasecmp(method, "POST") && clength && s->inlen==clength) { + pthread_mutex_lock(&s->lock); + strncpy(formdata, s->inbuf, clength); + s->inlen = 0; + pthread_mutex_unlock(&s->lock); + sprintf(status, "200 OK"); + } + } + } + + if (res < 0) + break; + + if (*status) { + HTTPHeader(s, status); + + /* now, let's transform and copy into a standard message block */ + if (!strcmp("200 OK", status) ) { + res = ParseHTTPInput(formdata, m); + return res; + } else { + pthread_mutex_lock(&s->lock); + s->outputcomplete = 1; + pthread_mutex_unlock(&s->lock); + return 0; + } + } + } + return -1; +} + +/* We do not define a _write or _onconnect method */ diff --git a/astmanproxy.h b/src/include/astmanproxy.h index 566ae69..dc7ac87 100644 --- a/astmanproxy.h +++ b/src/include/astmanproxy.h @@ -25,12 +25,13 @@ #include <sys/poll.h> #endif -#define BUFSIZE 150 +#define BUFSIZE 1024 #define MAX_HEADERS 256 -#define MAX_LEN 150 +#define MAX_LEN 1024 #define PROXY_BANNER "Asterisk Call Manager Proxy" #define PROXY_SHUTDOWN "ProxyMessage: Proxy Shutting Down" +#define ACTION_ID "ActionID" struct ast_server { char nickname[80]; @@ -39,30 +40,47 @@ struct ast_server { char ast_user[80]; char ast_pass[80]; char ast_events[10]; + int use_ssl; /* Use SSL when Connecting to Server? */ int status; /* TODO: have this mean something */ struct ast_server *next; }; +struct proxy_user { + char username[80]; + char secret[80]; + char channel[80]; + char icontext[80]; + char ocontext[80]; + struct proxy_user *next; +}; + struct proxyconfig { struct ast_server *serverlist; + struct proxy_user *userlist; char listen_addr[INET_ADDRSTRLEN]; int listen_port; char inputformat[80]; char outputformat[80]; - int autofilter; + int autofilter; /* enable autofiltering? */ + int authrequired; /* is authentication required? */ char key[80]; - char proc_user[30]; - char proc_group[30]; - char logfile[80]; + char proc_user[40]; + char proc_group[40]; + char logfile[256]; int retryinterval; int maxretries; + int asteriskwritetimeout; /* ms to wait when writing to asteriskfor ast_carefulwrite */ + int clientwritetimeout; /* ms to wait when writing to client ast_carefulwrite */ + int sslclhellotimeout; /* ssl client hello timeout -- how long to wait before assuming not ssl */ + int acceptencryptedconnection; /* accept encrypted connections? */ + int acceptunencryptedconnection; /* accept unencrypted connections? */ + char certfile[256]; /* our SERVER-side SSL certificate file */ }; struct iohandler { int (*read) (); int (*write) (); int (*onconnect) (); - int *(*autodisconnect)(void); char formatname[80]; void *dlhandle; struct iohandler *next; @@ -78,11 +96,17 @@ struct mansession { struct iohandler *input; struct iohandler *output; int autofilter; - int inputcomplete; int authenticated; int connected; + int dead; /* Whether we are dead */ + int busy; /* Whether we are busy */ + int inputcomplete; /* Whether we want any more input from this session (http) */ + int outputcomplete; /* Whether output to this session is done (http) */ struct ast_server *server; + struct proxy_user user; char actionid[MAX_LEN]; + char challenge[10]; /*! Authentication challenge */ + int writetimeout; /* Timeout for ast_carefulwrite() */ struct mansession *next; }; @@ -110,4 +134,16 @@ int proxyerror_do(struct mansession *s, char *err); int get_input(struct mansession *s, char *output); int SetIOHandlers(struct mansession *s, char *ifmt, char *ofmt); void destroy_session(struct mansession *s); -int connect_nonb(int sockfd, const struct sockaddr *saptr, socklen_t salen, int nsec); +int ast_carefulwrite(int fd, char *s, int len, int timeoutms); +extern void *SendError(struct mansession *s, char *errmsg); + +int close_sock(int socket); +int ProxyChallenge(struct mansession *s, struct message *m); +int ast_connect(struct mansession *a); +int is_encrypt_request(int sslclhellotimeout, int fd); +int saccept(int s); +int get_real_fd(int fd); +int client_init_secure(void); +int init_secure(char *certfile); +int m_send(int fd, const void *data, size_t len); +int m_recv(int s, void *buf, size_t len, int flags); diff --git a/dlfcn-compat.h b/src/include/dlfcn-compat.h index 7c5e87f..7c5e87f 100644 --- a/dlfcn-compat.h +++ b/src/include/dlfcn-compat.h diff --git a/src/include/endian.h b/src/include/endian.h new file mode 100644 index 0000000..f5e20fb --- /dev/null +++ b/src/include/endian.h @@ -0,0 +1,60 @@ +/* + * Asterisk -- A telephony toolkit for Linux. + * + * Asterisk architecture endianess compatibility definitions + * + * Copyright (C) 1999 - 2005, Digium, Inc. + * + * Mark Spencer <markster@digium.com> + * + * This program is free software, distributed under the terms of + * the GNU Lesser General Public License. Other components of + * Asterisk are distributed under The GNU General Public License + * only. + */ + +#ifndef _ASTERISK_ENDIAN_H +#define _ASTERISK_ENDIAN_H + +/* + * Autodetect system endianess + */ + +#ifdef SOLARIS +#include "solaris-compat/compat.h" +#endif + +#ifndef __BYTE_ORDER +#ifdef __linux__ +#include <endian.h> +#elif defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__) +#if defined(__OpenBSD__) +#include <machine/types.h> +#endif /* __OpenBSD__ */ +#include <machine/endian.h> +#define __BYTE_ORDER BYTE_ORDER +#define __LITTLE_ENDIAN LITTLE_ENDIAN +#define __BIG_ENDIAN BIG_ENDIAN +#else +#ifdef __LITTLE_ENDIAN__ +#define __BYTE_ORDER __LITTLE_ENDIAN +#endif /* __LITTLE_ENDIAN */ + +#if defined(i386) || defined(__i386__) +#define __BYTE_ORDER __LITTLE_ENDIAN +#endif /* defined i386 */ + +#if defined(sun) && defined(unix) && defined(sparc) +#define __BYTE_ORDER __BIG_ENDIAN +#endif /* sun unix sparc */ + +#endif /* linux */ + +#endif /* __BYTE_ORDER */ + +#ifndef __BYTE_ORDER +#error Need to know endianess +#endif /* __BYTE_ORDER */ + +#endif /* _ASTERISK_ENDIAN_H */ + diff --git a/src/include/md5.h b/src/include/md5.h new file mode 100644 index 0000000..30ac30c --- /dev/null +++ b/src/include/md5.h @@ -0,0 +1,18 @@ +#ifndef MD5_H +#define MD5_H + +#include <inttypes.h> + +struct MD5Context { + uint32_t buf[4]; + uint32_t bits[2]; + unsigned char in[64]; +}; + +void MD5Init(struct MD5Context *context); +void MD5Update(struct MD5Context *context, unsigned char const *buf, + unsigned len); +void MD5Final(unsigned char digest[16], struct MD5Context *context); +void MD5Transform(uint32_t buf[4], uint32_t const in[16]); + +#endif /* !MD5_H */ diff --git a/poll-compat.h b/src/include/poll-compat.h index 79eab15..79eab15 100644 --- a/poll-compat.h +++ b/src/include/poll-compat.h diff --git a/src/include/ssl.h b/src/include/ssl.h new file mode 100644 index 0000000..123bd43 --- /dev/null +++ b/src/include/ssl.h @@ -0,0 +1,89 @@ +/* + * ssl_addon: Encrypts the asterisk management interface + * + * Copyrights: + * Copyright (C) 2005-2006, Tello Corporation, Inc. + * + * Contributors: + * Remco Treffkorn(Architect) and Mahesh Karoshi + * + * This program is free software, distributed under the terms of + * the GNU Lesser (Library) General Public License + * + * Copyright on this file is disclaimed to Digium for inclusion in Asterisk + */ + +#ifndef _SSL_ADDON_H_ +#define _SSL_ADDON_H_ + +#include <openssl/ssl.h> +#include "astmanproxy.h" + +int connect_nonb(struct mansession *a); + +/*! \brief + This data structure holds the additional SSL data needed to use the ssl functions. + The negative fd is used as an index into this data structure (after processing). + Choose SEC_MAX to be impossibly large for the application. +*/ +#define SEC_MAX 16 +struct { + int fd; + SSL* ssl; +} sec_channel[SEC_MAX]; + +/*! \brief + this has to be called before any other function dealing with ssl. +*/ +int init_secure(char* certfile); + +/*! \brief + Returns the real fd, that is received from os, when we accept the connection. +*/ +int get_real_fd(int fd); + +/*! \brief + Returns the ssl structure from the fd. +*/ +SSL *get_ssl(int fd); + +/*! \brief + Returns the availabe security slot. This restricts the maximun number of security connection, + the asterisk server can have for AMI. +*/ +int sec_getslot(void); + +/*! \brief + Accepts the connection, if the security is enabled it returns the negative fd. -1 is flase, -2, -3 + etc are ssl connections. +*/ +int saccept(int s); + +/*! \brief + Sends the data over secured or unsecured connections. +*/ +int m_send(int fd, const void *data, size_t len); + + +/*! \brief + Receives the connection from either ssl or fd. +*/ +int m_recv(int s, void *buf, size_t len, int flags); + + +/*! \brief + Needs to be called instead of close() to close a socket. + It also closes the ssl meta connection. +*/ + +int close_sock(int socket); + +int errexit(char s[]); + +int is_encrypt_request(int sslclhellotimeout, int fd); +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/src/md5.c b/src/md5.c new file mode 100644 index 0000000..cda3441 --- /dev/null +++ b/src/md5.c @@ -0,0 +1,260 @@ +/* MD5 checksum routines used for authentication. Not covered by GPL, but + in the public domain as per the copyright below */ + +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ + +#include "endian.h" +#include "astmanproxy.h" +#include "md5.h" + +# if __BYTE_ORDER == __BIG_ENDIAN +# define HIGHFIRST 1 +# endif +#ifndef HIGHFIRST +#define byteReverse(buf, len) /* Nothing */ +#else +void byteReverse(unsigned char *buf, unsigned longs); + +#ifndef ASM_MD5 +/* + * Note: this code is harmless on little-endian machines. + */ +void byteReverse(unsigned char *buf, unsigned longs) +{ + uint32_t t; + do { + t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 | + ((unsigned) buf[1] << 8 | buf[0]); + *(uint32_t *) buf = t; + buf += 4; + } while (--longs); +} +#endif +#endif + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void MD5Init(struct MD5Context *ctx) +{ + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void MD5Update(struct MD5Context *ctx, unsigned char const *buf, unsigned len) +{ + uint32_t t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if (t) { + unsigned char *p = (unsigned char *) ctx->in + t; + + t = 64 - t; + if (len < t) { + memcpy(p, buf, len); + return; + } + memcpy(p, buf, t); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32_t *) ctx->in); + buf += t; + len -= t; + } + /* Process data in 64-byte chunks */ + + while (len >= 64) { + memcpy(ctx->in, buf, 64); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32_t *) ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + memcpy(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void MD5Final(unsigned char digest[16], struct MD5Context *ctx) +{ + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset(p, 0, count); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32_t *) ctx->in); + + /* Now fill the next block with 56 bytes */ + memset(ctx->in, 0, 56); + } else { + /* Pad block to 56 bytes */ + memset(p, 0, count - 8); + } + byteReverse(ctx->in, 14); + + /* Append length in bits and transform */ + ((uint32_t *) ctx->in)[14] = ctx->bits[0]; + ((uint32_t *) ctx->in)[15] = ctx->bits[1]; + + MD5Transform(ctx->buf, (uint32_t *) ctx->in); + byteReverse((unsigned char *) ctx->buf, 4); + memcpy(digest, ctx->buf, 16); + memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */ +} + +#ifndef ASM_MD5 + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +void MD5Transform(uint32_t buf[4], uint32_t const in[16]) +{ + register uint32_t a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +#endif diff --git a/proxyfunc.c b/src/proxyfunc.c index 11132ff..434baff 100644 --- a/proxyfunc.c +++ b/src/proxyfunc.c @@ -1,8 +1,10 @@ #include "astmanproxy.h" +#include "md5.h" extern struct mansession *sessions; extern struct iohandler *iohandlers; extern pthread_mutex_t serverlock; +extern pthread_mutex_t userslock; void *ProxyListIOHandlers(struct mansession *s) { struct message m; @@ -52,7 +54,6 @@ void *ProxySetOutputFormat(struct mansession *s, struct message *m) { value = astman_get_header(m, "OutputFormat"); SetIOHandlers(s, s->input->formatname, value); - /* TODO: this is retarded */ memset(&mo, 0, sizeof(struct message)); AddHeader(&mo, "ProxyResponse: Success"); @@ -63,6 +64,25 @@ void *ProxySetOutputFormat(struct mansession *s, struct message *m) { return 0; } +int ProxyChallenge(struct mansession *s, struct message *m) { + struct message mo; + + if ( strcasecmp("MD5", astman_get_header(m, "AuthType")) ) { + SendError(s, "Must specify AuthType"); + return 1; + } + + if (!*s->challenge) + snprintf(s->challenge, sizeof(s->challenge), "%d", rand()); + + memset(&mo, 0, sizeof(struct message)); + AddHeader(&mo, "Response: Success"); + AddHeader(&mo, "Challenge: %s", s->challenge); + + s->output->write(s, &mo); + return 0; +} + void *ProxySetAutoFilter(struct mansession *s, struct message *m) { struct message mo; char *value; @@ -86,20 +106,80 @@ void *ProxySetAutoFilter(struct mansession *s, struct message *m) { return 0; } -void *ProxyLogin(struct mansession *s) { - struct message m; +int AuthMD5(char *key, char *challenge, char *password) { + int x; + int len=0; + char md5key[256] = ""; + struct MD5Context md5; + unsigned char digest[16]; - /* Send back dummy output for clients that insist on authenticating the old way */ - /* Mostly, we want to avoid sending this to Asterisk */ + if (!*key || !*challenge || !*password ) + return 1; - /* Response: Success */ - /* Message: Authentication accepted */ + if (debug) + debugmsg("MD5 password=%s, challenge=%s", password, challenge); + + MD5Init(&md5); + MD5Update(&md5, (unsigned char *) challenge, strlen(challenge)); + MD5Update(&md5, (unsigned char *) password, strlen(password)); + MD5Final(digest, &md5); + for (x=0;x<16;x++) + len += sprintf(md5key + len, "%2.2x", digest[x]); + if( debug ) { + debugmsg("MD5 computed=%s, received=%s", md5key, key); + } + if (!strcmp(md5key, key)) + return 0; + else + return 1; +} - memset(&m, 0, sizeof(struct message)); - AddHeader(&m, "Response: Success"); - AddHeader(&m, "Message: Authentication accepted"); +void *ProxyLogin(struct mansession *s, struct message *m) { + struct message mo; + struct proxy_user *pu; + char *user, *secret, *key; + + user = astman_get_header(m, "Username"); + secret = astman_get_header(m, "Secret"); + key = astman_get_header(m, "Key"); + + memset(&mo, 0, sizeof(struct message)); + if( debug ) + debugmsg("Login attempt as: %s/%s", user, secret); + + pthread_mutex_lock(&userslock); + pu = pc.userlist; + while( pu ) { + if ( !strcmp(user, pu->username) ) { + if (!AuthMD5(key, s->challenge, pu->secret) || + !strcmp(secret, pu->secret) ) { + AddHeader(&mo, "Response: Success"); + AddHeader(&mo, "Message: Authentication accepted"); + s->output->write(s, &mo); + pthread_mutex_lock(&s->lock); + s->authenticated = 1; + strcpy(s->user.channel, pu->channel); + strcpy(s->user.icontext, pu->icontext); + strcpy(s->user.ocontext, pu->ocontext); + pthread_mutex_unlock(&s->lock); + if( debug ) + debugmsg("Login as: %s", user); + break; + } + } + pu = pu->next; + } + pthread_mutex_unlock(&userslock); + + if( !pu ) { + SendError(s, "Authentication failed"); + pthread_mutex_lock(&s->lock); + s->authenticated = 0; + pthread_mutex_unlock(&s->lock); + if( debug ) + debugmsg("Login failed as: %s/%s", user, secret); + } - s->output->write(s, &m); return 0; } @@ -130,10 +210,7 @@ int ProxyAddServer(struct mansession *s, struct message *m) { fprintf(stderr, "Failed to allocate server credentials: %s\n", strerror(errno)); exit(1); } - memset(srv, 0, sizeof (struct ast_server) ); - - /* TODO: Disallow adding of duplicate servers? Or not, I suppose that could be useful (events on/off) */ memset(srv, 0, sizeof(struct ast_server) ); memset(&mo, 0, sizeof(struct message)); strcpy(srv->ast_host, astman_get_header(m, "Server")); @@ -256,3 +333,58 @@ int proxyerror_do(struct mansession *s, char *err) return 0; } +int ValidateAction(struct message *m, struct mansession *s, int inbound) { + char *channel, *channel1, *channel2; + char *context; + char *uchannel; + char *ucontext; + + if( pc.authrequired && !s->authenticated ) + return 0; + + if( inbound ) + ucontext = s->user.icontext; + else + ucontext = s->user.ocontext; + uchannel = s->user.channel; + + channel = astman_get_header(m, "Channel"); + if( channel[0] != '\0' && uchannel[0] != '\0' ) + if( strncasecmp( channel, uchannel, strlen(uchannel) ) ) { + if( debug ) + debugmsg("Message filtered (chan): %s != %s", channel, uchannel); + return 0; + } + + channel1 = astman_get_header(m, "Channel1"); + channel2 = astman_get_header(m, "Channel2"); + if( (channel1[0] != '\0' || channel2[0] != '\0') && uchannel[0] != '\0' ) + if( !(strncasecmp( channel1, uchannel, strlen(uchannel) ) == 0 || + strncasecmp( channel2, uchannel, strlen(uchannel) ) == 0) ) { + if( debug ) + debugmsg("Message filtered (chan): %s/%s != %s", channel1, channel2, uchannel); + return 0; + } + + context = astman_get_header(m, "Context"); + if( context[0] != '\0' && ucontext[0] != '\0' ) + if( strcasecmp( context, ucontext ) ) { + if( debug ) + debugmsg("Message filtered (ctxt): %s != %s", context, ucontext); + return 0; + } + + return 1; +} + +void *SendError(struct mansession *s, char *errmsg) { + struct message m; + + memset(&m, 0, sizeof(struct message)); + AddHeader(&m, "Response: Error"); + AddHeader(&m, "Message: %s", errmsg); + + s->output->write(s, &m); + + return 0; +} diff --git a/src/ssl.c b/src/ssl.c new file mode 100644 index 0000000..9f5b341 --- /dev/null +++ b/src/ssl.c @@ -0,0 +1,438 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2006, Tello Corporation, Inc. + * + * Remco Treffkorn(Architect) and Mahesh Karoshi(Senior Software Developer) + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief SSL for The Asterisk Management Interface - AMI + * + * Channel Management and more + * + * \author Remco Treffkorn(Architect) and Mahesh Karoshi(Senior Software Developer) + * \ref amiconf + */ + +/*! \addtogroup Group_AMI AMI functions +*/ +/*! @{ + Doxygen group */ + +/*! \note We use negative file descriptors for secure channels. The file descriptor + -1 is reseved for errors. -2 to -... are secure file descriptors. 0 to ... + are regular file descriptors. + + NOTE: Commonly error checks for routines returning fd's are done with (value<0). + You must check for (value==-1) instead, since all other negative fd's now + are valid fd's. +*/ +#include <sys/types.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> +#include <errno.h> +#include <time.h> +#include <sys/time.h> +#include <stdio.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/tcp.h> + +#include "ssl.h" + +SSL_CTX *sctx; +SSL_CTX *cctx; +static long rec_bytes; +static long sent_bytes; +static int ssl_initialized; + + +/*! \brief this has to be called before any other function dealing with ssl. + Initializes all the ssl related stuff here. */ +int init_secure(char *certfile) +{ + SSL_METHOD *meth; + + SSLeay_add_ssl_algorithms(); + SSL_load_error_strings(); + + /* server init */ + meth = SSLv23_server_method(); + sctx = SSL_CTX_new(meth); + + if (!sctx) { + return errexit("Failed to create a server ssl context!"); + } + + if (SSL_CTX_use_certificate_file(sctx, certfile, SSL_FILETYPE_PEM) <= 0) { + return errexit("Failed to use the certificate file!"); + } + + if (SSL_CTX_use_PrivateKey_file(sctx, certfile, SSL_FILETYPE_PEM) <= 0) { + return errexit("Failed to use the key file!\n"); + } + + if (!SSL_CTX_check_private_key(sctx)) { + return errexit("Private key does not match the certificate public key"); + } + ssl_initialized = 1; + return 0; +} + + +/* Initializes all the client-side ssl related stuff here. +*/ +int client_init_secure(void) +{ + SSL_METHOD *meth; + + /* client init */ + SSLeay_add_ssl_algorithms(); + meth = SSLv23_client_method(); + SSL_load_error_strings(); + cctx = SSL_CTX_new (meth); + + if (!cctx) + debugmsg("Failed to create a client ssl context!"); + else + debugmsg("Client SSL Context Initialized"); + return 0; +} + +/*! \brief Takes the negative ssl fd and returns the positive fd recieved from the os. + * It goes through arrray of fixed maximum number of secured channels. +*/ +int get_real_fd(int fd) +{ + if (fd<-1) { + fd = -fd - 2; + if (fd>=0 && fd <SEC_MAX) + fd = sec_channel[fd].fd; + else fd = -1; + + } + return fd; +} + +/*! \brief Returns the SSL pointer from the fd. This structure is filled when we accept + * the ssl connection and used + * for reading and writing through ssl. +*/ +SSL *get_ssl(int fd) +{ + SSL *ssl = NULL; + + fd = -fd - 2; + + if (fd>=0 && fd <SEC_MAX) + ssl = sec_channel[fd].ssl; + + return ssl; +} + +/*! \brief Returns the empty ssl slot. Used to save ssl information. +*/ +int sec_getslot(void) +{ + int i; + + for (i=0; i<SEC_MAX; i++) { + if(sec_channel[i].ssl==NULL) + break; + } + + if (i==SEC_MAX) + return -1; + return i; +} + +/*! \brief Accepts the ssl connection. Returns the negative fd. negative fd's are + * chosen to differentiate between ssl and non-ssl connections. Positive + * fd's are used for non-ssl connections and negative fd's are used for ssl + * connections. So we purposefully calculate and return negative fds. + * You can always get positive fd by calling get_real_fd(negative fd). + * The positive fd's are required for system calls. + * +*/ +int saccept(int s) +{ + int fd, err; + SSL* ssl; + + if (!ssl_initialized) + return s; + + if (((fd=sec_getslot())!=-1)) { + ssl=SSL_new(sctx); + SSL_set_fd(ssl, s); + sec_channel[fd].ssl = ssl; /* remember ssl */ + sec_channel[fd].fd = s; /* remember the real fd */ + do { + err = SSL_accept(ssl); + err = SSL_get_error(ssl, err); + } while( err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE); + + SSL_set_mode(ssl, SSL_MODE_ENABLE_PARTIAL_WRITE); + debugmsg("ssl_addon: Connection accepted"); + + err=1; + + fd = -(fd+2); + + if (err!=1 || !ssl) { + /* it did not work */ + sec_channel[fd].ssl = NULL; /* free the slot */ + fd = -1; + } + } + return fd; +} + +/*! + * \brief Writes through secured ssl connection +*/ +int m_send(int fd, const void *data, size_t len) +{ + sent_bytes += len; + + if (fd < -1) { + SSL* ssl = get_ssl(fd); + return SSL_write(ssl, data, len); + } + return write(fd, data, len); +} + +/*! + * \brief Receives data from the SSL connection. +*/ +int m_recv(int s, void *buf, size_t len, int flags) +{ + int ret = 0; + + if (s<-1) { + SSL* ssl = get_ssl(s); + ret = SSL_read (ssl, buf, len); + } else + ret = recv(s, buf, len, flags); + + if (ret > 0) + rec_bytes += ret; + + if (debug && s<-1) + debugmsg("Received %d bytes from SSL socket", ret); + return ret; +} + + +/*! \brief + Needs to be called instead of close() to close a socket. + It also closes the SSL meta connection. +*/ + +int close_sock(int socket) +{ + int ret=0; + SSL* ssl = NULL; + + if (socket < -1) { + socket = - socket - 2; + + ssl = sec_channel[socket].ssl; + sec_channel[socket].ssl = NULL; + socket = sec_channel[socket].fd; + } + + ret= close(socket); + + if (ssl) + SSL_free (ssl); + + return(ret); +} + +/*! \brief This process cannot continue without fixing this error. +*/ +int errexit(char s[]) +{ + debugmsg("SSL critical error: %s", s); + return -1; +} + +/*! \brief Checks whether the client is requesting an ssl encrypted connection or not. If its encrypted + * request we expect "Client Hello" in the beginning of the message and ssl version 2. + * This can be verified by checking buf[0x02], buf[0x03] and buf[0x04]. If the contents are + * 0x01, 0x00, 0x02, then its an ssl packet with content "Client Hello", "SSL version 2". + * For SSL version 3, we might need to check for 0x01, 0x00, 0x03. + * +*/ +int is_encrypt_request(int sslclhellotimeout, int fd) +{ + fd_set listeners; + struct timeval tv; + char buf[1024]; + int ready_fdescriptors; + int ret; + + tv.tv_sec = 0; + tv.tv_usec = sslclhellotimeout * 1000; + + FD_ZERO(&listeners); + FD_SET(fd, &listeners); + + ready_fdescriptors = select (fd + 1, &listeners, NULL, NULL, &tv); + + if (ready_fdescriptors < 0 ) { + debugmsg("is_encrypt_request: select returned error, This should not happen:"); + return 0; + } else if (ready_fdescriptors == 0) { + return 0; + } + ret = recv(fd, buf, 100, MSG_PEEK); + if(ret > 0) { + /* check for sslv3 or tls*/ + if ((buf[0x00] == 0x16) && (buf[0x01] == 0x03) && + /* for tls buf[0x02] = 0x01 and ssl v3 buf[0x02] = 0x02 */ + ((buf[0x02] == 0x00) || (buf[0x02] == 0x01))) { + if (debug) + debugmsg("Received a SSL request"); + return 1; + /* check for sslv23_client_method */ + } else if ((buf[0x02] == 0x01) && (buf[0x03] == 0x03) && (buf[0x04] == 0x01)) { + if (debug) + debugmsg("Received a SSL request for SSLv23_client_method()"); + return 1; + } + /* check for sslv2 and return -1 */ + else if ((buf[0x02] == 0x01) && (buf[0x03] == 0x00) && (buf[0x04] == 0x02)) { + if (debug) + debugmsg("Received a SSLv2 request()"); + return -1; + } + } + return 0; +} + + +/* Connects to an asterisk server either plain or SSL as appropriate +*/ +int ast_connect(struct mansession *a) { + int s, err=-1, fd; + SSL* ssl; + + fd = connect_nonb(a); + if ( fd < 0 ) + return -1; + + if (a->server->use_ssl) { + debugmsg("initiating ssl connection"); + if ((s=sec_getslot())!=-1) { /* find a slot for the ssl handle */ + sec_channel[s].fd = fd; /* remember the real fd */ + + if((ssl=SSL_new(cctx))) { /* get a new ssl */ + sec_channel[s].ssl = ssl; + SSL_set_fd(ssl, fd); /* and attach the real fd */ + err = SSL_connect(ssl); /* now try and connect */ + } else + debugmsg("couldn't create ssl client context"); + fd = -(s+2); /* offset by two and negate */ + /* this tells us it is a ssl fd */ + } else + debugmsg("couldn't get SSL slot!"); + + if (err==-1) { + close_sock(fd); /* that frees the ssl too */ + fd = -1; + } + } + + debugmsg("returning ast_connect with %d", fd); + pthread_mutex_lock(&a->lock); + a->fd = fd; + pthread_mutex_unlock(&a->lock); + + return fd; +} + +int connect_nonb(struct mansession *a) +{ + int flags, n, error; + socklen_t len; + fd_set rset, wset; + struct timeval tval; + int nsec = 1, sockfd; + + sockfd = get_real_fd(a->fd); + + flags = fcntl(sockfd, F_GETFL, 0); + fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); + + error = 0; + if ( (n = connect(sockfd, (struct sockaddr *) &a->sin, sizeof(a->sin)) ) < 0 ) { + /* TODO: This seems like the nine pound hammer to me... */ + /* perhaps something a bit more elegant; errno seems to change too */ + if (errno == EISCONN || errno == 103 || errno==111) { + debugmsg("connect_nonb: error %d, closing old fd and grabbing a new one...", errno); + /* looks like our old socket died, let's round up a new one and try again */ + close_sock(a->fd); + pthread_mutex_lock(&a->lock); + a->fd = socket(AF_INET, SOCK_STREAM, 0); + pthread_mutex_unlock(&a->lock); + return(-1); + } + if (errno != EINPROGRESS) + return(-1); + } + + /* Do whatever we want while the connect is taking place. */ + + if (n == 0) + goto done; /* connect completed immediately */ + + FD_ZERO(&rset); + FD_SET(sockfd, &rset); + wset = rset; + tval.tv_sec = nsec; + tval.tv_usec = 0; + + if ( (n = select(sockfd+1, &rset, &wset, NULL, + nsec ? &tval : NULL)) == 0) { + /*close(sockfd);*/ /* we want to retry */ + errno = ETIMEDOUT; + return(-1); + } + + if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) { + len = sizeof(error); + if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) + return(-1); /* Solaris pending error */ + } else { + /*err_quit("select error: sockfd not set");*/ + logmsg("select error: sockfd not set"); + return(-1); + } + +done: + fcntl(sockfd, F_SETFL, flags); /* restore file status flags */ + + if (error) { + /* close(sockfd); */ /* disable for now, we want to retry... */ + errno = error; + return(-1); + } + return(sockfd); +} diff --git a/standard.c b/src/standard.c index de62cf7..9e8f200 100644 --- a/standard.c +++ b/src/standard.c @@ -15,7 +15,6 @@ extern struct mansession *sessions; int _read(struct mansession *s, struct message *m) { int res; - if (debug) debugmsg("in standard_read module..."); for (;;) { res = get_input(s, m->headers[m->hdrcount]); @@ -46,13 +45,12 @@ int _read(struct mansession *s, struct message *m) { int _write(struct mansession *s, struct message *m) { int i; - if (debug) debugmsg("in standard_write module..."); pthread_mutex_lock(&s->lock); for (i=0; i<m->hdrcount; i++) { - write(s->fd, m->headers[i], strlen(m->headers[i]) ); - write(s->fd, "\r\n", 2); + ast_carefulwrite(s->fd, m->headers[i], strlen(m->headers[i]) , s->writetimeout); + ast_carefulwrite(s->fd, "\r\n", 2, s->writetimeout); } - write(s->fd, "\r\n", 2); + ast_carefulwrite(s->fd, "\r\n", 2, s->writetimeout); pthread_mutex_unlock(&s->lock); return 0; @@ -64,12 +62,9 @@ int _onconnect(struct mansession *s, struct message *m) { sprintf(banner, "%s/%s\r\n", PROXY_BANNER, PROXY_VERSION); pthread_mutex_lock(&s->lock); - write(s->fd, banner, strlen(banner)); + ast_carefulwrite(s->fd, banner, strlen(banner), s->writetimeout); pthread_mutex_unlock(&s->lock); return 0; } -int _autodisconnect() { - return 0; -} @@ -74,7 +74,7 @@ int _write(struct mansession *s, struct message *m) { sprintf(buf, "<%s>\r\n", xmldoctag); pthread_mutex_lock(&s->lock); - write(s->fd, buf, strlen(buf)); + ast_carefulwrite(s->fd, buf, strlen(buf), s->writetimeout); for (i=0; i<m->hdrcount; i++) { memset(xmlescaped, 0, sizeof xmlescaped); @@ -89,20 +89,15 @@ int _write(struct mansession *s, struct message *m) { strcat(outstring, "\"/>\r\n"); } else sprintf(outstring, " <%s Value=\"%s\"/>\r\n", XML_UNPARSED, lpos); - write(s->fd, outstring, strlen(outstring) ); + ast_carefulwrite(s->fd, outstring, strlen(outstring), s->writetimeout); } sprintf(buf, "</%s>\r\n\r\n", xmldoctag); - write(s->fd, buf, strlen(buf)); + ast_carefulwrite(s->fd, buf, strlen(buf), s->writetimeout); pthread_mutex_unlock(&s->lock); return 0; } -int _autodisconnect() { - return 0; -} - - /* Takes a single manager header line and converts xml entities */ void xml_quote_string(char *s, char *o) { |