diff --git a/mfshell/main.c b/mfshell/main.c index ee0c6ef..b947c37 100644 --- a/mfshell/main.c +++ b/mfshell/main.c @@ -24,6 +24,8 @@ #include #include #include +#include +#include #include "../utils/strings.h" #include "mfshell.h" @@ -32,6 +34,16 @@ static void mfshell_run(mfshell * shell); static void mfshell_parse_commands(mfshell * shell, char *command); +struct mfshell_user_options { + char *username; + char *password; + char *command; + char *server; + char *config; +} mfshell_user_options = { + NULL, NULL, NULL, NULL, NULL +}; + void print_help(const char *cmd) { fprintf(stderr, "A shell to access a MediaFire account.\n"); @@ -59,22 +71,39 @@ void print_help(const char *cmd) fprintf(stderr, "\n"); fprintf(stderr, " %s -c help\n", cmd); fprintf(stderr, "\n"); + fprintf(stderr, + "If arguments are given multiple times, the first one will be\n" + "used.\n"); + fprintf(stderr, "\n"); + fprintf(stderr, + "Arguments given on the commandline will overwrite arguments\n" + "given in the configuration file\n"); + fprintf(stderr, "\n"); + fprintf(stderr, + "The configuration file is parsed as if it was an argument\n" + "string except that linebreaks are allowed between arguments.\n" + "Lines starting with a # (hash) are ignored\n"); } void -parse_argv(int argc, char *const argv[], char **username, - char **password, char **server, char **command) +parse_argv(int argc, char *const argv[]) { static struct option long_options[] = { {"command", required_argument, 0, 'c'}, + {"config", required_argument, 0, 'f'}, {"username", required_argument, 0, 'u'}, {"password", required_argument, 0, 'p'}, {"server", required_argument, 0, 's'}, {"help", no_argument, 0, 'h'}, - {"version", no_argument, 0, 'v'} + {"version", no_argument, 0, 'v'}, + {0, 0, 0, 0} }; int c; + // resetting optind because we want to be able to call getopt_long + // multiple times for the commandline arguments as well as for the + // configuration file + optind = 0; for (;;) { c = getopt_long(argc, argv, "c:u:p:s:hv", long_options, NULL); if (c == -1) @@ -82,17 +111,24 @@ parse_argv(int argc, char *const argv[], char **username, switch (c) { case 'c': - *command = strdup(optarg); + if (mfshell_user_options.command == NULL) + mfshell_user_options.command = strdup(optarg); break; case 'u': - *username = strdup(optarg); + if (mfshell_user_options.username == NULL) + mfshell_user_options.username = strdup(optarg); break; case 'p': - *password = strdup(optarg); + if (mfshell_user_options.password == NULL) + mfshell_user_options.password = strdup(optarg); break; case 's': - *server = strdup(optarg); + if (mfshell_user_options.server == NULL) + mfshell_user_options.server = strdup(optarg); break; + case 'f': + if (mfshell_user_options.config == NULL) + mfshell_user_options.config = strdup(optarg); case 'h': print_help(argv[0]); exit(0); @@ -112,56 +148,158 @@ parse_argv(int argc, char *const argv[], char **username, exit(1); } - if (*password != NULL && *username == NULL) { + if (mfshell_user_options.password != NULL && + mfshell_user_options.username == NULL) { fprintf(stderr, "You cannot pass the password without the username\n"); exit(1); } } +static void parse_config_file(FILE *fp) +{ + // read the config file line by line and pass each line to wordexp to + // retain proper quoting + char *line = NULL; + size_t len = 0; + ssize_t read; + wordexp_t p; + int ret, i; + int argc; + char **argv; + + while ((read = getline(&line, &len, fp)) != -1) { + if (line[0] == '#') + continue; + + // replace possible trailing newline by zero + if (line[strlen(line) - 1] == '\n') + line[strlen(line) - 1] = '\0'; + ret = wordexp(line, &p, WRDE_SHOWERR | WRDE_UNDEF); + if (ret != 0) { + switch (ret) { + case WRDE_BADCHAR: + fprintf(stderr, "wordexp: WRDE_BADCHAR\n"); + break; + case WRDE_BADVAL: + fprintf(stderr, "wordexp: WRDE_BADVAL\n"); + break; + case WRDE_CMDSUB: + fprintf(stderr, "wordexp: WRDE_CMDSUB\n"); + break; + case WRDE_NOSPACE: + fprintf(stderr, "wordexp: WRDE_NOSPACE\n"); + break; + case WRDE_SYNTAX: + fprintf(stderr, "wordexp: WRDE_SYNTAX\n"); + break; + } + wordfree(&p); + continue; + } + // prepend a dummy program name so that getopt_long will be able to + // parse it + argc = p.we_wordc + 1; + // allocate one more than argc for trailing NULL + argv = (char **)malloc(sizeof(char*)*(argc+1)); + argv[0] = ""; // dummy program name + for (i = 1; i < argc; i++) + argv[i] = p.we_wordv[i-1]; + argv[argc] = NULL; + parse_argv(argc, argv); + free(argv); + wordfree(&p); + } + free(line); +} + +static void parse_config() +{ + FILE *fp; + char *homedir; + // parse the configuration file + // if a value is not set (it is NULL) then it has not been set by the + // commandline and should be set by the configuration file + + // try set config file first if set + if (mfshell_user_options.config != NULL && + (fp = fopen(mfshell_user_options.config, "r")) != NULL) { + parse_config_file(fp); + fclose(fp); + return; + } + + // try "./.mediafire-tools.conf" second + if ((fp = fopen("./.mediafire-tools.conf", "r")) != NULL) { + parse_config_file(fp); + fclose(fp); + return; + } + + // try "~/.mediafire-tools.conf" third + if ((homedir = getenv("HOME")) == NULL) { + homedir = getpwuid(getuid())->pw_dir; + } + homedir = strdup_printf("%s/.mediafire-tools.conf", homedir); + if ((fp = fopen("./.mediafire-tools.conf", "r")) != NULL) { + parse_config_file(fp); + fclose(fp); + } + free(homedir); +} + int main(int argc, char *const argv[]) { mfshell *shell; - char *server = "www.mediafire.com"; - char *username = NULL; - char *password = NULL; - char *command = NULL; char *auth_cmd; SSL_library_init(); - parse_argv(argc, argv, &username, &password, &server, &command); + parse_argv(argc, argv); + + parse_config(); + + // if server was neither set on the commandline nor in the config, set it + // to the default value + if (mfshell_user_options.server == NULL) { + mfshell_user_options.server = strdup("www.mediafire.com"); + } shell = mfshell_create(35860, "2c6dq0gb2sr8rgsue5a347lzpjnaay46yjazjcjg", - server); + mfshell_user_options.server); - if (username != NULL) { - if (password != NULL) { - auth_cmd = strdup_printf("auth %s %s", username, password); + // if at least username was set, authenticate automatically + if (mfshell_user_options.username != NULL) { + if (mfshell_user_options.password != NULL) { + auth_cmd = strdup_printf("auth %s %s", + mfshell_user_options.username, + mfshell_user_options.password); } else { - auth_cmd = strdup_printf("auth %s", username); + auth_cmd = strdup_printf("auth %s", mfshell_user_options.username); } mfshell_parse_commands(shell, auth_cmd); free(auth_cmd); } - if (command == NULL) { + if (mfshell_user_options.command == NULL) { // begin shell mode mfshell_run(shell); } else { // interpret command - mfshell_parse_commands(shell, command); + mfshell_parse_commands(shell, mfshell_user_options.command); } mfshell_destroy(shell); - if (server != NULL && strcmp("www.mediafire.com", server) != 0) - free(server); - if (username != NULL) - free(username); - if (password != NULL) - free(password); - if (command != NULL) - free(command); + if (mfshell_user_options.server != NULL) + free(mfshell_user_options.server); + if (mfshell_user_options.username != NULL) + free(mfshell_user_options.username); + if (mfshell_user_options.password != NULL) + free(mfshell_user_options.password); + if (mfshell_user_options.command != NULL) + free(mfshell_user_options.command); + if (mfshell_user_options.config != NULL) + free(mfshell_user_options.config); return 0; } diff --git a/tests/valgrind.sh b/tests/valgrind.sh index df46617..505eb1e 100755 --- a/tests/valgrind.sh +++ b/tests/valgrind.sh @@ -17,4 +17,8 @@ esac cmd="valgrind --suppressions="${source_dir}/valgrind.supp" --fullpath-after="$source_dir" --tool=memcheck --leak-check=yes --show-reachable=yes --num-callers=20 --track-fds=yes --track-origins=yes" -$cmd "${binary_dir}/mediafire-shell" -c "help; whoami" +if [ ! -f "./.mediafire-tools.conf" -a ! -f "~/.mediafire-tools.conf" ]; then + echo "no configuration file found" >&2 + exit 1 +fi +$cmd "${binary_dir}/mediafire-shell" -c "help; ls; whoami"