You are viewing a plain text version of this content. The canonical link for it is here.
Posted to c-commits@axis.apache.org by ro...@apache.org on 2026/10/20 02:16:42 UTC

(axis-axis2-c-core) branch master updated: Make camera sample stubs fail clearly and drop shell examples

This is an automated email from the ASF dual-hosted git repository.

robertlazarski pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/axis-axis2-c-core.git


The following commit(s) were added to refs/heads/master by this push:
     new 74aec7cc2 Make camera sample stubs fail clearly and drop shell examples
74aec7cc2 is described below

commit 74aec7cc2835d0928202b7ed60a7114ad4c09c4c
Author: Robert Lazarski <ro...@gmail.com>
AuthorDate: Mon Oct 19 16:01:20 2026 -1000

    Make camera sample stubs fail clearly and drop shell examples
    
    The stubs returned success without doing anything, so a deployment that
    missed one reported recordings and transfers that never happened; they now
    answer "not implemented" naming the function, unless built with
    -DCAMERA_STUBS_SUCCEED. The commented examples ran commands through
    system(), open to shell injection, and disabled SSH host key checking.
    
    Co-Authored-By: Claude Opus 5.5 <no...@anthropic.com>
---
 .../user_guide/camera-control-service/README.md    |  19 +-
 .../src/camera_control_service.c                   | 219 ++++++++++++++++-----
 2 files changed, 183 insertions(+), 55 deletions(-)

diff --git a/samples/user_guide/camera-control-service/README.md b/samples/user_guide/camera-control-service/README.md
index f7db28bd2..ca09ffeb8 100644
--- a/samples/user_guide/camera-control-service/README.md
+++ b/samples/user_guide/camera-control-service/README.md
@@ -212,7 +212,17 @@ ls -la /usr/local/axis2c/services/CameraControlService/
 
 ## Testing with Stub Implementation
 
-The service includes stub implementations that provide basic functionality for testing:
+Until a stub is replaced, its operation answers with an error naming the function
+to implement, so a deployment that forgot one cannot report success for work it
+never did:
+
+```json
+{"success":false,"error":"Not implemented: replace the stub camera_device_start_recording_impl() in camera_control_service.c"}
+```
+
+To exercise the HTTP/2 and JSON path before any camera code exists, build with
+`-DCAMERA_STUBS_SUCCEED` (for example `CFLAGS=-DCAMERA_STUBS_SUCCEED`); the stubs
+then succeed and return placeholder data:
 
 ```bash
 # Start Apache httpd
@@ -223,7 +233,7 @@ curl -k --http2 -H "Content-Type: application/json" \
      -d '{"action":"start_recording","clip_name":"test","quality":"1080p"}' \
      https://localhost:443/services/CameraControlService/startRecording
 
-# Expected response:
+# Expected response with -DCAMERA_STUBS_SUCCEED:
 # {"success":true,"message":"Recording started successfully"}
 
 # Check logs for stub warnings
@@ -312,6 +322,11 @@ STUB: camera_device_start_recording_impl() - Replace with camera-specific implem
 
 This is expected behavior until you implement camera-specific functions.
 
+When a stub runs an external program (`am`, `scp`), follow the examples in
+`camera_control_service.c`: pass arguments with `fork()`/`execvp()`, never a
+command line through `system()`, and check SSH host keys against a provisioned
+`known_hosts` file.
+
 ## Next Steps
 
 1. **Test stub functionality** - Verify service responds to HTTP/2 JSON requests
diff --git a/samples/user_guide/camera-control-service/src/camera_control_service.c b/samples/user_guide/camera-control-service/src/camera_control_service.c
index 2546b541f..bedaf7447 100644
--- a/samples/user_guide/camera-control-service/src/camera_control_service.c
+++ b/samples/user_guide/camera-control-service/src/camera_control_service.c
@@ -59,6 +59,31 @@ static json_object* create_success_response(const axutil_env_t *env,
 static axis2_bool_t is_valid_clip_name(const axis2_char_t *clip_name);
 static axis2_bool_t is_valid_quality(const axis2_char_t *quality);
 
+/*
+ * Until a stub below is replaced it answers "not implemented": a function that
+ * returns a status returns CAMERA_NOT_IMPLEMENTED, getStatus and listFiles
+ * return the marker objects below, and the service turns either into an error
+ * naming the function to replace. A stub that reported success would let a
+ * deployment that forgot one claim to have recorded, transferred or deleted
+ * what it never touched.
+ *
+ * To exercise the HTTP/2 and JSON path before any camera code exists, build
+ * with -DCAMERA_STUBS_SUCCEED: the stubs then succeed with placeholder data.
+ */
+#define CAMERA_NOT_IMPLEMENTED (-1)
+static camera_status_t camera_status_not_implemented;
+static file_list_result_t camera_file_list_not_implemented;
+
+static json_object* not_implemented_response(const axutil_env_t *env,
+                                             const char *function)
+{
+    char message[256];
+    snprintf(message, sizeof(message),
+             "Not implemented: replace the stub %s() in camera_control_service.c",
+             function);
+    return create_error_response(env, message);
+}
+
 /**
  * Create Camera Recording Parameters from JSON string using pure json-c
  */
@@ -602,6 +627,32 @@ file_delete_params_free(file_delete_params_t *params,
  * - Custom camera hardware
  */
 
+/*
+ * Running an external program from a service
+ *
+ * Several examples below run a program (am, scp). Never build a command line
+ * with snprintf() and hand it to system() or popen(): the shell parses the
+ * caller's values, so a quote in a clip name or file pattern becomes shell
+ * syntax and runs whatever follows it. Pass each argument separately with
+ * fork() and execvp(), so no shell ever sees them:
+ *
+ *    static int run_without_shell(char *const argv[])
+ *    {
+ *        pid_t pid = fork();
+ *        if (pid < 0) return -1;
+ *        if (pid == 0) {
+ *            execvp(argv[0], argv);
+ *            _exit(127);                      // exec failed
+ *        }
+ *        int status;
+ *        if (waitpid(pid, &status, 0) < 0) return -1;
+ *        return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
+ *    }
+ *
+ * Separate arguments stop shell injection, not path traversal: still reject
+ * "..", "/" and the like in names before using them.
+ */
+
 /**
  * STUB: Start camera recording implementation
  *
@@ -631,14 +682,18 @@ file_delete_params_free(file_delete_params_t *params,
  *    // Save to params->clip_name
  *    ```
  *
