Ejemplo n.º 1
1
func connector(context *cli.Context) int {
	config, configFilename, err := lib.GetConfig(context)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to read config file: %s", err)
		return 1
	}

	logFileMaxBytes := config.LogFileMaxMegabytes * 1024 * 1024
	var logWriter io.Writer
	logWriter, err = log.NewLogRoller(config.LogFileName, logFileMaxBytes, config.LogMaxFiles)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to start log roller: %s", err)
		return 1
	}

	if context.Bool("log-to-console") {
		logWriter = io.MultiWriter(logWriter, os.Stderr)
	}
	logLevel, ok := log.LevelFromString(config.LogLevel)
	if !ok {
		fmt.Fprintf(os.Stderr, "Log level %s is not recognized", config.LogLevel)
		return 1
	}
	log.SetLevel(logLevel)
	log.SetWriter(logWriter)

	if configFilename == "" {
		log.Info("No config file was found, so using defaults")
	}

	log.Error(lib.FullName)
	fmt.Println(lib.FullName)

	if !config.CloudPrintingEnable && !config.LocalPrintingEnable {
		log.Error("Cannot run connector with both local_printing_enable and cloud_printing_enable set to false")
		return 1
	}

	if _, err := os.Stat(config.MonitorSocketFilename); !os.IsNotExist(err) {
		if err != nil {
			log.Errorf("Failed to stat monitor socket: %s", err)
		} else {
			log.Errorf(
				"A connector is already running, or the monitoring socket %s wasn't cleaned up properly",
				config.MonitorSocketFilename)
		}
		return 1
	}

	jobs := make(chan *lib.Job, 10)
	xmppNotifications := make(chan xmpp.PrinterNotification, 5)

	var g *gcp.GoogleCloudPrint
	var x *xmpp.XMPP
	if config.CloudPrintingEnable {
		xmppPingTimeout, err := time.ParseDuration(config.XMPPPingTimeout)
		if err != nil {
			log.Fatalf("Failed to parse xmpp ping timeout: %s", err)
			return 1
		}
		xmppPingInterval, err := time.ParseDuration(config.XMPPPingInterval)
		if err != nil {
			log.Fatalf("Failed to parse xmpp ping interval default: %s", err)
			return 1
		}

		g, err = gcp.NewGoogleCloudPrint(config.GCPBaseURL, config.RobotRefreshToken,
			config.UserRefreshToken, config.ProxyName, config.GCPOAuthClientID,
			config.GCPOAuthClientSecret, config.GCPOAuthAuthURL, config.GCPOAuthTokenURL,
			config.GCPMaxConcurrentDownloads, jobs)
		if err != nil {
			log.Error(err)
			return 1
		}

		x, err = xmpp.NewXMPP(config.XMPPJID, config.ProxyName, config.XMPPServer, config.XMPPPort,
			xmppPingTimeout, xmppPingInterval, g.GetRobotAccessToken, xmppNotifications)
		if err != nil {
			log.Error(err)
			return 1
		}
		defer x.Quit()
	}

	cupsConnectTimeout, err := time.ParseDuration(config.CUPSConnectTimeout)
	if err != nil {
		log.Fatalf("Failed to parse CUPS connect timeout: %s", err)
		return 1
	}
	c, err := cups.NewCUPS(config.CopyPrinterInfoToDisplayName, config.PrefixJobIDToJobTitle,
		config.DisplayNamePrefix, config.CUPSPrinterAttributes, config.CUPSMaxConnections,
		cupsConnectTimeout)
	if err != nil {
		log.Fatal(err)
		return 1
	}
	defer c.Quit()

	var s *snmp.SNMPManager
	if config.SNMPEnable {
		log.Info("SNMP enabled")
		s, err = snmp.NewSNMPManager(config.SNMPCommunity, config.SNMPMaxConnections)
		if err != nil {
			log.Error(err)
			return 1
		}
		defer s.Quit()
	}

	var priv *privet.Privet
	if config.LocalPrintingEnable {
		if g == nil {
			priv, err = privet.NewPrivet(jobs, config.GCPBaseURL, nil)
		} else {
			priv, err = privet.NewPrivet(jobs, config.GCPBaseURL, g.ProximityToken)
		}
		if err != nil {
			log.Error(err)
			return 1
		}
		defer priv.Quit()
	}

	cupsPrinterPollInterval, err := time.ParseDuration(config.CUPSPrinterPollInterval)
	if err != nil {
		log.Fatalf("Failed to parse CUPS printer poll interval: %s", err)
		return 1
	}
	pm, err := manager.NewPrinterManager(c, g, priv, s, cupsPrinterPollInterval,
		config.CUPSJobQueueSize, config.CUPSJobFullUsername, config.CUPSIgnoreRawPrinters,
		config.ShareScope, jobs, xmppNotifications)
	if err != nil {
		log.Error(err)
		return 1
	}
	defer pm.Quit()

	m, err := monitor.NewMonitor(c, g, priv, pm, config.MonitorSocketFilename)
	if err != nil {
		log.Error(err)
		return 1
	}
	defer m.Quit()

	if config.CloudPrintingEnable {
		if config.LocalPrintingEnable {
			log.Errorf("Ready to rock as proxy '%s' and in local mode", config.ProxyName)
			fmt.Printf("Ready to rock as proxy '%s' and in local mode\n", config.ProxyName)
		} else {
			log.Errorf("Ready to rock as proxy '%s'", config.ProxyName)
			fmt.Printf("Ready to rock as proxy '%s'\n", config.ProxyName)
		}
	} else {
		log.Error("Ready to rock in local-only mode")
		fmt.Println("Ready to rock in local-only mode")
	}

	waitIndefinitely()

	log.Error("Shutting down")
	fmt.Println("")
	fmt.Println("Shutting down")

	return 0
}
Ejemplo n.º 2
0
func (api *privetAPI) accesstoken(w http.ResponseWriter, r *http.Request) {
	log.Debugf("Received /accesstoken request: %+v", r)
	if ok := api.checkRequest(w, r, "GET"); !ok {
		return
	}

	user := r.Form.Get("user")
	if len(user) == 0 {
		writeError(w, "invalid_params", "user parameter expected")
		return
	}

	responseBody, httpStatusCode, err := api.getProximityToken(api.gcpID, user)
	if err != nil {
		log.Errorf("Failed to get proximity token: %s", err)
	}

	if responseBody == nil || len(responseBody) == 0 {
		log.Warning("Cloud returned empty response body")
		writeError(w, "server_error", "Check connector logs")
		return
	}

	var response struct {
		Success        bool                   `json:"success"`
		Message        string                 `json:"message"`
		ErrorCode      int                    `json:"errorCode"`
		ProximityToken map[string]interface{} `json:"proximity_token"`
	}
	if err = json.Unmarshal(responseBody, &response); err != nil {
		log.Errorf("Failed to unmarshal ticket from cloud: %s", err)
		writeError(w, "server_error", "Check connector logs")
		return
	}

	if response.Success {
		token, err := json.MarshalIndent(response.ProximityToken, "", "  ")
		if err != nil {
			log.Errorf("Failed to marshal something that was just unmarshalled: %s", err)
			writeError(w, "server_error", "Check connector logs")
		} else {
			w.Write(token)
		}
		return
	}

	if response.ErrorCode != 0 {
		e := privetError{
			Error:          "server_error",
			Description:    response.Message,
			ServerAPI:      "/proximitytoken",
			ServerCode:     response.ErrorCode,
			ServerHTTPCode: httpStatusCode,
		}.json()
		w.Write(e)
		return
	}

	writeError(w, "server_error", "Check connector logs")
}
Ejemplo n.º 3
0
// handleClientStateChange makes clean transitions as the connection with
// avahi-daemon changes.
//export handleClientStateChange
func handleClientStateChange(client *C.AvahiClient, newState C.AvahiClientState, userdata unsafe.Pointer) {
	z := instance
	z.spMutex.Lock()
	defer z.spMutex.Unlock()

	// Name conflict.
	if newState == C.AVAHI_CLIENT_S_COLLISION {
		log.Warning("Avahi reports a host name collision.")
	}

	// Transition from not connecting to connecting. Warn in logs.
	if newState == C.AVAHI_CLIENT_CONNECTING {
		log.Warning("Cannot find Avahi daemon. Is it running?")
	}

	// Transition from running to not running. Free all groups.
	if newState != C.AVAHI_CLIENT_S_RUNNING {
		log.Info("Local printing disabled (Avahi client is not running).")
		for name, r := range z.printers {
			if r.group != nil {
				if errstr := C.removeAvahiGroup(z.threadedPoll, r.group); errstr != nil {
					err := errors.New(C.GoString(errstr))
					log.Errorf("Failed to remove Avahi group: %s", err)
				}
				r.group = nil
				z.printers[name] = r
			}
		}
	}

	// Transition from not running to running. Recreate all groups.
	if newState == C.AVAHI_CLIENT_S_RUNNING {
		log.Info("Local printing enabled (Avahi client is running).")
		for name, r := range z.printers {
			txt := prepareTXT(r.ty, r.url, r.id, r.online)
			defer C.avahi_string_list_free(txt)

			if errstr := C.addAvahiGroup(z.threadedPoll, z.client, &r.group, r.name, C.ushort(r.port), txt); errstr != nil {
				err := errors.New(C.GoString(errstr))
				log.Errorf("Failed to add Avahi group: %s", err)
			}

			z.printers[name] = r
		}
	}

	// Transition from not failure to failure. Recreate thread poll and client.
	if newState == C.AVAHI_CLIENT_FAILURE {
		z.restart <- struct{}{}
	}

	z.state = newState
}
Ejemplo n.º 4
0
func (e privetError) json() []byte {
	marshalled, err := json.MarshalIndent(e, "", "  ")
	if err != nil {
		log.Errorf("Failed to marshal Privet Error: %s", err)
	}
	return marshalled
}
Ejemplo n.º 5
0
// HandleJobs gets and processes jobs waiting on a printer.
func (gcp *GoogleCloudPrint) HandleJobs(printer *lib.Printer, reportJobFailed func()) {
	jobs, err := gcp.Fetch(printer.GCPID)
	if err != nil {
		log.Errorf("Failed to fetch jobs for GCP printer %s: %s", printer.GCPID, err)
	} else {
		for i := range jobs {
			go gcp.processJob(&jobs[i], printer, reportJobFailed)
		}
	}
}
Ejemplo n.º 6
0
func (api *privetAPI) serve() {
	sm := http.NewServeMux()
	sm.HandleFunc("/privet/info", api.info)
	if api.online {
		sm.HandleFunc("/privet/accesstoken", api.accesstoken)
	}
	sm.HandleFunc("/privet/capabilities", api.capabilities)
	sm.HandleFunc("/privet/printer/createjob", api.createjob)
	sm.HandleFunc("/privet/printer/submitdoc", api.submitdoc)
	sm.HandleFunc("/privet/printer/jobstate", api.jobstate)

	err := http.Serve(api.listener, sm)
	if err != nil && err != closed {
		log.Errorf("Privet API HTTP server failed: %s", err)
	}
}
Ejemplo n.º 7
0
func (api *privetAPI) createjob(w http.ResponseWriter, r *http.Request) {
	log.Debugf("Received /createjob request: %+v", r)
	if ok := api.checkRequest(w, r, "POST"); !ok {
		return
	}

	requestBody, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Warningf("Failed to read request body: %s", err)
		writeError(w, "invalid_ticket", "Check connector logs")
		return
	}

	var ticket cdd.CloudJobTicket
	if err = json.Unmarshal(requestBody, &ticket); err != nil {
		log.Warningf("Failed to read request body: %s", err)
		writeError(w, "invalid_ticket", "Check connector logs")
		return
	}

	printer, exists := api.getPrinter(api.name)
	if !exists {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	if printer.State.State == cdd.CloudDeviceStateStopped {
		writeError(w, "printer_error", "Printer is stopped")
		return
	}

	jobID, expiresIn := api.jc.createJob(&ticket)
	var response struct {
		JobID     string `json:"job_id"`
		ExpiresIn int32  `json:"expires_in"`
	}
	response.JobID = jobID
	response.ExpiresIn = expiresIn
	j, err := json.MarshalIndent(response, "", "  ")
	if err != nil {
		api.jc.deleteJob(jobID)
		log.Errorf("Failed to marrshal createJob response: %s", err)
		w.WriteHeader(http.StatusInternalServerError)
	} else {
		w.Write(j)
	}
}
Ejemplo n.º 8
0
func (m *Monitor) listen(listener net.Listener) {
	ch := make(chan net.Conn)
	quitReq := make(chan bool, 1)
	quitAck := make(chan bool)

	go func() {
		for {
			conn, err := listener.Accept()
			if err != nil {
				select {
				case <-quitReq:
					quitAck <- true
					return
				}
				log.Errorf("Error listening to monitor socket: %s", err)
			} else {
				ch <- conn
			}
		}
	}()

	for {
		select {
		case conn := <-ch:
			log.Info("Received monitor request")
			stats, err := m.getStats()
			if err != nil {
				log.Warningf("Monitor request failed: %s", err)
				conn.Write([]byte("error"))
			} else {
				conn.Write([]byte(stats))
			}
			conn.Close()

		case <-m.listenerQuit:
			quitReq <- true
			listener.Close()
			<-quitAck
			m.listenerQuit <- true
			return
		}
	}
}
Ejemplo n.º 9
0
// keepXMPPAlive restarts XMPP when it fails.
func (x *XMPP) keepXMPPAlive() {
	for {
		select {
		case <-x.dead:
			log.Error("XMPP conversation died; restarting")
			if err := x.startXMPP(); err != nil {
				for err != nil {
					log.Errorf("XMPP restart failed, will try again in 10s: %s", err)
					time.Sleep(10 * time.Second)
					err = x.startXMPP()
				}
				log.Error("XMPP conversation restarted successfully")
			}

		case <-x.quit:
			// Close XMPP.
			x.ix.Quit()
			return
		}
	}
}
Ejemplo n.º 10
0
func (z *zeroconf) restartAndQuit() {
	for {
		select {
		case <-z.restart:
			log.Warning("Avahi client failed. Make sure that avahi-daemon is running while I restart the client.")

			C.stopAvahiClient(z.threadedPoll, z.client)

			if errstr := C.startAvahiClient(&z.threadedPoll, &z.client); errstr != nil {
				err := errors.New(C.GoString(errstr))
				log.Errorf("Failed to restart Avahi client: %s", err)
			}

		case <-z.q:
			for name := range z.printers {
				z.removePrinter(name)
			}
			C.stopAvahiClient(z.threadedPoll, z.client)
			close(z.q)
			return
		}
	}
}
Ejemplo n.º 11
0
// jobState gets the state of the job identified by jobID as JSON-encoded response.
//
// Returns an empty byte array if the job doesn't exist (because it expired).
func (jc *jobCache) jobState(jobID string) ([]byte, bool) {
	jc.entriesMutex.Lock()
	defer jc.entriesMutex.Unlock()

	entry, exists := jc.entries[jobID]
	if !exists {
		return []byte{}, false
	}

	var response struct {
		JobID         string            `json:"job_id"`
		State         cdd.JobStateType  `json:"state"`
		ExpiresIn     int32             `json:"expires_in"`
		JobType       string            `json:"job_type,omitempty"`
		JobSize       int64             `json:"job_size,omitempty"`
		JobName       string            `json:"job_name,omitempty"`
		SemanticState cdd.PrintJobState `json:"semantic_state"`
	}

	response.JobID = jobID
	response.State = entry.state.Type
	response.ExpiresIn = entry.expiresIn()
	response.JobType = entry.jobType
	response.JobSize = entry.jobSize
	response.JobName = entry.jobName
	response.SemanticState.Version = "1.0"
	response.SemanticState.State = entry.state
	response.SemanticState.PagesPrinted = entry.pagesPrinted

	j, err := json.MarshalIndent(response, "", "  ")
	if err != nil {
		log.Errorf("Failed to marshal Privet jobState: %s", err)
		return []byte{}, false
	}

	return j, true
}
Ejemplo n.º 12
0
func (api *privetAPI) capabilities(w http.ResponseWriter, r *http.Request) {
	log.Debugf("Received /capabilities request: %+v", r)
	if ok := api.checkRequest(w, r, "GET"); !ok {
		return
	}

	printer, exists := api.getPrinter(api.name)
	if !exists {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	capabilities := cdd.CloudDeviceDescription{
		Version: "1.0",
		Printer: printer.Description,
	}
	j, err := json.MarshalIndent(capabilities, "", "  ")
	if err != nil {
		log.Errorf("Failed to marshal capabilities response: %s", err)
		w.WriteHeader(http.StatusInternalServerError)
	} else {
		w.Write(j)
	}
}
Ejemplo n.º 13
0
// dispatchIncoming listens for new XMPP notifications and puts them into
// separate channels, by type of message.
func (x *internalXMPP) dispatchIncoming(dying chan<- struct{}) {
	for {
		// The xml.StartElement tells us what is coming up.
		startElement, err := readStartElement(x.xmlDecoder)
		if err != nil {
			if isXMLErrorClosedConnection(err) {
				break
			}
			log.Errorf("Failed to read the next start element: %s", err)
			break
		}

		// Parse the message.
		if startElement.Name.Local == "message" {
			var message struct {
				XMLName xml.Name `xml:"message"`
				Data    string   `xml:"push>data"`
			}

			if err := x.xmlDecoder.DecodeElement(&message, startElement); err != nil {
				if isXMLErrorClosedConnection(err) {
					break
				}
				log.Warningf("Error while parsing print jobs notification via XMPP: %s", err)
				continue
			}

			messageData, err := base64.StdEncoding.DecodeString(message.Data)
			if err != nil {
				log.Warningf("Failed to convert XMPP message data from base64: %s", err)
				continue
			}

			messageDataString := string(messageData)
			if strings.ContainsRune(messageDataString, '/') {
				if strings.HasSuffix(messageDataString, "/delete") {
					gcpID := strings.TrimSuffix(messageDataString, "/delete")
					x.notifications <- PrinterNotification{gcpID, PrinterDelete}
				}
				// Ignore other suffixes, like /update_settings.
			} else {
				x.notifications <- PrinterNotification{messageDataString, PrinterNewJobs}
			}

		} else if startElement.Name.Local == "iq" {
			var message struct {
				XMLName xml.Name `xml:"iq"`
				ID      string   `xml:"id,attr"`
				Type    string   `xml:"type,attr"`
			}

			if err := x.xmlDecoder.DecodeElement(&message, startElement); err != nil {
				if isXMLErrorClosedConnection(err) {
					break
				}
				log.Warningf("Error while parsing XMPP pong: %s", err)
				continue
			}

			pingID, err := strconv.ParseUint(message.ID, 10, 8)
			if err != nil {
				log.Warningf("Failed to convert XMPP ping ID: %s", err)
				continue
			}
			x.pongs <- uint8(pingID)

		} else {
			log.Warningf("Unexpected element while waiting for print message: %+v", startElement)
		}
	}

	dying <- struct{}{}
}
Ejemplo n.º 14
0
func (api *privetAPI) submitdoc(w http.ResponseWriter, r *http.Request) {
	log.Debugf("Received /submitdoc request: %+v", r)
	if ok := api.checkRequest(w, r, "POST"); !ok {
		return
	}

	file, err := ioutil.TempFile("", "cups-connector-privet-")
	if err != nil {
		log.Errorf("Failed to create file for new Privet job: %s", err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	defer file.Close()

	jobSize, err := io.Copy(file, r.Body)
	if err != nil {
		log.Errorf("Failed to copy new print job file: %s", err)
		w.WriteHeader(http.StatusInternalServerError)
		os.Remove(file.Name())
		return
	}
	if length, err := strconv.ParseInt(r.Header.Get("Content-Length"), 10, 64); err != nil || length != jobSize {
		writeError(w, "invalid_params", "Content-Length header doesn't match length of content")
		os.Remove(file.Name())
		return
	}

	jobType := r.Header.Get("Content-Type")
	if jobType == "" {
		writeError(w, "invalid_document_type", "Content-Type header is missing")
		os.Remove(file.Name())
		return
	}

	printer, exists := api.getPrinter(api.name)
	if !exists {
		w.WriteHeader(http.StatusInternalServerError)
		os.Remove(file.Name())
		return
	}
	if printer.State.State == cdd.CloudDeviceStateStopped {
		writeError(w, "printer_error", "Printer is stopped")
		os.Remove(file.Name())
		return
	}

	jobName := r.Form.Get("job_name")
	userName := r.Form.Get("user_name")
	jobID := r.Form.Get("job_id")
	var expiresIn int32
	var ticket *cdd.CloudJobTicket
	if jobID == "" {
		jobID, expiresIn = api.jc.createJob(nil)
	} else {
		var ok bool
		if expiresIn, ticket, ok = api.jc.getJobExpiresIn(jobID); !ok {
			pe := privetError{
				Error:   "invalid_print_job",
				Timeout: 5,
			}.json()
			w.Write(pe)
			os.Remove(file.Name())
			return
		}
	}

	api.jobs <- &lib.Job{
		CUPSPrinterName: api.name,
		Filename:        file.Name(),
		Title:           jobName,
		User:            userName,
		JobID:           jobID,
		Ticket:          ticket,
		UpdateJob:       api.jc.updateJob,
	}

	var response struct {
		JobID     string `json:"job_id"`
		ExpiresIn int32  `json:"expires_in"`
		JobType   string `json:"job_type"`
		JobSize   int64  `json:"job_size"`
		JobName   string `json:"job_name,omitempty"`
	}

	response.JobID = jobID
	response.ExpiresIn = expiresIn
	response.JobType = jobType
	response.JobSize = jobSize
	response.JobName = jobName
	j, err := json.MarshalIndent(response, "", "  ")
	if err != nil {
		log.ErrorJobf(jobID, "Failed to marshal submitdoc response: %s", err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	w.Write(j)
}
Ejemplo n.º 15
0
func (api *privetAPI) info(w http.ResponseWriter, r *http.Request) {
	log.Debugf("Received /info request: %+v", r)
	if r.Method != "GET" {
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}
	if _, exists := r.Header["X-Privet-Token"]; !exists {
		w.WriteHeader(http.StatusBadRequest)
		writeError(w, "invalid_x_privet_token",
			"X-Privet-Token request header is missing or invalid")
		return
	}

	printer, exists := api.getPrinter(api.name)
	if !exists {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	var s cdd.CloudConnectionStateType
	if api.online {
		s = cdd.CloudConnectionStateOnline
	} else {
		s = cdd.CloudConnectionStateOffline
	}
	state := cdd.CloudDeviceState{
		Version:              "1.0",
		CloudConnectionState: &s,
		Printer:              printer.State,
	}

	var connectionState string
	var supportedAPIs []string
	if api.online {
		connectionState = "online"
		supportedAPIs = supportedAPIsOnline
	} else {
		connectionState = "offline"
		supportedAPIs = supportedAPIsOffline
	}

	response := infoResponse{
		Version:         "1.0",
		Name:            printer.DefaultDisplayName,
		URL:             api.gcpBaseURL,
		Type:            []string{"printer"},
		ID:              printer.GCPID,
		DeviceState:     strings.ToLower(string(printer.State.State)),
		ConnectionState: connectionState,
		Manufacturer:    printer.Manufacturer,
		Model:           printer.Model,
		SerialNumber:    printer.UUID,
		Firmware:        printer.ConnectorVersion,
		Uptime:          uint(time.Since(api.startTime).Seconds()),
		SetupURL:        printer.SetupURL,
		SupportURL:      printer.SupportURL,
		UpdateURL:       printer.UpdateURL,
		XPrivetToken:    api.xsrf.newToken(),
		API:             supportedAPIs,
		SemanticState:   state,
	}

	j, err := json.MarshalIndent(response, "", "  ")
	if err != nil {
		log.Errorf("Failed to marshal Privet info: %s", err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	w.Write(j)
}