/* Simple FTP-like protocol, used both for command line and * server communication. */ #include "cmd_base.h" #include "cmd_list.h" #include "syslog.h" #include #include #include #include #include #include #include #include // Delimiters for command arguments. static const char delim[] = " \t\n"; /* Utility functions for reading command arguments inside command handlers. * Return false on invalid value. */ bool cmd_string_arg(cmdargs_t *args, char **dest) { *dest = strtok_r(NULL, delim, &args->saveptr); return (*dest != NULL && **dest != '\0'); } bool cmd_signed_arg(cmdargs_t *args, signed *dest) { char *arg = strtok_r(NULL, delim, &args->saveptr); char *endptr; if (arg == NULL || *arg == '\0') return false; *dest = strtol(arg, &endptr, 0); return *endptr == '\0'; } bool cmd_unsigned_arg(cmdargs_t *args, unsigned *dest) { char *arg = strtok_r(NULL, delim, &args->saveptr); char *endptr; if (arg == NULL || *arg == '\0') return false; *dest = strtoul(arg, &endptr, 0); return *endptr == '\0'; } /* Check that all arguments have been read. */ bool cmd_last_arg(cmdargs_t *args) { return strtok_r(NULL, delim, &args->saveptr) == NULL; } /* Utility function for returning replies. * Example usage: set_result(args, 200, "OK"); */ void cmd_result(cmdargs_t *args, int status, const char *info) { args->status = status; fprintf(args->out, "%d %s\n", status, info); } void cmd_resultf(cmdargs_t *args, int status, const char *info, ...) { args->status = status; va_list va; va_start(va, info); fprintf(args->out, "%d ", status); vfprintf(args->out, info, va); fprintf(args->out, "\n"); va_end(va); } /* Single command execution. * Note: this function modifies the contents of cmdline. */ void cmd_run(cmdargs_t *args, char *cmdline) { args->saveptr = NULL; args->status = 0; args->cmd = strtok_r(cmdline, delim, &args->saveptr); if (args->cmd == NULL) // Empty command { cmd_result(args, 200, "OK"); return; } bool status = false; for (int i = 0; i < COMMANDS_COUNT; i++) { if (strcasecmp(COMMANDS[i].cmd, args->cmd) == 0) { status = COMMANDS[i].handler(args); if (!status) { cmd_resultf(args, 214, "usage: %s %s\n", COMMANDS[i].cmd, COMMANDS[i].help); status = true; } break; } } if (!status) { cmd_result(args, 502, "Unknown command"); } syslog("cmd status %d", args->status, 0); } /* Continuous chat with a bi-directional stream. * Returns when connection is closed with a command or a read timeouts. */ void cmd_chat(FILE *stream, int cmdbuf_size) { char *cmdbuf = malloc(cmdbuf_size); cmdargs_t args = {0}; args.in = args.out = stream; for(;;) { if (!fgets(cmdbuf, cmdbuf_size, stream)) break; // EOF or timeout if (cmdbuf[strlen(cmdbuf) - 1] != '\n') { cmd_result(&args, 500, "Command line too long"); continue; } cmd_run(&args, cmdbuf); if (args.status == 221) break; // Closing connection } free(cmdbuf); }