implement client and start tester daemon
This commit is contained in:
		
							parent
							
								
									79459ff864
								
							
						
					
					
						commit
						a67d26d5a2
					
				|  | @ -15,3 +15,5 @@ | ||||||
| .glide/ | .glide/ | ||||||
| 
 | 
 | ||||||
| tmp | tmp | ||||||
|  | yaja*.conf | ||||||
|  | !yaja*example.conf | ||||||
|  |  | ||||||
|  | @ -0,0 +1,275 @@ | ||||||
|  | package client | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"crypto/md5" | ||||||
|  | 	"crypto/rand" | ||||||
|  | 	"crypto/tls" | ||||||
|  | 	"encoding/base64" | ||||||
|  | 	"encoding/xml" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"math/big" | ||||||
|  | 	"net" | ||||||
|  | 	"strings" | ||||||
|  | 
 | ||||||
|  | 	"dev.sum7.eu/genofire/yaja/messages" | ||||||
|  | 	"dev.sum7.eu/genofire/yaja/model" | ||||||
|  | 	"dev.sum7.eu/genofire/yaja/server/utils" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Client holds XMPP connection opitons
 | ||||||
|  | type Client struct { | ||||||
|  | 	conn net.Conn // connection to server
 | ||||||
|  | 	Out  *xml.Encoder | ||||||
|  | 	In   *xml.Decoder | ||||||
|  | 
 | ||||||
|  | 	JID *model.JID | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func NewClient(jid model.JID, password string) (*Client, error) { | ||||||
|  | 	conn, err := net.Dial("tcp", jid.Domain+":5222") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	client := &Client{ | ||||||
|  | 		conn: conn, | ||||||
|  | 		In:   xml.NewDecoder(conn), | ||||||
|  | 		Out:  xml.NewEncoder(conn), | ||||||
|  | 
 | ||||||
|  | 		JID: &jid, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err = client.connect(password); err != nil { | ||||||
|  | 		client.Close() | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return client, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Close closes the XMPP connection
 | ||||||
|  | func (c *Client) Close() error { | ||||||
|  | 	if c.conn != (*tls.Conn)(nil) { | ||||||
|  | 		return c.conn.Close() | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (client *Client) Read() (*xml.StartElement, error) { | ||||||
|  | 	for { | ||||||
|  | 		nextToken, err := client.In.Token() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		switch nextToken.(type) { | ||||||
|  | 		case xml.StartElement: | ||||||
|  | 			element := nextToken.(xml.StartElement) | ||||||
|  | 			return &element, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | func (client *Client) ReadElement(p interface{}) error { | ||||||
|  | 	element, err := client.Read() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return client.In.DecodeElement(p, element) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (client *Client) init() error { | ||||||
|  | 	// XMPP-Connection
 | ||||||
|  | 	_, err := fmt.Fprintf(client.conn, "<?xml version='1.0'?>\n"+ | ||||||
|  | 		"<stream:stream to='%s' xmlns='%s'\n"+ | ||||||
|  | 		" xmlns:stream='%s' version='1.0'>\n", | ||||||
|  | 		model.XMLEscape(client.JID.Domain), messages.NSClient, messages.NSStream) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	element, err := client.Read() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if element.Name.Space != messages.NSStream || element.Name.Local != "stream" { | ||||||
|  | 		return errors.New("is not stream") | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | func (client *Client) connect(password string) error { | ||||||
|  | 	if err := client.init(); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	var f messages.StreamFeatures | ||||||
|  | 	if err := client.ReadElement(&f); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if err := client.Out.Encode(&messages.TLSStartTLS{}); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var p messages.TLSProceed | ||||||
|  | 	if err := client.ReadElement(&p); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	// Change tcp to tls
 | ||||||
|  | 	tlsconn := tls.Client(client.conn, &tls.Config{ | ||||||
|  | 		ServerName: client.JID.Domain, | ||||||
|  | 	}) | ||||||
|  | 	client.conn = tlsconn | ||||||
|  | 	client.In = xml.NewDecoder(client.conn) | ||||||
|  | 	client.Out = xml.NewEncoder(client.conn) | ||||||
|  | 
 | ||||||
|  | 	if err := tlsconn.Handshake(); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if err := tlsconn.VerifyHostname(client.JID.Domain); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if err := client.init(); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	//auth:
 | ||||||
|  | 	if err := client.ReadElement(&f); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	mechanism := "" | ||||||
|  | 	for _, m := range f.Mechanisms.Mechanism { | ||||||
|  | 		if m == "PLAIN" { | ||||||
|  | 			mechanism = m | ||||||
|  | 			// Plain authentication: send base64-encoded \x00 user \x00 password.
 | ||||||
|  | 			raw := "\x00" + client.JID.Local + "\x00" + password | ||||||
|  | 			enc := make([]byte, base64.StdEncoding.EncodedLen(len(raw))) | ||||||
|  | 			base64.StdEncoding.Encode(enc, []byte(raw)) | ||||||
|  | 			fmt.Fprintf(client.conn, "<auth xmlns='%s' mechanism='PLAIN'>%s</auth>\n", messages.NSSASL, enc) | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 		if m == "DIGEST-MD5" { | ||||||
|  | 			mechanism = m | ||||||
|  | 			// Digest-MD5 authentication
 | ||||||
|  | 			fmt.Fprintf(client.conn, "<auth xmlns='%s' mechanism='DIGEST-MD5'/>\n", messages.NSSASL) | ||||||
|  | 			var ch string | ||||||
|  | 			if err := client.ReadElement(&ch); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			b, err := base64.StdEncoding.DecodeString(string(ch)) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			tokens := map[string]string{} | ||||||
|  | 			for _, token := range strings.Split(string(b), ",") { | ||||||
|  | 				kv := strings.SplitN(strings.TrimSpace(token), "=", 2) | ||||||
|  | 				if len(kv) == 2 { | ||||||
|  | 					if kv[1][0] == '"' && kv[1][len(kv[1])-1] == '"' { | ||||||
|  | 						kv[1] = kv[1][1 : len(kv[1])-1] | ||||||
|  | 					} | ||||||
|  | 					tokens[kv[0]] = kv[1] | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			realm, _ := tokens["realm"] | ||||||
|  | 			nonce, _ := tokens["nonce"] | ||||||
|  | 			qop, _ := tokens["qop"] | ||||||
|  | 			charset, _ := tokens["charset"] | ||||||
|  | 			cnonceStr := cnonce() | ||||||
|  | 			digestURI := "xmpp/" + client.JID.Domain | ||||||
|  | 			nonceCount := fmt.Sprintf("%08x", 1) | ||||||
|  | 			digest := saslDigestResponse(client.JID.Local, realm, password, nonce, cnonceStr, "AUTHENTICATE", digestURI, nonceCount) | ||||||
|  | 			message := "username=\"" + client.JID.Local + "\", realm=\"" + realm + "\", nonce=\"" + nonce + "\", cnonce=\"" + cnonceStr + | ||||||
|  | 				"\", nc=" + nonceCount + ", qop=" + qop + ", digest-uri=\"" + digestURI + "\", response=" + digest + ", charset=" + charset | ||||||
|  | 
 | ||||||
|  | 			fmt.Fprintf(client.conn, "<response xmlns='%s'>%s</response>\n", messages.NSSASL, base64.StdEncoding.EncodeToString([]byte(message))) | ||||||
|  | 
 | ||||||
|  | 			err = client.ReadElement(&ch) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			_, err = base64.StdEncoding.DecodeString(ch) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			fmt.Fprintf(client.conn, "<response xmlns='%s'/>\n", messages.NSSASL) | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if mechanism == "" { | ||||||
|  | 		return fmt.Errorf("PLAIN authentication is not an option: %v", f.Mechanisms.Mechanism) | ||||||
|  | 	} | ||||||
|  | 	element, err := client.Read() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if element.Name.Local != "success" { | ||||||
|  | 		return errors.New("auth failed: " + element.Name.Local) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	err = client.init() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if err := client.ReadElement(&f); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	// bind to resource
 | ||||||
|  | 	var msg string | ||||||
|  | 	if client.JID.Resource == "" { | ||||||
|  | 		msg = fmt.Sprintf("<bind xmlns='%s'></bind>", messages.NSBind) | ||||||
|  | 	} else { | ||||||
|  | 		msg = fmt.Sprintf( | ||||||
|  | 			`<bind xmlns='%s'> | ||||||
|  | 				<resource>%s</resource> | ||||||
|  | 			</bind>`, | ||||||
|  | 			messages.NSBind, client.JID.Resource) | ||||||
|  | 	} | ||||||
|  | 	client.Out.Encode(&messages.IQClient{ | ||||||
|  | 		Type: messages.IQTypeSet, | ||||||
|  | 		To:   client.JID.Domain, | ||||||
|  | 		From: client.JID.Full(), | ||||||
|  | 		ID:   utils.CreateCookieString(), | ||||||
|  | 		Body: []byte(msg), | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	var iq messages.IQClient | ||||||
|  | 	if err := client.ReadElement(&iq); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if &iq.Bind == nil { | ||||||
|  | 		return errors.New("<iq> result missing <bind>") | ||||||
|  | 	} | ||||||
|  | 	if iq.Bind.JID != nil { | ||||||
|  | 		client.JID.Local = iq.Bind.JID.Local | ||||||
|  | 		client.JID.Domain = iq.Bind.JID.Domain | ||||||
|  | 		client.JID.Resource = iq.Bind.JID.Resource | ||||||
|  | 	} else { | ||||||
|  | 		return errors.New(string(iq.Body)) | ||||||
|  | 	} | ||||||
|  | 	// set status
 | ||||||
|  | 	client.Out.Encode(&messages.PresenceClient{Show: "online", Status: "yaja client"}) | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func saslDigestResponse(username, realm, passwd, nonce, cnonceStr, authenticate, digestURI, nonceCountStr string) string { | ||||||
|  | 	h := func(text string) []byte { | ||||||
|  | 		h := md5.New() | ||||||
|  | 		h.Write([]byte(text)) | ||||||
|  | 		return h.Sum(nil) | ||||||
|  | 	} | ||||||
|  | 	hex := func(bytes []byte) string { | ||||||
|  | 		return fmt.Sprintf("%x", bytes) | ||||||
|  | 	} | ||||||
|  | 	kd := func(secret, data string) []byte { | ||||||
|  | 		return h(secret + ":" + data) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	a1 := string(h(username+":"+realm+":"+passwd)) + ":" + nonce + ":" + cnonceStr | ||||||
|  | 	a2 := authenticate + ":" + digestURI | ||||||
|  | 	response := hex(kd(hex(h(a1)), nonce+":"+nonceCountStr+":"+cnonceStr+":auth:"+hex(h(a2)))) | ||||||
|  | 	return response | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func cnonce() string { | ||||||
|  | 	randSize := big.NewInt(0) | ||||||
|  | 	randSize.Lsh(big.NewInt(1), 64) | ||||||
|  | 	cn, err := rand.Int(rand.Reader, randSize) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 	return fmt.Sprintf("%016x", cn) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,18 @@ | ||||||
|  | package cmd | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"dev.sum7.eu/genofire/yaja/daemon" | ||||||
|  | 	"github.com/spf13/cobra" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // DaemonCMD represents the serve command
 | ||||||
|  | var DaemonCMD = &cobra.Command{ | ||||||
|  | 	Use:   "daemon", | ||||||
|  | 	Short: "daemon of yaja", | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func init() { | ||||||
|  | 	DaemonCMD.AddCommand(daemon.ServerCMD) | ||||||
|  | 	DaemonCMD.AddCommand(daemon.TesterCMD) | ||||||
|  | 	RootCMD.AddCommand(DaemonCMD) | ||||||
|  | } | ||||||
|  | @ -11,7 +11,7 @@ var ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // RootCmd represents the base command when called without any subcommands
 | // RootCmd represents the base command when called without any subcommands
 | ||||||
| var RootCmd = &cobra.Command{ | var RootCMD = &cobra.Command{ | ||||||
| 	Use:   "yaja", | 	Use:   "yaja", | ||||||
| 	Short: "Yet another jabber server", | 	Short: "Yet another jabber server", | ||||||
| 	Long:  `A small standalone jabber server, for easy deployment`, | 	Long:  `A small standalone jabber server, for easy deployment`, | ||||||
|  | @ -20,7 +20,7 @@ var RootCmd = &cobra.Command{ | ||||||
| // Execute adds all child commands to the root command and sets flags appropriately.
 | // Execute adds all child commands to the root command and sets flags appropriately.
 | ||||||
| // This is called by main.main(). It only needs to happen once to the rootCmd.
 | // This is called by main.main(). It only needs to happen once to the rootCmd.
 | ||||||
| func Execute() { | func Execute() { | ||||||
| 	if err := RootCmd.Execute(); err != nil { | 	if err := RootCMD.Execute(); err != nil { | ||||||
| 		log.Panicln(err) | 		log.Panicln(err) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,3 @@ | ||||||
|  | package daemon | ||||||
|  | 
 | ||||||
|  | var configPath string | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| package cmd | package daemon | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"crypto/tls" | 	"crypto/tls" | ||||||
|  | @ -10,8 +10,8 @@ import ( | ||||||
| 
 | 
 | ||||||
| 	"golang.org/x/crypto/acme/autocert" | 	"golang.org/x/crypto/acme/autocert" | ||||||
| 
 | 
 | ||||||
|  | 	serverDaemon "dev.sum7.eu/genofire/yaja/daemon/server" | ||||||
| 	"dev.sum7.eu/genofire/yaja/database" | 	"dev.sum7.eu/genofire/yaja/database" | ||||||
| 	"dev.sum7.eu/genofire/yaja/model/config" |  | ||||||
| 	"dev.sum7.eu/genofire/yaja/server/extension" | 	"dev.sum7.eu/genofire/yaja/server/extension" | ||||||
| 
 | 
 | ||||||
| 	"dev.sum7.eu/genofire/golang-lib/file" | 	"dev.sum7.eu/genofire/golang-lib/file" | ||||||
|  | @ -22,10 +22,8 @@ import ( | ||||||
| 	"github.com/spf13/cobra" | 	"github.com/spf13/cobra" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var configPath string |  | ||||||
| 
 |  | ||||||
| var ( | var ( | ||||||
| 	configData       = &config.Config{} | 	serverConfig     = &serverDaemon.Config{} | ||||||
| 	db               = &database.State{} | 	db               = &database.State{} | ||||||
| 	statesaveWorker  *worker.Worker | 	statesaveWorker  *worker.Worker | ||||||
| 	srv              *server.Server | 	srv              *server.Server | ||||||
|  | @ -34,35 +32,35 @@ var ( | ||||||
| 	extensionsServer extension.Extensions | 	extensionsServer extension.Extensions | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // serverCmd represents the serve command
 | // ServerCMD represents the serve command
 | ||||||
| var serverCmd = &cobra.Command{ | var ServerCMD = &cobra.Command{ | ||||||
| 	Use:     "server", | 	Use:     "server", | ||||||
| 	Short:   "Runs the yaja server", | 	Short:   "runs xmpp server", | ||||||
| 	Example: "yaja serve -c /etc/yaja.conf", | 	Example: "yaja daemon server -c /etc/yaja.conf", | ||||||
| 	Run: func(cmd *cobra.Command, args []string) { | 	Run: func(cmd *cobra.Command, args []string) { | ||||||
| 
 | 
 | ||||||
| 		if err := file.ReadTOML(configPath, configData); err != nil { | 		if err := file.ReadTOML(configPath, serverConfig); err != nil { | ||||||
| 			log.Fatal("unable to load config file:", err) | 			log.Fatal("unable to load config file:", err) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		log.SetLevel(configData.Logging.Level) | 		log.SetLevel(serverConfig.Logging.Level) | ||||||
| 
 | 
 | ||||||
| 		if err := file.ReadJSON(configData.StatePath, db); err != nil { | 		if err := file.ReadJSON(serverConfig.StatePath, db); err != nil { | ||||||
| 			log.Warn("unable to load state file:", err) | 			log.Warn("unable to load state file:", err) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		statesaveWorker = worker.NewWorker(time.Minute, func() { | 		statesaveWorker = worker.NewWorker(time.Minute, func() { | ||||||
| 			file.SaveJSON(configData.StatePath, db) | 			file.SaveJSON(serverConfig.StatePath, db) | ||||||
| 			log.Info("save state to:", configData.StatePath) | 			log.Info("save state to:", serverConfig.StatePath) | ||||||
| 		}) | 		}) | ||||||
| 
 | 
 | ||||||
| 		m := autocert.Manager{ | 		m := autocert.Manager{ | ||||||
| 			Cache:  autocert.DirCache(configData.TLSDir), | 			Cache:  autocert.DirCache(serverConfig.TLSDir), | ||||||
| 			Prompt: autocert.AcceptTOS, | 			Prompt: autocert.AcceptTOS, | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// https server to handle acme (by letsencrypt)
 | 		// https server to handle acme (by letsencrypt)
 | ||||||
| 		for _, addr := range configData.Address.Webserver { | 		for _, addr := range serverConfig.Address.Webserver { | ||||||
| 			hs := &http.Server{ | 			hs := &http.Server{ | ||||||
| 				Addr:      addr, | 				Addr:      addr, | ||||||
| 				TLSConfig: &tls.Config{GetCertificate: m.GetCertificate}, | 				TLSConfig: &tls.Config{GetCertificate: m.GetCertificate}, | ||||||
|  | @ -77,12 +75,12 @@ var serverCmd = &cobra.Command{ | ||||||
| 		srv = &server.Server{ | 		srv = &server.Server{ | ||||||
| 			TLSManager:       &m, | 			TLSManager:       &m, | ||||||
| 			Database:         db, | 			Database:         db, | ||||||
| 			ClientAddr:       configData.Address.Client, | 			ClientAddr:       serverConfig.Address.Client, | ||||||
| 			ServerAddr:       configData.Address.Server, | 			ServerAddr:       serverConfig.Address.Server, | ||||||
| 			LoggingClient:    configData.Logging.LevelClient, | 			LoggingClient:    serverConfig.Logging.LevelClient, | ||||||
| 			LoggingServer:    configData.Logging.LevelServer, | 			LoggingServer:    serverConfig.Logging.LevelServer, | ||||||
| 			RegisterEnable:   configData.Register.Enable, | 			RegisterEnable:   serverConfig.Register.Enable, | ||||||
| 			RegisterDomains:  configData.Register.Domains, | 			RegisterDomains:  serverConfig.Register.Domains, | ||||||
| 			ExtensionsServer: extensionsServer, | 			ExtensionsServer: extensionsServer, | ||||||
| 			ExtensionsClient: extensionsClient, | 			ExtensionsClient: extensionsClient, | ||||||
| 		} | 		} | ||||||
|  | @ -117,12 +115,12 @@ func quit() { | ||||||
| 	srv.Close() | 	srv.Close() | ||||||
| 	statesaveWorker.Close() | 	statesaveWorker.Close() | ||||||
| 
 | 
 | ||||||
| 	file.SaveJSON(configData.StatePath, db) | 	file.SaveJSON(serverConfig.StatePath, db) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func reload() { | func reload() { | ||||||
| 	log.Info("start reloading...") | 	log.Info("start reloading...") | ||||||
| 	var configNewData *config.Config | 	var configNewData *serverDaemon.Config | ||||||
| 
 | 
 | ||||||
| 	if err := file.ReadTOML(configPath, configNewData); err != nil { | 	if err := file.ReadTOML(configPath, configNewData); err != nil { | ||||||
| 		log.Warn("unable to load config file:", err) | 		log.Warn("unable to load config file:", err) | ||||||
|  | @ -136,7 +134,7 @@ func reload() { | ||||||
| 
 | 
 | ||||||
| 	//TODO fetch changing address (to set restart)
 | 	//TODO fetch changing address (to set restart)
 | ||||||
| 
 | 
 | ||||||
| 	if configNewData.StatePath != configData.StatePath { | 	if configNewData.StatePath != serverConfig.StatePath { | ||||||
| 		statesaveWorker.Close() | 		statesaveWorker.Close() | ||||||
| 		statesaveWorker := worker.NewWorker(time.Minute, func() { | 		statesaveWorker := worker.NewWorker(time.Minute, func() { | ||||||
| 			file.SaveJSON(configNewData.StatePath, db) | 			file.SaveJSON(configNewData.StatePath, db) | ||||||
|  | @ -147,10 +145,10 @@ func reload() { | ||||||
| 
 | 
 | ||||||
| 	restartServer := false | 	restartServer := false | ||||||
| 
 | 
 | ||||||
| 	if configNewData.TLSDir != configData.TLSDir { | 	if configNewData.TLSDir != serverConfig.TLSDir { | ||||||
| 
 | 
 | ||||||
| 		m := autocert.Manager{ | 		m := autocert.Manager{ | ||||||
| 			Cache:  autocert.DirCache(configData.TLSDir), | 			Cache:  autocert.DirCache(serverConfig.TLSDir), | ||||||
| 			Prompt: autocert.AcceptTOS, | 			Prompt: autocert.AcceptTOS, | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -176,7 +174,7 @@ func reload() { | ||||||
| 		srv = newServer | 		srv = newServer | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	configData = configNewData | 	serverConfig = configNewData | ||||||
| 	log.Info("reloaded") | 	log.Info("reloaded") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -200,7 +198,6 @@ func init() { | ||||||
| 			&extension.IQPing{}, | 			&extension.IQPing{}, | ||||||
| 		}) | 		}) | ||||||
| 
 | 
 | ||||||
| 	RootCmd.AddCommand(serverCmd) | 	ServerCMD.Flags().StringVarP(&configPath, "config", "c", "yaja-server.conf", "Path to configuration file") | ||||||
| 	serverCmd.Flags().StringVarP(&configPath, "config", "c", "yaja.conf", "Path to configuration file") |  | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| package config | package server | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	log "github.com/sirupsen/logrus" | 	log "github.com/sirupsen/logrus" | ||||||
|  | @ -0,0 +1,117 @@ | ||||||
|  | package daemon | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"crypto/tls" | ||||||
|  | 	"net/http" | ||||||
|  | 	"os" | ||||||
|  | 	"os/signal" | ||||||
|  | 	"syscall" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/crypto/acme/autocert" | ||||||
|  | 
 | ||||||
|  | 	"dev.sum7.eu/genofire/golang-lib/file" | ||||||
|  | 	"dev.sum7.eu/genofire/golang-lib/worker" | ||||||
|  | 	"dev.sum7.eu/genofire/yaja/client" | ||||||
|  | 	"dev.sum7.eu/genofire/yaja/daemon/tester" | ||||||
|  | 	"dev.sum7.eu/genofire/yaja/messages" | ||||||
|  | 	log "github.com/sirupsen/logrus" | ||||||
|  | 
 | ||||||
|  | 	"github.com/spf13/cobra" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var configTester = &tester.Config{} | ||||||
|  | 
 | ||||||
|  | // TesterCMD represents the serve command
 | ||||||
|  | var TesterCMD = &cobra.Command{ | ||||||
|  | 	Use:     "tester", | ||||||
|  | 	Short:   "runs xmpp tester server", | ||||||
|  | 	Example: "yaja daemon tester -c /etc/yaja.conf", | ||||||
|  | 	Run: func(cmd *cobra.Command, args []string) { | ||||||
|  | 
 | ||||||
|  | 		if err := file.ReadTOML(configPath, configTester); err != nil { | ||||||
|  | 			log.Fatal("unable to load config file:", err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		log.SetLevel(configTester.Logging) | ||||||
|  | 
 | ||||||
|  | 		if err := file.ReadJSON(configTester.StatePath, db); err != nil { | ||||||
|  | 			log.Warn("unable to load state file:", err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		statesaveWorker = worker.NewWorker(time.Minute, func() { | ||||||
|  | 			file.SaveJSON(configTester.StatePath, db) | ||||||
|  | 			log.Info("save state to:", configTester.StatePath) | ||||||
|  | 		}) | ||||||
|  | 
 | ||||||
|  | 		// https server to handle acme (by letsencrypt)
 | ||||||
|  | 		hs := &http.Server{ | ||||||
|  | 			Addr: configTester.Webserver, | ||||||
|  | 		} | ||||||
|  | 		if configTester.TLSDir != "" { | ||||||
|  | 			m := autocert.Manager{ | ||||||
|  | 				Cache:  autocert.DirCache(configTester.TLSDir), | ||||||
|  | 				Prompt: autocert.AcceptTOS, | ||||||
|  | 			} | ||||||
|  | 			hs.TLSConfig = &tls.Config{GetCertificate: m.GetCertificate} | ||||||
|  | 			go func(hs *http.Server) { | ||||||
|  | 				if err := hs.ListenAndServeTLS("", ""); err != http.ErrServerClosed { | ||||||
|  | 					log.Errorf("webserver with addr %s: %s", hs.Addr, err) | ||||||
|  | 				} | ||||||
|  | 			}(hs) | ||||||
|  | 		} else { | ||||||
|  | 			go func(hs *http.Server) { | ||||||
|  | 				if err := hs.ListenAndServe(); err != http.ErrServerClosed { | ||||||
|  | 					log.Errorf("webserver with addr %s: %s", hs.Addr, err) | ||||||
|  | 				} | ||||||
|  | 			}(hs) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		mainClient, err := client.NewClient(configTester.Client.JID, configTester.Client.Password) | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Fatal("unable to connect with main jabber client: ", err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		for _, admin := range configTester.Admins { | ||||||
|  | 			mainClient.Out.Encode(&messages.MessageClient{ | ||||||
|  | 				From: mainClient.JID.Full(), | ||||||
|  | 				To:   admin.Full(), | ||||||
|  | 				Type: "chat", | ||||||
|  | 				Body: "yaja tester starts", | ||||||
|  | 			}) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		go statesaveWorker.Start() | ||||||
|  | 
 | ||||||
|  | 		log.Infoln("yaja tester started ") | ||||||
|  | 
 | ||||||
|  | 		// Wait for INT/TERM
 | ||||||
|  | 		sigs := make(chan os.Signal, 1) | ||||||
|  | 		signal.Notify(sigs, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT) | ||||||
|  | 		for sig := range sigs { | ||||||
|  | 			log.Infoln("received", sig) | ||||||
|  | 			switch sig { | ||||||
|  | 			case syscall.SIGTERM: | ||||||
|  | 				log.Panic("terminated") | ||||||
|  | 				os.Exit(0) | ||||||
|  | 			case syscall.SIGQUIT: | ||||||
|  | 				quitTester() | ||||||
|  | 			case syscall.SIGHUP: | ||||||
|  | 				quitTester() | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	}, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func quitTester() { | ||||||
|  | 	srv.Close() | ||||||
|  | 	statesaveWorker.Close() | ||||||
|  | 
 | ||||||
|  | 	file.SaveJSON(configTester.StatePath, db) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func init() { | ||||||
|  | 	TesterCMD.Flags().StringVarP(&configPath, "config", "c", "yaja-tester.conf", "Path to configuration file") | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,18 @@ | ||||||
|  | package tester | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"dev.sum7.eu/genofire/yaja/model" | ||||||
|  | 	log "github.com/sirupsen/logrus" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type Config struct { | ||||||
|  | 	TLSDir    string      `toml:"tlsdir"` | ||||||
|  | 	StatePath string      `toml:"state_path"` | ||||||
|  | 	Logging   log.Level   `toml:"logging"` | ||||||
|  | 	Webserver string      `toml:"webserver"` | ||||||
|  | 	Admins    []model.JID `toml:"admins"` | ||||||
|  | 	Client    struct { | ||||||
|  | 		JID      model.JID `toml:"jid"` | ||||||
|  | 		Password string    `toml:"password"` | ||||||
|  | 	} `toml:"client"` | ||||||
|  | } | ||||||
|  | @ -0,0 +1,43 @@ | ||||||
|  | package messages | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"encoding/xml" | ||||||
|  | 
 | ||||||
|  | 	"dev.sum7.eu/genofire/yaja/model" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // RFC 3920  C.1  Streams name space
 | ||||||
|  | type StreamFeatures struct { | ||||||
|  | 	XMLName    xml.Name `xml:"http://etherx.jabber.org/streams features"` | ||||||
|  | 	StartTLS   *TLSStartTLS | ||||||
|  | 	Mechanisms SASLMechanisms | ||||||
|  | 	Bind       Bind | ||||||
|  | 	Session    bool | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type StreamError struct { | ||||||
|  | 	XMLName xml.Name `xml:"http://etherx.jabber.org/streams error"` | ||||||
|  | 	Any     xml.Name | ||||||
|  | 	Text    string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // RFC 3920  C.3  TLS name space
 | ||||||
|  | type TLSStartTLS struct { | ||||||
|  | 	XMLName  xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls starttls"` | ||||||
|  | 	Required *string  `xml:"required"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type TLSFailure struct { | ||||||
|  | 	XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls failure"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type TLSProceed struct { | ||||||
|  | 	XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls proceed"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // RFC 3920  C.5  Resource binding name space
 | ||||||
|  | type Bind struct { | ||||||
|  | 	XMLName  xml.Name   `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"` | ||||||
|  | 	Resource string     `xml:"resource"` | ||||||
|  | 	JID      *model.JID `xml:"jid"` | ||||||
|  | } | ||||||
|  | @ -2,8 +2,8 @@ package messages | ||||||
| 
 | 
 | ||||||
| import "encoding/xml" | import "encoding/xml" | ||||||
| 
 | 
 | ||||||
| // Error element
 | // ErrorClient element
 | ||||||
| type Error struct { | type ErrorClient struct { | ||||||
| 	XMLName xml.Name `xml:"jabber:client error"` | 	XMLName xml.Name `xml:"jabber:client error"` | ||||||
| 	Code    string   `xml:"code,attr"` | 	Code    string   `xml:"code,attr"` | ||||||
| 	Type    string   `xml:"type,attr"` | 	Type    string   `xml:"type,attr"` | ||||||
|  |  | ||||||
|  | @ -12,14 +12,14 @@ const ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // IQ element - info/query
 | // IQ element - info/query
 | ||||||
| type IQ struct { | type IQClient struct { | ||||||
| 	XMLName xml.Name `xml:"jabber:client iq"` | 	XMLName xml.Name     `xml:"jabber:client iq"` | ||||||
| 	From    string   `xml:"from,attr"` | 	From    string       `xml:"from,attr"` | ||||||
| 	ID      string   `xml:"id,attr"` | 	ID      string       `xml:"id,attr"` | ||||||
| 	To      string   `xml:"to,attr"` | 	To      string       `xml:"to,attr"` | ||||||
| 	Type    IQType   `xml:"type,attr"` | 	Type    IQType       `xml:"type,attr"` | ||||||
| 	Error   *Error   `xml:"error"` | 	Error   *ErrorClient `xml:"error"` | ||||||
| 	//Bind    bindBind    `xml:"bind"`
 | 	Bind    Bind         `xml:"bind"` | ||||||
| 	Body []byte `xml:",innerxml"` | 	Body    []byte       `xml:",innerxml"` | ||||||
| 	// RosterRequest - better detection of iq's
 | 	// RosterRequest - better detection of iq's
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -14,8 +14,8 @@ const ( | ||||||
| 	PresenceTypeError        PresenceType = "error" | 	PresenceTypeError        PresenceType = "error" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Presence element
 | // PresenceClient element
 | ||||||
| type Presence struct { | type PresenceClient struct { | ||||||
| 	XMLName xml.Name `xml:"jabber:client presence"` | 	XMLName xml.Name `xml:"jabber:client presence"` | ||||||
| 	From    string   `xml:"from,attr,omitempty"` | 	From    string   `xml:"from,attr,omitempty"` | ||||||
| 	ID      string   `xml:"id,attr,omitempty"` | 	ID      string   `xml:"id,attr,omitempty"` | ||||||
|  | @ -27,6 +27,19 @@ type Presence struct { | ||||||
| 	Status   string `xml:"status,omitempty"` // sb []clientText
 | 	Status   string `xml:"status,omitempty"` // sb []clientText
 | ||||||
| 	Priority string `xml:"priority,omitempty"` | 	Priority string `xml:"priority,omitempty"` | ||||||
| 	// Caps     *ClientCaps  `xml:"c"`
 | 	// Caps     *ClientCaps  `xml:"c"`
 | ||||||
| 	Error *Error `xml:"error"` | 	Error *ErrorClient `xml:"error"` | ||||||
| 	// Delay    Delay        `xml:"delay"`
 | 	// Delay    Delay        `xml:"delay"`
 | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // MessageClient element
 | ||||||
|  | type MessageClient struct { | ||||||
|  | 	XMLName xml.Name `xml:"jabber:client message"` | ||||||
|  | 	From    string   `xml:"from,attr,omitempty"` | ||||||
|  | 	ID      string   `xml:"id,attr,omitempty"` | ||||||
|  | 	To      string   `xml:"to,attr,omitempty"` | ||||||
|  | 	Type    string   `xml:"type,attr,omitempty"` | ||||||
|  | 	Lang    string   `xml:"lang,attr,omitempty"` | ||||||
|  | 	Subject string   `xml:"subject"` | ||||||
|  | 	Body    string   `xml:"body"` | ||||||
|  | 	Thread  string   `xml:"thread"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -8,3 +8,9 @@ type SASLAuth struct { | ||||||
| 	Mechanism string   `xml:"mechanism,attr"` | 	Mechanism string   `xml:"mechanism,attr"` | ||||||
| 	Body      string   `xml:",chardata"` | 	Body      string   `xml:",chardata"` | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // RFC 3920  C.4  SASL name space
 | ||||||
|  | type SASLMechanisms struct { | ||||||
|  | 	XMLName   xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl mechanisms"` | ||||||
|  | 	Mechanism []string `xml:"mechanism"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -52,13 +52,13 @@ func (jid *JID) Full() string { | ||||||
| 	return jid.Bare() | 	return jid.Bare() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| //MarshalTOML to bytearray
 | //MarshalText to bytearray
 | ||||||
| func (jid JID) MarshalTOML() ([]byte, error) { | func (jid JID) MarshalText() ([]byte, error) { | ||||||
| 	return []byte(jid.Full()), nil | 	return []byte(jid.Full()), nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // UnmarshalTOML from bytearray
 | // UnmarshalText from bytearray
 | ||||||
| func (jid *JID) UnmarshalTOML(data []byte) (err error) { | func (jid *JID) UnmarshalText(data []byte) (err error) { | ||||||
| 	newJID := NewJID(string(data)) | 	newJID := NewJID(string(data)) | ||||||
| 	if newJID == nil { | 	if newJID == nil { | ||||||
| 		return errors.New("not a valid jid") | 		return errors.New("not a valid jid") | ||||||
|  |  | ||||||
|  | @ -137,14 +137,14 @@ func TestMarshal(t *testing.T) { | ||||||
| 	assert := assert.New(t) | 	assert := assert.New(t) | ||||||
| 
 | 
 | ||||||
| 	jid := &JID{} | 	jid := &JID{} | ||||||
| 	err := jid.UnmarshalTOML([]byte("juliet@example.com/foo")) | 	err := jid.UnmarshalText([]byte("juliet@example.com/foo")) | ||||||
| 
 | 
 | ||||||
| 	assert.NoError(err) | 	assert.NoError(err) | ||||||
| 	assert.Equal(jid.Local, "juliet") | 	assert.Equal(jid.Local, "juliet") | ||||||
| 	assert.Equal(jid.Domain, "example.com") | 	assert.Equal(jid.Domain, "example.com") | ||||||
| 	assert.Equal(jid.Resource, "foo") | 	assert.Equal(jid.Resource, "foo") | ||||||
| 
 | 
 | ||||||
| 	err = jid.UnmarshalTOML([]byte("juliet@example.com/ foo")) | 	err = jid.UnmarshalText([]byte("juliet@example.com/ foo")) | ||||||
| 
 | 
 | ||||||
| 	assert.Error(err) | 	assert.Error(err) | ||||||
| 
 | 
 | ||||||
|  | @ -153,7 +153,7 @@ func TestMarshal(t *testing.T) { | ||||||
| 		Domain:   "example.com", | 		Domain:   "example.com", | ||||||
| 		Resource: "bar", | 		Resource: "bar", | ||||||
| 	} | 	} | ||||||
| 	jidString, err := jid.MarshalTOML() | 	jidString, err := jid.MarshalText() | ||||||
| 	assert.NoError(err) | 	assert.NoError(err) | ||||||
| 	assert.Equal("romeo@example.com/bar", string(jidString)) | 	assert.Equal("romeo@example.com/bar", string(jidString)) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,13 @@ | ||||||
|  | package model | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"encoding/xml" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func XMLEscape(s string) string { | ||||||
|  | 	var b bytes.Buffer | ||||||
|  | 	xml.Escape(&b, []byte(s)) | ||||||
|  | 
 | ||||||
|  | 	return b.String() | ||||||
|  | } | ||||||
|  | @ -11,8 +11,8 @@ type IQExtensions []IQExtension | ||||||
| 
 | 
 | ||||||
| type IQExtension interface { | type IQExtension interface { | ||||||
| 	Extension | 	Extension | ||||||
| 	Get(*messages.IQ, *utils.Client) bool | 	Get(*messages.IQClient, *utils.Client) bool | ||||||
| 	Set(*messages.IQ, *utils.Client) bool | 	Set(*messages.IQClient, *utils.Client) bool | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (iex IQExtensions) Spaces() (result []string) { | func (iex IQExtensions) Spaces() (result []string) { | ||||||
|  | @ -27,7 +27,7 @@ func (iex IQExtensions) Process(element *xml.StartElement, client *utils.Client) | ||||||
| 	log := client.Log.WithField("extension", "iq") | 	log := client.Log.WithField("extension", "iq") | ||||||
| 
 | 
 | ||||||
| 	// iq encode
 | 	// iq encode
 | ||||||
| 	var msg messages.IQ | 	var msg messages.IQClient | ||||||
| 	if err := client.In.DecodeElement(&msg, element); err != nil { | 	if err := client.In.DecodeElement(&msg, element); err != nil { | ||||||
| 		return false | 		return false | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -15,7 +15,7 @@ type IQDisco struct { | ||||||
| 
 | 
 | ||||||
| func (ex *IQDisco) Spaces() []string { return []string{"http://jabber.org/protocol/disco#items"} } | func (ex *IQDisco) Spaces() []string { return []string{"http://jabber.org/protocol/disco#items"} } | ||||||
| 
 | 
 | ||||||
| func (ex *IQDisco) Get(msg *messages.IQ, client *utils.Client) bool { | func (ex *IQDisco) Get(msg *messages.IQClient, client *utils.Client) bool { | ||||||
| 	log := client.Log.WithField("extension", "disco-item").WithField("id", msg.ID) | 	log := client.Log.WithField("extension", "disco-item").WithField("id", msg.ID) | ||||||
| 
 | 
 | ||||||
| 	// query encode
 | 	// query encode
 | ||||||
|  | @ -57,7 +57,7 @@ func (ex *IQDisco) Get(msg *messages.IQ, client *utils.Client) bool { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// reply
 | 	// reply
 | ||||||
| 	client.Messages <- &messages.IQ{ | 	client.Messages <- &messages.IQClient{ | ||||||
| 		Type: messages.IQTypeResult, | 		Type: messages.IQTypeResult, | ||||||
| 		To:   client.JID.String(), | 		To:   client.JID.String(), | ||||||
| 		From: client.JID.Domain, | 		From: client.JID.Domain, | ||||||
|  |  | ||||||
|  | @ -14,7 +14,7 @@ type IQExtensionDiscovery struct { | ||||||
| 
 | 
 | ||||||
| func (ex *IQExtensionDiscovery) Spaces() []string { return []string{} } | func (ex *IQExtensionDiscovery) Spaces() []string { return []string{} } | ||||||
| 
 | 
 | ||||||
| func (ex *IQExtensionDiscovery) Get(msg *messages.IQ, client *utils.Client) bool { | func (ex *IQExtensionDiscovery) Get(msg *messages.IQClient, client *utils.Client) bool { | ||||||
| 	log := client.Log.WithField("extension", "roster").WithField("id", msg.ID) | 	log := client.Log.WithField("extension", "roster").WithField("id", msg.ID) | ||||||
| 
 | 
 | ||||||
| 	// query encode
 | 	// query encode
 | ||||||
|  | @ -57,7 +57,7 @@ func (ex *IQExtensionDiscovery) Get(msg *messages.IQ, client *utils.Client) bool | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// replay
 | 	// replay
 | ||||||
| 	client.Messages <- &messages.IQ{ | 	client.Messages <- &messages.IQClient{ | ||||||
| 		Type: messages.IQTypeResult, | 		Type: messages.IQTypeResult, | ||||||
| 		To:   client.JID.String(), | 		To:   client.JID.String(), | ||||||
| 		From: client.JID.Domain, | 		From: client.JID.Domain, | ||||||
|  |  | ||||||
|  | @ -15,7 +15,7 @@ type IQLast struct { | ||||||
| 
 | 
 | ||||||
| func (ex *IQLast) Spaces() []string { return []string{"jabber:iq:last"} } | func (ex *IQLast) Spaces() []string { return []string{"jabber:iq:last"} } | ||||||
| 
 | 
 | ||||||
| func (ex *IQLast) Get(msg *messages.IQ, client *utils.Client) bool { | func (ex *IQLast) Get(msg *messages.IQClient, client *utils.Client) bool { | ||||||
| 	log := client.Log.WithField("extension", "last").WithField("id", msg.ID) | 	log := client.Log.WithField("extension", "last").WithField("id", msg.ID) | ||||||
| 
 | 
 | ||||||
| 	// query encode
 | 	// query encode
 | ||||||
|  | @ -45,7 +45,7 @@ func (ex *IQLast) Get(msg *messages.IQ, client *utils.Client) bool { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// reply
 | 	// reply
 | ||||||
| 	client.Messages <- &messages.IQ{ | 	client.Messages <- &messages.IQClient{ | ||||||
| 		Type: messages.IQTypeResult, | 		Type: messages.IQTypeResult, | ||||||
| 		To:   client.JID.String(), | 		To:   client.JID.String(), | ||||||
| 		From: client.JID.Domain, | 		From: client.JID.Domain, | ||||||
|  |  | ||||||
|  | @ -13,7 +13,7 @@ type IQPing struct { | ||||||
| 
 | 
 | ||||||
| func (ex *IQPing) Spaces() []string { return []string{"urn:xmpp:ping"} } | func (ex *IQPing) Spaces() []string { return []string{"urn:xmpp:ping"} } | ||||||
| 
 | 
 | ||||||
| func (ex *IQPing) Get(msg *messages.IQ, client *utils.Client) bool { | func (ex *IQPing) Get(msg *messages.IQClient, client *utils.Client) bool { | ||||||
| 	log := client.Log.WithField("extension", "ping").WithField("id", msg.ID) | 	log := client.Log.WithField("extension", "ping").WithField("id", msg.ID) | ||||||
| 
 | 
 | ||||||
| 	// ping encode
 | 	// ping encode
 | ||||||
|  | @ -26,7 +26,7 @@ func (ex *IQPing) Get(msg *messages.IQ, client *utils.Client) bool { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// reply
 | 	// reply
 | ||||||
| 	client.Messages <- &messages.IQ{ | 	client.Messages <- &messages.IQClient{ | ||||||
| 		Type: messages.IQTypeResult, | 		Type: messages.IQTypeResult, | ||||||
| 		To:   client.JID.String(), | 		To:   client.JID.String(), | ||||||
| 		From: client.JID.Domain, | 		From: client.JID.Domain, | ||||||
|  |  | ||||||
|  | @ -17,12 +17,12 @@ type iqPrivateQuery struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type iqPrivateExtension interface { | type iqPrivateExtension interface { | ||||||
| 	Handle(*messages.IQ, *iqPrivateQuery, *utils.Client) bool | 	Handle(*messages.IQClient, *iqPrivateQuery, *utils.Client) bool | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (ex *IQPrivate) Spaces() []string { return []string{"jabber:iq:private"} } | func (ex *IQPrivate) Spaces() []string { return []string{"jabber:iq:private"} } | ||||||
| 
 | 
 | ||||||
| func (ex *IQPrivate) Get(msg *messages.IQ, client *utils.Client) bool { | func (ex *IQPrivate) Get(msg *messages.IQClient, client *utils.Client) bool { | ||||||
| 	log := client.Log.WithField("extension", "private").WithField("id", msg.ID) | 	log := client.Log.WithField("extension", "private").WithField("id", msg.ID) | ||||||
| 
 | 
 | ||||||
| 	// query encode
 | 	// query encode
 | ||||||
|  |  | ||||||
|  | @ -11,7 +11,7 @@ type IQPrivateBookmark struct { | ||||||
| 	iqPrivateExtension | 	iqPrivateExtension | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (ex *IQPrivateBookmark) Handle(msg *messages.IQ, q *iqPrivateQuery, client *utils.Client) bool { | func (ex *IQPrivateBookmark) Handle(msg *messages.IQClient, q *iqPrivateQuery, client *utils.Client) bool { | ||||||
| 	log := client.Log.WithField("extension", "private").WithField("id", msg.ID) | 	log := client.Log.WithField("extension", "private").WithField("id", msg.ID) | ||||||
| 
 | 
 | ||||||
| 	// storage encode
 | 	// storage encode
 | ||||||
|  | @ -35,7 +35,7 @@ func (ex *IQPrivateBookmark) Handle(msg *messages.IQ, q *iqPrivateQuery, client | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// reply
 | 	// reply
 | ||||||
| 	client.Messages <- &messages.IQ{ | 	client.Messages <- &messages.IQClient{ | ||||||
| 		Type: messages.IQTypeResult, | 		Type: messages.IQTypeResult, | ||||||
| 		To:   client.JID.String(), | 		To:   client.JID.String(), | ||||||
| 		From: client.JID.Domain, | 		From: client.JID.Domain, | ||||||
|  |  | ||||||
|  | @ -11,7 +11,7 @@ type IQPrivateMetacontact struct { | ||||||
| 	iqPrivateExtension | 	iqPrivateExtension | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (ex *IQPrivateMetacontact) Handle(msg *messages.IQ, q *iqPrivateQuery, client *utils.Client) bool { | func (ex *IQPrivateMetacontact) Handle(msg *messages.IQClient, q *iqPrivateQuery, client *utils.Client) bool { | ||||||
| 	log := client.Log.WithField("extension", "private-metacontact").WithField("id", msg.ID) | 	log := client.Log.WithField("extension", "private-metacontact").WithField("id", msg.ID) | ||||||
| 
 | 
 | ||||||
| 	// storage encode
 | 	// storage encode
 | ||||||
|  | @ -36,7 +36,7 @@ func (ex *IQPrivateMetacontact) Handle(msg *messages.IQ, q *iqPrivateQuery, clie | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// reply
 | 	// reply
 | ||||||
| 	client.Messages <- &messages.IQ{ | 	client.Messages <- &messages.IQClient{ | ||||||
| 		Type: messages.IQTypeResult, | 		Type: messages.IQTypeResult, | ||||||
| 		To:   client.JID.String(), | 		To:   client.JID.String(), | ||||||
| 		From: client.JID.Domain, | 		From: client.JID.Domain, | ||||||
|  |  | ||||||
|  | @ -11,7 +11,7 @@ type IQPrivateRoster struct { | ||||||
| 	iqPrivateExtension | 	iqPrivateExtension | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (ex *IQPrivateRoster) Handle(msg *messages.IQ, q *iqPrivateQuery, client *utils.Client) bool { | func (ex *IQPrivateRoster) Handle(msg *messages.IQClient, q *iqPrivateQuery, client *utils.Client) bool { | ||||||
| 	log := client.Log.WithField("extension", "private").WithField("id", msg.ID) | 	log := client.Log.WithField("extension", "private").WithField("id", msg.ID) | ||||||
| 
 | 
 | ||||||
| 	// roster encode
 | 	// roster encode
 | ||||||
|  | @ -40,7 +40,7 @@ func (ex *IQPrivateRoster) Handle(msg *messages.IQ, q *iqPrivateQuery, client *u | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// reply
 | 	// reply
 | ||||||
| 	client.Messages <- &messages.IQ{ | 	client.Messages <- &messages.IQClient{ | ||||||
| 		Type: messages.IQTypeResult, | 		Type: messages.IQTypeResult, | ||||||
| 		To:   client.JID.String(), | 		To:   client.JID.String(), | ||||||
| 		From: client.JID.Domain, | 		From: client.JID.Domain, | ||||||
|  |  | ||||||
|  | @ -15,7 +15,7 @@ type IQRoster struct { | ||||||
| 
 | 
 | ||||||
| func (ex *IQRoster) Spaces() []string { return []string{"jabber:iq:roster"} } | func (ex *IQRoster) Spaces() []string { return []string{"jabber:iq:roster"} } | ||||||
| 
 | 
 | ||||||
| func (ex *IQRoster) Get(msg *messages.IQ, client *utils.Client) bool { | func (ex *IQRoster) Get(msg *messages.IQClient, client *utils.Client) bool { | ||||||
| 	log := client.Log.WithField("extension", "roster").WithField("id", msg.ID) | 	log := client.Log.WithField("extension", "roster").WithField("id", msg.ID) | ||||||
| 
 | 
 | ||||||
| 	// query encode
 | 	// query encode
 | ||||||
|  | @ -59,7 +59,7 @@ func (ex *IQRoster) Get(msg *messages.IQ, client *utils.Client) bool { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// reply
 | 	// reply
 | ||||||
| 	client.Messages <- &messages.IQ{ | 	client.Messages <- &messages.IQClient{ | ||||||
| 		Type: messages.IQTypeResult, | 		Type: messages.IQTypeResult, | ||||||
| 		To:   client.JID.String(), | 		To:   client.JID.String(), | ||||||
| 		From: client.JID.Domain, | 		From: client.JID.Domain, | ||||||
|  |  | ||||||
|  | @ -19,11 +19,11 @@ func (p *Presence) Process(element *xml.StartElement, client *utils.Client) bool | ||||||
| 	log := client.Log.WithField("extension", "presence") | 	log := client.Log.WithField("extension", "presence") | ||||||
| 
 | 
 | ||||||
| 	// iq encode
 | 	// iq encode
 | ||||||
| 	var msg messages.Presence | 	var msg messages.PresenceClient | ||||||
| 	if err := client.In.DecodeElement(&msg, element); err != nil { | 	if err := client.In.DecodeElement(&msg, element); err != nil { | ||||||
| 		return false | 		return false | ||||||
| 	} | 	} | ||||||
| 	client.Messages <- &messages.Presence{ | 	client.Messages <- &messages.PresenceClient{ | ||||||
| 		ID: msg.ID, | 		ID: msg.ID, | ||||||
| 	} | 	} | ||||||
| 	log.Debug("send") | 	log.Debug("send") | ||||||
|  |  | ||||||
|  | @ -199,7 +199,7 @@ func (state *AuthedStream) Process() state.State { | ||||||
| 		state.Client.Log.Warn("unable to read: ", err) | 		state.Client.Log.Warn("unable to read: ", err) | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	var msg messages.IQ | 	var msg messages.IQClient | ||||||
| 	if err = state.Client.In.DecodeElement(&msg, element); err != nil { | 	if err = state.Client.In.DecodeElement(&msg, element); err != nil { | ||||||
| 		state.Client.Log.Warn("is no iq: ", err) | 		state.Client.Log.Warn("is no iq: ", err) | ||||||
| 		return nil | 		return nil | ||||||
|  | @ -212,11 +212,8 @@ func (state *AuthedStream) Process() state.State { | ||||||
| 		state.Client.Log.Warn("iq with error: ", msg.Error.Code) | 		state.Client.Log.Warn("iq with error: ", msg.Error.Code) | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	type query struct { | 
 | ||||||
| 		XMLName  xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"` | 	var q messages.Bind | ||||||
| 		Resource string   `xml:"resource"` |  | ||||||
| 	} |  | ||||||
| 	q := &query{} |  | ||||||
| 	err = xml.Unmarshal(msg.Body, q) | 	err = xml.Unmarshal(msg.Body, q) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		state.Client.Log.Warn("is no iq bind: ", err) | 		state.Client.Log.Warn("is no iq bind: ", err) | ||||||
|  | @ -228,7 +225,7 @@ func (state *AuthedStream) Process() state.State { | ||||||
| 		state.Client.JID.Resource = q.Resource | 		state.Client.JID.Resource = q.Resource | ||||||
| 	} | 	} | ||||||
| 	state.Client.Log = state.Client.Log.WithField("jid", state.Client.JID.Full()) | 	state.Client.Log = state.Client.Log.WithField("jid", state.Client.JID.Full()) | ||||||
| 	state.Client.Out.Encode(&messages.IQ{ | 	state.Client.Out.Encode(&messages.IQClient{ | ||||||
| 		Type: messages.IQTypeResult, | 		Type: messages.IQTypeResult, | ||||||
| 		To:   state.Client.JID.String(), | 		To:   state.Client.JID.String(), | ||||||
| 		From: state.Client.JID.Domain, | 		From: state.Client.JID.Domain, | ||||||
|  |  | ||||||
|  | @ -29,7 +29,7 @@ func (state *RegisterFormRequest) Process() state.State { | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	var msg messages.IQ | 	var msg messages.IQClient | ||||||
| 	if err := state.Client.In.DecodeElement(&msg, state.element); err != nil { | 	if err := state.Client.In.DecodeElement(&msg, state.element); err != nil { | ||||||
| 		state.Client.Log.Warn("is no iq: ", err) | 		state.Client.Log.Warn("is no iq: ", err) | ||||||
| 		return state | 		return state | ||||||
|  | @ -52,7 +52,7 @@ func (state *RegisterFormRequest) Process() state.State { | ||||||
| 		state.Client.Log.Warn("is no iq register: ", err) | 		state.Client.Log.Warn("is no iq register: ", err) | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	state.Client.Out.Encode(&messages.IQ{ | 	state.Client.Out.Encode(&messages.IQClient{ | ||||||
| 		Type: messages.IQTypeResult, | 		Type: messages.IQTypeResult, | ||||||
| 		To:   state.Client.JID.String(), | 		To:   state.Client.JID.String(), | ||||||
| 		From: state.Client.JID.Domain, | 		From: state.Client.JID.Domain, | ||||||
|  | @ -90,7 +90,7 @@ func (state *RegisterRequest) Process() state.State { | ||||||
| 		state.Client.Log.Warn("unable to read: ", err) | 		state.Client.Log.Warn("unable to read: ", err) | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	var msg messages.IQ | 	var msg messages.IQClient | ||||||
| 	if err = state.Client.In.DecodeElement(&msg, element); err != nil { | 	if err = state.Client.In.DecodeElement(&msg, element); err != nil { | ||||||
| 		state.Client.Log.Warn("is no iq: ", err) | 		state.Client.Log.Warn("is no iq: ", err) | ||||||
| 		return state | 		return state | ||||||
|  | @ -120,7 +120,7 @@ func (state *RegisterRequest) Process() state.State { | ||||||
| 	account := model.NewAccount(state.Client.JID, q.Password) | 	account := model.NewAccount(state.Client.JID, q.Password) | ||||||
| 	err = state.database.AddAccount(account) | 	err = state.database.AddAccount(account) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		state.Client.Out.Encode(&messages.IQ{ | 		state.Client.Out.Encode(&messages.IQClient{ | ||||||
| 			Type: messages.IQTypeResult, | 			Type: messages.IQTypeResult, | ||||||
| 			To:   state.Client.JID.String(), | 			To:   state.Client.JID.String(), | ||||||
| 			From: state.Client.JID.Domain, | 			From: state.Client.JID.Domain, | ||||||
|  | @ -129,7 +129,7 @@ func (state *RegisterRequest) Process() state.State { | ||||||
| 					<username>%s</username> | 					<username>%s</username> | ||||||
| 					<password>%s</password> | 					<password>%s</password> | ||||||
| 				</query>`, messages.NSIQRegister, q.Username, q.Password)), | 				</query>`, messages.NSIQRegister, q.Username, q.Password)), | ||||||
| 			Error: &messages.Error{ | 			Error: &messages.ErrorClient{ | ||||||
| 				Code: "409", | 				Code: "409", | ||||||
| 				Type: "cancel", | 				Type: "cancel", | ||||||
| 				Any: xml.Name{ | 				Any: xml.Name{ | ||||||
|  | @ -141,7 +141,7 @@ func (state *RegisterRequest) Process() state.State { | ||||||
| 		state.Client.Log.Warn("database error: ", err) | 		state.Client.Log.Warn("database error: ", err) | ||||||
| 		return state | 		return state | ||||||
| 	} | 	} | ||||||
| 	state.Client.Out.Encode(&messages.IQ{ | 	state.Client.Out.Encode(&messages.IQClient{ | ||||||
| 		Type: messages.IQTypeResult, | 		Type: messages.IQTypeResult, | ||||||
| 		To:   state.Client.JID.String(), | 		To:   state.Client.JID.String(), | ||||||
| 		From: state.Client.JID.Domain, | 		From: state.Client.JID.Domain, | ||||||
|  |  | ||||||
|  | @ -0,0 +1,11 @@ | ||||||
|  | tlsdir = "tmp/ssl" | ||||||
|  | state_path = "tmp/yaja-tester.json" | ||||||
|  | logging = 5 | ||||||
|  | 
 | ||||||
|  | webserver = ":https" | ||||||
|  | 
 | ||||||
|  | admins = ["a.admin@chat.sum7.eu"] | ||||||
|  | 
 | ||||||
|  | [client] | ||||||
|  | jid = "bot@chat.sum7.eu" | ||||||
|  | password = "test" | ||||||
		Reference in New Issue