@@ -72,6 +72,12 @@ struct pss {
7272 int send_ext_ips ;
7373};
7474
75+ struct published_jws_info {
76+ lws_dll2_t list ;
77+ char domain [128 ];
78+ time_t mtime ;
79+ };
80+
7581struct vhd {
7682 struct lws_context * context ;
7783 struct lws_vhost * vhost ;
@@ -103,6 +109,8 @@ struct vhd {
103109 char ext_ips [256 ];
104110 lws_dll2_owner_t completed_checks ;
105111 lws_dll2_owner_t whois_queries ;
112+ lws_dll2_owner_t published_jws ;
113+ lws_sorted_usec_list_t sul_timer_scan ;
106114};
107115
108116struct cert_check_result {
@@ -155,13 +163,22 @@ static void
155163whois_cb (void * opaque , const struct lws_whois_results * res )
156164{
157165 struct whois_query_info * wqi = (struct whois_query_info * )opaque ;
158- char buf [ 2048 ], ns_list [ 1024 ] = "" ;
159- char * p_ns , * saveptr , * token ;
160-
161- if (! wqi )
162- return ;
166+ int n ;
167+ char buf [ 2048 ] ;
168+ char ns_list [ 1024 ] = "" ;
169+
170+ lwsl_notice ( "[INSTRUMENT] %s: callback triggered for %s. res is %s\n" , __func__ , wqi -> domain , res ? "NOT NULL" : "NULL" ) ;
163171
172+ char s_dnssec [256 ] = "" , s_ds [1024 ] = "" ;
164173 if (res ) {
174+ lws_strncpy (s_dnssec , res -> dnssec , sizeof (s_dnssec ));
175+ lws_strncpy (s_ds , res -> ds_data , sizeof (s_ds ));
176+ for (size_t i = 0 ; i < strlen (s_dnssec ); i ++ )
177+ if (s_dnssec [i ] < 32 || s_dnssec [i ] == '"' ) s_dnssec [i ] = ' ' ;
178+ for (size_t i = 0 ; i < strlen (s_ds ); i ++ )
179+ if (s_ds [i ] < 32 || s_ds [i ] == '"' ) s_ds [i ] = ' ' ;
180+
181+ char * p_ns , * token , * saveptr ;
165182 /* Convert comma-separated nameservers to JSON array of strings */
166183 p_ns = strdup (res -> nameservers );
167184 if (p_ns ) {
@@ -176,53 +193,45 @@ whois_cb(void *opaque, const struct lws_whois_results *res)
176193 }
177194 free (p_ns );
178195 }
179- }
180-
181- char s_dnssec [256 ] = "" , s_ds [1024 ] = "" ;
182- if (res ) {
183- lws_strncpy (s_dnssec , res -> dnssec , sizeof (s_dnssec ));
184- lws_strncpy (s_ds , res -> ds_data , sizeof (s_ds ));
185- for (size_t i = 0 ; i < strlen (s_dnssec ); i ++ )
186- if (s_dnssec [i ] < 32 || s_dnssec [i ] == '"' ) s_dnssec [i ] = ' ' ;
187- for (size_t i = 0 ; i < strlen (s_ds ); i ++ )
188- if (s_ds [i ] < 32 || s_ds [i ] == '"' ) s_ds [i ] = ' ' ;
196+
189197 for (size_t i = 0 ; i < strlen (ns_list ); i ++ )
190198 if (ns_list [i ] < 32 && ns_list [i ] != '\0' ) ns_list [i ] = ' ' ;
191- }
192199
193- int n ;
194- if (res ) {
195200 n = lws_snprintf (buf , sizeof (buf ),
196201 "{\n \"creation_date\": %llu,\n \"expiry_date\": %llu,\n \"updated_date\": %llu,\n"
197202 " \"nameservers\": [%s],\n"
198203 " \"dnssec\": \"%s\",\n \"ds_data\": \"%s\",\n \"last_query\": %llu\n}\n" ,
199204 (unsigned long long )res -> creation_date , (unsigned long long )res -> expiry_date ,
200205 (unsigned long long )res -> updated_date ,
201206 ns_list , s_dnssec , s_ds , (unsigned long long )lws_now_secs ());
207+
208+ lwsl_notice ("[INSTRUMENT] whois_cb: formatted JSON for %s, size = %d\n" , wqi -> domain , n );
202209 } else {
203- n = lws_snprintf (buf , sizeof (buf ),
204- "{\n \"creation_date\": 0,\n \"expiry_date\": 0,\n \"updated_date\": 0,\n"
205- " \"nameservers\": [],\n"
206- " \"dnssec\": \"\",\n \"ds_data\": \"\",\n \"last_query\": %llu\n}\n" ,
207- (unsigned long long )lws_now_secs ());
210+ lwsl_notice ("[INSTRUMENT] whois_cb: res is NULL for %s, skipping UDS publish\n" , wqi -> domain );
211+ n = 0 ; /* Let it organically fail or retry without caching `{}` */
208212 }
209213
210214 if (n > 0 ) {
211- char b64 [4096 ] = {0 }, jwt [1024 ] = {0 }, uds_json [6144 ] = {0 }, temp [2048 ] = {0 };
215+ char b64 [8192 ] = {0 }, jwt [1024 ] = {0 }, uds_json [10240 ] = {0 }, temp [2048 ] = {0 };
212216 size_t jwt_len = sizeof (jwt );
213- lws_b64_encode_string (buf , n , b64 , sizeof (b64 ));
217+ lws_b64_encode_string (buf , ( int ) strlen ( buf ) , b64 , sizeof (b64 ));
214218
215219 if (wqi -> vhd -> auth_jwk .kty == LWS_GENCRYPTO_KTY_OCT ) {
220+ char jwt_payload [512 ];
221+ unsigned long now = (unsigned long )lws_now_secs ();
222+ lws_snprintf (jwt_payload , sizeof (jwt_payload ),
223+ "{\"iss\":\"acme-ipc\",\"aud\":\"dnssec-monitor\",\"nbf\":%lu,\"exp\":%lu}" ,
224+ now , now + 300 );
225+
216226 if (lws_jwt_sign_compact (wqi -> vhd -> context , & wqi -> vhd -> auth_jwk , "HS256" ,
217- jwt , & jwt_len , temp , sizeof (temp ), "{\"iss\":\"dnssec-monitor\"}" )) {
218- lwsl_err ("%s: failed to generate jwt for whois\n" , __func__ );
227+ jwt , & jwt_len , temp , sizeof (temp ), jwt_payload )) {
228+ lwsl_err ("[INSTRUMENT] %s: failed to generate jwt for whois\n" , __func__ );
219229 }
220230 }
221231
222232 int payload_n = lws_snprintf (uds_json , sizeof (uds_json ),
223233 "{\"req\":\"update_whois\",\"domain\":\"%s\",\"jwt\":\"%s\",\"zone\":\"%s\"}\n" ,
224234 wqi -> domain , jwt , b64 );
225-
226235 int fd = socket (AF_UNIX , SOCK_STREAM , 0 );
227236 if (fd >= 0 ) {
228237 struct sockaddr_un sun ;
@@ -231,19 +240,21 @@ whois_cb(void *opaque, const struct lws_whois_results *res)
231240 lws_strncpy (sun .sun_path , wqi -> vhd -> uds_path , sizeof (sun .sun_path ));
232241 if (connect (fd , (struct sockaddr * )& sun , sizeof (sun )) == 0 ) {
233242 if (write (fd , uds_json , (size_t )payload_n ) < 0 ) {
234- lwsl_err ("%s: Failed writing whois payload to UDS\n" , __func__ );
243+ lwsl_err ("[INSTRUMENT] %s: Failed writing whois payload to UDS, errno: %d \n" , __func__ , errno );
235244 } else {
236- lwsl_notice ("%s: Tunneled WHOIS for %s to Root over UDS\n" , __func__ , wqi -> domain );
245+ lwsl_notice ("[INSTRUMENT] %s: Tunneled WHOIS for %s to Root over UDS (payload %d bytes) \n" , __func__ , wqi -> domain , payload_n );
237246 }
238247 } else {
239- lwsl_err ("%s: Failed connecting to root UDS for whois pass-back\n" , __func__ );
248+ lwsl_err ("[INSTRUMENT] %s: Failed connecting to root UDS at %s for whois pass-back, errno: %d \n" , __func__ , sun . sun_path , errno );
240249 }
241250 close (fd );
251+ } else {
252+ lwsl_err ("[INSTRUMENT] %s: socket creation failed! errno: %d\n" , __func__ , errno );
242253 }
254+
255+ lws_dll2_remove (& wqi -> list );
256+ free (wqi );
243257 }
244-
245- lws_dll2_remove (& wqi -> list );
246- free (wqi );
247258}
248259
249260static int
@@ -524,17 +535,70 @@ dir_notify_cb(const char *path, int is_file, void *user)
524535}
525536#endif
526537
538+ static int
539+ scan_jws_publish_cb (const char * dirpath , void * user , struct lws_dir_entry * lde )
540+ {
541+ struct vhd * vhd = (struct vhd * )user ;
542+
543+ if (lde -> type != LDOT_DIR || lde -> name [0 ] == '.' )
544+ return 0 ;
545+
546+ if (vhd -> ops && vhd -> ops -> publish_jws ) {
547+ char jws_path [1024 ];
548+ struct stat st ;
549+
550+ lws_snprintf (jws_path , sizeof (jws_path ), "%s/domains/%s/%s.zone.signed.jws" , vhd -> base_dir , lde -> name , lde -> name );
551+
552+ if (stat (jws_path , & st ) == 0 ) {
553+ /* Check if we already published this version */
554+ struct published_jws_info * p = NULL ;
555+ lws_start_foreach_dll (struct lws_dll2 * , d , vhd -> published_jws .head ) {
556+ struct published_jws_info * tp = lws_container_of (d , struct published_jws_info , list );
557+ if (!strcmp (tp -> domain , lde -> name )) {
558+ p = tp ;
559+ break ;
560+ }
561+ } lws_end_foreach_dll (d );
562+
563+ if (!p || p -> mtime != st .st_mtime ) {
564+ if (!p ) {
565+ p = malloc (sizeof (* p ));
566+ if (!p ) return 0 ;
567+ memset (p , 0 , sizeof (* p ));
568+ lws_strncpy (p -> domain , lde -> name , sizeof (p -> domain ));
569+ lws_dll2_add_tail (& p -> list , & vhd -> published_jws );
570+ }
571+ p -> mtime = st .st_mtime ;
572+ lwsl_notice ("%s: Engaging parent monitor to execute DHT publication for %s\n" , __func__ , lde -> name );
573+ vhd -> ops -> publish_jws (vhd -> vhost , jws_path );
574+ }
575+ }
576+ }
577+ return 0 ;
578+ }
579+
527580static void
528581parent_dnssec_monitor_timer_cb (struct lws_sorted_usec_list * sul )
529582{
530583 struct vhd * vhd = lws_container_of (sul , struct vhd , sul_timer );
531584 char scan_path [1024 ];
532585
533586 lws_snprintf (scan_path , sizeof (scan_path ), "%s/domains" , vhd -> base_dir );
534- lws_dir (scan_path , vhd , scan_dir_cb );
587+ lws_dir (scan_path , vhd , scan_jws_publish_cb );
535588 lws_sul_schedule (vhd -> context , 0 , & vhd -> sul_timer , parent_dnssec_monitor_timer_cb , 5 * LWS_US_PER_SEC );
536589}
537590
591+ static void
592+ root_dnssec_scan_timer_cb (struct lws_sorted_usec_list * sul )
593+ {
594+ struct vhd * vhd = lws_container_of (sul , struct vhd , sul_timer_scan );
595+ char scan_path [1024 ];
596+
597+ lws_snprintf (scan_path , sizeof (scan_path ), "%s/domains" , vhd -> base_dir );
598+ lws_dir (scan_path , vhd , scan_dir_cb );
599+ lws_sul_schedule (vhd -> context , 0 , & vhd -> sul_timer_scan , root_dnssec_scan_timer_cb , 5 * LWS_US_PER_SEC );
600+ }
601+
538602
539603
540604
@@ -650,6 +714,10 @@ monitor_req_cb(struct lejp_ctx *ctx, char reason)
650714 }
651715 }
652716
717+ if (reason == LEJPCB_FAILED ) {
718+ lwsl_err ("[INSTRUMENT] monitor_req_cb: LEJP JSON Parse FAILED at struct offset %d\n" , (int )ctx -> st [ctx -> sp ].s );
719+ }
720+
653721 return 0 ;
654722}
655723
@@ -1153,22 +1221,31 @@ handle_req_update_whois(struct vhd *vhd, struct pss *root_pss, struct monitor_re
11531221{
11541222 char * tx = (char * )& root_pss -> tx [LWS_PRE ];
11551223
1224+ lwsl_notice ("[INSTRUMENT] handle_req_update_whois START for domain: '%s', zone_buf present: %d\n" , a -> domain , !!a -> zone_buf );
1225+
11561226 if (a -> domain [0 ] && a -> zone_buf ) {
11571227 char path [1024 ];
11581228 lws_snprintf (path , sizeof (path ), "%s/domains/%s/whois.json" , vhd -> base_dir , a -> domain );
11591229 int fd = open (path , O_CREAT | O_WRONLY | O_TRUNC , 0644 );
11601230 if (fd >= 0 ) {
1161- char decoded [4096 ];
1231+ char decoded [8192 ];
11621232 int n = lws_b64_decode_string (a -> zone_buf , decoded , sizeof (decoded ));
1233+ lwsl_notice ("[INSTRUMENT] lws_b64_decode_string returned %d for %s\n" , n , a -> domain );
11631234 if (n > 0 ) {
11641235 if (write (fd , decoded , (size_t )n ) < 0 ) {
1165- lwsl_err ("%s: Failed writing to %s\n" , __func__ , path );
1236+ lwsl_err ("[INSTRUMENT] %s: Failed writing to %s (errno: %d) \n" , __func__ , path , errno );
11661237 } else {
1167- lwsl_info ("%s: Successfully synced WHOIS via UDS IPC for %s\n" , __func__ , a -> domain );
1238+ lwsl_info ("[INSTRUMENT] %s: Successfully synced WHOIS via UDS IPC for %s\n" , __func__ , a -> domain );
11681239 }
1240+ } else {
1241+ lwsl_err ("[INSTRUMENT] %s: Failed B64 decode on whois zone payload size=%d\n" , __func__ , (int )a -> zone_len );
11691242 }
11701243 close (fd );
1244+ } else {
1245+ lwsl_err ("[INSTRUMENT] %s: Failed to open %s for writing! errno: %d\n" , __func__ , path , errno );
11711246 }
1247+ } else {
1248+ lwsl_err ("[INSTRUMENT] %s: Failed prerequisites. domain: '%s', zone_buf present: %d\n" , __func__ , a -> domain , !!a -> zone_buf );
11721249 }
11731250
11741251 /* Empty response is fine, IPC fire-and-forget */
@@ -1522,6 +1599,7 @@ callback_dht_dnssec_monitor(struct lws *wsi, enum lws_callback_reasons reason,
15221599 if (!vhd -> dn )
15231600 lwsl_err ("%s: Failed to attach lws_dir_notify to %s\n" , __func__ , scan_path );
15241601#endif
1602+ lws_sul_schedule (cx , 0 , & vhd -> sul_timer_scan , root_dnssec_scan_timer_cb , 5 * LWS_US_PER_SEC );
15251603 } else {
15261604 lwsl_err ("%s: Skipped scheduling timer on %s because vhd->ops is NULL!\n" , __func__ , lws_get_vhost_name (vhost ));
15271605 /* It will organically retry when the next vhost runs PROTOCOL_INIT */
@@ -1717,7 +1795,17 @@ callback_dht_dnssec_monitor(struct lws *wsi, enum lws_callback_reasons reason,
17171795 global_root_vhd = vhd ;
17181796 lwsl_notice ("%s: Spawned root monitor process successfully and assigned global_root_vhd=%p (fallback active)\n" , __func__ , global_root_vhd );
17191797
1720- /* Engage parent monitor to execute DHT publications off completed JWS child drops cleanly */
1798+ /*
1799+ * Privilege Separation Policy:
1800+ * - The "root daemon" drops its privileges to run as the `lwsws-priv` user.
1801+ * - Only the `lwsws-priv` daemon can write to the base dir (e.g., /var/dnssec)
1802+ * and read secrets like cert keys.
1803+ * - The less-privileged network-facing side (here) asks the daemon to handle
1804+ * write operations securely.
1805+ * - We keep the privileged daemon isolated from external network content.
1806+ * Therefore, this unprivileged side leverages a timer to securely scan for
1807+ * completed .jws drops and natively handles the DHT network publication.
1808+ */
17211809 lws_sul_schedule (vhd -> context , 0 , & vhd -> sul_timer , parent_dnssec_monitor_timer_cb , 1 * LWS_US_PER_SEC );
17221810 } else {
17231811 /* Already globally spawned! Just map the auth context */
@@ -2158,12 +2246,9 @@ callback_dht_dnssec_monitor(struct lws *wsi, enum lws_callback_reasons reason,
21582246 }
21592247 break ;
21602248
2161-
2162-
21632249 default :
21642250 break ;
21652251 }
2166-
21672252 return 0 ;
21682253}
21692254
0 commit comments