Compare commits

...

28 Commits

Author SHA1 Message Date
Chteufleur ce6a3a26af Oups! Forgot to add the lang file and explained it has to be added next to the configuration file. 2016-11-13 21:19:06 +01:00
Chteufleur a85f476600 Send multiple bodies with support of xml:lang. 2016-11-13 20:27:32 +01:00
Chteufleur 2f301ffc80 Remove unused code 2016-11-11 18:06:33 +01:00
Chteufleur c146c8dae2 Fix typo in README 2016-11-07 22:40:33 +01:00
Chteufleur bed3e755f7 Add packaging instructions and script 2016-11-07 22:38:49 +01:00
Chteufleur 90269e40e5 Add subdirectory « http-auth » for configuration file. 2016-09-29 20:18:12 +02:00
Chteufleur 88f7537bbd Add the XDG specification for configuration file location. 2016-09-29 19:55:33 +02:00
Chteufleur dfa6cd1b32 Fix forgoten change variables' names in go-xmpp 2016-09-28 22:49:40 +02:00
Chteufleur 39d865b082 Add namespace « jabber:iq:version » into disco feature. 2016-09-14 09:06:17 +02:00
Chteufleur a6f6ef86ac Add disco namespaces in feature send by disco. 2016-09-13 18:55:06 +02:00
Chteufleur a070b03c0f Add Disco support 2016-09-13 16:38:21 +02:00
Chteufleur a06062e714 Make the transaction ID mandatory again (for security issue). 2016-08-27 16:34:44 +02:00
Chteufleur b61490b75c Replace ampersand by semicolon in example request (README) to follow the RFC. 2016-08-27 16:31:49 +02:00
Chteufleur 89efb0137b Merge branch 'master' of https://git.kingpenguin.tk/chteufleur/HTTPAuthentificationOverXMPP
Conflicts:
	httpAuth.cfg
2016-08-21 21:24:50 +02:00
Chteufleur c788a29d46 Add bind address in config (IPv4 and IPv6) 2016-08-21 21:22:07 +02:00
Chteufleur 0b0611fc45 Add bind address in config (IPv4 and IPv6) 2016-08-21 21:16:35 +02:00
Chteufleur a1cd475788 Change default HTTP port in README 2016-08-21 13:14:04 +02:00
Chteufleur 178a9b4796 Change version to dev one 2016-08-20 22:18:53 +02:00
Chteufleur 7da25057cf Default HTTP(S) port to -1 + random port on config set to 0 2016-08-20 22:00:15 +02:00
Chteufleur 8847f220fd Send stanzas From attribute with jid.Full() 2016-08-20 18:42:19 +02:00
Chteufleur 4a5ff9f227 Remove cert.pem and key.pem, that way people will not use it 2016-08-20 18:16:45 +02:00
Chteufleur 2b101c1c1d Fix certificat domain check in case of SRV 2016-08-15 22:40:16 +02:00
Chteufleur b63a944cb2 Fix typo on http_timeout_sec 2016-08-15 22:21:50 +02:00
Chteufleur e2fbb6c8e3 Add the possibility to disable certificat verification 2016-08-15 22:16:03 +02:00
Chteufleur 7e9fd69879 Add how to build and run the project into readme 2016-08-09 22:58:28 +02:00
Chteufleur 78c8aecb8e Version 0.4
- Change README to add connection as client
 - Change config xmpp_hostname to xmpp_jid
 - Make it possible to configure client connection (server and port)
2016-07-23 13:03:18 +02:00
Chteufleur c88ec7a32d Add the possibility to connect as a client 2016-07-23 12:03:21 +02:00
Chteufleur 20dc1ee9e8 Change Client struct to Confirmation struct in order to be more clear 2016-07-21 22:32:44 +02:00
13 changed files with 461 additions and 237 deletions

View File

