diff -Naur util-linux-2.12p/mount/lomount.c util-linux-2.12p.new/mount/lomount.c --- util-linux-2.12p/mount/lomount.c 2005-02-10 14:46:23 +0300 +++ util-linux-2.12p.new/mount/lomount.c 2005-02-10 14:47:08 +0300 @@ -17,6 +17,10 @@ #include #include #include +#include +#include +#include +#include #include "loop.h" #include "lomount.h" @@ -26,6 +30,7 @@ extern int verbose; extern char *progname; extern char *xstrdup (const char *s); /* not: #include "sundries.h" */ +extern void *xmalloc (size_t size); /* idem */ extern void error (const char *fmt, ...); /* idem */ #ifdef LOOP_SET_FD @@ -190,52 +195,121 @@ return 0; } -/* - * A function to read the passphrase either from the terminal or from - * an open file descriptor. - */ -static char * -xgetpass(int pfd, const char *prompt) { - char *pass; - int buflen, i; - - if (pfd < 0) /* terminal */ - return getpass(prompt); +static int +digits_only(const char *s) { + while (*s) + if (!isdigit(*s++)) + return 0; + return 1; +} - pass = NULL; - buflen = 0; - for (i=0; ; i++) { - if (i >= buflen-1) { - /* we're running out of space in the buffer. - * Make it bigger: */ - char *tmppass = pass; - buflen += 128; - pass = realloc(tmppass, buflen); - if (pass == NULL) { - /* realloc failed. Stop reading. */ - error("Out of memory while reading passphrase"); - pass = tmppass; /* the old buffer hasn't changed */ +/* A function to check the encryption parameters against /proc/crypto. * + * Returns 1 if everything checks out, 0 if there's any problem. * + * The purpose of this function is not so much to verify the parameters * + * before setting up the loop device; that task is made difficult by * + * the possibility of loadable encryption modules and the necessity of * + * falling back to the old (kerneli.org) CryptoAPI. Rather the intent * + * is to come up with a more useful error message after the setup * + * fails. Thus we return 1 in the event that some system error prevents * + * us from getting the info we want from /proc/crypto. */ +static int +check_crypto(struct loop_info64 *loopinfo64) { + char *s = xmalloc(LINE_MAX), *name = xmalloc(LINE_MAX), + cipher_name[LO_NAME_SIZE+1]; + int cipher_found = 0, min_size = 0, max_size = 0, retval; + FILE *fp; + struct stat st; + + if (stat("/proc/crypto", &st) == -1) { + retval = 1; + goto end; + } else if (S_ISDIR(st.st_mode)) { + /* Hm... must be using the old CryptoAPI. */ + retval = 1; + goto end; + } else if ((fp = fopen("/proc/crypto", "r")) == NULL) { + retval = 1; + goto end; + } + + xstrncpy(cipher_name, loopinfo64->lo_crypt_name, LO_NAME_SIZE); + /* Chop off the cipher mode (ie. everything after the dash) */ + cipher_name[strcspn(cipher_name, "-")] = '\0'; + + while (fgets(s, LINE_MAX, fp) != NULL) { + if (sscanf(s, "name : %s", name) > 0) { + if (strcmp(name, cipher_name) == 0) + cipher_found = 1; + else if (cipher_found) break; - } + } else { + sscanf(s, "min keysize : %d", &min_size); + sscanf(s, "max keysize : %d", &max_size); } - if (read(pfd, pass+i, 1) != 1 || - pass[i] == '\n' || pass[i] == 0) - break; } + fclose(fp); - if (pass == NULL) - return ""; - - pass[i] = 0; - return pass; + if (!cipher_found) { + fprintf(stderr, _("Invalid cipher \"%s\"\n"), cipher_name); + retval = 0; + goto end; + } else if (loopinfo64->lo_encrypt_key_size < min_size || + loopinfo64->lo_encrypt_key_size > max_size) { + fprintf(stderr, _("Invalid key length (%d) for %s cipher\n"), + loopinfo64->lo_encrypt_key_size, cipher_name); + retval = 0; + goto end; + } + + retval = 1; +end: + free(s); + free(name); + return retval; } +/* Same thing, for the old CryptoAPI. */ static int -digits_only(const char *s) { - while (*s) - if (!isdigit(*s++)) - return 0; - return 1; +check_crypto_old(struct loop_info *loopinfo) { + char *s = xmalloc(LINE_MAX), *name = xmalloc(PATH_MAX+1), + cipher_name[LO_NAME_SIZE+1]; + int mask = 0, retval; + FILE *fp; + struct stat st; + + if (stat("/proc/crypto/cipher", &st) == -1) { + retval = 1; + goto end; + } else if (!S_ISDIR(st.st_mode)) { + retval = 1; + goto end; + } + + xstrncpy(cipher_name, loopinfo->lo_name, LO_NAME_SIZE); + snprintf(name, PATH_MAX+1, "/proc/crypto/cipher/%s", cipher_name); + if ((fp = fopen(name, "r")) == NULL) { + fprintf(stderr, _("Invalid cipher \"%s\"\n"), cipher_name); + retval = 0; + goto end; + } + + while (fgets(s, LINE_MAX, fp) != NULL) { + sscanf(s, "keysize_mask : %i", &mask); + } + fclose(fp); + + if (!(mask & (1 << (loopinfo->lo_encrypt_key_size - 1)))) { + fprintf(stderr, _("Invalid key length (%d) for %s cipher\n"), + loopinfo->lo_encrypt_key_size, cipher_name); + retval = 0; + goto end; + } + + retval = 1; +end: + free(s); + free(name); + return retval; } int @@ -269,9 +343,40 @@ if (digits_only(encryption)) { loopinfo64.lo_encrypt_type = atoi(encryption); } else { + regex_t keysize_re; + regmatch_t keysize_rm; + int rerror; + char *rerror_buf, *encryption_dup = xstrdup(encryption); + loopinfo64.lo_encrypt_type = LO_CRYPT_CRYPTOAPI; + + if ((rerror = regcomp(&keysize_re, "-[[:digit:]]+$", + REG_EXTENDED)) != 0) { + fprintf(stderr, _("RE error: ")); + rerror_buf = xmalloc(LINE_MAX+1); + regerror(rerror, &keysize_re, rerror_buf, LINE_MAX); + fprintf(stderr, "%s\n", rerror_buf); + free(rerror_buf); + return -1; + } + rerror = regexec(&keysize_re, encryption_dup, + 1, &keysize_rm, 0); + regfree(&keysize_re); + if (rerror) { + fprintf(stderr, + _("You must specify a key size (in bits) " + "for use with CryptoAPI encryption.\n")); + return -1; + } + + /* convert the key size from #bits to #bytes */ + loopinfo64.lo_encrypt_key_size = + atoi(encryption_dup + keysize_rm.rm_so + 1) / 8; + /* now cut off the key size */ + encryption_dup[keysize_rm.rm_so] = '\0'; snprintf(loopinfo64.lo_crypt_name, LO_NAME_SIZE, - "%s", encryption); + "%s", encryption_dup); + free(encryption_dup); } } @@ -294,15 +399,28 @@ case LO_CRYPT_NONE: loopinfo64.lo_encrypt_key_size = 0; break; - case LO_CRYPT_XOR: - pass = getpass(_("Password: ")); - goto gotpass; default: - pass = xgetpass(pfd, _("Password: ")); - gotpass: memset(loopinfo64.lo_encrypt_key, 0, LO_KEY_SIZE); - xstrncpy(loopinfo64.lo_encrypt_key, pass, LO_KEY_SIZE); - memset(pass, 0, strlen(pass)); + if (pfd == -1) { + pass = getpass(_("Password: ")); + xstrncpy(loopinfo64.lo_encrypt_key, pass, LO_KEY_SIZE); + memset(pass, 0, strlen(pass)); + } else { + /* If we're reading from an extenral program, * + * odds are good that a SIGCHLD will interrupt * + * this read(), and ruin our whole day. So we * + * must block it. */ + sigset_t ss, oss; + sigemptyset(&ss); + sigaddset(&ss, SIGCHLD); + sigprocmask(SIG_BLOCK, &ss, &oss); + if (read(pfd, loopinfo64.lo_encrypt_key, + LO_KEY_SIZE) == -1) { + perror("read"); + fprintf(stderr, _("Error reading encryption key, exiting\n")); + } + sigprocmask(SIG_SETMASK, &oss, NULL); + } loopinfo64.lo_encrypt_key_size = LO_KEY_SIZE; } @@ -317,20 +435,40 @@ struct loop_info loopinfo; int errsv = errno; + if (errno == EINVAL && + loopinfo64.lo_encrypt_type == LO_CRYPT_CRYPTOAPI && + !check_crypto(&loopinfo64)) { + fprintf(stderr, + _("Error in crypto parameters, exiting\n")); + goto fail; + } + + i = loop_info64_to_old(&loopinfo64, &loopinfo); if (i) { errno = errsv; perror("ioctl: LOOP_SET_STATUS64"); } else { i = ioctl(fd, LOOP_SET_STATUS, &loopinfo); - if (i) - perror("ioctl: LOOP_SET_STATUS"); + if (i) { + errsv = errno; + if (errno == EINVAL && + loopinfo.lo_encrypt_type == LO_CRYPT_CRYPTOAPI && + !check_crypto_old(&loopinfo)) { + fprintf(stderr, + _("Error in crypto parameters, exiting\n")); + } else { + errno = errsv; + perror("ioctl: LOOP_SET_STATUS"); + } + } } memset(&loopinfo, 0, sizeof(loopinfo)); } memset(&loopinfo64, 0, sizeof(loopinfo64)); if (i) { +fail: ioctl (fd, LOOP_CLR_FD, 0); close (fd); return 1; @@ -430,6 +568,23 @@ return t; } +void * +xmalloc (size_t size) { + void *t; + + if (size == 0) + return NULL; + + t = malloc (size); + if (t == NULL) { + fprintf(stderr, _("not enough memory")); + exit(1); + } + + return t; +} + + void error (const char *fmt, ...) { va_list args; diff -Naur util-linux-2.12p/mount/mount.8 util-linux-2.12p.new/mount/mount.8 --- util-linux-2.12p/mount/mount.8 2004-12-20 01:30:14 +0300 +++ util-linux-2.12p.new/mount/mount.8 2005-02-10 14:46:41 +0300 @@ -1928,6 +1928,10 @@ .BR mke2fs (8), .BR tune2fs (8), .BR losetup (8) +You can also use the +.BR keygen +option to have mount call an external program from, which it will read the +encryption key. Arguments to this program can be given, separated by semicolons. .SH BUGS It is possible for a corrupted file system to cause a crash. .PP diff -Naur util-linux-2.12p/mount/mount.c util-linux-2.12p.new/mount/mount.c --- util-linux-2.12p/mount/mount.c 2005-02-10 14:46:23 +0300 +++ util-linux-2.12p.new/mount/mount.c 2005-02-10 14:46:41 +0300 @@ -168,7 +168,7 @@ }; static const char *opt_loopdev, *opt_vfstype, *opt_offset, *opt_encryption, - *opt_speed, *opt_comment; + *opt_speed, *opt_comment, *opt_keygen; static struct string_opt_map { char *tag; @@ -181,6 +181,7 @@ { "encryption=", 0, &opt_encryption }, { "speed=", 0, &opt_speed }, { "comment=", 1, &opt_comment }, + { "keygen=", 0, &opt_keygen }, { NULL, 0, NULL } }; @@ -607,7 +608,7 @@ *type = opt_vfstype; } - *loop = ((*flags & MS_LOOP) || *loopdev || opt_offset || opt_encryption); + *loop = ((*flags & MS_LOOP) || *loopdev || opt_offset || opt_encryption || opt_keygen); *loopfile = *spec; if (*loop) { @@ -617,6 +618,12 @@ printf(_("mount: skipping the setup of a loop device\n")); } else { int loopro = (*flags & MS_RDONLY); + /* Extra args to the keygen program. Right now there are 2: * + * - the looped file * + * - the encryption type used */ + char *keygen_args[] = {*loopfile, opt_encryption}; + const int _n_keygen_args = 2; + if (!*loopdev || !**loopdev) *loopdev = find_unused_loop_device(); @@ -625,6 +632,8 @@ if (verbose) printf(_("mount: going to use the loop device %s\n"), *loopdev); offset = opt_offset ? strtoull(opt_offset, NULL, 0) : 0; + if (opt_keygen) + pfd = use_keygen_prog(opt_keygen, keygen_args, _n_keygen_args); if (set_loop(*loopdev, *loopfile, offset, opt_encryption, pfd, &loopro)) { if (verbose) diff -Naur util-linux-2.12p/mount/sundries.c util-linux-2.12p.new/mount/sundries.c --- util-linux-2.12p/mount/sundries.c 2004-12-21 22:12:31 +0300 +++ util-linux-2.12p.new/mount/sundries.c 2005-02-10 14:46:41 +0300 @@ -12,6 +12,8 @@ #include #include #include /* for MNTTYPE_SWAP */ +#include +#include #include "fstab.h" #include "sundries.h" #include "realpath.h" @@ -246,3 +248,100 @@ return xstrdup(path); } + +static volatile int keygen_wait = 1; + +/* Handle a SIGCHLD -- wait for the child process */ +static void +child_handler (int i) { + int status; + if (keygen_wait && wait(&status) != -1) { + keygen_wait = 0; + if (WEXITSTATUS(status) != 0) + exit(WEXITSTATUS(status)); + } +} + +/* Make sure we clean up after the child */ +static void +child_cleanup (void) { + /* "Clean up" means wait. So we let child_handler do all the work */ + while (keygen_wait) + sleep(1); +} + +/* Split a string into pieces, using delim as the delimiter. * + * Returns the number of pieces. */ +static int +split_args (char *args[], const char *str, const char *delim, int nargs) { + int i=0; + char *s = xstrdup(str); + args[0] = strtok(s, delim); + if (args[0] == NULL) + return 0; + + while (++i < nargs) { + if ((args[i] = strtok(NULL, delim)) == NULL) + break; + } + + return i; +} + +#define KEYGEN_MAX_ARGS 64 /* more than anyone will need, right? */ + +/* Call an external program to give us the encryption key for an * + * encrypted device. We split the string s into a command and args on * + * semicolons ('cuz you can't put spaces in the fs_mntopts field), then * + * add some specific args (eg. the looped file or device, the * + * encryption method used; check the caller to see the actual list). * + * Returns a file descriptor from which we read the key. */ +int +use_keygen_prog (const char *s, const char *addl_args[], int naddl_args) { + int fd[2]; + pid_t keygen_pid; + struct sigaction sa; + sa.sa_handler = child_handler; + sa.sa_flags = SA_NOCLDSTOP; + + if (pipe(fd) == -1) { + perror("pipe"); + exit(EX_SYSERR); + } + if (sigaction(SIGCHLD, &sa, NULL) == -1) { + perror("sigaction"); + exit(EX_SYSERR); + } + if ((keygen_pid = fork()) == -1) { + perror("fork"); + exit(EX_SYSERR); + } else if (keygen_pid) { /* parent */ + atexit(child_cleanup); + close(fd[1]); + return fd[0]; + } else { /* child */ + char *args[KEYGEN_MAX_ARGS+1]; + int i, n = split_args(args, s, ";", KEYGEN_MAX_ARGS - naddl_args); + if (!n) { + fprintf(stderr, _("Invalid keygen program, exiting\n")); + exit(EX_USAGE); + } + for(i=0; i < naddl_args && n+i < KEYGEN_MAX_ARGS; i++) + args[n+i] = (char *) addl_args[i]; + args[n+i] = NULL; + + close(fd[0]); + if (dup2(fd[1], STDOUT_FILENO) == -1) { + perror("dup2"); + exit(EX_SYSERR); + } + setuid(getuid()); /* set euid to ruid */ + if (execvp(args[0], args) == -1) { + perror(args[0]); + exit(EX_USAGE); + } + } + + return 0; /* so gcc will shut up */ +} + diff -Naur util-linux-2.12p/mount/sundries.h util-linux-2.12p.new/mount/sundries.h --- util-linux-2.12p/mount/sundries.h 2004-12-22 00:59:59 +0300 +++ util-linux-2.12p.new/mount/sundries.h 2005-02-10 14:46:41 +0300 @@ -25,6 +25,7 @@ void error (const char *fmt, ...); int matching_type (const char *type, const char *types); int matching_opts (const char *options, const char *test_opts); +int use_keygen_prog (const char *s, const char *addl_args[], int naddl_args); void *xmalloc (size_t size); char *xstrdup (const char *s); char *xstrndup (const char *s, int n);