- * 3. IP Camera Integration:
+ * 3. IP Camera Integration, with libcurl and json-c so neither a shell nor
+ *    hand-built JSON sees the caller's values:
  *    ```c
- *    // Send HTTP/RTSP command to start recording
- *    char cmd[512];
- *    snprintf(cmd, sizeof(cmd),
- *        "curl -X POST http://camera-ip/api/record -d '{\"filename\":\"%s\",\"quality\":\"%s\"}'",
- *        params->clip_name, params->quality);
- *    system(cmd);
+ *    json_object *body = json_object_new_object();
+ *    json_object_object_add(body, "filename", json_object_new_string(params->clip_name));
+ *    json_object_object_add(body, "quality", json_object_new_string(params->quality));
+ *    CURL *curl = curl_easy_init();
+ *    curl_easy_setopt(curl, CURLOPT_URL, "http://camera-ip/api/record");
+ *    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_object_to_json_string(body));
+ *    CURLcode rc = curl_easy_perform(curl);
+ *    curl_easy_cleanup(curl);
+ *    json_object_put(body);
  *    ```
  */
 AXIS2_EXTERN axis2_status_t AXIS2_CALL
@@ -679,7 +734,11 @@ camera_device_start_recording_impl(
      * Replace this entire function with camera-specific code
      */
 
-    return AXIS2_SUCCESS; /* Stub always succeeds for testing */
+#ifdef CAMERA_STUBS_SUCCEED
+    return AXIS2_SUCCESS;
+#else
+    return CAMERA_NOT_IMPLEMENTED;
+#endif
 }
 
 /**
@@ -719,7 +778,11 @@ camera_device_stop_recording_impl(const axutil_env_t *env)
      * Replace this entire function with camera-specific code
      */
 
-    return AXIS2_SUCCESS; /* Stub always succeeds for testing */
+#ifdef CAMERA_STUBS_SUCCEED
+    return AXIS2_SUCCESS;
+#else
+    return CAMERA_NOT_IMPLEMENTED;
+#endif
 }
 
 /**
@@ -739,6 +802,12 @@ camera_device_get_status_impl(const axutil_env_t *env)
         return NULL;
     }
 
+#ifndef CAMERA_STUBS_SUCCEED
+    AXIS2_LOG_WARNING(env->log, AXIS2_LOG_SI,
+        "STUB: camera_device_get_status_impl() is not implemented");
+    return &camera_status_not_implemented;
+#endif
+
     status = AXIS2_MALLOC(env->allocator, sizeof(camera_status_t));
     if (!status)
     {
@@ -801,7 +870,11 @@ camera_device_configure_settings_impl(
      * Apply settings to actual camera device
      */
 