@ -1,52 +1,66 @@
# HTTPAuthentificationOverXMPP
# HTTPAuthenticationOverXMPP
Provide an HTTP anthentification over XMPP. Implementation of [XEP-0070](https://xmpp.org/extensions/xep-0070.html).
Provide an HTTP authentication over XMPP. Implementation of [XEP-0070](https://xmpp.org/extensions/xep-0070.html).
Can be run as a XMPP client or XMPP component.
## Compilation
### Dependencies
* [go-xmpp](https://git.kingpenguin.tk/chteufleur/go-xmpp) for the XMPP part.
* [cfg](https://github.com/jimlawless/cfg) for the configuration file.
### Build and run
You must first [install go environment](https://golang.org/doc/install) on your system.
Then, go into your $GOPATH directory and go get the source code.
```sh
go get git.kingpenguin.tk/chteufleur/HTTPAuthentificationOverXMPP.git
```
First, you need to go into directory ``$GOPATH/src/chteufleur/HTTPAuthentificationOverXMPP.git``.
Then, you can run the project directly by using command ``go run main.go``.
Or, in order to build the project you can run the command ``go build main.go``.
It will generate a binary that you can run as any binary file.
### Configure
Configure the gateway by editing the ``httpAuth.cfg`` file in order to give all XMPP component and HTTP server informations.
An example of the config file can be found in [the repos](https://git.kingpenguin.tk/chteufleur/HTTPAuthentificationOverXMPP/src/master/httpAuth.cfg).
Configure the gateway by editing the ``httpAuth.conf`` file in order to give all XMPP and HTTP server informations. This configuration file has to be placed following the [XDG specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) (example ``/etc/xdg/http-auth/httpAuth.conf``).
An example of the config file can be found in [the repos](https://git.kingpenguin.tk/chteufleur/HTTPAuthentificationOverXMPP/src/master/httpAuth.conf).
XMPP
* xmpp_server_address : Component server address connection (default: 127.0.0.1)
* xmpp_server_port : Component server port connection (default: 5347)
* xmpp_hostname : Component hostname
* xmpp_secret : Component password
* __xmpp_jid__ : Account JID
* __xmpp_secret__ : Account password
* xmpp_debug : Enable debug log at true (default: false)
* xmpp_verify_cert_validity : Enable certificate verification (default: true)
HTTP
* http_port : HTTP port to bind (default: 9090, desactive: -1)
* https_port : HTTPS port to bind (default: 9093, desactive: -1)
* http_port : HTTP port to bind (default: -1, desactive: -1)
* https_port : HTTPS port to bind (default: -1, desactive: -1)
* https_cert_path : Path to the certificate file (default: ./cert.pem)
* https_key_path : Path to the key file (default: ./key.pem)
* http_timeoute_sec : Define a timeout if user did not give an answer to the request (default: 60)
* http_timeout_sec : Define a timeout if user did not give an answer to the request (default: 60)
* http_bind_address_ipv4 : Bind address on IPv4 (default: 127.0.0.1)
* http_bind_address_ipv6 : Bind address on IPv6 (default: [::1])
__Bold config__ are mandatory.
### Utilization
If ``http_bind_address_ipv4`` is set to ``0.0.0.0``, it will bind all address on IPv4 __AND__ IPv6.
### Usage
To ask authorization, just send an HTTP request to the path ``/auth`` with parameters:
* __jid__ : JID of the user (user@host/resource or user@host)
* __domain__ : Domain you want to access
* __method__ : Method you access the domain
* transaction_id : Transaction identifier (auto generated if not provide)
* __transaction_id__ : Transaction identifier (auto generated if not provide)
* timeout : Timeout of the request in second (default : 60, max : 300)
__Bold parameters__ are mandatory.
Example:
```
GET /auth?jid=user%40host%2fresource&domain=example.org&method=POST&transaction_id=WhatEverYouWant&timeout=120 HTTP/1.1
GET /auth?jid=user%40host%2fresource;domain=example.org;method=POST;transaction_id=WhatEverYouWant;timeout=120 HTTP/1.1
```
This will send a request to the given JID, then return HTTP code depending on what appended.

View File

@ -1,22 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDmjCCAoICCQDTJ1wt8ibb0DANBgkqhkiG9w0BAQsFADCBjjELMAkGA1UEBhMC
RlIxEzARBgNVBAgMClNvbWUtU3RhdGUxEDAOBgNVBAcMB1ZhbGVuY2UxITAfBgNV
BAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEUMBIGA1UEAwwLZXhhbXBsZS5v
cmcxHzAdBgkqhkiG9w0BCQEWEHRvdG9AZXhhbXBsZS5vcmcwHhcNMTYwNzE0MDgx
MDA3WhcNMTcwNzE0MDgxMDA3WjCBjjELMAkGA1UEBhMCRlIxEzARBgNVBAgMClNv
bWUtU3RhdGUxEDAOBgNVBAcMB1ZhbGVuY2UxITAfBgNVBAoMGEludGVybmV0IFdp
ZGdpdHMgUHR5IEx0ZDEUMBIGA1UEAwwLZXhhbXBsZS5vcmcxHzAdBgkqhkiG9w0B
CQEWEHRvdG9AZXhhbXBsZS5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQCjNtyD6vXdVSj+Vmin/1t4JApafQ4475oJsvsNfd2cMgQ9856RZPyZFCCe
9veffUSV9ffYcgtPF8ZfRkOLZvSzYNYrrgI+Qsp2Y/Mw1hAupn2IadjdB0ZAFpZi
fnH5tuXSIiPbrl1sQZxSdIhgRPdj6scnBFwjbbm+DfyQYvjtm5LTOYbNOc/Sali+
lSdC22Z69nL7rscg3LFeBb8Oqx6MU8cvQ/nNsfxP+Ynimon3E28mP8VftyL81J6z
g8H6ly9R3kkapPrUg3CWP1z5rya+MdujcxkhEfO9oybSokaU/VkpMfrM4iBjs9m5
+hU3kWfVqB38OYUVEz2GagWrvDdJAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAD3/
fkCOvAhy38R10IgYEVH8MlrG3Rpy7CCYhUPgBrdFRgFAhwegDSc5my4rgiGmETkX
SlEppuHyoMNe8Sv70ibflqrCcwMPz/BWRlTDSWdwUqNjr4RfOT6LrO/9bZQT4tV8
Av4smslUaXiKuFyJLY1uFXbw2BqZTQQgaDQGdaqRNkCPRT00+VUbzR/qOEQ0Kv0U
QAX1tN1kmrJo5ccGB9WVvijp1ZlimMjtvh5v7Uxa6Wl4l7D9B1+5glEhPKunOlcK
MAHrHIuBRuuQUmtdk9zj3HYZtniChflPUO3+QWr3nfA6IhVEvz1wa4BQFzfwm4NY
eFcBWz6pr3zuawKjUlk=
-----END CERTIFICATE-----

View File

@ -5,6 +5,7 @@ import (
"fmt"
"log"
"math/rand"
"net/http"
"strconv"
"strings"
@ -28,23 +29,31 @@ const (
RETURN_VALUE_OK = "OK"
RETURN_VALUE_NOK = "NOK"
MAX_PORT_VAL = 65535
StatusUnknownError = 520
StatusUnreachable = 523
)
var (
HttpPortBind = 9090
HttpsPortBind = 9093
HttpPortBind = -1
HttpsPortBind = -1
CertPath = "./cert.pem"
KeyPath = "./key.pem"
ChanRequest = make(chan interface{}, 5)
TimeoutSec = 60 // 1 min
MaxTimeout = 300 // 5 min
BindAddressIPv4 = "127.0.0.1"
BindAddressIPv6 = "[::1]"
)
func init() {
rand.Seed(time.Now().UTC().UnixNano())
}
func indexHandler(w http.ResponseWriter, r *http.Request) {
// TODO
fmt.Fprintf(w, "Welcome to HTTP authentification over XMPP")
}
@ -53,15 +62,15 @@ func authHandler(w http.ResponseWriter, r *http.Request) {
jid := strings.Join(r.Form[PARAM_JID], "")
method := strings.Join(r.Form[METHOD_ACCESS], "")
domain := strings.Join(r.Form[DOMAIN_ACCESS], "")
transaction := strings.Join(r.Form[TRANSACTION_ID], "")
if jid == "" || method == "" || domain == "" {
if jid == "" || method == "" || domain == "" || transaction == "" {
// If mandatory params is missing
log.Printf("%sMandatory params is missing", LogInfo)
w.WriteHeader(http.StatusBadRequest)
return
}
transaction := strings.Join(r.Form[TRANSACTION_ID], "")
timeoutStr := strings.Join(r.Form[TIMEOUTE], "")
log.Printf("%sAuth %s", LogInfo, jid)
timeout, err := strconv.Atoi(timeoutStr)
@ -74,13 +83,13 @@ func authHandler(w http.ResponseWriter, r *http.Request) {
chanAnswer := make(chan string)
client := new(xmpp.Client)
client.JID = jid
client.Method = method
client.Domain = domain
client.Transaction = transaction
client.ChanReply = chanAnswer
client.QueryClient()
confirmation := new(xmpp.Confirmation)
confirmation.JID = jid
confirmation.Method = method
confirmation.Domain = domain
confirmation.Transaction = transaction
confirmation.ChanReply = chanAnswer
confirmation.SendConfirmation()
select {
case answer := <-chanAnswer:
@ -101,14 +110,14 @@ func authHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusUnauthorized)
}
switch client.TypeSend {
switch confirmation.TypeSend {
case xmpp.TYPE_SEND_IQ:
log.Printf("%sDelete IQ", LogDebug)
delete(xmpp.WaitIqMessages, client.IdMap)
delete(xmpp.WaitIqMessages, confirmation.IdMap)
case xmpp.TYPE_SEND_MESSAGE:
log.Printf("%sDelete Message", LogDebug)
delete(xmpp.WaitMessageAnswers, client.IdMap)
delete(xmpp.WaitMessageAnswers, confirmation.IdMap)
}
}
@ -119,26 +128,44 @@ func Run() {
http.HandleFunc(ROUTE_AUTH, authHandler)
if HttpPortBind > 0 {
go runHttp()
go runHttp(BindAddressIPv4)
if BindAddressIPv4 != "0.0.0.0" {
go runHttp(BindAddressIPv6)
}
} else if HttpPortBind == 0 {
HttpPortBind = rand.Intn(MAX_PORT_VAL)
go runHttp(BindAddressIPv4)
if BindAddressIPv4 != "0.0.0.0" {
go runHttp(BindAddressIPv6)
}
}
if HttpsPortBind > 0 {
go runHttps()
go runHttps(BindAddressIPv4)
if BindAddressIPv6 != "0.0.0.0" {
go runHttps(BindAddressIPv6)
}
} else if HttpsPortBind == 0 {
HttpsPortBind = rand.Intn(MAX_PORT_VAL)
go runHttps(BindAddressIPv4)
if BindAddressIPv6 != "0.0.0.0" {
go runHttps(BindAddressIPv6)
}
}
}
func runHttp() {
func runHttp(bindAddress string) {
port := strconv.Itoa(HttpPortBind)
log.Printf("%sHTTP listenning on port %s", LogInfo, port)
err := http.ListenAndServe(":"+port, nil)
log.Printf("%sHTTP listenning on %s:%s", LogInfo, bindAddress, port)
err := http.ListenAndServe(bindAddress+":"+port, nil)
if err != nil {
log.Fatal("%sListenAndServe: ", LogError, err)
}
}
func runHttps() {
func runHttps(bindAddress string) {
port := strconv.Itoa(HttpsPortBind)
log.Printf("%sHTTPS listenning on port %s", LogInfo, port)
err := http.ListenAndServeTLS(":"+port, CertPath, KeyPath, nil)
log.Printf("%sHTTPS listenning on %s:%s", LogInfo, bindAddress, port)
err := http.ListenAndServeTLS(bindAddress+":"+port, CertPath, KeyPath, nil)
if err != nil {
log.Fatal("%sListenAndServe: ", LogError, err)
}

View File

@ -1,13 +1,16 @@
# XMPP informations
# XMPP informations (component)
xmpp_server_address=192.168.1.2
xmpp_server_port=5347
xmpp_hostname=xmppsteam.kingpenguin.tk
xmpp_jid=xmppsteam.kingpenguin.tk
xmpp_secret=xmpp4steam_password
xmpp_debug=true
xmpp_verify_cert_validity=true
# HTTP informations
http_bind_address_ipv4=127.0.0.1
http_bind_address_ipv6=[::1]
http_port=9090
https_port=9093
https_cert_path=./cert.pem
https_key_path=./key.pem
http_timeoute_sec=60
http_timeout_sec=60

27
key.pem
View File

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAozbcg+r13VUo/lZop/9beCQKWn0OOO+aCbL7DX3dnDIEPfOe
kWT8mRQgnvb3n31ElfX32HILTxfGX0ZDi2b0s2DWK64CPkLKdmPzMNYQLqZ9iGnY
3QdGQBaWYn5x+bbl0iIj265dbEGcUnSIYET3Y+rHJwRcI225vg38kGL47ZuS0zmG
zTnP0mpYvpUnQttmevZy+67HINyxXgW/DqsejFPHL0P5zbH8T/mJ4pqJ9xNvJj/F
X7ci/NSes4PB+pcvUd5JGqT61INwlj9c+a8mvjHbo3MZIRHzvaMm0qJGlP1ZKTH6
zOIgY7PZufoVN5Fn1agd/DmFFRM9hmoFq7w3SQIDAQABAoIBAQCAI6lbVJP1Uk/d
5v9BrkUk/L64LmiFIPAB32glPoVHhSk5blQ2+F8s29WEmIbuy42WYsdUQq1ISnUv
Bd4vywQg9M0Q/Au8z/lem7gpxlZsGcCC4f8mAPkRhepJp9ZZ5FNo9+7JIYstXBGb
1uvfESZdZs02f8DK+/GRGjAJN/sRqAo4MvwZzrX9DIMnTt5MiujCAWOMX4rXT316
epSyUAxMnNzC3On32TmGqrHXTF7KMGPjbSupAHDS4F8iL9ntFF2QPcW5um8jEneZ
ln/oJo2+2LYFJPcj0BbvCAcRaIumvHNQgfC0ispStCR37IlEZKyQXUtX/z/BPQO/
d8KzylqRAoGBANl1tMVz4d20kkQzyXOCmQ7C/y98AHWDiFH2Iq/QMV21TFomit66
SR8RVwWH3G6C8cQRHFa504iDP3Lo/jq+rf4SMwEJq+X5aVkThCq5DDZech/qslyF
FTA4IIkcqxF5/UOQg3UxGKU2I7mKLLgnhaPvPqcd7t/PzXAgJqV45ElFAoGBAMAk
AHPZFpb5IMPbVSen/jZ6cbNtRsOyUv2Vlz+Pkjbfl0SsxOInPZxwwEcEgLC2micN
CH1rTZXpMLKOr/VBdkbp4uMMR8X2kro02b9zlmBmAXleyuAPbQAbRn4epnnfzqxb
TcJNHQJT4uQRhtshTQ2GqnHXei/ZMBrI35VCN5w1AoGBAM9c3LqE3Fbrv6Zls64A
VS+sZmbDWjS07qMpkL4SS2DOZzZ4Fmh5PwzvHgpaGasQFrcekeVpYfuFHFXZM8SU
25mxhQ1ySYcNJJYadCfBOZIG0dD5nod3KFNI0k2tFrudlhJ9lb2EybmRPNPKnQYm
OduvYhE+C/FEWOSY5AFanGX5AoGARxWKrVFlUBl/C7a7fF5kaFdIdW86PPBeT77m
I/fDylVSK3AXruuBmb0FBcEes0H7KfNibrQiEhIhmA29/2hmj7m73PAQJachhY5D
+NaUjblvVi3BtL9APkfY/pPsVy570bw9umK5FsFeMa5iS/O4BAcMS+3CIK2jZGVo
glnrJPkCgYEAiI0nFwbd5oP4bP41zDKU7lrnwrmbCKZr5ZYScw1NtSqKo7aJnhEi
fBYMT8aXHVt4ALcI/VzR8LnoTQcJ6Pibn0pZ91sE8FRk99qfnF0rK4LLKF6R+UJH
X+dmoMOcHQ9MEyPhNvdgsTo72KMPzJn8q58VTq/6I4we+VCOlZNwlCA=
-----END RSA PRIVATE KEY-----

84
main.go
View File

@ -10,16 +10,17 @@ import (
"os"
"os/signal"
"strconv"
"strings"
"syscall"
"time"
)
const (
Version = "v0.3.1"
configurationFilePath = "httpAuth.cfg"
default_xmpp_server_address = "127.0.0.1"
default_xmpp_server_port = "5347"
Version = "v0.5-dev"
configurationFilePath = "http-auth/httpAuth.conf"
langFilePath = "http-auth/messages.lang"
PathConfEnvVariable = "XDG_CONFIG_DIRS"
DefaultXdgConfigDirs = "/etc/xdg"
)
var (
@ -29,13 +30,13 @@ var (
func init() {
log.Printf("Running HTTP-Auth %v", Version)
err := cfg.Load(configurationFilePath, mapConfig)
if err != nil {
log.Fatal("Failed to load configuration file.", err)
if !loadConfigFile() {
log.Fatal("Failed to load configuration file.")
}
loadLangFile()
// HTTP config
httpTimeout, err := strconv.Atoi(mapConfig["http_timeoute_sec"])
httpTimeout, err := strconv.Atoi(mapConfig["http_timeout_sec"])
if err == nil && httpTimeout > 0 && httpTimeout < http.MaxTimeout {
log.Println("Define HTTP timeout to " + strconv.Itoa(httpTimeout) + " second")
http.TimeoutSec = httpTimeout
@ -52,28 +53,71 @@ func init() {
http.CertPath = mapConfig["https_cert_path"]
http.KeyPath = mapConfig["https_key_path"]
}
bindAddressIPv4 := mapConfig["http_bind_address_ipv4"]
if bindAddressIPv4 != "" {
http.BindAddressIPv4 = bindAddressIPv4
}
bindAddressIPv6 := mapConfig["http_bind_address_ipv6"]
if bindAddressIPv6 != "" {
http.BindAddressIPv6 = bindAddressIPv6
}
// XMPP config
xmpp_server_address := mapConfig["xmpp_server_address"]
if xmpp_server_address == "" {
xmpp_server_address = default_xmpp_server_address
if xmpp_server_address != "" {
xmpp.Addr = xmpp_server_address
}
xmpp_server_port := mapConfig["xmpp_server_port"]
if xmpp_server_port == "" {
xmpp_server_port = default_xmpp_server_port
if xmpp_server_port != "" {
xmpp.Port = xmpp_server_port
}
xmpp.Addr = xmpp_server_address + ":" + xmpp_server_port
xmpp.JidStr = mapConfig["xmpp_hostname"]
xmpp.JidStr = mapConfig["xmpp_jid"]
xmpp.Secret = mapConfig["xmpp_secret"]
xmpp.Debug = mapConfig["xmpp_debug"] == "true"
xmpp.VerifyCertValidity = mapConfig["xmpp_verify_cert_validity"] != "false" // Default TRUE
}
func getChanString(c chan interface{}) string {
ret := ""
i := <-c
if v, ok := i.(string); ok {
ret = v
func loadConfigFile() bool {
ret := false
envVariable := os.Getenv(PathConfEnvVariable)
if envVariable == "" {
envVariable = DefaultXdgConfigDirs
}
for _, path := range strings.Split(envVariable, ":") {
log.Println("Try to find configuration file into " + path)
configFile := path + "/" + configurationFilePath
if _, err := os.Stat(configFile); err == nil {
// The config file exist
if cfg.Load(configFile, mapConfig) == nil {
// And has been loaded succesfully
log.Println("Find configuration file at " + configFile)
ret = true
break
}
}
}
return ret
}
func loadLangFile() bool {
ret := false
envVariable := os.Getenv(PathConfEnvVariable)
if envVariable == "" {
envVariable = DefaultXdgConfigDirs
}
for _, path := range strings.Split(envVariable, ":") {
log.Println("Try to find messages lang file into " + path)
langFile := path + "/" + langFilePath
if _, err := os.Stat(langFile); err == nil {
// The config file exist
if cfg.Load(langFile, xmpp.MapLangs) == nil {
// And has been loaded succesfully
log.Println("Find messages lang file at " + langFile)
ret = true
break
}
}
}
return ret
}

2
messages.lang Normal file
View File

@ -0,0 +1,2 @@
en=_DOMAIN_ (with method _METHOD_) need to validate your identity, do you agree ?\nValidation code : _VALIDE_CODE_\nPlease check that this code is the same as on _DOMAIN_.\n\nIf your client doesn't support that functionnality, please send back the validation code to confirm the request.
fr=_DOMAIN_ (avec la methode _METHOD_) a besoin de valider votre identitée, acceptez-vous ?\nVeuillez vérifier que ce code est le même que sur _DOMAIN_.\n\nSi votre client ne support pas cette fonctionnalitée, veuilez renvoyer le code de validation pour confirmer la requête.

40
packaging/PACKAGE.md Normal file
View File

@ -0,0 +1,40 @@
# Packaging
## Build libraries as shared
In order to packaged the project, you will need to follow those different steps.
We will build libraries as shared libraries.
The first step is to buid the GO standard library as shared.
```
$ cd $GOPATH
$ go install -buildmode=shared std
```
Next, we will build the 2 libraries to make them shared…
```
$ cd $GOPATH
$ go install -buildmode=shared -linkshared github.com/jimlawless/cfg
$ go install -buildmode=shared -linkshared git.kingpenguin.tk/chteufleur/go-xmpp.git/src/xmpp
```
Then finally, build the project with the option that tell to use libraries as shared and not static.
```
$ cd $GOPATH
$ go install -linkshared git.kingpenguin.tk/chteufleur/HTTPAuthentificationOverXMPP.git
```
You will find shared libraries over here :
* ``$GOROOT/pkg/linux_amd64_dynlink/libstd.so``
* ``$GOPATH/pkg/linux_amd64_dynlink/libgithub.com-jimlawless-cfg.so``
* ``$GOPATH/pkg/linux_amd64_dynlink/libgit.kingpenguin.tk-chteufleur-go-xmpp.git-src-xmpp.so``
And the binary file here :
* ``$GOPATH/bin/HTTPAuthentificationOverXMPP.git``
An example can be found [here](https://github.com/jbuberel/buildmodeshared/tree/master/gofromgo).
## Configuration
At the installation, the folder ``$XDG_CONFIG_DIRS/http-auth/`` need to be created (example ``/etc/xdg/http-auth``) where to place the configuration file named ``httpAuth.conf`` and the file lang configuration named ``messages.lang``.

View File

@ -0,0 +1,9 @@
[Unit]
Description=Provide an HTTP authentication over XMPP (https://xmpp.org/extensions/xep-0070.html)
[Service]
ExecStart=/etc/init.d/http-auth.sh start
ExecStop=/etc/init.d/http-auth.sh stop
[Install]
WantedBy=multi-user.target

53
scripts/http-auth.sh Normal file
View File

@ -0,0 +1,53 @@
#!/bin/sh
#######################
name="httpAuth"
dirPath="/usr/local/sbin"
#######################
sh="/bin/bash"
pidPath="/var/run/http-auth/http-auth.pid"
#######################
do_start() {
nohup $dirPath/$name &
/bin/echo $! > $pidPath
}
do_stop() {
if [ -e $pidPath ]
then
pid=$(/bin/cat $pidPath)
kill $pid
rm $pidPath
fi
}
get_status() {
if [ -e $pidPath ] ; then
pid=$(/bin/cat $pidPath) ;
/bin/echo "$name is running with PID $pid" ;
else
/bin/echo "$name is stopped" ;
fi
}
case $1 in
start)
do_start
;;
stop)
do_stop
;;
restart)
do_stop
do_start
;;
status)
get_status
;;
*)
/bin/echo "Usage: $0 {start|stop|restart|status}"
exit 2
;;
esac

View File

@ -1,93 +0,0 @@
package xmpp
import (
"git.kingpenguin.tk/chteufleur/go-xmpp.git/src/xmpp"
"crypto/rand"
"log"
"strconv"
)
const (
dictionary = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
REPLY_UNREACHABLE = "reply_unreachable"
REPLY_DENY = "reply_deny"
REPLY_OK = "reply_ok"
TYPE_SEND_MESSAGE = "type_send_message"
TYPE_SEND_IQ = "type_send_iq"
)
type Client struct {
JID string
Method string
Domain string
Transaction string
TypeSend string
IdMap string
ChanReply chan string
}
func (client *Client) QueryClient() {
log.Printf("%sQuery JID %s", LogInfo, client.JID)
isAutoGeneratedTranctionID := false
if client.Transaction == "" {
// Random transaction ID generation
client.Transaction = TransactionID()
isAutoGeneratedTranctionID = true
}
clientJID, _ := xmpp.ParseJID(client.JID)
if clientJID.Resource == "" {
client.askViaMessage(isAutoGeneratedTranctionID)
} else {
client.askViaIQ()
}
}
func (client *Client) askViaIQ() {
stanzaID++
stanzaIDstr := strconv.Itoa(stanzaID)
m := xmpp.Iq{Type: xmpp.IQTypeGet, To: client.JID, From: jid.Domain, Id: stanzaIDstr}
confirm := &xmpp.Confirm{Id: client.Transaction, Method: client.Method, URL: client.Domain}
m.PayloadEncode(confirm)
WaitIqMessages[stanzaIDstr] = client
comp.Out <- m
client.TypeSend = TYPE_SEND_IQ
client.IdMap = stanzaIDstr
}
func (client *Client) askViaMessage(isAutoGeneratedTranctionID bool) {
m := xmpp.Message{From: jid.Domain, To: client.JID, Type: xmpp.MessageTypeNormal}
m.Thread = xmpp.SessionID()
m.Body = client.Domain + " (with method " + client.Method + ") need to validate your identity, do you agree ?"
m.Body += "\nValidation code : " + client.Transaction
if !isAutoGeneratedTranctionID {
// Send only if the transaction ID is not autogenerated
m.Body += "\nPlease check that this code is the same as on " + client.Domain
}
m.Body += "\n\nIf your client doesn't support that functionnality, please send back the validation code to confirm the request."
m.Confir = &xmpp.Confirm{Id: client.Transaction, Method: client.Method, URL: client.Domain}
log.Printf("%sSend message %v", LogInfo, m)
WaitMessageAnswers[client.Transaction] = client
comp.Out <- m
client.TypeSend = TYPE_SEND_MESSAGE
client.IdMap = client.Transaction
}
func TransactionID() string {
var bytes = make([]byte, 8)
if _, err := rand.Read(bytes); err != nil {
panic(err)
}
for k, v := range bytes {
bytes[k] = dictionary[v%byte(len(dictionary))]
}
return string(bytes)
}

97
xmpp/confirmation.go Normal file
View File

@ -0,0 +1,97 @@
package xmpp
import (
"git.kingpenguin.tk/chteufleur/go-xmpp.git/src/xmpp"
"log"
"strconv"
"strings"
)
const (
REPLY_UNREACHABLE = "reply_unreachable"
REPLY_DENY = "reply_deny"
REPLY_OK = "reply_ok"
TYPE_SEND_MESSAGE = "type_send_message"
TYPE_SEND_IQ = "type_send_iq"
TEMPLATE_DOMAIN = "_DOMAIN_"
TEMPLATE_METHOD = "_METHOD_"
TEMPLATE_VALIDATION_CODE = "_VALIDE_CODE_"
DEFAULT_MESSAGE = "_DOMAIN_ (with method _METHOD_) need to validate your identity, do you agree ?\nValidation code : _VALIDE_CODE_\nPlease check that this code is the same as on _DOMAIN_.\n\nIf your client doesn't support that functionnality, please send back the validation code to confirm the request."
)
var (
MapLangs = make(map[string]string)
)
type Confirmation struct {
JID string
Method string
Domain string
Transaction string
TypeSend string
IdMap string
ChanReply chan string
}
func (confirmation *Confirmation) SendConfirmation() {
log.Printf("%sQuery JID %s", LogInfo, confirmation.JID)
clientJID, _ := xmpp.ParseJID(confirmation.JID)
if clientJID.Resource == "" {
confirmation.askViaMessage()
} else {
confirmation.askViaIQ()
}
}
func (confirmation *Confirmation) askViaIQ() {
stanzaID++
stanzaIDstr := strconv.Itoa(stanzaID)
m := xmpp.Iq{Type: xmpp.IQTypeGet, To: confirmation.JID, From: jid.Full(), Id: stanzaIDstr}
confirm := &xmpp.Confirm{Id: confirmation.Transaction, Method: confirmation.Method, URL: confirmation.Domain}
m.PayloadEncode(confirm)
WaitIqMessages[stanzaIDstr] = confirmation
comp.Out <- m
confirmation.TypeSend = TYPE_SEND_IQ
confirmation.IdMap = stanzaIDstr
}
func (confirmation *Confirmation) askViaMessage() {
m := xmpp.Message{From: jid.Full(), To: confirmation.JID, Type: xmpp.MessageTypeNormal}
m.Thread = xmpp.SessionID()
confirmation.setBodies(&m)
m.Confir = &xmpp.Confirm{Id: confirmation.Transaction, Method: confirmation.Method, URL: confirmation.Domain}
log.Printf("%sSend message %v", LogInfo, m)
WaitMessageAnswers[confirmation.Transaction] = confirmation
comp.Out <- m
confirmation.TypeSend = TYPE_SEND_MESSAGE
confirmation.IdMap = confirmation.Transaction
}
func (confirmation *Confirmation) setBodies(message *xmpp.Message) {
msg := DEFAULT_MESSAGE
if len(MapLangs) == 0 {
msg = strings.Replace(msg, TEMPLATE_DOMAIN, confirmation.Domain, -1)
msg = strings.Replace(msg, TEMPLATE_METHOD, confirmation.Method, -1)
msg = strings.Replace(msg, TEMPLATE_VALIDATION_CODE, confirmation.Transaction, -1)
message.Body = append(message.Body, xmpp.MessageBody{Lang: "en", Value: msg})
} else {
for key, val := range MapLangs {
msg = val
msg = strings.Replace(msg, TEMPLATE_DOMAIN, confirmation.Domain, -1)
msg = strings.Replace(msg, TEMPLATE_METHOD, confirmation.Method, -1)
msg = strings.Replace(msg, TEMPLATE_VALIDATION_CODE, confirmation.Transaction, -1)
msg = strings.Replace(msg, "\\n", "\n", -1)
msg = strings.Replace(msg, "\\r", "\r", -1)
msg = strings.Replace(msg, "\\t", "\t", -1)
message.Body = append(message.Body, xmpp.MessageBody{Lang: key, Value: msg})
}
}
}

View File

@ -8,13 +8,17 @@ import (
)
const (
LogInfo = "\t[XMPP COMPONENT INFO]\t"
LogError = "\t[XMPP COMPONENT ERROR]\t"
LogDebug = "\t[XMPP COMPONENT DEBUG]\t"
LogInfo = "\t[XMPP INFO]\t"
LogError = "\t[XMPP ERROR]\t"
LogDebug = "\t[XMPP DEBUG]\t"
DEFAULT_SERVER_ADDRESS = "127.0.0.1"
DEFAULT_SERVER_PORT = "5347"
)
var (
Addr = "127.0.0.1:5347"
Addr = ""
Port = ""
JidStr = ""
Secret = ""
@ -28,18 +32,55 @@ var (
ChanAction = make(chan string)
WaitMessageAnswers = make(map[string]*Client)
WaitIqMessages = make(map[string]*Client)
WaitMessageAnswers = make(map[string]*Confirmation)
WaitIqMessages = make(map[string]*Confirmation)
Debug = true
Debug = true
VerifyCertValidity = true
identity = &xmpp.DiscoIdentity{Category: "client", Type: "bot", Name: "HTTP authentication over XMPP"}
)
func Run() {
var addr string
var isComponent bool
log.Printf("%sRunning", LogInfo)
// Create stream and configure it as a component connection.
jid = must(xmpp.ParseJID(JidStr)).(xmpp.JID)
stream = must(xmpp.NewStream(Addr, &xmpp.StreamConfig{LogStanzas: Debug})).(*xmpp.Stream)
comp = must(xmpp.NewComponentXMPP(stream, jid, Secret)).(*xmpp.XMPP)
isComponent = jid.Node == ""
if isComponent {
// component
if Addr == "" {
Addr = DEFAULT_SERVER_ADDRESS
}
if Port == "" {
Port = DEFAULT_SERVER_PORT
}
addr = Addr + ":" + Port
} else {
// client
if Addr == "" {
addrs := must(xmpp.HomeServerAddrs(jid)).([]string)
addr = addrs[0]
} else {
if Port == "" {
Port = DEFAULT_SERVER_PORT
}
addr = Addr + ":" + Port
}
}
log.Printf("%sConnecting to %s", LogInfo, addr)
stream = must(xmpp.NewStream(addr, &xmpp.StreamConfig{LogStanzas: Debug, ConnectionDomain: jid.Domain})).(*xmpp.Stream)
if isComponent {
comp = must(xmpp.NewComponentXMPP(stream, jid, Secret)).(*xmpp.XMPP)
} else {
comp = must(xmpp.NewClientXMPP(stream, jid, Secret, &xmpp.ClientConfig{InsecureSkipVerify: !VerifyCertValidity})).(*xmpp.XMPP)
comp.Out <- xmpp.Presence{}
}
mainXMPP()
log.Printf("%sReach main method's end", LogInfo)
@ -54,22 +95,27 @@ func mainXMPP() {
case *xmpp.Message:
confirm := v.Confir
if confirm != nil {
client := WaitMessageAnswers[confirm.Id]
processConfirm(v, client)
confirmation := WaitMessageAnswers[confirm.Id]
processConfirm(v, confirmation)
} else {
// If body is the confirmation id, it will be considerated as accepted.
// In order to be compatible with all clients.
client := WaitMessageAnswers[v.Body]
jidFrom, _ := xmpp.ParseJID(v.From)
if client != nil && client.JID == jidFrom.Bare() {
processConfirm(v, client)
// In order to be compatible with all confirmations.
if len(v.Body) > 0 {
confirmation := WaitMessageAnswers[v.Body[0].Value]
jidFrom, _ := xmpp.ParseJID(v.From)
if confirmation != nil && confirmation.JID == jidFrom.Bare() {
processConfirm(v, confirmation)
}
}
}
case *xmpp.Iq:
switch v.PayloadName().Space {
case xmpp.NSDiscoInfo:
execDisco(v)
case xmpp.NSDiscoItems:
execDiscoCommand(v)
execDisco(v)
case xmpp.NSVCardTemp:
reply := v.Response(xmpp.IQTypeResult)
@ -85,17 +131,17 @@ func mainXMPP() {
case xmpp.NSHTTPAuth:
confirm := &xmpp.Confirm{}
v.PayloadDecode(confirm)
client := WaitIqMessages[v.Id]
processConfirm(v, client)
confirmation := WaitIqMessages[v.Id]
processConfirm(v, confirmation)
default:
// Handle reply iq that doesn't contain HTTP-Auth namespace
client := WaitIqMessages[v.Id]
processConfirm(v, client)
confirmation := WaitIqMessages[v.Id]
processConfirm(v, confirmation)
if client == nil {
if confirmation == nil {
reply := v.Response(xmpp.IQTypeError)
reply.PayloadEncode(xmpp.NewError("cancel", xmpp.FeatureNotImplemented, ""))
reply.PayloadEncode(xmpp.NewError("cancel", xmpp.ErrorFeatureNotImplemented, ""))
comp.Out <- reply
}
}
@ -106,38 +152,38 @@ func mainXMPP() {
}
}
func processConfirm(x interface{}, client *Client) {
func processConfirm(x interface{}, confirmation *Confirmation) {
mes, mesOK := x.(*xmpp.Message)
iq, iqOK := x.(*xmpp.Iq)
if client != nil {
if confirmation != nil {
if mesOK && mes.Error != nil {
// Message error
errCondition := mes.Error.Condition()
if errCondition == xmpp.ServiceUnavailable {
if errCondition == xmpp.ErrorServiceUnavailable {
// unreachable
client.ChanReply <- REPLY_UNREACHABLE
confirmation.ChanReply <- REPLY_UNREACHABLE
} else {
client.ChanReply <- REPLY_DENY
confirmation.ChanReply <- REPLY_DENY
}
} else if iqOK && iq.Error != nil {
// IQ error
errCondition := iq.Error.Condition()
if errCondition == xmpp.ServiceUnavailable || errCondition == xmpp.FeatureNotImplemented {
if errCondition == xmpp.ErrorServiceUnavailable || errCondition == xmpp.ErrorFeatureNotImplemented {
// send by message if client doesn't implemente it
client.JID = strings.SplitN(client.JID, "/", 2)[0]
go client.QueryClient()
} else if errCondition == xmpp.RemoteServerNotFound {
confirmation.JID = strings.SplitN(confirmation.JID, "/", 2)[0]
go confirmation.SendConfirmation()
} else if errCondition == xmpp.ErrorRemoteServerNotFound {
// unreachable
client.ChanReply <- REPLY_UNREACHABLE
confirmation.ChanReply <- REPLY_UNREACHABLE
} else {
client.ChanReply <- REPLY_DENY
confirmation.ChanReply <- REPLY_DENY
}
} else {
// No error
client.ChanReply <- REPLY_OK
confirmation.ChanReply <- REPLY_OK
}
}
}
@ -149,8 +195,39 @@ func must(v interface{}, err error) interface{} {
return v
}
func execDisco(iq *xmpp.Iq) {
log.Printf("%sDisco Feature", LogInfo)
discoInfoReceived := &xmpp.DiscoItems{}
iq.PayloadDecode(discoInfoReceived)
switch iq.PayloadName().Space {
case xmpp.NSDiscoInfo:
reply := iq.Response(xmpp.IQTypeResult)
discoInfo := &xmpp.DiscoInfo{}
discoInfo.Identity = append(discoInfo.Identity, *identity)
discoInfo.Feature = append(discoInfo.Feature, xmpp.DiscoFeature{Var: xmpp.NSDiscoInfo})
discoInfo.Feature = append(discoInfo.Feature, xmpp.DiscoFeature{Var: xmpp.NSDiscoItems})
discoInfo.Feature = append(discoInfo.Feature, xmpp.DiscoFeature{Var: xmpp.NSJabberClient})
reply.PayloadEncode(discoInfo)
comp.Out <- reply
case xmpp.NSDiscoItems:
if discoInfoReceived.Node == xmpp.NodeAdHocCommand {
execDiscoCommand(iq)
} else {
reply := iq.Response(xmpp.IQTypeResult)
discoItems := &xmpp.DiscoItems{}
reply.PayloadEncode(discoItems)
comp.Out <- reply
}
}
}
func execDiscoCommand(iq *xmpp.Iq) {
log.Printf("%sDiscovery item iq received", LogInfo)
log.Printf("%sAd-Hoc Command", LogInfo)
reply := iq.Response(xmpp.IQTypeResult)
discoItem := &xmpp.DiscoItems{Node: xmpp.NodeAdHocCommand}
reply.PayloadEncode(discoItem)