The agent is organised as a thin entry-point that wires together five loosely-coupled subsystems: configuration, logging, HTTP transport, crypto abstraction, and job scheduling/dispatch.
This page is a code-level map. Start with
overview.md for the conceptual introduction.
| File | Responsibility |
|---|---|
agent.c / agent.h |
Entry point, CLI parsing, initialisation sequence, main job loop, chained-job dispatch, version macros. |
config.c / config.h |
Load / save config.json, validate fields, build platform URLs, free configuration memory. |
session.c / session.h |
Build and POST /Session/Register, handle first-enroll vs re-register, persist session state. |
schedule.c / schedule.h |
Priority-ordered linked list of scheduled jobs, reschedule, one-time job removal. |
inventory.c / inventory.h |
Handler for CertStores.PEM.Inventory jobs (cms_job_inventory). |
management.c / management.h |
Handler for CertStores.PEM.Management jobs (cms_job_manage). |
enrollment.c / enrollment.h |
Handler for CertStores.PEM.Reenrollment jobs (cms_job_enroll). |
csr.c / csr.h |
Keypair generation and CSR assembly (routes to the active SSL wrapper). |
httpclient.c / httpclient.h |
libcurl wrapper: retries, mTLS, optional X-ARR-ClientCert injection. |
dto.c / dto.h |
JSON (de)serialisation of every request/response DTO; also manages ClientParameter_t arrays. |
logging.c / logging.h |
In-memory log buffer, level gating, on-disk rolling log, .index persistence. |
utils.c / utils.h |
File helpers (read_file_bytes, replace_file, backup_file), string helpers, path checks. |
constants.h |
Capability GUIDs, enumerations (InventoryStatus, AgentApiResultStatus, JobCompleteStatus, OperationType, AgentPlatform). |
global.h |
Project-wide flags and globals (__HTTP_1_1__, engine id, UUID size). |
openssl_wrapper/ |
OpenSSL implementation of the ssl_* abstraction. |
wolfssl_wrapper/ |
wolfSSL implementation of the same abstraction. |
lib/json.{c,h} |
ccan JSON library (Joseph Adams), with small C99-pedantic tweaks. |
lib/base64.{c,h} |
Base64 encode/decode (Matt Gallagher). |
openssl_compat.h |
Shim for pre-1.1.0 OpenSSL RSA_get0_key. |
From main() → init_platform() in agent.c:
parse_parameters()— read CLI switches; defaultsconfig_locationtoconfig.jsonif-cis not passed.config_load()— read the file atconfig_locationinto aConfigData_t. ReturnsNULLon any parse failure.init_logging()/load_log_buffer()— allocate the in-memory log buffer. From this point onwards the logger writes both to stdout (gated by level) and to the buffer.validate_configuration()— hard-fail if required fields are missing or out of range (seeconfiguration.md).- On TPM builds only:
init_tpm_engine()→initialize_engine()loads the named OpenSSL engine and registers it for all algorithms. init_ssl_and_curl()—ssl_init()on the active wrapper pluscurl_global_init(CURL_GLOBAL_DEFAULT).
Any failure returns 0 from init_platform(), which flips the global
success flag to false; the agent still drops through to
release_platform() and cleanup_logs() so state and logs are
flushed.
register_session() in session.c drives the interaction with the
platform:
register_session()
├── SessionRegisterReq_new(ClientParameterPath) // dto.c
├── set_registration_parameters() // capabilities, TenantId, etc.
├── prepare_enrollment()
│ ├── [EnrollOnStartup && UseAgentCert] → register_agent()
│ │ // generates keypair + CSR
│ └── [otherwise] is_cert_active() check on existing AgentCert
├── send_session_request()
│ └── http_post_json() → POST /Session/Register
├── (response has Token?)
│ ├── yes → handle_token_response()
│ │ ├── schedule jobs by priority (prioritize_jobs())
│ │ └── on first-enroll success → finalize_first_registration()
│ │ └── do_second_registration()
│ │ (flips EnrollOnStartup → false)
│ └── no → handle_no_token_response()
│ └── checks for A0100007 / A0100008 cert-renewal errors;
│ on match → re_register_agent()
The full list of response codes handled: 0 = OK, 997 = JSON decode
failure, 998 = setup / internal failure, plus any direct HTTP status
from libcurl.
After register_session() returns, control goes back to main_loop()
in agent.c, which walks JobList via execute_all_jobs():
while (currentJob) {
if (0 != run_job(currentJob->Job)) local_success = false;
currentJob = currentJob->NextJob;
}Each element of JobList is a ScheduledJob_t (linked list) wrapping
a SessionJob_t DTO. run_job() calls dispatch_job_by_type(), which
compares JobTypeId against the three supported capability GUIDs from
constants.h using strcasecmp:
CAP_PEM_INVENTORY → cms_job_inventory()
CAP_PEM_MANAGEMENT → cms_job_manage()
CAP_PEM_REENROLLMENT→ cms_job_enroll()Any other capability GUID logs "Unimplemented support for job type"
and returns 0 without running.
When a handler returns 0 (success), it may also populate the
chainJobId out-parameter with a GUID. If __RUN_CHAIN_JOBS__ is
defined (the default — see build.md),
run_chain_job_if_needed() looks that GUID up in JobList via
get_job_by_id() and invokes it immediately. This is how
Inventory → Management and Reenrollment → Inventory can be wired
server-side.
schedule.c maintains JobList as a singly-linked list.
schedule_job()appends a new job, or if a job with the sameJobIdis already present:- One-time jobs (
Schedule[0] == 'O') are removed and replaced. - Otherwise, the existing job's
NextExecutionis updated and the new allocation is discarded.
- One-time jobs (
get_runnable_job()scans forward for the first job whoseNextExecution <= now.get_job_by_id()walks the list and returns byJobId(case-insensitive).clear_job_schedules()walks and frees every node and itsSessionJob_tpayload.
Priority ordering of the initial list is built in prioritize_jobs()
in session.c based on SessionJob_t.Priority.
Both openssl_wrapper/openssl_wrapper.h and
wolfssl_wrapper/wolfssl_wrapper.h expose the same ssl_* API. Which
one is linked is a compile-time choice (-D__OPEN_SSL__ or
-D__WOLF_SSL__). The shared surface:
ssl_init, ssl_cleanup
ssl_seed_rng
ssl_generate_rsa_keypair, ssl_generate_ecc_keypair
ssl_generate_csr
ssl_save_cert_key
ssl_read_store_inventory
ssl_PemInventoryItem_create
ssl_Store_Cert_add
ssl_remove_cert_from_store
ssl_is_cert_active
PemInventoryItem_free, PemInventoryList_free
PemInventoryItem / PemInventoryList are defined identically in both
headers — they carry the PEM text, a SHA-1 thumbprint string, and a
has_private_key flag.
On TPM builds, ssl_generate_rsa_keypair takes an extra file
argument that names the TPM-backed key file; the key itself never
leaves the TPM.
dto.c owns every struct exchanged with the platform:
- Session:
SessionRegisterReq_t,SessionRegisterResp_t, withClientParameter_tarrays. - Common:
CommonConfigReq_t,CommonCompleteReq_t,CommonCompleteResp_t— used to fetch a job's config from itsConfigurationEndpointand to report completion on itsCompletionEndpoint. - Per-capability:
InventoryConfigResp_t/InventoryUpdateReq_t/InventoryUpdateResp_t/InventoryCurrentItem_t/InventoryUpdateItem_t/InventoryUpdateList_t,ManagementConfigResp_t/ManagementCompleteResp_t,EnrollmentConfigResp_t/EnrollmentEnrollReq_t/EnrollmentEnrollResp_t/EnrollmentCompleteResp_t,FetchLogsConfigResp_t/FetchLogsCompleteReq_t.
Each struct has matching _new/_free/_toJson/_fromJson helpers
as appropriate. The underlying parser is ccan JSON in
lib/json.c.
AgentApiResult_t is the common envelope for all platform responses;
AgentApiResult_log() logs it in a uniform way and extracts a message
string and status.
http_post_json() in httpclient.c is the single network entry
point. It:
- Builds a libcurl easy handle per request.
- Honors
httpRetries/retryIntervalfrom config. - Supports basic auth via
Username/Password. - Supplies the PEM trust store (
TrustStore), agent cert, key, and optional key password for mTLS. - When
add_client_cert_to_headeris set (via CLI-a), the agent's PEM cert is urlencoded and injected into theX-ARR-ClientCertHTTP header. - Follows HTTP 1.1 semantics by default (
__HTTP_1_1__is defined inglobal.h).
release_platform() (called unconditionally from main()):
cleanup_jobs()→clear_job_schedules().cleanup_curl()→curl_global_cleanup()if curl was initialised.- On TPM builds,
ENGINE_free()on the loaded engine. ssl_cleanup()on the active wrapper.
Then cleanup_logs():
write_log_file()flushes the in-memory buffer toLogFile, also updating<LogFile>.index.cleanup_config()freesConfigDataandconfig_location.free_log_heap()releases the buffer.
Finally main() returns EXIT_SUCCESS if the global success flag is
still true, else EXIT_FAILURE.