/// Routine to free/destroy/finalize our ChildProcess objects. FSTATIC void _childprocess_finalize(AssimObj* aself) { ChildProcess* self = CASTTOCLASS(ChildProcess, aself); if (self->stdout_src) { g_source_destroy(&self->stdout_src->baseclass); g_source_unref(&self->stdout_src->baseclass); //self->stdout_src->finalize(self->stdout_src); self->stdout_src = NULL; } if (self->stderr_src) { g_source_destroy(&self->stderr_src->baseclass.baseclass); g_source_unref(&self->stderr_src->baseclass.baseclass); //self->stderr_src->baseclass.finalize(&self->stderr_src->baseclass); self->stderr_src = NULL; } if (self->loggingname) { g_free(self->loggingname); self->loggingname = NULL; } if (self->timeoutsrc_id > 0) { g_source_remove(self->timeoutsrc_id); DEBUGMSG3("%s:%d: Removed timeout for process with user_data = %p" , __FUNCTION__, __LINE__, self); self->timeoutsrc_id = 0; } _assimobj_finalize(aself); self = NULL; aself = NULL; }
/// Finalize (free) a RscQElem object FSTATIC void _resource_queue_qelem_finalize(RscQElem* self) { DEBUGMSG3("%s.%d: UNREF(self->cmd, refcount=%d)" , __FUNCTION__, __LINE__, self->cmd->baseclass._refcount); UNREF(self->cmd); FREECLASSOBJ(self); }
/// Examine our queues and run anything that needs running. /// (this code is more expensive than it could be, but in practice it may not matter) FSTATIC gboolean _resource_queue_runqueue(gpointer pself) { ResourceQueue* self = CASTTOCLASS(ResourceQueue, pself); GHashTableIter iter; gpointer key; gpointer value; gint64 now = g_get_monotonic_time(); gboolean anyelems = FALSE; DEBUGMSG3("%s.%d: Examining queues", __FUNCTION__, __LINE__); g_hash_table_iter_init(&iter, self->resources); while(g_hash_table_iter_next(&iter, &key, &value)) { GQueue* rsc_q = (GQueue*)value; GList* qelem; gboolean any_running = FALSE; for (qelem=rsc_q->head; NULL != qelem; qelem=qelem->next) { RscQElem* qe = CASTTOCLASS(RscQElem, qelem->data); anyelems = TRUE; if (qe->cmd->is_running) { any_running = TRUE; break; } } if (any_running) { continue; } DEBUGMSG4("%s.%d: No resource jobs are running.", __FUNCTION__, __LINE__); for (qelem=rsc_q->head; NULL != qelem; qelem=qelem->next) { RscQElem* qe = CASTTOCLASS(RscQElem, qelem->data); if (now >= qe->cmd->starttime) { REF(self); // We undo this when process exits self->activechildcnt += 1; qe->cmd->execute(qe->cmd); break; } } } if (!anyelems && self->timerid >= 0) { g_source_remove(self->timerid); self->timerid = -1; } return anyelems; }
/// /// We update the data in the packet from our CryptCurve25519 object with the /// side-effect of encrypting all the frames already put into the packet. Note that /// this only works because we always construct the packet from the end back to the /// beginning. We do this in-place - fortunately the algorithms allow that... /// We effectively suck all the remaining frames into a single encrypted frame... FSTATIC void _cryptcurve25519_updatedata(Frame* f, ///< Frame to marshall gpointer tlvstart, ///< Start of our Frame in the packet gconstpointer pktend, ///< Last byte in the allocated packet FrameSet* unused_fs) ///< Pointer to our containing frameset { CryptCurve25519*self = CASTTOCLASS(CryptCurve25519, f); const guint8* pktend8 = pktend; //guint8* tlvstart8 = tlvstart; guint8* tlvval; guint8* valptr; guint32 plaintextoffset; guint32 plaintextsize; guint32 cyphertextoffset; guint32 nonceoffset; guint32 tlvsize; unsigned char* nonce; int j; (void)unused_fs; // [key1, key2, nonce, MAC, plaintext] DUMP3(__FUNCTION__, &f->baseclass, " is CryptCurve25519 Frame being processed."); DEBUGMSG3("%s.%d: tlvstart:%p, pktend:%p", __FUNCTION__, __LINE__, tlvstart, pktend); // The plain text starts immediately after our (incoming) frame plaintextoffset = f->length; // Plain text starts here cyphertextoffset = plaintextoffset - crypto_box_MACBYTES; // Preceded by MAC nonceoffset = cyphertextoffset - crypto_box_NONCEBYTES; // Preceded by nonce // Our (outgoing) frame consists of the original incoming frame plus all other frames after ours tlvval = get_generic_tlv_nonconst_value(tlvstart, pktend); tlvsize = pktend8 - tlvval; plaintextsize = (tlvsize - plaintextoffset); // Generate a "nonce" as part of the packet - make known plaintext attacks harder // ... lots of our plaintext is easy to figure out ... nonce = tlvval + nonceoffset; DEBUGMSG3("%s.%d: generating random nonce (%p, %d, %p)", __FUNCTION__, __LINE__ , nonce, (int)crypto_box_NONCEBYTES, nonce+crypto_box_NONCEBYTES); randombytes_buf(nonce, crypto_box_NONCEBYTES); DEBUGMSG3("%s.%d: random nonce generated.", __FUNCTION__, __LINE__); DEBUGMSG3("%s.%d: public->key_id: [%s], private_key->key_id: [%s]", __FUNCTION__, __LINE__ , self->public_key->key_id, self->private_key->key_id); DEBUGMSG3("%s.%d: calling crypto_box_easy(%p,%p,%d,%p,%p,%p)", __FUNCTION__, __LINE__ , tlvval+cyphertextoffset, tlvval+plaintextoffset, plaintextsize , nonce, self->public_key->public_key, self->private_key->private_key); DEBUGCKSUM4("plain text cksum:", tlvval+plaintextoffset, plaintextsize); DEBUGCKSUM4("receiver public key cksum:",self->public_key -> public_key, crypto_box_PUBLICKEYBYTES); DEBUGCKSUM4("sender private key cksum:", self->private_key->private_key, crypto_box_SECRETKEYBYTES); DEBUGCKSUM4("nonce cksum:", nonce, crypto_box_NONCEBYTES); // Encrypt in-place [we previously allocated enough space for authentication info] crypto_box_easy(tlvval+cyphertextoffset, tlvval+plaintextoffset, plaintextsize , nonce, self->public_key->public_key, self->private_key->private_key); DEBUGMSG4("cypher offset versus tlvstart: %ld", (long)(tlvval+cyphertextoffset-(guint8*)tlvstart)); DEBUGCKSUM4("cypher text checksum:", tlvval+cyphertextoffset, plaintextsize+crypto_box_MACBYTES); set_generic_tlv_type(tlvstart, self->baseclass.baseclass.type, pktend); set_generic_tlv_len(tlvstart, tlvsize, pktend); // Put in the frame type, length, key name length, and key name for both keys // We're the sender - our [private] key name goes first, then the receiver's [public] key name valptr = get_generic_tlv_nonconst_value(tlvstart, pktend); for (j=0; j < 2; ++j) { char * key_id = (j == 0 ? self->baseclass.sender_key_id : self->baseclass.receiver_key_id); int keylen = strlen(key_id)+1; tlv_set_guint8(valptr, keylen, pktend); valptr += 1; g_strlcpy((char *)valptr, key_id, keylen); valptr += keylen; } DEBUGMSG3("%s.%d: returning after next assert (tlvval:%p, tlvsize%d, pktend:%p" , __FUNCTION__, __LINE__, tlvval, (int)tlvsize, pktend); g_assert((tlvval + tlvsize) == pktend); DEBUGMSG3("%s.%d: returning (assert passed).", __FUNCTION__, __LINE__); }
/** * @ref ChildProcess constructor. * Here's what we're going to do: * 1) Create child process using g_spawn_async_with_pipes() * 2) ...In child process become our own process group * 3) Create LogSourceFd object for stderr * 4) Create LogSourceFd or GMainFd object for stdout * 5) Set timer (if any) * 6) Initialize the child state to running * 7) Return. */ WINEXPORT ChildProcess* childprocess_new(gsize cpsize ///< Size of created ChildProcess object , char** argv ///< NULL-terminated argv for the ChildProcess , const char** envp ///< Environment for the ChildProcess , ConfigContext* envmod ///< Modifications to the ChildProcess environment , const char* curdir ///< Current directory to start the child in , void (*notify)(ChildProcess*, enum HowDied, int rc, int signal, gboolean core_dumped) ///< Function to call if/when the child terminates , gboolean save_stdout ///< TRUE to save stdout, FALSE to log it , const char*logdomain ///< Glib log domain , const char*logprefix ///< Prefix to prepend to log entries , GLogLevelFlags loglevel ///< Glib Log level , guint32 timeout_seconds ///< How long to wait before killing it - zero for no timeout , gpointer user_data ///< Data our user wants us to keep , enum ChildErrLogMode logmode ///< How to log child exits , const char* logname) ///< Name to use when logging child exits as requested { AssimObj* aself; ChildProcess* self; gint stdoutfd; gint stderrfd; GError* failcode = NULL; gchar** childenv = NULL; BINDDEBUG(ChildProcess); g_return_val_if_fail(logprefix != NULL, NULL); if (cpsize < sizeof(ChildProcess)) { cpsize = sizeof(ChildProcess); } aself = assimobj_new(cpsize); g_return_val_if_fail(aself != NULL, NULL); self = NEWSUBCLASS(ChildProcess, aself); childenv = assim_merge_environ(envp, envmod); if (!g_spawn_async_with_pipes( curdir, // Current directory argv, // Arguments childenv, // environment G_SPAWN_DO_NOT_REAP_CHILD, // GSpawnFlags flags, _childprocess_setup_child, // GSpawnChildSetupFunc child_setup, self, // gpointer user_data, &self->child_pid, // GPid *child_pid, NULL, // gint *standard_input, &stdoutfd, // gint *standard_output, &stderrfd, // gint *standard_error, &failcode)) { // GError **error // OOPS! Failed! const char * msg = "unknown exec error"; if (failcode && failcode->message) { msg = failcode->message; } g_critical("%s.%d: %s", __FUNCTION__, __LINE__, msg); if (failcode) { g_clear_error(&failcode); // sets failcode back to NULL } assim_free_environ(childenv); childenv = NULL; UNREF(self); aself = NULL; return NULL; } DEBUGMSG2("%s.%d: Spawned process with user_data = %p", __FUNCTION__, __LINE__, self); aself->_finalize = _childprocess_finalize; aself->toString = _childprocess_toString; self->stderr_src = logsourcefd_new(0, stderrfd, G_PRIORITY_HIGH, g_main_context_default() , logdomain, loglevel, logprefix); self->user_data = user_data; self->logmode = logmode; if (NULL == logname) { logname = argv[0]; } self->loggingname = g_strdup(logname); assim_free_environ(childenv); childenv = NULL; if (!save_stdout) { LogSourceFd* logsrc; logsrc = logsourcefd_new(0, stdoutfd, G_PRIORITY_HIGH , g_main_context_default(), logdomain, loglevel, logprefix); self->stdout_src = &logsrc->baseclass; }else{ self->stdout_src = gmainfd_new(0, stdoutfd, G_PRIORITY_HIGH, g_main_context_default()); } self->childsrc_id = g_child_watch_add(self->child_pid, _childprocess_childexit, self); self->notify = notify; if (0 == timeout_seconds) { DEBUGMSG2("No timeout for process with user_data = %p", self); self->timeoutsrc_id = 0; }else{ self->timeoutsrc_id = g_timeout_add_seconds(timeout_seconds , _childprocess_timeout, self); DEBUGMSG3("%s.%d: Set %d second timeout %d for process with user_data = %p" , __FUNCTION__, __LINE__, timeout_seconds, self->timeoutsrc_id, self); } self->child_state = CHILDSTATE_RUNNING; DEBUGMSG5("%s.%d: REF child: %p", __FUNCTION__,__LINE__, self); REF(self); // We do this because we need to still be here when the process exits return self; }
/// Function called when the child (finally) exits... FSTATIC void _childprocess_childexit(GPid pid, gint status, gpointer childprocess_object) { ChildProcess* self = CASTTOCLASS(ChildProcess, childprocess_object); gboolean signalled = WIFSIGNALED(status); int exitrc = 0; int signal = 0; gboolean logexit = FALSE; enum HowDied howwedied = NOT_EXITED; (void)pid; if (self->timeoutsrc_id > 0) { g_source_remove(self->timeoutsrc_id); DEBUGMSG3("%s.%d: Removed timeout %d for process with user_data = %p" , __FUNCTION__, __LINE__, self->timeoutsrc_id, self); self->timeoutsrc_id = 0; } // If it refused to die, then the status is invalid if ((guint)(self->child_state) >= DIMOF(signalmap)) { howwedied = EXITED_HUNG; }else if ((guint)self->child_state != CHILDSTATE_RUNNING) { // Then we tried to kill it... howwedied = EXITED_TIMEOUT; signal = signalled ? WTERMSIG(status) : 0; }else{ if (signalled) { signal = WTERMSIG(status); howwedied = EXITED_SIGNAL; }else{ exitrc = WEXITSTATUS(status); howwedied = (exitrc == 0 ? EXITED_ZERO : EXITED_NONZERO); } } switch (howwedied) { case EXITED_SIGNAL: /*FALLTHROUGH*/ case EXITED_TIMEOUT: /*FALLTHROUGH*/ case EXITED_HUNG: logexit = self->logmode > CHILD_NOLOG; break; case EXITED_NONZERO: logexit = self->logmode >= CHILD_LOGERRS; break; case EXITED_ZERO: logexit = self->logmode >= CHILD_LOGALL; break; default: // We'll never produce any other values above /*NOTREACHED*/ logexit = TRUE; break; } if (logexit) { switch (howwedied) { case EXITED_SIGNAL: g_warning("Child process [%s] died from signal %d%s." , self->loggingname, signal, WCOREDUMP(status) ? " (core dumped)" : ""); break; case EXITED_TIMEOUT: if (signalled) { g_warning("Child process [%s] timed out after %d seconds [signal %d%s]." , self->loggingname, self->timeout, signal , WCOREDUMP(status) ? " (core dumped)" : ""); }else{ g_warning("Child process [%s] timed out after %d seconds." , self->loggingname, self->timeout); } break; case EXITED_HUNG: g_warning("Child process [%s] timed out after %d seconds and could not be killed." , self->loggingname, self->timeout); break; case EXITED_NONZERO: g_message("Child process [%s] exited with return code %d." , self->loggingname, exitrc); break; case EXITED_ZERO: g_message("Child process [%s] exited normally.", self->loggingname); break; default:/*NOTREACHED*/ break; } } DEBUGMSG2("%s.%d: Exit happened howwedied:%d", __FUNCTION__, __LINE__ , howwedied); if (!self->stdout_src->atEOF) { //DEBUGMSG3("Child %d [%s] EXITED but output is not at EOF [fd%d]", pid //, self->loggingname, self->stdout_src->gfd.fd); self->stdout_src->readmore(self->stdout_src); } if (!self->stderr_src->baseclass.atEOF) { self->stderr_src->baseclass.readmore(&self->stderr_src->baseclass); } self->notify(self, howwedied, exitrc, signal, WCOREDUMP(status)); self->child_state = -1; DEBUGMSG5("%s.%d: UNREF child: %p", __FUNCTION__,__LINE__, self); UNREF(self); // Undo the REF(self) in our constructor }