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") }
// 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.note, 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 }
func (e privetError) json() []byte { marshalled, err := json.MarshalIndent(e, "", " ") if err != nil { log.Errorf("Failed to marshal Privet Error: %s", err) } return marshalled }
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) } }
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) } }
// 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 } } }
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 } } }
// 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 }
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) } }
// 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{}{} }
func (service *service) Execute(args []string, r <-chan svc.ChangeRequest, s chan<- svc.Status) (bool, uint32) { if service.interactive { if err := log.Start(true); err != nil { fmt.Fprintf(os.Stderr, "Failed to start event log: %s\n", err) return false, 1 } } else { if err := log.Start(false); err != nil { fmt.Fprintf(os.Stderr, "Failed to start event log: %s\n", err) return false, 1 } } defer log.Stop() config, configFilename, err := lib.GetConfig(service.context) if err != nil { fmt.Fprintf(os.Stderr, "Failed to read config file: %s\n", err) return false, 1 } logLevel, ok := log.LevelFromString(config.LogLevel) if !ok { fmt.Fprintf(os.Stderr, "Log level %s is not recognized\n", config.LogLevel) return false, 1 } log.SetLevel(logLevel) if configFilename == "" { log.Info("No config file was found, so using defaults") } else { log.Infof("Using config file %s", configFilename) } completeConfig, _ := json.MarshalIndent(config, "", " ") log.Debugf("Config: %s", string(completeConfig)) log.Info(lib.FullName) if !config.CloudPrintingEnable && !config.LocalPrintingEnable { log.Fatal("Cannot run connector with both local_printing_enable and cloud_printing_enable set to false") return false, 1 } else if config.LocalPrintingEnable { log.Fatal("Local printing has not been implemented in this version of the Windows connector.") return false, 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 false, 1 } xmppPingInterval, err := time.ParseDuration(config.XMPPPingInterval) if err != nil { log.Fatalf("Failed to parse xmpp ping interval default: %s", err) return false, 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.Fatal(err) return false, 1 } x, err = xmpp.NewXMPP(config.XMPPJID, config.ProxyName, config.XMPPServer, config.XMPPPort, xmppPingTimeout, xmppPingInterval, g.GetRobotAccessToken, xmppNotifications) if err != nil { log.Fatal(err) return false, 1 } defer x.Quit() } ws, err := winspool.NewWinSpool(*config.PrefixJobIDToJobTitle, config.DisplayNamePrefix, config.PrinterBlacklist, config.PrinterWhitelist) if err != nil { log.Fatal(err) return false, 1 } nativePrinterPollInterval, err := time.ParseDuration(config.NativePrinterPollInterval) if err != nil { log.Fatalf("Failed to parse printer poll interval: %s", err) return false, 1 } pm, err := manager.NewPrinterManager(ws, g, nil, nativePrinterPollInterval, config.NativeJobQueueSize, *config.CUPSJobFullUsername, config.ShareScope, jobs, xmppNotifications) if err != nil { log.Fatal(err) return false, 1 } defer pm.Quit() if config.CloudPrintingEnable { if config.LocalPrintingEnable { log.Infof("Ready to rock as proxy '%s' and in local mode", config.ProxyName) } else { log.Infof("Ready to rock as proxy '%s'", config.ProxyName) } } else { log.Info("Ready to rock in local-only mode") } s <- runningStatus for { request := <-r switch request.Cmd { case svc.Interrogate: s <- runningStatus case svc.Stop: s <- stoppingStatus log.Info("Shutting down") time.AfterFunc(time.Second*30, func() { log.Fatal("Failed to stop quickly; stopping forcefully") os.Exit(1) }) return false, 0 default: log.Errorf("Received unsupported service command from service control manager: %d", request.Cmd) } } }
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("", "cloud-print-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{ NativePrinterName: 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) }
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) }