-    return AXIS2_SUCCESS; /* Stub always succeeds for testing */
+#ifdef CAMERA_STUBS_SUCCEED
+    return AXIS2_SUCCESS;
+#else
+    return CAMERA_NOT_IMPLEMENTED;
+#endif
 }
 
 /**
@@ -824,26 +897,25 @@ camera_device_configure_settings_impl(
  *    // Transfer file data
  *    ```
  *
- * 2. System sftp command (simpler but less control):
+ * 2. The scp command, run without a shell (run_without_shell() above):
  *    ```c
- *    char sftp_cmd[1024];
- *    snprintf(sftp_cmd, sizeof(sftp_cmd),
- *        "sftp -o PasswordAuthentication=no -o StrictHostKeyChecking=no "
- *        "-i %s -P %d %s@%s:%s <<< 'put %s'",
- *        params->private_key_path, params->port, params->username,
- *        params->hostname, params->remote_path, params->local_file_path);
- *    int result = system(sftp_cmd);
+ *    char port[8];
+ *    char target[1024];
+ *    snprintf(port, sizeof(port), "%d", params->port);
+ *    snprintf(target, sizeof(target), "%s@%s:%s",
+ *             params->username, params->hostname, params->remote_path);
+ *    char *const argv[] = {
+ *        "scp", "-o", "BatchMode=yes",
+ *        "-o", "StrictHostKeyChecking=yes",
+ *        "-o", "UserKnownHostsFile=/etc/camera/known_hosts",
+ *        "-i", (char *) params->private_key_path, "-P", port,
+ *        "--", (char *) params->local_file_path, target, NULL };
+ *    int result = run_without_shell(argv);
  *    ```
  *
- * 3. SSH with scp (alternative file transfer):
- *    ```c
- *    char scp_cmd[1024];
- *    snprintf(scp_cmd, sizeof(scp_cmd),
- *        "scp -o PasswordAuthentication=no -i %s -P %d %s %s@%s:%s",
- *        params->private_key_path, params->port, params->local_file_path,
- *        params->username, params->hostname, params->remote_path);
- *    int result = system(scp_cmd);
- *    ```
+ *    Check the server's host key against a known_hosts file you provision, and
+ *    fail when it is missing or does not match. StrictHostKeyChecking=no accepts
+ *    any key, so anyone on the network path could receive the recordings.
  */
 AXIS2_EXTERN axis2_status_t AXIS2_CALL
 camera_device_sftp_transfer_impl(
@@ -916,7 +988,11 @@ camera_device_sftp_transfer_impl(
      * 8. Clean up connections and return status
      */
 
-    return AXIS2_SUCCESS; /* Stub always succeeds for testing */
+#ifdef CAMERA_STUBS_SUCCEED
+    return AXIS2_SUCCESS;
+#else
+    return CAMERA_NOT_IMPLEMENTED;
+#endif
 }
 
 /**
@@ -926,20 +1002,20 @@ camera_device_sftp_transfer_impl(
  *
  * Example implementations (reference: Kanaha camera app):
  *
- * 1. Android Integration via Intent IPC:
+ * 1. Android Integration via Intent IPC (run_without_shell() above):
  *    ```c
- *    // Send broadcast to Java receiver
- *    char intent_command[1024];
- *    snprintf(intent_command, sizeof(intent_command),
- *        "am broadcast --user 0 "
- *        "-n org.myapp/org.myapp.CameraReceiver "
- *        "-a org.myapp.CAMERA_CONTROL "
- *        "--es action list_files "
- *        "--es pattern '%s'",
- *        params->pattern ? params->pattern : "");
- *    system(intent_command);
- *    // Read response from file written by Java receiver
+ *    char *const argv[] = {
+ *        "am", "broadcast", "--user", "0",
+ *        "-n", "org.myapp/org.myapp.CameraReceiver",
+ *        "-a", "org.myapp.CAMERA_CONTROL",
+ *        "--es", "action", "list_files",
+ *        "--es", "pattern", (char *) (params->pattern ? params->pattern : ""),
+ *        NULL };
+ *    run_without_shell(argv);
+ *    // Read the response file written by the Java receiver, then delete it
  *    ```
+ *    Protect the receiver with a signature-level permission, or any app on
+ *    the device can send it the same broadcast.
  *
  * 2. Linux filesystem integration:
  *    ```c
@@ -970,6 +1046,12 @@ camera_device_list_files_impl(
         return NULL;
     }
 
+#ifndef CAMERA_STUBS_SUCCEED
+    AXIS2_LOG_WARNING(env->log, AXIS2_LOG_SI,
+        "STUB: camera_device_list_files_impl() is not implemented");
+    return &camera_file_list_not_implemented;
+#endif
+
     result = AXIS2_MALLOC(env->allocator, sizeof(file_list_result_t));
     if (!result)
     {
@@ -1020,17 +1102,16 @@ camera_device_list_files_impl(
  *
  * Example implementations:
  *
- * 1. Android Integration via Intent IPC:
+ * 1. Android Integration via Intent IPC (run_without_shell() above):
  *    ```c
- *    char intent_command[1024];
- *    snprintf(intent_command, sizeof(intent_command),
- *        "am broadcast --user 0 "
- *        "-n org.myapp/org.myapp.CameraReceiver "
- *        "-a org.myapp.CAMERA_CONTROL "
- *        "--es action delete_files "
- *        "--es pattern '%s'",
- *        params->pattern);
- *    system(intent_command);
+ *    char *const argv[] = {
+ *        "am", "broadcast", "--user", "0",
+ *        "-n", "org.myapp/org.myapp.CameraReceiver",
+ *        "-a", "org.myapp.CAMERA_CONTROL",
+ *        "--es", "action", "delete_files",
+ *        "--es", "pattern", (char *) params->pattern,
+ *        NULL };
+ *    run_without_shell(argv);
  *    ```
  *
  * 2. Linux filesystem integration:
@@ -1103,7 +1184,11 @@ camera_device_delete_files_impl(
      * 4. Return AXIS2_SUCCESS if all deletions succeeded
      */
 
-    return AXIS2_SUCCESS; /* Stub always succeeds for testing */
+#ifdef CAMERA_STUBS_SUCCEED
+    return AXIS2_SUCCESS;
+#else
+    return CAMERA_NOT_IMPLEMENTED;
+#endif
 }
 
 /* ========================================================================
@@ -1184,6 +1269,10 @@ camera_control_service_invoke_json(const axutil_env_t *env,
         {
             response = create_success_response(env, "Recording started successfully");
         }
+        else if (result == CAMERA_NOT_IMPLEMENTED)
+        {
+            response = not_implemented_response(env, "camera_device_start_recording_impl");
+        }
         else
         {
             response = create_error_response(env, "Failed to start recording");
@@ -1200,6 +1289,10 @@ camera_control_service_invoke_json(const axutil_env_t *env,
         {
             response = create_success_response(env, "Recording stopped successfully");
         }
+        else if (result == CAMERA_NOT_IMPLEMENTED)
+        {
+            response = not_implemented_response(env, "camera_device_stop_recording_impl");
+        }
         else
         {
             response = create_error_response(env, "Failed to stop recording");
@@ -1210,7 +1303,11 @@ camera_control_service_invoke_json(const axutil_env_t *env,
         /* Handle status request */
         camera_status_t *status = camera_device_get_status_impl(env);
 
