// Returns a PeerConnection for talking to addr, which can be either
// IP:ADDR or HOST:ADDR (where HOST is something that DNS can resolve).
func Peer(addr string) *PeerConnection {
	addr, err := util.Resolve(addr, config.IP)
	if err != nil {
		return &PeerConnection{err: err}
	}

	host, port, err := net.SplitHostPort(addr)
	if err != nil {
		return &PeerConnection{err: err}
	}

	addr = host + ":" + port

	if addr == config.ServerSourceAddress {
		panic("Peer() called with my own address. This is a bug!")
	}

	connections_mutex.Lock()
	defer connections_mutex.Unlock()

	conn, have_already := connections[addr]
	if !have_already {
		conn = &PeerConnection{is_gosusi: false, addr: addr}
		connections[addr] = conn
		go util.WithPanicHandler(func() { conn.handleConnection() })
	}
	return conn
}
Exemple #2
0
// Accepts UDP connections for TFTP requests on listen_address, serves read requests
// for path P based on request_re and reply as follows:
//
// request_re and reply have to be lists of
// equal length. Let request_re[i] be the first entry in request_re that
// matches P, then reply[i] specifies the data to return for the request.
// If reply[i] == "", then a file not found error is returned to the requestor.
// If reply[i] starts with the character '|', the remainder is taken as the path
// of a hook to execute and its stdout is returned to the requestor.
// Otherwise reply[i] is taken as the path of the file whose contents to send to
// the requestor.
//
// When executing a hook, an environment variable called "tftp_request"
// is passed containing P. If request_re[i] has a capturing
// group named "macaddress", the captured substring will be converted to
// a MAC address by converting to lowercase, removing all characters
// except 0-9a-f, left-padding to length 12 with 0s or truncating to length 12
// and inserting ":"s. The result will be added to
// the hook environment in a variable named "macaddress" and if there
// is an LDAP object for that macaddress, its attributes will be added
// to the environment, too.
//
// Named subexpressions in request_re[i] other than "macaddress" will be
// exported to the hook verbatim in like-named environment variables.
func ListenAndServe(listen_address string, request_re []*regexp.Regexp, reply []string) {
	for i := range request_re {
		util.Log(1, "INFO! TFTP: %v -> %v", request_re[i], reply[i])
	}

	udp_addr, err := net.ResolveUDPAddr("udp", listen_address)
	if err != nil {
		util.Log(0, "ERROR! Cannot start TFTP server: %v", err)
		return
	}

	udp_conn, err := net.ListenUDP("udp", udp_addr)
	if err != nil {
		util.Log(0, "ERROR! ListenUDP(): %v", err)
		return
	}
	defer udp_conn.Close()

	readbuf := make([]byte, 16384)
	for {
		n, return_addr, err := udp_conn.ReadFromUDP(readbuf)
		if err != nil {
			util.Log(0, "ERROR! ReadFromUDP(): %v", err)
			continue
		}

		// Make a copy of the buffer BEFORE starting the goroutine to prevent subsequent requests from
		// overwriting the buffer.
		payload := string(readbuf[:n])

		go util.WithPanicHandler(func() { handleConnection(return_addr, payload, request_re, reply) })

	}
}
Exemple #3
0
// Tries to re-establish communication with a client/server at the given IP,
// by
//   1) sending here_i_am to the server where we are registered. We do this
//      even if config.RunServer (i.e. we are registered at ourselves) because
//      this will trigger new_foreign_client messages sent to peers so that other
//      servers that may believe they own us correct their data.
//   2) sending (if config.RunServer) new_server messages to all known servers
//      we find for the IP in our servers database.
//   3) if config.RunServer and in 2) we did not find a server at that IP,
//      maybe it's a client that thinks we are its server. Send "deregistered" to
//      all ClientPorts in that case to cause re-registration.
func tryToReestablishCommunicationWith(ip string) {
	// Wait a little to limit the rate of spam wars between
	// 2 machines that can't re-establish communication (e.g. because of changed
	// keys in server.conf).
	mapIP2ReestablishDelay_mutex.Lock()
	var delay time.Duration
	var ok bool
	if delay, ok = mapIP2ReestablishDelay[ip]; !ok {
		delay = 1 * time.Minute
	}
	mapIP2ReestablishDelay[ip] = 2 * delay
	mapIP2ReestablishDelay_mutex.Unlock()

	// if the delay exceeds 24h this means that we got multiple
	// reestablish requests while we're still waiting to begin one
	// in that case, bail out.
	if delay > 24*time.Hour {
		return
	}

	util.Log(0, "WARNING! Will try to re-establish communication with %v after waiting %v", ip, delay)
	time.Sleep(delay)

	// if we actually completed a 10h wait, reset the timer to 1 minute
	if delay >= 10*time.Hour {
		mapIP2ReestablishDelay_mutex.Lock()
		mapIP2ReestablishDelay[ip] = 1 * time.Minute
		mapIP2ReestablishDelay_mutex.Unlock()
	}

	util.Log(0, "WARNING! Will try to re-establish communication with %v", ip)
	ConfirmRegistration() // 1)

	ip, err := util.Resolve(ip, config.IP)
	if err != nil {
		util.Log(0, "ERROR! Resolve(): %v", err)
	}

	if config.RunServer { // 2)
		sendmuell := true
		for _, server := range db.ServerAddresses() {
			if strings.HasPrefix(server, ip) {
				sendmuell = false
				srv := server
				go util.WithPanicHandler(func() { Send_new_server("new_server", srv) })
			}
		}

		if sendmuell {
			for _, port := range config.ClientPorts {
				addr := ip + ":" + port
				if addr != config.ServerSourceAddress { // never send "deregistered" to our own server
					dereg := "<xml><header>deregistered</header><source>" + config.ServerSourceAddress + "</source><target>" + addr + "</target></xml>"
					go security.SendLnTo(addr, dereg, "", false)
				}
			}
		}
	}
}
func (f *LoggingFileStorer) Store(data *xml.Hash) (err error) {
	util.WithPanicHandler(func() {
		err = f.FileStorer.Store(data)
		if err != nil {
			util.Log(0, "ERROR! Cannot store database: %v", err)
		}
	})

	return err
}
Exemple #5
0
// Handles the message "new_server".
//  xmlmsg: the decrypted and parsed message
func new_server(xmlmsg *xml.Hash) {
	server, _ := util.Resolve(xmlmsg.Text("source"), config.IP)
	if server == config.ServerSourceAddress {
		return
	} // never accept our own address as peer
	setGoSusi(xmlmsg)
	db.ServerUpdate(xmlmsg)
	handleClients(xmlmsg)
	go util.WithPanicHandler(func() {
		Send_new_server("confirm_new_server", server)
		Peer(server).SyncAll()
	})
	return
}
Exemple #6
0
func faimon(listen_address string) {
	listener, err := net.Listen("tcp", listen_address)
	if err != nil {
		util.Log(0, "ERROR! Cannot start FAI monitor: %v", err)
		return
	}

	for {
		conn, err := listener.Accept()
		if err != nil {
			util.Log(0, "ERROR! FAI monitor error: %v", err)
			continue
		}

		go util.WithPanicHandler(func() { faiConnection(conn.(*net.TCPConn)) })
	}
}
// Handles "detect_hardware".
//  xmlmsg: the decrypted and parsed message
func detect_hardware(xmlmsg *xml.Hash) {
	server := xmlmsg.Text("source")
	if server == "" {
		util.Log(0, "ERROR! Received detect_hardware from unknown source")
		return
	}

	c := make(chan *xml.Hash, 2)
	go func() {
		time.Sleep(config.DetectHardwareTimeout)
		c <- nil
	}()

	go util.WithPanicHandler(func() { sendDetectedHardwareReply(server, c) })

	start := time.Now()
	env := config.HookEnvironment()
	for _, tag := range xmlmsg.Subtags() {
		env = append(env, tag+"="+strings.Join(xmlmsg.Get(tag), "\n"))
	}
	cmd := exec.Command(config.DetectHardwareHookPath)
	env = append(env, "xml="+xmlmsg.String())
	cmd.Env = append(env, os.Environ()...)
	util.Log(1, "INFO! Running detect-hardware-hook %v with parameters %v", config.DetectHardwareHookPath, env)
	hwlist, err := xml.LdifToHash("detected_hardware", false, cmd) // !!C'n'P WARNING: casefold=false!!
	if err != nil {
		util.Log(0, "ERROR! detect-hardware-hook %v: %v", config.DetectHardwareHookPath, err)
		return
	}
	util.Log(1, "INFO! Finished detect-hardware-hook. Running time: %v", time.Since(start))
	for hwlist.RemoveFirst("dn") != nil {
	} // dn is ignored (see manual)
	util.Log(1, "INFO! Hardware detection result: %v", hwlist)

	c <- hwlist
}
Exemple #8
0
// Launches a background job that queries the systemdb for the name of
// the machine with the given macaddress and if/when the answer arrives,
// schedules an update of all entries in the jobdb that match the macaddress.
func JobsUpdateNameForMAC(macaddress string) {
	updatename := func(request *jobDBRequest) {
		plainname := request.Job.Text("plainname")
		found := jobDB.Query(request.Filter).First("job")
		for ; found != nil; found = found.Next() {
			if found.Text("plainname") != plainname {
				found.FirstOrAdd("plainname").SetText(plainname)
				jobDB.Replace(xml.FilterSimple("id", found.Text("id")), true, found)

				// if the job is one of ours, then send out fju to gosa-si peers
				// because they don't look up the name themselves
				if found.Text("siserver") == config.ServerSourceAddress {
					fju := xml.NewHash("xml", "header", "foreign_job_updates")
					clone := found.Clone()
					clone.Rename("answer1")

					fju.AddWithOwnership(clone)

					fju.Add("source", config.ServerSourceAddress)
					fju.Add("target", "gosa-si") // only gosa-si peers
					fju.Add("sync", "ordered")
					ForeignJobUpdates <- fju
				}
			}
		}
	}

	go util.WithPanicHandler(func() {
		filter := xml.FilterSimple("macaddress", macaddress)
		plainname := SystemPlainnameForMAC(macaddress)
		if plainname != "none" {
			job := xml.NewHash("job", "plainname", plainname)
			jobDBRequests <- &jobDBRequest{updatename, filter, job, nil}
		}
	})
}
Exemple #9
0
// This function runs in a single goroutine and is responsible for handling
// all actions that affect the jobDB as well as starting local jobs whose time
// has come.
// The general idea behind synchronized job processing is this:
//  * a single goroutine processes all requests that affect the jobDB and pushes
//    the resulting changes into the ForeignJobUpdates queue
//  * a single goroutine processes the items from ForeignJobUpdates and passes them
//    on to the appropriate PeerConnection(s).
//  * each PeerConnection has a single goroutine forwarding the updates over a
//    single TCP connection to its respective peer.
//  * The above ensures that each peer receives foreign_job_updates messages in
//    exactly the same order in which the corresponding edits are made on the jobDB.
//    This makes sure that a peer that applies foreign_job_updates messages in
//    order will always have a consistent jobdb.
//  * Because <sync>all</sync> messages are prepared by the same single goroutine
//    that performs the edits and creates <sync>ordered</sync> messages and because
//    all these messages go over the same channels, they cannot overtake each other
//    and will always fit together.
func handleJobDBRequests() {
	groom_ticker := time.Tick(config.JobDBGroomInterval)
	hour, min, _ := time.Now().Clock()
	next_groom_minutes_since_midnight := hour*60 + min + int(config.JobDBGroomInterval/time.Minute)
	backwardsjump := false
	var request *jobDBRequest
	for {
		select {
		case request = <-jobDBRequests:
			request.Action(request)

		case _ = <-groom_ticker:
			hour, min, _ = time.Now().Clock()
			minutes_since_midnight := hour*60 + min

			// If everything is normal, groomlag should be 0. A negative number
			// can only happend if the clock changes because (unless there's a bug)
			// groom_ticker will never fire too early.
			// The number 1 is possible in the pathological case
			// that the timer fires just before the minute wraps around and in
			// the few milliseconds delay between the timer firing and us reading
			// the clock the minute wraps around.
			groomlag := minutes_since_midnight - next_groom_minutes_since_midnight
			// normalize groomlag to be in the range (-12*60,12*60]
			if groomlag <= -12*60 {
				groomlag += 24 * 60
			}
			if groomlag > 12*60 {
				groomlag -= 24 * 60
			}

			if groomlag > 3 { // we accept 3 minutes lag as normal (extremely high server load)
				// Do not groom jobDB after a clock jump forward because groomJobDB()
				// would assume that jobs have not started correctly even though they
				// haven't got their chance yet and will probably start up in the next
				// few minutes.
				// NOTE: The message does not include the word "FORWARD" because
				// a backwards adjustment by an amount that is not a multiple of
				// config.JobDBGroomInterval will first trigger the BACKWARD case
				// and then trigger this case at the next grooming time. So we
				// keep this message phrased in a way that it will not create the
				// mistaken impression that the clock jumps around wildly.
				util.Log(1, "INFO! Grooming jobdb SKIPPED because of clock adjustment")

				// The next grooming will take place as usual.
				next_groom_minutes_since_midnight = minutes_since_midnight + int(config.JobDBGroomInterval/time.Minute)

				// Ping processPendingActions because as mentioned further above we can
				// enter this case not only after an actual forwards jump of the clock
				// but also after a backwards jump once our clock has finally caught up.
				// And in that case the comment further below still applies, that we
				// need to ping processPendingActions to make sure that jobs are
				// triggered before the next grooming.
				go func() { processPendingActions <- true }()

				// Clear backwardsjump marker if it is set (which is possible; see
				// previous comments)
				backwardsjump = false

			} else if groomlag < -3 { // we accept groom_ticker to fire up to 3 minutes early (clock adjustment for drift)
				util.Log(1, "INFO! Grooming jobdb SKIPPED because of clock jump BACKWARDS")

				// We keep next_groom_minutes_since_midnight at its current value
				// (which is in the future) until our wall clock has caught up.
				// We do this so that every config.JobDBGroomInterval we run into
				// this case again and can ping processPendingActions, because
				// if the backwards jump happened right between the firing of
				// a job's processPendingActions ping and the time the database
				// is queried for pending jobs, the query will not see the jobs
				// as pending and won't trigger them. The next groomJobDB() would then
				// discover the unlaunched jobs, report a bug and kill them.
				// But by pinging processPendingActions here without grooming until
				// our clock has caught up with the originally scheduled grooming time,
				// we make sure that jobs will be started, if not at the right time,
				// at least before the next grooming.
				go func() { processPendingActions <- true }()

				backwardsjump = true

			} else {
				next_groom_minutes_since_midnight = minutes_since_midnight + int(config.JobDBGroomInterval/time.Minute)
				groom_delay := time.Duration(0)
				// If we get here after a backward jump of the clock, there may
				// still be jobs waiting to be executed, so we ping
				// processPendingActions and delay the grooming a little
				if backwardsjump {
					go func() { processPendingActions <- true }()
					groom_delay = time.Minute
					backwardsjump = false
				}

				go func(dly time.Duration) {
					time.Sleep(dly)
					util.WithPanicHandler(groomJobDB)
				}(groom_delay)
			}

		case _ = <-processPendingActions:
			/*** WARNING! WARNING! ***
			  Using the function JobsQuery() here will cause deadlock!
			  Other functions like JobsModifyLocal() are okay, but remember
			  that they will not be executed until this case ends.
			  *************************/
			localwait := xml.FilterSimple("siserver", config.ServerSourceAddress,
				"status", "waiting")
			beforenow := xml.FilterRel("timestamp", util.MakeTimestamp(time.Now()), -1, 0)
			filter := xml.FilterAnd([]xml.HashFilter{localwait, beforenow})
			JobsModifyLocal(filter, xml.NewHash("job", "status", "launch"))
		}
	}
}
Exemple #10
0
// Infinite loop that consumes *xml.Hash job descriptors from
// db.PendingActions and launches goroutines to perform the appropriate
// action depending on the job's status ("done" or "processing").
// This function is also responsible for adding a new job when a periodic
// job is done.
func Init() { // not init() because we need to call it from go-susi.go
	go func() {
		for {
			job := db.PendingActions.Next().(*xml.Hash)

			if job.Text("status") != "done" {

				util.Log(1, "INFO! Taking action for job: %v", job)

				go util.WithPanicHandler(func() {

					if !Forward(job) {

						// Tell the lucky winner what we're going to do with it.

						macaddress := job.Text("macaddress")
						headertag := job.Text("headertag")
						if headertag != "send_user_msg" && // send_user_msg does not target a machine
							headertag != "set_activated_for_installation" { // set_activated_for_installation is sent when the action is taken
							client := db.ClientWithMAC(macaddress)
							if client == nil {
								util.Log(0, "ERROR! Client with MAC %v not in clientdb. Cannot send %v", macaddress, headertag)
								// Don't abort. Some jobs work even if we can't reach the client.
							} else {
								client_addr := client.Text("client")
								util.Log(1, "INFO! Sending %v to %v", headertag, client_addr)
								trigger_action := "<xml><header>" + headertag + "</header><" + headertag + "></" + headertag + "><source>" + config.ServerSourceAddress + "</source><target>" + client_addr + "</target></xml>"
								message.Client(client_addr).Tell(trigger_action, config.ActionAnnouncementTTL)
							}
						}

						// Now that the client is rightfully excited, give it our best shot.

						done := true
						switch headertag {
						case "send_user_msg":
							SendUserMsg(job)
						case "trigger_action_wake":
							Wake(job) // "Aufwecken"
						case "trigger_action_lock":
							Lock(job) // "Sperre"
						case "trigger_action_localboot":
							Localboot(job) // "Erzwinge lokalen Start"
						case "trigger_action_halt":
							Halt(job) // "Anhalten"
						case "trigger_action_reboot":
							Reboot(job) // "Neustarten"
						case "trigger_action_faireboot":
							FAIReboot(job) // "Job abbrechen"
						case "set_activated_for_installation",
							"trigger_action_activate":
							Activate(job) // "Sperre aufheben"
						case "trigger_action_update":
							Update(job) // "Aktualisieren"
							done = false
						case "trigger_action_reinstall":
							Reinstall(job) // "Neuinstallation"
							done = false
						default:
							util.Log(0, "ERROR! Unknown headertag in PendingActions for job: %v", job)
						}

						if done {
							util.Log(1, "INFO! No further processing required => Removing job: %v", job)
							db.JobsRemoveLocal(xml.FilterSimple("id", job.Text("id")), false)
						}
					}
				})

			} else // if status == "done"
			{
				util.Log(1, "INFO! Job is done or cancelled: %v", job)

				go util.WithPanicHandler(func() {

					switch job.Text("headertag") {
					case "send_user_msg":
					case "trigger_action_lock": // "Sperre"
					case "trigger_action_halt": // "Anhalten"
					case "trigger_action_localboot": // "Erzwinge lokalen Start"
					case "trigger_action_reboot": // "Neustarten"
					case "trigger_action_faireboot": // "Job abbrechen"
					case "set_activated_for_installation",
						"trigger_action_activate": // "Sperre aufheben"
					case "trigger_action_wake": // "Aufwecken"

					case "trigger_action_update", // "Aktualisieren"
						"trigger_action_reinstall": // "Neuinstallation"
						macaddress := job.Text("macaddress")
						faistate := db.SystemGetState(macaddress, "faiState")
						if faistate == "" || strings.HasPrefix(faistate, "softupdat") || strings.HasPrefix(faistate, "install") {
							if job.Text("progress") == "forward" || job.Text("progress") == "groom" {
								util.Log(1, "INFO! Job removed due to %ving => will NOT set faiState to \"localboot\" for client with MAC %v", job.Text("progress"), macaddress)
							} else {
								util.Log(1, "INFO! Setting faiState \"localboot\" for client with MAC %v", macaddress)
								db.SystemSetState(macaddress, "faiState", "localboot")
							}
						} else if faistate != "localboot" {
							util.Log(1, "INFO! Client with MAC %v has faiState \"%v\" => will NOT overwrite this with \"localboot\"", macaddress, faistate)
						}

					default:
						util.Log(0, "ERROR! Unknown headertag \"%v\" in PendingActions", job.Text("headertag"))
					}

					periodic := job.Text("periodic")
					if periodic != "none" && periodic != "" {
						t := util.ParseTimestamp(job.Text("timestamp"))
						p := strings.Split(periodic, "_")
						if len(p) != 2 {
							util.Log(0, "ERROR! Illegal <periodic>: %v", periodic)
							return
						}
						period, err := strconv.ParseUint(p[0], 10, 64)
						if err != nil || period == 0 {
							util.Log(0, "ERROR! Illegal <periodic>: %v: %v", periodic, err)
							return
						}

						for t.Before(time.Now()) {
							switch p[1] {
							case "seconds":
								t = t.Add(time.Duration(period) * time.Second)
							case "minutes":
								t = t.Add(time.Duration(period) * time.Minute)
							case "hours":
								t = t.Add(time.Duration(period) * time.Hour)
							case "days":
								t = t.AddDate(0, 0, int(period))
							case "weeks":
								t = t.AddDate(0, 0, int(period*7))
							case "months":
								t = t.AddDate(0, int(period), 0)
							case "years":
								t = t.AddDate(int(period), 0, 0)
							default:
								util.Log(0, "ERROR! Unknown periodic unit: %v", p[1])
								return
							}
						}
						job.FirstOrAdd("timestamp").SetText(util.MakeTimestamp(t))
						job.FirstOrAdd("result").SetText("none")
						job.FirstOrAdd("progress").SetText("none")
						job.FirstOrAdd("status").SetText("waiting")
						util.Log(1, "INFO! Scheduling next instance of periodic job: %v", job)
						db.JobAddLocal(job)
					}
				})

			}
		}
	}()
}
Exemple #11
0
// If job belongs to an unknown client or a client registered here or if
// job has <progress>forward-failed</progress> or if the job's <headertag>
// is trigger_action_wake,_lock or _localboot or send_user_msg,
// this function returns false.
// Otherwise this function removes the job from the jobdb and then tries to
// forward the job to the siserver where the client is registered.
// If forwarding fails, the job is re-added to the jobdb but marked with
// <progress>forward-failed</progress>. No matter if forwarding succeeds or fails,
// if it is attempted this function returns true. Note, that the re-added job has
// a different id from the original job (which has been removed from the database)
// and will independently come up in PendingActions. This is why it doesn't make
// sense to return false in the case of a failed forward.
func Forward(job *xml.Hash) bool {
	if job.Text("progress") == "forward-failed" {
		return false
	}
	switch job.Text("headertag") {
	case "send_user_msg":
		return false
	case "trigger_action_wake", "trigger_action_lock", "trigger_action_localboot":
		return false
	}

	macaddress := job.Text("macaddress")

	client := db.ClientWithMAC(macaddress)
	if client == nil || client.Text("source") == config.ServerSourceAddress {
		return false
	}

	siserver := client.Text("source")
	headertag := job.Text("headertag")

	util.Log(1, "INFO! %v for client %v must be forwarded to server %v where client is registered", headertag, macaddress, siserver)

	if message.Peer(siserver).Downtime() != 0 {
		util.Log(0, "ERROR! Peer %v is down => Will try to execute %v for client %v myself.", siserver, headertag, macaddress)
		return false
	}

	// remove job with stop_periodic=true after setting progress="forward" to suppress forcing "localboot"
	db.JobsModifyLocal(xml.FilterSimple("id", job.Text("id")), xml.NewHash("job", "progress", "forward"))
	db.JobsRemoveLocal(xml.FilterSimple("id", job.Text("id")), true)

	if !message.Peer(siserver).IsGoSusi() {
		// Wait if the peer is not a go-susi, to prevent the fju caused by
		// db.JobsRemoveLocal() above from killing the forwarded job; which
		// might otherwise happen because gosa-si uses macaddress+headertag to
		// identify jobs and therefore cannot differentiate between the old and
		// the new job.
		time.Sleep(5 * time.Second)
	}

	util.Log(1, "INFO! Forwarding %v for client %v to server %v", headertag, macaddress, siserver)

	// gosa-si-server does not seem to process some jobs when sent as job_...
	// So we use gosa_.... However this means that <periodic> won't work properly with
	// non-go-susi peers :-(
	// We make an exception for reinstall and update because these need to be sent
	// as job_ or they won't appear in deployment status.
	header := "gosa_" + headertag
	if headertag == "trigger_action_update" || headertag == "trigger_action_reinstall" {
		header = "job_" + headertag
	}
	gosa_trigger_action := xml.NewHash("xml", "header", header)
	gosa_trigger_action.Add("source", "GOSA")
	gosa_trigger_action.Add("macaddress", macaddress)
	gosa_trigger_action.Add("target", macaddress)
	if job.First("timestamp") != nil {
		gosa_trigger_action.Add("timestamp", job.Text("timestamp"))
	}
	if job.First("periodic") != nil {
		gosa_trigger_action.Add("periodic", job.Text("periodic"))
	}

	request := gosa_trigger_action.String()

	// clone job, because we want to use it in a new goroutine and don't want to risk
	// having it changed concurrently.
	job_clone := job.Clone()

	go util.WithPanicHandler(func() {
		util.Log(2, "DEBUG! Forwarding to %v: %v", siserver, request)
		conn, _ := security.SendLnTo(siserver, request, config.ModuleKey["[GOsaPackages]"], true)
		if conn != nil {
			conn.Close()
			return
		}

		util.Log(0, "ERROR! Could not forward %v for client %v to server %v => Will try to execute job myself.", headertag, macaddress, siserver)

		job_clone.FirstOrAdd("result").SetText("none")
		job_clone.FirstOrAdd("progress").SetText("forward-failed")
		job_clone.FirstOrAdd("status").SetText("waiting")

		util.Log(1, "INFO! Re-Scheduling job tagged with \"forward-failed\": %v", job_clone)
		db.JobAddLocal(job_clone)
	})

	return true
}
// Handles the message "CLMSG_save_fai_log".
//  buf: the decrypted message
func clmsg_save_fai_log(buf *bytes.Buffer) {
	macaddress := ""
	action := ""
	start := 0
	end := 0
	data := buf.Bytes()
	for i := 0; i < len(data)-19; i++ {
		if data[i] == '<' {
			if i+12+17 <= len(data) && match(data, i, "<macaddress>") {
				macaddress = string(data[i+12 : i+12+17])
			} else if match(data, i, "<fai_action>") {
				for k := i + 12; k < len(data); k++ {
					if data[k] == '<' {
						action = string(data[i+12 : k])
						i = k
						break
					}
				}
			} else if match(data, i, "<CLMSG_save_fai_log>") {
				start = i + 20
			} else if match(data, i, "</CLMSG_save_fai_log>") {
				end = i
			}
		}
	}

	if !macAddressRegexp.MatchString(macaddress) {
		util.Log(0, "ERROR! CLMSG_save_fai_log with illegal <macaddress> \"%v\"", macaddress)
		return
	}

	if !actionRegexp.MatchString(action) {
		util.Log(0, "ERROR! CLMSG_save_fai_log with illegal <fai_action> \"%v\"", action)
		return
	}

	util.Log(1, "INFO! Received log files from client %v. Assuming CLMSG_PROGRESS 100", macaddress)
	progress_msg := xml.NewHash("xml", "CLMSG_PROGRESS", "100")
	progress_msg.Add("macaddress", macaddress)
	clmsg_progress(progress_msg)

	timestamp := util.MakeTimestamp(time.Now())
	logname := action + "_" + timestamp[0:8] + "_" + timestamp[8:]
	logdir := path.Join(config.FAILogPath, strings.ToLower(macaddress), logname)

	// NOTE: 1kB = 1000B, 1kiB = 1024B
	util.Log(1, "INFO! Storing %vkB of %v log files from %v in %v", len(data)/1000, action, macaddress, logdir)

	err := os.MkdirAll(logdir, 0755)
	if err != nil {
		util.Log(0, "ERROR! Error creating log directory \"%v\": %v", logdir, err)
		return
	}

	// Create convenience symlink with the system's name as alias for MAC address.
	go util.WithPanicHandler(func() {
		if plainname := db.SystemPlainnameForMAC(macaddress); plainname != "none" {
			linkpath := path.Join(config.FAILogPath, strings.ToLower(plainname))
			link_target, err := os.Readlink(linkpath)
			if err != nil && !os.IsNotExist(err.(*os.PathError).Err) {
				util.Log(0, "ERROR! %v exists but is not a symlink: %v", linkpath, err)
				return
			}
			if err == nil {
				if link_target == strings.ToLower(macaddress) {
					return // symlink is already correct => nothing to do
				}

				util.Log(0, "WARNING! Machine %v has a new MAC %v . Removing old symlink %v => %v", plainname, macaddress, linkpath, link_target)
				err = os.Remove(linkpath)
				if err != nil {
					util.Log(0, "ERROR! Removing %v failed: %v", linkpath, err)
					// Don't bail out. Maybe we can create the new symlink anyway.
				}
			}
			err = os.Symlink(strings.ToLower(macaddress), linkpath)
			if err != nil && !os.IsExist(err.(*os.LinkError).Err) {
				util.Log(0, "ERROR! Could not create symlink %v => %v: %v", linkpath, strings.ToLower(macaddress), err)
			}
		}
	})

	files := []int{}
	for i := start; i < end; i++ {
		if data[i] == ':' && match(data, i-8, "log_file") {
			k := i
			i++
			for i < end {
				if data[i] == ':' {
					if k+1 < i {
						files = append(files, k+1, i)
					}
					break
				}
				i++
			}
		}
	}

	files = append(files, end+8)

	for i := 0; i < len(files)-1; i += 2 {
		fname := string(data[files[i]:files[i+1]])
		logdata := data[files[i+1]+1 : files[i+2]-8]
		util.Log(1, "INFO! Processing \"%v\" (%vkB)", fname, len(logdata)/1000)

		logdata = util.Base64DecodeInPlace(logdata)

		// As a precaution, make sure fname contains no slashes.
		fname = strings.Replace(fname, "/", "_", -1)
		err = ioutil.WriteFile(path.Join(logdir, fname), logdata, 0644)
		if err != nil {
			util.Log(0, "ERROR! Could not store \"%v\": %v", path.Join(logdir, fname), err)
			continue
		}
	}
}
// Tell(msg, ttl): Tries to send text to the client.
//                 The ttl determines how long the message will be buffered for
//                 resend attempts if sending fails. ttl values smaller than
//                 100ms will be treated as 100ms.
func (conn *ClientConnection) Tell(text string, ttl time.Duration) {
	if ttl < 100*time.Millisecond {
		ttl = 100 * time.Millisecond
	}
	util.Log(2, "DEBUG! Tell(): Queuing message for client %v with TTL %v: %v", conn.addr, ttl, text)

	msg := &ClientMessage{text, time.Now().Add(ttl)}

	go util.WithPanicHandler(func() {
		var try uint = 0

		if msg.Expires.Before(time.Now()) {
			util.Log(0, "ERROR! Scheduling of goroutine for sending message to %v delayed more than TTL %v => Message will not be sent", conn.addr, ttl)
		} else {
			for {
				if try > 0 {
					expiry := msg.Expires.Sub(time.Now())
					if expiry <= 0 {
						break
					}
					delay := (1 << try) * time.Second
					if delay > 60*time.Second {
						delay = 60 * time.Second
					}
					if delay > expiry {
						delay = expiry - 1*time.Second
					}
					if delay <= 0 {
						break
					}
					util.Log(2, "DEBUG! Sleeping %v before next send attempt", delay)
					time.Sleep(delay)
				}

				try++

				util.Log(1, "INFO! Attempt #%v to send message to %v: %v", try, conn.addr, msg.Text)

				client := db.ClientWithAddress(conn.addr)
				if client == nil {
					if conn.addr == config.ServerSourceAddress {
						// If sending to myself (e.g. new_ldap_config), fake a client object
						client = xml.NewHash("xml", "source", config.ServerSourceAddress)
						key := "" // default to empty key which signals TLS
						if config.TLSClientConfig == nil {
							key = config.ModuleKey["[ClientPackages]"]
						}
						client.Add("key", key)
					} else {
						util.Log(0, "ERROR! Client %v not found in clientdb", conn.addr)
						continue
					}
				}

				// if client is registered at a foreign server
				if client.Text("source") != config.ServerSourceAddress {
					util.Log(1, "INFO! Client %v is registered at %v => Forwarding message", conn.addr, client.Text("source"))

					// MESSAGE FORWARDING NOT YET IMPLEMENTED
					util.Log(0, "ERROR! Message forwarding not yet implemented")
					break

				} else { // if client is registered at our server

					keys := client.Get("key")
					if len(keys) == 0 {
						// This case should be impossible. A client's here_i_am message always contains a key (unless the client is buggy).
						util.Log(0, "ERROR! No key known for client %v", conn.addr)
						break
					}

					encrypted := msg.Text // default is unencrypted for TLS connection

					var tcpConn net.Conn
					var err error

					if keys[0] == "" { // TLS client
						// We just use security.SendLnTo() to establish the TLS connection
						// The empty line that is sent is ignored by the receiving go-susi.
						tcpConn, _ = security.SendLnTo(conn.addr, "", "", true)
						if tcpConn == nil {
							// Error message already logged by SendLnTo()
							continue
						}
					} else { // non-TLS client
						encrypted = security.GosaEncrypt(msg.Text, keys[0])

						tcpConn, err = net.Dial("tcp", conn.addr)
						if err != nil {
							util.Log(0, "ERROR! Dial() could not connect to %v: %v", conn.addr, err)
							continue
						}

						err = tcpConn.(*net.TCPConn).SetKeepAlive(true)
						if err != nil {
							util.Log(0, "ERROR! SetKeepAlive: %v", err)
							// This is not fatal => Don't abort send attempt
						}
					}

					if msg.Expires.Before(time.Now()) {
						util.Log(0, "ERROR! Connection to %v established, but TTL %v has expired in the meantime => Message will not be sent", conn.addr, ttl)
						tcpConn.Close()
						break
					}

					util.Log(2, "DEBUG! Sending message to %v encrypted with key %v", conn.addr, keys[0])
					err = util.SendLn(tcpConn, encrypted, config.Timeout)
					tcpConn.Close()
					if err == nil {
						util.Log(2, "DEBUG! Successfully sent message to %v: %v", conn.addr, msg.Text)
						return // not break! break would cause an error message to be logged
					} else {
						util.Log(0, "ERROR! SendLn() to %v failed: %v", conn.addr, err)
					}
				}
			}
		}

		util.Log(0, "ERROR! Cannot send message to %v: %v", conn.addr, msg.Text)
	})
}
Exemple #14
0
// Sends all local jobs and clients to the peer. If the peer is not a go-susi, also
// requests all of the peer's local jobs and converts them to a <sync>all</sync>
// message and feeds it into foreign_job_updates().
func (conn *PeerConnection) SyncAll() {

	// send all our clients as new_foreign_client messages
	for nfc := db.ClientsRegisteredAtThisServer().First("xml"); nfc != nil; nfc = nfc.Next() {
		nfc.FirstOrAdd("target").SetText(conn.addr)
		conn.Tell(nfc.String(), "")
	}

	if conn.IsGoSusi() {
		util.Log(1, "INFO! Full sync (go-susi protocol) with %v", conn.addr)
		db.JobsSyncAll(conn.addr, nil)
	} else { // peer is not go-susi (or not known to be one, yet)
		go util.WithPanicHandler(func() {
			util.Log(1, "INFO! Full sync (gosa-si fallback) with %v", conn.addr)

			// Query the peer's database for
			// * all jobs the peer is responsible for
			// * all jobs the peer thinks we are responsible for
			query := xml.NewHash("xml", "header", "gosa_query_jobdb")
			query.Add("source", "GOSA")
			query.Add("target", "GOSA")
			clause := query.Add("where").Add("clause")
			clause.Add("connector", "or")
			clause.Add("phrase").Add("siserver", "localhost")
			clause.Add("phrase").Add("siserver", conn.addr)
			clause.Add("phrase").Add("siserver", config.ServerSourceAddress)

			jobs_str := <-conn.Ask(query.String(), config.ModuleKey["[GOsaPackages]"])
			jobs, err := xml.StringToHash(jobs_str)
			if err != nil {
				util.Log(0, "ERROR! gosa_query_jobdb: Error decoding reply from peer %v: %v", conn.addr, err)
				// Bail out. Otherwise we would end up removing all of the peer's jobs from
				// our database if the peer is down. While that would be one way of dealing
				// with this case, we prefer to keep those jobs and convert them into
				// state "error" with an error message about the downtime. This happens
				// in gosa_query_jobdb.go.
				return
			}

			if jobs.First("error_string") != nil {
				util.Log(0, "ERROR! gosa_query_jobdb: Peer %v returned error: %v", conn.addr, jobs.Text("error_string"))
				// Bail out. See explanation further above.
				return
			}

			// Now we extract from jobs those that are the responsibility of the
			// peer and synthesize a foreign_job_updates with <sync>all</sync> from them.
			// This leaves in jobs those the peer believes belong to us.

			fju := jobs.Remove(xml.FilterOr([]xml.HashFilter{xml.FilterSimple("siserver", "localhost"), xml.FilterSimple("siserver", conn.addr)}))
			fju.Rename("xml")
			fju.Add("header", "foreign_job_updates")
			fju.Add("source", conn.addr)
			fju.Add("target", config.ServerSourceAddress)
			fju.Add("sync", "all")

			util.Log(2, "DEBUG! Queuing synthetic fju: %v", fju)
			foreign_job_updates(fju)

			db.JobsSyncAll(conn.addr, jobs)
		})
	}
}
Exemple #15
0
// Encrypts request with key, sends it to the peer and returns a channel
// from which the peer's reply can be received (already decrypted with
// the same key). It is guaranteed that a reply will
// be available from this channel even if the peer connection breaks
// or the peer does not reply within a certain time. In the case of
// an error, the reply will be an error reply (as returned by
// message.ErrorReply()). The returned channel will be buffered and
// the producer goroutine will close it after writing the reply. This
// means it is permissible to ignore the reply without risk of a
// goroutine leak.
// If key == "" the first key from db.ServerKeys(peer) is used.
func (conn *PeerConnection) Ask(request, key string) <-chan string {
	c := make(chan string, 1)

	if conn.err != nil {
		c <- ErrorReply(conn.err)
		close(c)
		return c
	}

	keys := db.ServerKeys(conn.addr)
	// If we use TLS and the target does, too
	if config.TLSClientConfig != nil && len(keys) > 0 && keys[0] == "" {
		key = ""
	} else if key == "" {
		if len(keys) == 0 {
			c <- ErrorReply("PeerConnection.Ask: No key known for peer " + conn.addr)
			close(c)
			return c
		}
		key = keys[0]
	}

	go util.WithPanicHandler(func() {
		defer close(c)
		var tcpconn net.Conn
		var err error
		if key == "" { // TLS
			// We just use security.SendLnTo() to establish the TLS connection
			// The empty line that is sent is ignored by the receiving go-susi.
			tcpconn, _ = security.SendLnTo(conn.addr, "", "", true)
			if tcpconn == nil {
				// Unfortunately we don't have the actual error from SendLnTo(), so generate
				// a generic one.
				err = fmt.Errorf("Could not establish TLS connection to %v", conn.addr)
			}
		} else {
			tcpconn, err = net.Dial("tcp", conn.addr)
		}

		if err != nil {
			c <- ErrorReply(err)
			// make sure handleConnection()/monitorConnection() notice that the peer is unreachable
			if conn.tcpConn != nil {
				conn.tcpConn.Close()
			}
		} else {
			defer tcpconn.Close()
			util.Log(1, "INFO! Asking %v: %v", conn.addr, request)
			encrypted := request
			if key != "" {
				encrypted = security.GosaEncrypt(request, key)
			}
			err = util.SendLn(tcpconn, encrypted, config.Timeout)
			// make sure handleConnection()/monitorConnection() notice that the peer is unreachable
			if err != nil && conn.tcpConn != nil {
				conn.tcpConn.Close()
			}
			reply, err := util.ReadLn(tcpconn, config.Timeout)
			if err != nil && err != io.EOF {
				util.Log(0, "ERROR! ReadLn(): %v", err)
			}
			if key != "" {
				reply = security.GosaDecrypt(reply, key)
			}
			if reply == "" {
				reply = ErrorReply("Communication error in Ask()")
				// make sure handleConnection()/monitorConnection() notice that the peer is unreachable
				if conn.tcpConn != nil {
					conn.tcpConn.Close()
				}
			}
			util.Log(1, "INFO! Reply from %v: %v", conn.addr, reply)
			c <- reply
		}
	})
	return c
}
Exemple #16
0
// Run KernelListHook() and PackageListHook() to update the respective databases.
// This happens in the background. This function does not wait for them to complete.
// startup == true => This is the initial call right after go-susi starts.
func HooksExecute(startup bool) {
	go util.WithPanicHandler(func() { runHooks(startup) })
	go util.WithPanicHandler(FAIReleasesListUpdate)
}
Exemple #17
0
// Sends a new_server message to all known peer servers.
func Broadcast_new_server() {
	for _, server := range db.ServerAddresses() {
		srv := server
		go util.WithPanicHandler(func() { Send_new_server("new_server", srv) })
	}
}
Exemple #18
0
// Unit tests for the package github.com/mbenkmann/golib/util.
func Util_test() {
	fmt.Printf("\n==== util ===\n\n")

	addr, err := util.Resolve("1.2.3.4", "")
	check(err, nil)
	check(addr, "1.2.3.4")

	addr, err = util.Resolve("1.2.3.4:5", "")
	check(err, nil)
	check(addr, "1.2.3.4:5")

	addr, err = util.Resolve("::1:5", "")
	check(err, nil)
	check(addr, "[::1:5]")

	addr, err = util.Resolve("localhost:65535", "")
	check(err, nil)
	check(addr, "127.0.0.1:65535")

	addr, err = util.Resolve("localhost", "")
	check(err, nil)
	check(addr, "127.0.0.1")

	addr, err = util.Resolve("::1", "")
	check(err, nil)
	check(addr, "127.0.0.1")

	addr, err = util.Resolve("[::1]", "")
	check(err, nil)
	check(addr, "127.0.0.1")

	addr, err = util.Resolve("[::1]:12345", "")
	check(err, nil)
	check(addr, "127.0.0.1:12345")

	addr, err = util.Resolve("localhost:65535", "foo")
	check(err, nil)
	check(addr, "foo:65535")

	addr, err = util.Resolve("localhost", "foo")
	check(err, nil)
	check(addr, "foo")

	addr, err = util.Resolve("::1", "foo")
	check(err, nil)
	check(addr, "foo")

	addr, err = util.Resolve("[::1]", "foo")
	check(err, nil)
	check(addr, "foo")

	addr, err = util.Resolve("[::1]:12345", "foo")
	check(err, nil)
	check(addr, "foo:12345")

	addr, err = util.Resolve("", "")
	check(hasWords(err, "no", "such", "host"), "")
	check(addr, "")

	addr, err = util.Resolve(":10", "")
	check(hasWords(err, "no", "such", "host"), "")
	check(addr, ":10")

	check(util.WaitForDNS(3*time.Second), true)

	h, _ := exec.Command("hostname").CombinedOutput()
	hostname := strings.TrimSpace(string(h))

	ipp, _ := exec.Command("hostname", "-I").CombinedOutput()
	ips := strings.Fields(strings.TrimSpace(string(ipp)))
	addr, err = util.Resolve(hostname+":234", config.IP)
	check(err, nil)
	ip := ""
	for _, ip2 := range ips {
		if addr == ip2+":234" {
			ip = ip2
		}
	}
	check(addr, ip+":234")

	testLogging()

	buf := make([]byte, 80)
	for i := range buf {
		buf[i] = byte(util_test_rng.Intn(26) + 'a')
	}

	crap1 := &crappyConnection1{}
	n, err := util.WriteAll(crap1, buf)
	check(string(*crap1), string(buf))
	check(n, len(buf))
	check(err, nil)

	crap2 := &crappyConnection2{}
	n, err = util.WriteAll(crap2, buf)
	check(string(*crap2), string(buf))
	check(n, len(buf))
	check(err, nil)

	stalled1 := &stalledConnection1{}
	n, err = util.WriteAll(stalled1, buf)
	check(string(*stalled1), string(buf[0:16]))
	check(n, 16)
	check(err, io.ErrShortWrite)

	stalled2 := &stalledConnection2{}
	n, err = util.WriteAll(stalled2, buf)
	check(string(*stalled2), string(buf[0:16]))
	check(n, 16)
	check(err, io.ErrShortWrite)

	broken := &brokenConnection{}
	n, err = util.WriteAll(broken, buf)
	check(string(*broken), string(buf[0:16]))
	check(n, 16)
	check(err, io.ErrClosedPipe)

	panicker := func() {
		foobar = "bar"
		panic("foo")
	}

	var buffy bytes.Buffer
	util.LoggersSuspend()
	util.LoggerAdd(&buffy)
	defer util.LoggersRestore()

	util.WithPanicHandler(panicker)
	time.Sleep(200 * time.Millisecond) // make sure log message is written out
	check(foobar, "bar")
	check(len(buffy.String()) > 10, true)

	listener, err := net.Listen("tcp", "127.0.0.1:39390")
	if err != nil {
		panic(err)
	}

	go func() {
		r, err := listener.Accept()
		if err != nil {
			panic(err)
		}
		buf := make([]byte, 1)
		r.Read(buf)
		time.Sleep(10 * time.Second)
		r.Read(buf)
	}()
	long := make([]byte, 10000000)
	longstr := string(long)
	buffy.Reset()
	t0 := time.Now()
	util.SendLnTo("127.0.0.1:39390", longstr, 5*time.Second)
	duration := time.Since(t0)
	check(duration > 4*time.Second && duration < 6*time.Second, true)
	time.Sleep(200 * time.Millisecond) // make sure log message is written out
	check(strings.Contains(buffy.String(), "ERROR"), true)

	go func() {
		conn, err := listener.Accept()
		if err != nil {
			panic(err)
		}
		ioutil.ReadAll(conn)
	}()
	long = make([]byte, 10000000)
	longstr = string(long)
	buffy.Reset()
	t0 = time.Now()
	util.SendLnTo("127.0.0.1:39390", longstr, 5*time.Second)
	duration = time.Since(t0)
	check(duration < 2*time.Second, true)
	time.Sleep(200 * time.Millisecond) // make sure log message is written out
	check(buffy.String(), "")

	// Test that ReadLn() times out properly
	go func() {
		_, err := net.Dial("tcp", "127.0.0.1:39390")
		if err != nil {
			panic(err)
		}
	}()
	conn, err := listener.Accept()
	if err != nil {
		panic(err)
	}
	t0 = time.Now()
	st, err := util.ReadLn(conn, 5*time.Second)
	duration = time.Since(t0)
	check(duration > 4*time.Second && duration < 6*time.Second, true)
	check(st, "")
	check(hasWords(err, "timeout"), "")

	// Test that ReadLn() returns io.EOF if last line not terminated by \n
	go func() {
		conn, err := net.Dial("tcp", "127.0.0.1:39390")
		if err != nil {
			panic(err)
		}
		conn.Write([]byte("foo\r"))
		conn.Close()
	}()
	conn, err = listener.Accept()
	if err != nil {
		panic(err)
	}
	st, err = util.ReadLn(conn, 5*time.Second)
	check(err, io.EOF)
	check(st, "foo")

	go func() {
		conn, err := net.Dial("tcp", "127.0.0.1:39390")
		if err != nil {
			panic(err)
		}
		conn.Write([]byte("\r\r\n\rfo\ro\nbar\r\nfoxtrott"))
		conn.Close()
	}()
	conn, err = listener.Accept()
	if err != nil {
		panic(err)
	}
	// Test proper trimming of multiple \r
	st, err = util.ReadLn(conn, 0)
	check(err, nil)
	check(st, "")
	// Test that the empty first line has actually been read
	// and that the next ReadLn() reads the 2nd line
	// Also test that negative timeouts work the same as timeout==0
	// Also test that \r is not trimmed at start and within line.
	st, err = util.ReadLn(conn, -1*time.Second)
	check(err, nil)
	check(st, "\rfo\ro")
	// Check 3rd line
	st, err = util.ReadLn(conn, 0)
	check(err, nil)
	check(st, "bar")
	// Check 4th line and io.EOF error
	st, err = util.ReadLn(conn, 0)
	check(err, io.EOF)
	check(st, "foxtrott")

	// Test that delayed reads work with timeout==0
	go func() {
		conn, err := net.Dial("tcp", "127.0.0.1:39390")
		if err != nil {
			panic(err)
		}
		time.Sleep(1 * time.Second)
		_, err = conn.Write([]byte("foo\r\n"))
		if err != nil {
			panic(err)
		}
		time.Sleep(2 * time.Second)
	}()
	conn, err = listener.Accept()
	if err != nil {
		panic(err)
	}
	t0 = time.Now()
	st, err = util.ReadLn(conn, time.Duration(0))
	duration = time.Since(t0)
	check(duration < 2*time.Second, true)
	check(duration > 800*time.Millisecond, true)
	check(err, nil)
	check(st, "foo")

	counter := util.Counter(13)
	var b1 UintArray = make([]uint64, 100)
	var b2 UintArray = make([]uint64, 100)
	done := make(chan bool)
	fill := func(b UintArray) {
		for i := 0; i < 100; i++ {
			b[i] = <-counter
			time.Sleep(1 * time.Millisecond)
		}
		done <- true
	}
	go fill(b1)
	go fill(b2)
	<-done
	<-done
	check(sort.IsSorted(&b1), true)
	check(sort.IsSorted(&b2), true)
	var b3 UintArray = make([]uint64, 200)
	i := 0
	j := 0
	k := 0
	for i < 100 || j < 100 {
		if i == 100 {
			b3[k] = b2[j]
			j++
			k++
			continue
		}
		if j == 100 {
			b3[k] = b1[i]
			i++
			k++
			continue
		}
		if b1[i] == b2[j] {
			check(b1[i] != b2[j], true)
			break
		}
		if b1[i] < b2[j] {
			b3[k] = b1[i]
			i++
		} else {
			b3[k] = b2[j]
			j++
		}
		k++
	}

	one_streak := true
	b5 := make([]uint64, 200)
	for i := 0; i < 200; i++ {
		if i < 100 && b1[i] != uint64(13+i) && b2[i] != uint64(13+i) {
			one_streak = false
		}
		b5[i] = uint64(13 + i)
	}

	check(b3, b5)
	check(one_streak, false) // Check whether goroutines were actually executed concurrently rather than in sequence

	tempdir, err := ioutil.TempDir("", "util-test-")
	if err != nil {
		panic(err)
	}
	defer os.RemoveAll(tempdir)
	fpath := tempdir + "/foo.log"
	logfile := util.LogFile(fpath)
	check(logfile.Close(), nil)
	n, err = util.WriteAll(logfile, []byte("Test"))
	check(err, nil)
	check(n, 4)
	check(logfile.Close(), nil)
	n, err = util.WriteAll(logfile, []byte("12"))
	check(err, nil)
	check(n, 2)
	n, err = util.WriteAll(logfile, []byte("3"))
	check(err, nil)
	check(n, 1)
	check(os.Rename(fpath, fpath+".old"), nil)
	n, err = util.WriteAll(logfile, []byte("Fo"))
	check(err, nil)
	check(n, 2)
	f2, _ := os.OpenFile(fpath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
	f2.Write([]byte("o"))
	f2.Close()
	n, err = util.WriteAll(logfile, []byte("bar"))
	check(err, nil)
	check(n, 3)
	check(logfile.Close(), nil)
	data, err := ioutil.ReadFile(fpath)
	check(err, nil)
	if err == nil {
		check(string(data), "Foobar")
	}
	data, err = ioutil.ReadFile(fpath + ".old")
	check(err, nil)
	if err == nil {
		check(string(data), "Test123")
	}

	test_time := time.Date(2013, time.January, 20, 14, 7, 21, 0, time.Local)
	check(util.MakeTimestamp(test_time), "20130120140721")
	test_time = time.Date(2013, time.January, 20, 14, 7, 21, 0, time.UTC)
	check(util.MakeTimestamp(test_time), "20130120140721")
	test_time = time.Date(2013, time.January, 20, 14, 7, 21, 0, time.FixedZone("Fooistan", 45678))
	check(util.MakeTimestamp(test_time), "20130120140721")
	illegal := time.Unix(0, 0)
	buffy.Reset()
	check(util.ParseTimestamp(""), illegal)
	time.Sleep(200 * time.Millisecond) // make sure log message is written out
	check(strings.Contains(buffy.String(), "ERROR"), true)
	buffy.Reset()
	check(util.ParseTimestamp("20139910101010"), illegal)
	time.Sleep(200 * time.Millisecond) // make sure log message is written out
	check(strings.Contains(buffy.String(), "ERROR"), true)
	check(util.ParseTimestamp("20131110121314"), time.Date(2013, time.November, 10, 12, 13, 14, 0, time.Local))
	check(util.MakeTimestamp(util.ParseTimestamp(util.MakeTimestamp(test_time))), util.MakeTimestamp(test_time))
	test_time = test_time.Add(2400 * time.Hour)
	check(util.MakeTimestamp(util.ParseTimestamp(util.MakeTimestamp(test_time))), util.MakeTimestamp(test_time))
	test_time = test_time.Add(2400 * time.Hour)
	check(util.MakeTimestamp(util.ParseTimestamp(util.MakeTimestamp(test_time))), util.MakeTimestamp(test_time))
	test_time = test_time.Add(2400 * time.Hour)
	check(util.MakeTimestamp(util.ParseTimestamp(util.MakeTimestamp(test_time))), util.MakeTimestamp(test_time))
	test_time = test_time.Add(2400 * time.Hour)
	check(util.MakeTimestamp(util.ParseTimestamp(util.MakeTimestamp(test_time))), util.MakeTimestamp(test_time))

	diff := time.Since(util.ParseTimestamp(util.MakeTimestamp(time.Now())))
	if diff < time.Second {
		diff = 0
	}
	check(diff, time.Duration(0))

	t0 = time.Now()
	util.WaitUntil(t0.Add(-10 * time.Second))
	util.WaitUntil(t0.Add(-100 * time.Minute))
	dur := time.Now().Sub(t0)
	if dur < 1*time.Second {
		dur = 0
	}
	check(dur, 0)
	t0 = time.Now()
	util.WaitUntil(t0.Add(1200 * time.Millisecond))
	dur = time.Now().Sub(t0)
	if dur >= 1200*time.Millisecond && dur <= 1300*time.Millisecond {
		dur = 1200 * time.Millisecond
	}
	check(dur, 1200*time.Millisecond)

	mess := "WaitUntil(Jesus first birthday) takes forever"
	go func() {
		util.WaitUntil(time.Date(1, time.December, 25, 0, 0, 0, 0, time.UTC))
		mess = ""
	}()
	time.Sleep(100 * time.Millisecond)
	check(mess, "")

	mess = "WaitUntil(1000-11-10 00:00:00) takes forever"
	go func() {
		util.WaitUntil(time.Date(1000, time.October, 11, 0, 0, 0, 0, time.UTC))
		mess = ""
	}()
	time.Sleep(100 * time.Millisecond)
	check(mess, "")

	testBase64()
}
Exemple #19
0
func main() {
	// Intercept signals asap (in particular intercept SIGTTOU before the first output)
	signals := make(chan os.Signal, 32)
	signals_to_watch := []os.Signal{syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGTTOU, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT}
	signal.Notify(signals, signals_to_watch...)

	config.Init()
	config.ReadArgs(os.Args[1:])

	if config.PrintVersion {
		fmt.Printf(`go-susi %v (revision %v)
Copyright (c) 2013 Matthias S. Benkmann
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

`, config.Version, config.Revision)
	}

	if config.PrintHelp {
		fmt.Println(`USAGE: go-susi [args]

--help       print this text and exit
--version    print version and exit
--stats      print sistats info from running go-susi process

-v           print operator debug messages (INFO)
-vv          print developer debug messages (DEBUG)
             ATTENTION! developer messages include keys!

-f           start with a fresh database; discard old /var/lib/go-susi

--test=<dir> test mode:
             * read config files from <dir> instead of /etc/gosa-si
             * use <dir>/go-susi.log as log file
             * use <dir> as database directory instead /var/lib/go-susi

-c <file>    read config from <file> instead of default location
`)
	}

	if config.PrintVersion || config.PrintHelp {
		os.Exit(0)
	}

	config.ReadConfig()
	config.ReadCertificates() // after config.ReadConfig()

	if config.TLSRequired && config.TLSServerConfig == nil {
		util.Log(0, "ERROR! No cert, no keys => no service")
		util.LoggersFlush(5 * time.Second)
		os.Exit(1)
	}

	logfile, err := os.OpenFile(config.LogFilePath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
	if err != nil {
		util.Log(0, "ERROR! %v", err)
		// Do not exit. We can go on without logging to a file.

	} else {
		logfile.Close() // will be re-opened on the first write
		// Add file to loggers list. os.Stderr is on it by default.
		util.LoggerAdd(util.LogFile(logfile.Name()))
	}
	util.LogLevel = config.LogLevel

	if config.PrintStats {
		// We nead ReadNetwork() to determine config.IP which is necessary
		// for TLS certificate validation. We call this inside the if config.Printstats
		// block instead of outside because for a go-susi daemon it is important
		// to wait for DNS before calling ReadNetwork() which is something
		// we don't want to do for the --stats call.
		config.ReadNetwork()
		code := printStats()
		util.LoggersFlush(5 * time.Second)
		os.Exit(code)
	}

	util.Log(0, "=============================================================================")
	util.Log(0, "INFO! go-susi %v started", config.Version)

	if !config.RunServer {
		util.Log(1, "INFO! No ldap-admin-dn configured => Will run in client-only mode")
	}

	util.Log(1, "INFO! Expecting standard clients to communicate on these ports: %v", config.ClientPorts)

	util.Log(1, "INFO! Waiting up to 5 minutes for DNS to be available")
	if !util.WaitForDNS(5 * time.Minute) {
		util.Log(0, "ERROR! DNS not available")
		util.LoggersFlush(5 * time.Second)
		os.Exit(1)
	}
	util.Log(1, "INFO! DNS available")

	config.ReadNetwork() // after config.ReadConfig()

	if config.TLSServerConfig != nil {
		util.Log(1, "INFO! [SECURITY] CA certificate:\n%v", security.CertificateInfo(config.CACert[0]))
		util.Log(1, "INFO! [SECURITY] My certificate:\n%v", security.CertificateInfo(config.TLSServerConfig.Certificates[0].Leaf))
	}

	// ATTENTION! DO NOT MOVE THE FOLLOWING CODE FURTHER DOWN!
	// We want to try listening on our socket as early in the program as possible,
	// so that we can bail out if another go-susi instance is already running
	// before potentially damaging the databases.
	tcp_addr, err := net.ResolveTCPAddr("tcp4", config.ServerListenAddress)
	if err != nil {
		util.Log(0, "ERROR! ResolveTCPAddr: %v", err)
		util.LoggersFlush(5 * time.Second)
		os.Exit(1)
	}
	listener, err := net.ListenTCP("tcp4", tcp_addr)
	if err != nil {
		util.Log(0, "ERROR! ListenTCP: %v", err)
		util.LoggersFlush(5 * time.Second)
		os.Exit(1)
	}

	if config.RunServer {
		util.Log(1, "INFO! Waiting up to 5 minutes for %v to be available", config.LDAPURI)
		if !db.LDAPAvailable(5 * time.Minute) {
			util.Log(0, "ERROR! LDAP not available")
			util.LoggersFlush(5 * time.Second)
			os.Exit(1)
		}
		util.Log(1, "INFO! LDAP available")

		setConfigUnitTag() // after config.ReadNetwork()
		config.FAIBase = db.LDAPFAIBase()
		util.Log(1, "INFO! FAI base: %v", config.FAIBase)
		util.Log(1, "INFO! ou=servers.conf: %v", config.LDAPServerOUs)
		os.MkdirAll(path.Dir(config.JobDBPath), 0750)
		db.ServersInit()      // after config.ReadNetwork()
		db.JobsInit()         // after config.ReadConfig()
		db.ClientsInit()      // after config.ReadConfig()
		db.HooksExecute(true) // after config.ReadConfig()
		action.Init()
	}

	// Create channels for receiving events.
	// The main() goroutine receives on all these channels
	// and spawns new goroutines to handle the incoming events.
	tcp_connections := make(chan *net.TCPConn, 32)
	// NOTE: signals channel is created at the beginning of main()

	util.Log(1, "INFO! Intercepting these signals: %v", signals_to_watch)

	util.Log(1, "INFO! Accepting gosa-si protocol connections on TCP port %v", strings.SplitN(config.ServerSourceAddress, ":", 2)[1])
	go acceptConnections(listener, tcp_connections)

	go util.WithPanicHandler(faiProgressWatch)

	if config.RunServer {
		if config.FAIMonPort != "disabled" {
			util.Log(1, "INFO! Accepting FAI monitoring messages on TCP port %v", config.FAIMonPort)
			go faimon(":" + config.FAIMonPort)
		}

		util.Log(1, "INFO! Accepting TFTP requests on UDP port %v", config.TFTPPort)
		go tftp.ListenAndServe(":"+config.TFTPPort, config.TFTPRegexes, config.TFTPReplies)

		go message.CheckPossibleClients()
		go message.Broadcast_new_server()
		go message.DistributeForeignJobUpdates()
	}

	// http server for profiling
	//go func(){http.ListenAndServe("localhost:6060", nil)}()

	go message.RegistrationHandler()

	/********************  main event loop ***********************/
	for {
		select {
		case sig := <-signals: //os.Signal
			if sig != syscall.SIGTTOU { // don't log SIGTTOU as that may cause another
				util.Log(1, "INFO! Received signal \"%v\"", sig)
			}
			if sig == syscall.SIGUSR2 && config.RunServer {
				db.HooksExecute(false)
			}
			if sig == syscall.SIGHUP || sig == syscall.SIGTERM ||
				sig == syscall.SIGQUIT || sig == syscall.SIGINT {
				Shutdown = true
				util.Log(0, "WARNING! Shutting down!")
				util.Log(1, "INFO! Shutting down listener")
				listener.Close()
				if config.RunServer {
					wait := make(chan bool, 16)
					go func() { db.JobsShutdown(); wait <- true }()
					go func() { db.ServersShutdown(); wait <- true }()
					go func() { db.ClientsShutdown(); wait <- true }()
					<-wait // for jobdb
					<-wait // for serverdb
					<-wait // for clientdb
				}
				config.Shutdown()
				util.Log(1, "INFO! Average request processing time: %v", time.Duration((atomic.LoadInt64(&message.RequestProcessingTime)+50)/100))
				util.Log(1, "INFO! Databases have been saved => Exit program")
				util.LoggersFlush(5 * time.Second)
				os.Exit(0)
			}

		case conn := <-tcp_connections: // *net.TCPConn
			if Shutdown {
				util.Log(1, "INFO! Rejecting TCP request from %v because of go-susi shutdown", conn.RemoteAddr())
				conn.Close()
			} else {
				//util.Log(2, "DEBUG! Incoming TCP request from %v", conn.RemoteAddr())
				go util.WithPanicHandler(func() { handle_request(conn) })
			}
		}
	}
}