blob: dcbb1dfaaf749598c4205af55697f63bc2773eb0 [file] [view]
# 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:
1. **All builds use `-std=gnu99`** - Required for json-c header compatibility
2. **Android uses static linking** - No dlopen(), services use weak symbol registry
3. **Service code lives in applications** - Axis2/C core only has weak symbol stubs
4. **`--whole-archive` is required** - For weak/strong symbol resolution during linking
5. **IPC uses fork()/execvp()** - NEVER use system() for security (command injection)
6. **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:
1. **json-c headers** use C99 `inline` keyword
2. **Android NDK headers** require GNU extensions
3. **POSIX functions** are hidden by strict ANSI mode
**Implementation in all configure.ac files:**
```m4
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:
```m4
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:**
```bash
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:**
```bash
./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:
```bash
./build_android.sh
```
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:
```c
void *lib = dlopen("libmyservice.so", RTLD_NOW);
func = dlsym(lib, "service_invoke_json");
```
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`:**
```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
```
1. **Framework compiles** with weak symbol stubs (return NULL)
2. **Application provides** strong symbol implementations
3. **At link time**, strong symbols override weak symbols
4. **Result**: Application's service code handles requests
### Application Service Implementation
Applications provide two files:
**Adapter (bridges Axis2/C to app):**
```c
// 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):**
```c
// 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`:
```c
/* 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.
## Android vs Server-Side: Two `_invoke_json` Signatures
The `axis2_json_rpc_msg_recv` framework dispatches to service functions
using **different signatures** depending on the platform:
### Android (2-param, `json_object*`)
```c
json_object* camera_control_service_invoke_json(
const axutil_env_t *env,
json_object *json_request);
```
- Resolved via the **static service registry** (`android_static_service_lookup`)
- Uses weak/strong symbol linking — no `dlopen`/`dlsym` at runtime
- Works with `json_object*` (json-c object pointers)
- The Kanaha app's `axis2_static_service_adapter.c` bridges this to the
app's `_impl()` functions which use plain `char*` strings
### Server-side / Linux (4-param, `char*`)
```c
axis2_char_t* financial_benchmark_service_invoke_json(
axis2_svc_t *svc,
const axutil_env_t *env,
const axis2_char_t *json_request,
axis2_msg_ctx_t *msg_ctx);
```
- Resolved via `dlsym(RTLD_DEFAULT, "<serviceclass>_invoke_json")` after
the service `.so` is loaded by `axutil_class_loader_create_dll()`
- Has access to the `axis2_svc_t` and `axis2_msg_ctx_t` for operation
routing, endpoint info, and MCP catalog generation
- Works with `axis2_char_t*` (JSON strings), not `json_object*`
### Why Two Signatures?
Server-side services need `svc` and `msg_ctx` to:
- Extract the operation name from the message context (URL path dispatch)
- Access endpoint reference for MCP catalog intercepts
- Get configuration context for multi-service deployments
Android services don't need these because:
- The static registry already routes by service name
- Operations are identified by the `"action"` field in the JSON body
- There is no multi-service deployment — one app, one service
Services that want to work in both environments can export both:
- `axis2_char_t* <serviceclass>_invoke_json(svc, env, json_str, msg_ctx)` for server-side
- `axis2_char_t* <serviceclass>_process_json_only(env, json_str)` for simpler contexts
## Linking for Android
### Direct Linking Required
Libtool strips `--whole-archive` flags, so direct linking is necessary:
```bash
$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
```bash
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`:
```xml
<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`:
```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)
```c
#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:
```c
#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:
```apache
# 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):**
```c
/* 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):**
```c
/* 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
```c
/* 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:
```java
// 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](https://github.com/robertlazarski/kanaha) project's `docs/SECURITY.md` for complete security documentation including:
- mTLS configuration
- Certificate revocation (CRL)
- Audit logging
- Apache httpd security headers
**Note:** [Kanaha](https://github.com/robertlazarski/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](https://github.com/robertlazarski/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*