-        if (status)
+        if (status == &camera_status_not_implemented)
+        {
+            response = not_implemented_response(env, "camera_device_get_status_impl");
+        }
+        else if (status)
         {
             response = json_object_new_object();
             json_object_object_add(response, "success", json_object_new_boolean(1));
@@ -1247,6 +1344,10 @@ camera_control_service_invoke_json(const axutil_env_t *env,
         {
             response = create_success_response(env, "Settings configured successfully");
         }
+        else if (result == CAMERA_NOT_IMPLEMENTED)
+        {
+            response = not_implemented_response(env, "camera_device_configure_settings_impl");
+        }
         else
         {
             response = create_error_response(env, "Failed to configure settings");
@@ -1280,6 +1381,10 @@ camera_control_service_invoke_json(const axutil_env_t *env,
         {
             response = create_success_response(env, "File transferred successfully via SFTP");
         }
+        else if (result == CAMERA_NOT_IMPLEMENTED)
+        {
+            response = not_implemented_response(env, "camera_device_sftp_transfer_impl");
+        }
         else
         {
             response = create_error_response(env, "Failed to transfer file via SFTP");
@@ -1296,7 +1401,11 @@ camera_control_service_invoke_json(const axutil_env_t *env,
         /* Call user-implementable function */
         file_list_result_t *result = camera_device_list_files_impl(env, params);
 
-        if (result)
+        if (result == &camera_file_list_not_implemented)
+        {
+            response = not_implemented_response(env, "camera_device_list_files_impl");
+        }
+        else if (result)
         {
             /* Build JSON response with file list */
             response = json_object_new_object();
@@ -1351,6 +1460,10 @@ camera_control_service_invoke_json(const axutil_env_t *env,
         {
             response = create_success_response(env, "Files deleted successfully");
         }
+        else if (result == CAMERA_NOT_IMPLEMENTED)
+        {
+            response = not_implemented_response(env, "camera_device_delete_files_impl");
+        }
         else
         {
             response = create_error_response(env, "Failed to delete files");