Axis2/C Android Support¶
This document describes how Axis2/C supports Android deployment with HTTP/2 and JSON-RPC.
Quick Reference for Claude¶
When working with Axis2/C Android support, remember:
- All builds use
-std=gnu99- Required for json-c header compatibility - Android uses static linking - No dlopen(), services use weak symbol registry
- Service code lives in applications - Axis2/C core only has weak symbol stubs
--whole-archiveis required - For weak/strong symbol resolution during linking- IPC uses fork()/execvp() - NEVER use system() for security (command injection)
- Java validates all input - SecurityValidator class provides defense-in-depth
Build System¶
C Standard: Always gnu99¶
All Axis2/C builds use -std=gnu99 (GNU C99 mode). This is required because:
- json-c headers use C99
inlinekeyword - Android NDK headers require GNU extensions
- POSIX functions are hidden by strict ANSI mode
Implementation in all configure.ac files:
if test "$GCC" = "yes"; then
dnl Use gnu99 for json-c compatibility
CFLAGS="$CFLAGS -std=gnu99 -Wall -Wno-implicit-function-declaration -D_GNU_SOURCE"
fi
The following configure.ac files all use this pattern:
- configure.ac (root)
- util/configure.ac
- axiom/configure.ac
- neethi/configure.ac
- guththila/configure.ac
- samples/configure.ac
- tools/md5/configure.ac
- tools/tcpmon/configure.ac
Android Detection¶
Android builds are detected by the --host parameter:
AC_MSG_CHECKING([for Android target])
case "$host" in
*-linux-android*|*-android*)
android_build=yes
AC_DEFINE([AXIS2_ANDROID_BUILD], [1], [Building for Android])
;;
*)
android_build=no
;;
esac
When android_build=yes:
- AXIS2_ANDROID_BUILD is defined
- __ANDROID__ is typically already defined by the NDK compiler
- Static linking is expected
Android NDK Cross-Compilation¶
Environment setup:
export ANDROID_NDK_HOME=$HOME/Android/Sdk/ndk/28.0.12916984
export TOOLCHAIN=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64
export CC=$TOOLCHAIN/bin/aarch64-linux-android21-clang
export AR=$TOOLCHAIN/bin/llvm-ar
export RANLIB=$TOOLCHAIN/bin/llvm-ranlib
Configure command:
./configure \
--host=aarch64-linux-android \
--prefix=$DEPS \
--enable-static \
--disable-shared \
--enable-http2 \
--enable-json \
--with-apr=$DEPS \
--with-openssl=$DEPS
Convenience script: Use build_android.sh in the repository root for a complete build:
This script sets up the toolchain, configures with all required flags, and builds. Edit the script to adjust paths for your NDK installation and dependency locations.
Static Service Registry (Android)¶
The Problem¶
Traditional Axis2/C loads services dynamically:
Android with static linking has no .so files to load.
Solution: Weak Symbol Architecture¶
Axis2/C uses weak symbols so applications can provide service implementations at link time.
In src/core/receivers/axis2_json_rpc_msg_recv.c:
#ifdef __ANDROID__
typedef json_object* (*json_service_invoke_func_t)(
const axutil_env_t *env, json_object *json_request);
/* Weak symbol - returns NULL if no implementation linked */
__attribute__((weak))
json_object* camera_control_service_invoke_json(
const axutil_env_t *env, json_object *json_request)
{
(void)env;
(void)json_request;
return NULL;
}
/* Lookup function - returns function pointer for known services */
json_service_invoke_func_t
android_static_service_lookup(const char *service_name)
{
if (!service_name) return NULL;
if (strcmp(service_name, "CameraControlService") == 0) {
return camera_control_service_invoke_json;
}
/* Add more services here */
return NULL;
}
#endif
How It Works¶
[Axis2/C Framework] [Application]
| |
weak symbol: strong symbol:
camera_control_service_ camera_control_service_
invoke_json() -> NULL invoke_json() -> response
| |
+----------- Link Time ---------+
|
Strong symbol wins
- Framework compiles with weak symbol stubs (return NULL)
- Application provides strong symbol implementations
- At link time, strong symbols override weak symbols
- Result: Application's service code handles requests
Application Service Implementation¶
Applications provide two files:
Adapter (bridges Axis2/C to app):
// axis2_static_service_adapter.c
#include <axutil_env.h>
#include <json-c/json.h>
extern int camera_control_service_impl(const char* req, char* resp, size_t size);
/* Strong symbol overrides weak in Axis2/C */
json_object* camera_control_service_invoke_json(
const axutil_env_t *env, json_object *json_request)
{
const char *req_str = json_object_to_json_string(json_request);
char response[65536] = {0};
camera_control_service_impl(req_str, response, sizeof(response));
return json_tokener_parse(response);
}
Implementation (app-specific logic):
// camera_control_service.c
int camera_control_service_impl(const char* req, char* resp, size_t size)
{
/* Parse request, handle action, build response */
snprintf(resp, size, "{\"result\":\"ok\"}");
return 0;
}
Critical: Functions Must Be Non-Static¶
Registry functions MUST NOT use static:
/* WRONG - may be optimized away */
static json_service_invoke_func_t android_static_service_lookup(...) { }
/* CORRECT - global symbol preserved */
json_service_invoke_func_t android_static_service_lookup(...) { }
Static functions have internal linkage and may be excluded from static archives.
Linking for Android¶
Direct Linking Required¶
Libtool strips --whole-archive flags, so direct linking is necessary:
$CC -fPIC -o httpd modules.o buildmark.o \
-Wl,--export-dynamic \
-L$DEPS/lib \
# Apache modules... \
$DEPS/lib/libmod_axis2.a \
-Wl,--whole-archive $DEPS/lib/libaxis2_engine.a -Wl,--no-whole-archive \
-Wl,--whole-archive libapp_services.a -Wl,--no-whole-archive \
# Other Axis2 libraries... \
-ljson-c -lnghttp2 -laprutil-1 -lapr-1 \
-lm -llog -pthread
Key flags:
| Flag | Purpose |
|---|---|
-Wl,--whole-archive |
Include ALL objects from archive |
-Wl,--no-whole-archive |
Resume normal archive linking |
-Wl,--export-dynamic |
Export all symbols |
-llog |
Android logging |
Verify Symbol Inclusion¶
nm httpd | grep -E "android_static|camera_control"
# Expected (T = global text):
# 00000000007b4998 T android_static_service_lookup
# 00000000007b3ae0 T camera_control_service_invoke_json
services.xml Configuration¶
REST Dispatcher Parameters¶
Use RESTLocation and RESTMethod, NOT httpPath and httpMethod:
<service name="CameraControlService">
<operation name="getStatus">
<messageReceiver class="axis2_json_rpc_msg_recv"/>
<parameter name="RESTMethod">POST</parameter>
<parameter name="RESTLocation">/getStatus</parameter>
<parameter name="contentType">application/json</parameter>
</operation>
</service>
Adding New Services¶
Step 1: Add Weak Symbol to Axis2/C¶
In src/core/receivers/axis2_json_rpc_msg_recv.c:
#ifdef __ANDROID__
__attribute__((weak))
json_object* my_new_service_invoke_json(
const axutil_env_t *env, json_object *json_request)
{
(void)env; (void)json_request;
return NULL;
}
json_service_invoke_func_t android_static_service_lookup(const char *svc)
{
/* Existing services... */
if (strcmp(svc, "MyNewService") == 0) {
return my_new_service_invoke_json;
}
return NULL;
}
#endif
Step 2: Implement in Application¶
Create adapter and implementation files in your application, then link with --whole-archive.
Step 3: Create services.xml¶
Deploy to services/MyNewService/services.xml in the Axis2 repository.
mod_axis2 Configuration¶
Android-Specific Initialization¶
In mod_axis2.c, Android builds:
- Skip shared memory (SELinux restrictions)
- Use pool-based allocator
- Run in single-process mode (-X flag)
#ifdef __ANDROID__
/* Skip shared memory, use pool allocation */
allocator = apr_palloc(pconf, sizeof(axutil_allocator_t));
goto engine_init;
#endif
Debug Logging¶
Android logging uses INFO level:
#ifdef __ANDROID__
#include <android/log.h>
#define AXIS2_ANDROID_LOG(fmt, ...) \
__android_log_print(ANDROID_LOG_INFO, "Axis2-mod", fmt, ##__VA_ARGS__)
#else
#define AXIS2_ANDROID_LOG(fmt, ...) ((void)0)
#endif
HTTP/2 on Android¶
Single-Process Mode Considerations¶
Apache runs with -X (single-process) on Android. For older devices, configure HTTP/2 conservatively:
# In http2-performance.conf
H2MaxSessionStreams 1 # Single stream for stability
H2StreamTimeout 30 # Prevent hanging streams
H2WindowSize 65535 # Reduced memory
H2Push off # Not needed for JSON-RPC
H2MinWorkers 1
H2MaxWorkers 2
Android IPC Security¶
Native to Java Communication¶
Axis2/C services on Android often need to communicate with Java application components. This is done via Android Intent broadcasts using the am (Activity Manager) command.
CRITICAL: Never Use system()¶
INSECURE (vulnerable to command injection):
/* NEVER DO THIS - user input is parsed by shell */
char cmd[1024];
snprintf(cmd, sizeof(cmd),
"am broadcast --es pattern '%s'", user_pattern);
system(cmd); /* Shell injection if pattern contains: '; rm -rf / ; ' */
SECURE (use fork/execvp):
/* Arguments passed directly to process, no shell parsing */
typedef struct {
const char* type; /* "--es" for string, "--ei" for int */
const char* key;
const char* value;
} intent_extra_t;
static int send_intent_broadcast_secure(
const char* component,
const char* action,
const intent_extra_t* extras,
int num_extras)
{
/* Build argument array */
int total_args = 8 + (num_extras * 3) + 1; /* base args + extras + NULL */
char** argv = (char**)malloc(total_args * sizeof(char*));
int i = 0;
argv[i++] = "am";
argv[i++] = "broadcast";
argv[i++] = "--user";
argv[i++] = "0";
argv[i++] = "-n";
argv[i++] = (char*)component;
argv[i++] = "-a";
argv[i++] = (char*)action;
for (int j = 0; j < num_extras; j++) {
argv[i++] = (char*)extras[j].type;
argv[i++] = (char*)extras[j].key;
argv[i++] = (char*)extras[j].value; /* User data - safe, not parsed */
}
argv[i] = NULL;
pid_t pid = fork();
if (pid == 0) {
/* Child process */
execvp("/system/bin/am", argv);
_exit(127); /* exec failed */
}
/* Parent waits for child */
int status;
waitpid(pid, &status, 0);
free(argv);
return WIFEXITED(status) && WEXITSTATUS(status) == 0 ? 0 : -1;
}
Why fork/execvp Is Secure¶
| Approach | Shell Invoked | User Data Parsed | Injection Risk |
|---|---|---|---|
system("cmd " + input) |
Yes | Yes | HIGH |
popen("cmd " + input, "r") |
Yes | Yes | HIGH |
fork() + execvp(argv[]) |
No | No | None |
With execvp(), arguments are passed as an array directly to the new process. Shell metacharacters (', ;, |, $(), etc.) are treated as literal characters, not interpreted.
Usage Example¶
/* Safe: user-provided pattern passed directly to Java */
int camera_device_delete_files_impl(const char* pattern, char* op_id)
{
intent_extra_t extras[] = {
{"--es", "action", "delete_files"},
{"--es", "pattern", pattern}, /* User input - safe */
{"--es", "operation_id", op_id}
};
return send_intent_broadcast_secure(
"org.kanaha.camera/org.kanaha.camera.CameraControlReceiver",
"org.kanaha.CAMERA_CONTROL",
extras, 3);
}
Java-Side Validation¶
The Java BroadcastReceiver provides defense-in-depth validation:
// In CameraControlReceiver.java
class SecurityValidator {
// Length limits
static final int MAX_FILENAME_LENGTH = 255;
static final int MAX_PATH_LENGTH = 1024;
// Blocked patterns
private static final String[] PATH_TRAVERSAL = {"..", "%2e%2e", "%00"};
private static final String[] INJECTION = {"<script", "${", "`", "$("};
static String validateFilenameOrPattern(String input, String paramName) {
if (input == null || input.isEmpty()) return "empty " + paramName;
if (input.length() > MAX_FILENAME_LENGTH) return paramName + " too long";
// Check for path traversal
String lower = input.toLowerCase();
for (String pattern : PATH_TRAVERSAL) {
if (lower.contains(pattern)) return "path traversal in " + paramName;
}
// Check for injection attempts
for (String pattern : INJECTION) {
if (lower.contains(pattern)) return "injection attempt in " + paramName;
}
// Whitelist allowed characters
if (!input.matches("^[a-zA-Z0-9_\\-\\.\\*]+$")) {
return "invalid characters in " + paramName;
}
return null; // Valid
}
}
Security Headers Reference¶
See the Kanaha project's docs/SECURITY.md for complete security documentation including:
- mTLS configuration
- Certificate revocation (CRL)
- Audit logging
- Apache httpd security headers
Note: Kanaha is an independent project (GPLv3 licensed due to OpenCamera
dependency) and is not affiliated with Apache Software Foundation. It demonstrates
production deployment patterns for the CameraControlService sample in
samples/user_guide/camera-control-service/.
Summary Table¶
| Aspect | Desktop/Server | Android |
|---|---|---|
| C standard | -std=gnu99 |
-std=gnu99 |
| Library type | Shared (.so) | Static (.a) |
| Service loading | dlopen() | Weak symbol registry |
| Service location | External .so | Application provides strong symbols |
| Linker | Libtool | Direct clang with --whole-archive |
| Apache mode | Multi-process | Single-process (-X) |
| IPC method | N/A | fork()/execvp() (NOT system()) |
| Input validation | Application | Java SecurityValidator + C checks |
Files Reference¶
Key files for Android support:
Axis2/C Core:
- configure.ac - Android detection, C flags
- src/core/receivers/axis2_json_rpc_msg_recv.c - Static service registry
- src/core/transport/http/server/apache2/mod_axis2.c - Android init, logging
- src/core/transport/http/server/apache2/apache2_worker.c - Request processing
Application (Kanaha example):
- app/src/main/cpp/axis2c/camera_control_service.c - Service impl with secure IPC
- app/src/main/java/org/kanaha/camera/CameraControlReceiver.java - Java receiver with SecurityValidator
- app/src/main/assets/apache/httpd.conf - Apache security config
- app/src/main/assets/apache/ssl.conf - mTLS and CRL config
- docs/SECURITY.md - Comprehensive security documentation
Last updated: 2026-01-14