diff options
Diffstat (limited to 'src/encryption.c')
| -rw-r--r-- | src/encryption.c | 349 |
1 files changed, 113 insertions, 236 deletions
diff --git a/src/encryption.c b/src/encryption.c index b75a119..a01d4ff 100644 --- a/src/encryption.c +++ b/src/encryption.c | |||
| @@ -15,8 +15,7 @@ | |||
| 15 | #include <fcntl.h> | 15 | #include <fcntl.h> |
| 16 | #include <stdio.h> | 16 | #include <stdio.h> |
| 17 | 17 | ||
| 18 | 18 | #if ___VXGG___ALWAYS_CHECK_LIBSODIUM___ > 0 | |
| 19 | #if defined ___VXGG___ALWAYS_CHECK_LIBSODIUM___ && ___VXGG___ALWAYS_CHECK_LIBSODIUM___ > 0 | ||
| 20 | void naclfaildefault(void *none) { | 19 | void naclfaildefault(void *none) { |
| 21 | none = none; // Makes gcc happy | 20 | none = none; // Makes gcc happy |
| 22 | if(___VXGG___VERBOSE_ERRORS___) | 21 | if(___VXGG___VERBOSE_ERRORS___) |
| @@ -50,32 +49,17 @@ void checksodium(void) { | |||
| 50 | #if ___VXGG___ALWAYS_CHECK_LIBSODIUM___ > 0 | 49 | #if ___VXGG___ALWAYS_CHECK_LIBSODIUM___ > 0 |
| 51 | checksodiumcb(NULL, NULL); | 50 | checksodiumcb(NULL, NULL); |
| 52 | #else | 51 | #else |
| 52 | |||
| 53 | if(sodium_init() < 0) { | 53 | if(sodium_init() < 0) { |
| 54 | if(___VXGG___VERBOSE_ERRORS___) | 54 | errno = ENOTSUP; |
| 55 | error(1, ENOTSUP, "Couldn't initialize sodium for some reason. Quitting..."); | 55 | XALLOC_EXIT("Couldn't initialize sodium for some reason. Quitting..."); |
| 56 | exit(EXIT_FAILURE); | ||
| 57 | } | 56 | } |
| 57 | |||
| 58 | #endif | 58 | #endif |
| 59 | 59 | ||
| 60 | return; | 60 | return; |
| 61 | } | 61 | } |
| 62 | 62 | ||
| 63 | // To encrypt: | ||
| 64 | // 1- Create a temp file with the correct name in the root folder of the partition being encrypted -- | ||
| 65 | // 1.1- Detect the partition and find the root folder -- DONE || NOT NECESSARY | ||
| 66 | // 1.2- Create the temp file -- DONE | ||
| 67 | // 2- Encrypt the file's contents to the temp file -- | ||
| 68 | // 2.1- Open the file -- | ||
| 69 | // 2.2- Stream the file's contents into some encryption algo -- | ||
| 70 | // 2.2.1- Pick which encryption algo to use -- | ||
| 71 | // 2.2.2- Generate a key -- | ||
| 72 | // 2.2.2.1- Create a password to derrive a key from -- DONE | ||
| 73 | // 2.3- Pipe the output of the encryption into the temp file -- | ||
| 74 | // 3- Once the file has been encrypted, hard link it back to the original location, with the right name -- | ||
| 75 | // 4- Delete the original file -- | ||
| 76 | // 5- Delete the temp file -- | ||
| 77 | |||
| 78 | |||
| 79 | int maketmp(const char * const dest) { | 63 | int maketmp(const char * const dest) { |
| 80 | return open(dest, (O_TMPFILE | O_WRONLY | O_CLOEXEC | O_SYNC), (S_IRUSR | S_IWUSR)); | 64 | return open(dest, (O_TMPFILE | O_WRONLY | O_CLOEXEC | O_SYNC), (S_IRUSR | S_IWUSR)); |
| 81 | } | 65 | } |
| @@ -85,8 +69,6 @@ int encrypttotmp(const char * const target, const char * const output, const uns | |||
| 85 | checksodium(); | 69 | checksodium(); |
| 86 | #endif | 70 | #endif |
| 87 | 71 | ||
| 88 | unsigned char header[crypto_secretstream_xchacha20poly1305_HEADERBYTES]; | ||
| 89 | crypto_secretstream_xchacha20poly1305_state state; | ||
| 90 | int fd = -1, tfd = -1; | 72 | int fd = -1, tfd = -1; |
| 91 | 73 | ||
| 92 | // Open the target file | 74 | // Open the target file |
| @@ -97,74 +79,124 @@ int encrypttotmp(const char * const target, const char * const output, const uns | |||
| 97 | char *targetdir = xdirname(output); | 79 | char *targetdir = xdirname(output); |
| 98 | tfd = maketmp(targetdir); | 80 | tfd = maketmp(targetdir); |
| 99 | free(targetdir); | 81 | free(targetdir); |
| 100 | if(tfd < 0) | 82 | if(tfd < 0) { |
| 101 | return -2; | ||
| 102 | |||
| 103 | // Initialize crypto stuff | ||
| 104 | if(crypto_secretstream_xchacha20poly1305_init_push(&state, header, key) < 0) { | ||
| 105 | close(fd); | 83 | close(fd); |
| 106 | close(tfd); | 84 | return -1; |
| 107 | return -3; | ||
| 108 | } | ||
| 109 | |||
| 110 | ssize_t bytesread = -1; | ||
| 111 | unsigned char *buf = malloc(CHUNK_SIZE + 1); | ||
| 112 | unsigned char *cbuf = malloc((CHUNK_SIZE + 1) + crypto_secretstream_xchacha20poly1305_ABYTES); | ||
| 113 | if(!buf || !cbuf) { | ||
| 114 | close(fd); | ||
| 115 | close(tfd); | ||
| 116 | if(buf) | ||
| 117 | free(buf); | ||
| 118 | if(cbuf) | ||
| 119 | free(cbuf); | ||
| 120 | |||
| 121 | return -4; | ||
| 122 | } | ||
| 123 | |||
| 124 | // Write the header to the temp file before encrypting anything | ||
| 125 | if(writewholebuffer(tfd, header, sizeof(header)) < 0) { | ||
| 126 | close(fd); | ||
| 127 | close(tfd); | ||
| 128 | free(buf); | ||
| 129 | free(cbuf); | ||
| 130 | return -5; | ||
| 131 | } | 85 | } |
| 132 | 86 | ||
| 133 | // Read a chunk at a time, encrypt it, then push it to the temp file | 87 | FILE *src, *dst; |
| 134 | unsigned long long clen = 0; | 88 | if(!(src = fdopen(fd, "rb"))) |
| 135 | while((bytesread = read(fd, buf, CHUNK_SIZE)) >= 0) { | 89 | ERROR(1, errno, "Couldn't open \"%s\"", , target); |
| 136 | crypto_secretstream_xchacha20poly1305_push(&state, cbuf, &clen, buf, bytesread, NULL, 0, (bytesread > 0) ? 0 : crypto_secretstream_xchacha20poly1305_TAG_FINAL); | 90 | if(!(dst = fdopen(tfd, "wb"))) |
| 137 | if(writewholebuffer(tfd, cbuf, clen) < 0) { | 91 | ERROR(1, errno, "Couldn't open \"%s\"", , output); |
| 138 | close(fd); | 92 | if(encryptToFile(src, dst, key) < 0) |
| 139 | close(tfd); | 93 | ERROR(1, ENOTRECOVERABLE, "I don't even have a way to cause an error here. How did you do it?",); |
| 140 | free(buf); | ||
| 141 | free(cbuf); | ||
| 142 | return -6; | ||
| 143 | } | ||
| 144 | |||
| 145 | if(bytesread == 0) | ||
| 146 | break; | ||
| 147 | } | ||
| 148 | free(buf); | ||
| 149 | free(cbuf); | ||
| 150 | close(fd); | ||
| 151 | if(bytesread < 0) { | ||
| 152 | close(tfd); | ||
| 153 | return -7; | ||
| 154 | } | ||
| 155 | 94 | ||
| 156 | // Link the temp file into the system | 95 | // Link the temp file into the system |
| 157 | char *path = NULL; | 96 | char *path = NULL; |
| 158 | asprintf(&path, "/proc/self/fd/%d", tfd); | 97 | asprintf(&path, "/proc/self/fd/%d", tfd); |
| 159 | if(!path) { | 98 | if(!path) |
| 160 | close(tfd); | 99 | return -1; |
| 161 | return -8; | 100 | |
| 162 | } | ||
| 163 | remove(output); // Make sure an old version isn't sticking around | 101 | remove(output); // Make sure an old version isn't sticking around |
| 164 | linkat(AT_FDCWD, path, AT_FDCWD, output, AT_SYMLINK_FOLLOW); | 102 | linkat(AT_FDCWD, path, AT_FDCWD, output, AT_SYMLINK_FOLLOW); |
| 165 | 103 | ||
| 166 | free(path); | 104 | free(path); |
| 167 | close(tfd); | 105 | fclose(dst); |
| 106 | fclose(src); | ||
| 107 | // fclose alco closes fd and tfd, as fdopen does not dup the file descriptors | ||
| 108 | |||
| 109 | return 0; | ||
| 110 | } | ||
| 111 | |||
| 112 | int encrypttofile(FILE *dst, FILE *src, unsigned char key[crypto_secretstream_xchacha20poly1305_KEYBYTES]) { | ||
| 113 | unsigned char buf[CHUNKSIZE], cbuf[CHUNKSIZE + crypto_secretstream_xchacha20poly1305_ABYTES]; | ||
| 114 | unsigned char header[crypto_secretstream_xchacha20poly1305_HEADERBYTES]; | ||
| 115 | crypto_secretstream_xchacha20poly1305_state state; | ||
| 116 | unsigned long long cbuflen; | ||
| 117 | unsigned char tag; | ||
| 118 | size_t bytesread; | ||
| 119 | FILE *dst, *src; | ||
| 120 | int eof; | ||
| 121 | |||
| 122 | #if ___VXGG___ALWAYS_CHECK_LIBSODIUM___ > 0 | ||
| 123 | checksodium(); | ||
| 124 | #endif | ||
| 125 | |||
| 126 | if(!src) | ||
| 127 | RETURNWERR(EINVAL, -1); | ||
| 128 | if(!dst) | ||
| 129 | RETURNWERR(EINVAL, -1); | ||
| 130 | if(!key) | ||
| 131 | RETURNWERR(EINVAL, -1); | ||
| 132 | |||
| 133 | // Write the header | ||
| 134 | crypto_secretstream_xchacha20poly1305_init_push(&state, header, key); | ||
| 135 | if(fwrite(header, 1, sizeof(header), dst) < sizeof(header)) | ||
| 136 | if(ferror(dst)) | ||
| 137 | ERROR(1, errno, "Could not write header to \"%s\"",); | ||
| 138 | |||
| 139 | // Encrypt each chunk | ||
| 140 | do { | ||
| 141 | if((bytesread = fread(buf, 1, sizeof(buf), src)) < sizeof(buf)) | ||
| 142 | if(ferror(src)) | ||
| 143 | ERROR(1, errno, "Could not read from source \"%s\"",); | ||
| 144 | eof = feof(src); | ||
| 145 | tag = eof ? crypto_secretstream_xchacha20poly1305_TAG_FINAL : 0; | ||
| 146 | |||
| 147 | crypto_secretstream_xchacha20poly1305_push(&state, cbuf, &cbuflen, buf, bytesread, NULL, 0, tag); | ||
| 148 | if(fwrite(cbuf, 1, (size_t)cbuflen, dst) < (size_t)cbuflen) | ||
| 149 | if(ferror(dst)) | ||
| 150 | ERROR(1, errno, "Could not write to target \"%s\"",); | ||
| 151 | } while (!eof); | ||
| 152 | |||
| 153 | return 0; | ||
| 154 | } | ||
| 155 | |||
| 156 | int decrypttofile(FILE *dst, FILE *src, unsigned char key[crypto_secretstream_xchacha20poly1305_KEYBYTES]) { | ||
| 157 | unsigned char cbuf[CHUNKSIZE + crypto_secretstream_xchacha20poly1305_ABYTES], buf[CHUNKSIZE]; | ||
| 158 | unsigned char header[crypto_secretstream_xchacha20poly1305_HEADERBYTES]; | ||
| 159 | crypto_secretstream_xchacha20poly1305_state state; | ||
| 160 | unsigned long long buflen; | ||
| 161 | unsigned char tag; | ||
| 162 | size_t bytesread; | ||
| 163 | FILE *dst, *src; | ||
| 164 | int eof; | ||
| 165 | |||
| 166 | |||
| 167 | if(!src) | ||
| 168 | RETURNWERR(EINVAL, -1); | ||
| 169 | if(!dst) | ||
| 170 | RETURNWERR(EINVAL, -1); | ||
| 171 | if(!key) | ||
| 172 | RETURNWERR(EINVAL, -1); | ||
| 173 | |||
| 174 | // Read the header | ||
| 175 | if(fread(header, 1, sizeof(header), src) < sizeof(header)) | ||
| 176 | if(ferror(src)) | ||
| 177 | ERROR(1, errno, "Couldn't read header", ); | ||
| 178 | |||
| 179 | // Make sure the header isn't fuckey | ||
| 180 | if(crypto_secretstream_xchacha20poly1305_init_pull(&state, header, key) != 0) | ||
| 181 | ERROR(1, errno, "Incomplete header", ); | ||
| 182 | |||
| 183 | // Decrypt each chunk | ||
| 184 | do { | ||
| 185 | if((bytesread = fread(cbuf, 1, sizeof(cbuf), src)) < sizeof(cbuf)) | ||
| 186 | if(ferror(src)) | ||
| 187 | ERROR(1, errno, "Ran into problem reading for decryption", ); | ||
| 188 | eof = feof(src); | ||
| 189 | |||
| 190 | if (crypto_secretstream_xchacha20poly1305_pull(&state, buf, &buflen, &tag, cbuf, bytesread, NULL, 0) != 0) | ||
| 191 | ERROR(1, errno, "Corrupted chunk", ); | ||
| 192 | |||
| 193 | if(tag == crypto_secretstream_xchacha20poly1305_TAG_FINAL && !eof) | ||
| 194 | ERROR(1, errno, "End of stream before end of file", ); | ||
| 195 | if(eof && tag != crypto_secretstream_xchacha20poly1305_TAG_FINAL) | ||
| 196 | ERROR(1, errno, "End of file before end of stream", ); | ||
| 197 | |||
| 198 | fwrite(buf, 1, (size_t)buflen, dst); | ||
| 199 | } while(! eof); | ||
| 168 | 200 | ||
| 169 | return 0; | 201 | return 0; |
| 170 | } | 202 | } |
| @@ -211,9 +243,8 @@ void* xsodium_malloc(size_t size) { | |||
| 211 | return mem; | 243 | return mem; |
| 212 | } | 244 | } |
| 213 | 245 | ||
| 214 | #define TESTING | ||
| 215 | #ifdef TESTING | ||
| 216 | 246 | ||
| 247 | #ifdef TESTING | ||
| 217 | int main(void) { | 248 | int main(void) { |
| 218 | /*// Example code for creating a temp file, writing to it, then linking it back into the fs | 249 | /*// Example code for creating a temp file, writing to it, then linking it back into the fs |
| 219 | const char *dir = ".", *testmsg = "we do a little testing\n"; | 250 | const char *dir = ".", *testmsg = "we do a little testing\n"; |
| @@ -284,160 +315,6 @@ int main(void) { | |||
| 284 | 315 | ||
| 285 | //*/// | 316 | //*/// |
| 286 | 317 | ||
| 287 | //*// Example code for generating a key from a password and encrypting a test file | ||
| 288 | |||
| 289 | const char *dir = ".", *fname = "toBeEncrypted.test.txt", *pass = "this is a password"; | ||
| 290 | char *path = NULL, *message = NULL, *efname = NULL; | ||
| 291 | int fd = -1; | ||
| 292 | |||
| 293 | // Message is just going to be a bunch of words from the dictionary | ||
| 294 | checksodium(); | ||
| 295 | if(genpassword(&message, 1000) < 0) | ||
| 296 | error(1, errno, "Could not generate message to be encrypted"); | ||
| 297 | if(!message) | ||
| 298 | abort(); | ||
| 299 | |||
| 300 | // Get the temp file and write the message into it | ||
| 301 | if((fd = maketmp(dir)) < 0) | ||
| 302 | error(1, errno, "Could not make temp file to write to"); | ||
| 303 | |||
| 304 | // Write to the file and link it into the system | ||
| 305 | remove(fname); // Make sure the new file can be linked into | ||
| 306 | write(fd, message, strlen(message)); | ||
| 307 | asprintf(&path, "/proc/self/fd/%d", fd); | ||
| 308 | if(!path) | ||
| 309 | abort(); | ||
| 310 | linkat(AT_FDCWD, path, AT_FDCWD, fname, AT_SYMLINK_FOLLOW); | ||
| 311 | |||
| 312 | free(path); | ||
| 313 | free(message); | ||
| 314 | close(fd); | ||
| 315 | |||
| 316 | |||
| 317 | // Time for encryption | ||
| 318 | unsigned char header[crypto_secretstream_xchacha20poly1305_HEADERBYTES]; | ||
| 319 | unsigned char key[crypto_secretstream_xchacha20poly1305_KEYBYTES]; | ||
| 320 | crypto_secretstream_xchacha20poly1305_state state; | ||
| 321 | unsigned char salt[crypto_pwhash_SALTBYTES]; | ||
| 322 | |||
| 323 | // Generate a salt for derriving the key | ||
| 324 | randombytes_buf(salt, sizeof(salt)); | ||
| 325 | |||
| 326 | // Derrive the key from the password | ||
| 327 | if(crypto_pwhash(key, sizeof(key), pass, strlen(pass), salt, crypto_pwhash_OPSLIMIT_MODERATE, crypto_pwhash_MEMLIMIT_MODERATE, crypto_pwhash_ALG_DEFAULT) != 0) | ||
| 328 | error(1, ENOMEM, "Not enough memory to generate a key"); | ||
| 329 | |||
| 330 | // Initialize the encryption stream's state | ||
| 331 | crypto_secretstream_xchacha20poly1305_init_push(&state, header, key); | ||
| 332 | |||
| 333 | |||
| 334 | // Deal with getting the file | ||
| 335 | if((fd = open(fname, O_RDONLY)) < 0) | ||
| 336 | error(1, errno, "Could not open test file to encrypt"); | ||
| 337 | int tfd = -1; | ||
| 338 | if((tfd = maketmp(dir)) < 0) // Only because linking temp files is being annoying | ||
| 339 | error(1, errno, "Could not open temp file for encryption"); | ||
| 340 | |||
| 341 | // Read chunks of the file, encrypt them, then write them into the tmp file | ||
| 342 | ssize_t bytesread = -1; | ||
| 343 | // const int CHUNK_SIZE = 4096; | ||
| 344 | unsigned char *buf = xcalloc(CHUNK_SIZE + 1, sizeof(*buf)); | ||
| 345 | unsigned char *cbuf = xcalloc((CHUNK_SIZE + 1) + crypto_secretstream_xchacha20poly1305_ABYTES, sizeof(*cbuf)); | ||
| 346 | |||
| 347 | // TODO: WRITE HEADER TO FILE SO IT'S A VALID STREAM AND NOT BULLSHIT LIKE IT IS NOW | ||
| 348 | if(writewholebuffer(tfd, header, sizeof(header)) < 0) | ||
| 349 | error(1, errno, "Could not write header to file"); | ||
| 350 | |||
| 351 | while((bytesread = read(fd, buf, CHUNK_SIZE)) >= 0) { | ||
| 352 | crypto_secretstream_xchacha20poly1305_push(&state, cbuf, NULL, buf, bytesread, NULL, 0, (bytesread > 0) ? 0 : crypto_secretstream_xchacha20poly1305_TAG_FINAL); | ||
| 353 | if(writewholebuffer(tfd, cbuf, bytesread) < 0) | ||
| 354 | error(1, errno, "write() error"); | ||
| 355 | |||
| 356 | if(bytesread == 0) | ||
| 357 | break; | ||
| 358 | } | ||
| 359 | if(bytesread < 0) | ||
| 360 | error(1, errno, "read() error"); | ||
| 361 | |||
| 362 | close(fd); | ||
| 363 | |||
| 364 | asprintf(&efname, "%s.enc", fname); | ||
| 365 | asprintf(&path, "/proc/self/fd/%d", tfd); | ||
| 366 | if(!path || !efname) | ||
| 367 | abort(); | ||
| 368 | remove(efname); // Make sure an old version isn't sticking around | ||
| 369 | linkat(AT_FDCWD, path, AT_FDCWD, efname, AT_SYMLINK_FOLLOW); | ||
| 370 | |||
| 371 | close(tfd); | ||
| 372 | free(path); | ||
| 373 | free(buf); | ||
| 374 | free(cbuf); | ||
| 375 | |||
| 376 | //*/// | ||
| 377 | |||
| 378 | //*// Sample code to decrypt a file using a password-generated key | ||
| 379 | // Hint - Make sure the previous code block is also uncommented, or this will not work | ||
| 380 | |||
| 381 | char *dfname = NULL; | ||
| 382 | asprintf(&dfname, "%s.dec", efname); | ||
| 383 | |||
| 384 | if((fd = open(efname, O_RDONLY)) < 0) | ||
| 385 | error(1, errno, "Could not open encrypted file for decrypting"); | ||
| 386 | if((tfd = maketmp(xdirname(dfname))) < 0) | ||
| 387 | error(1, errno, "Could not open temp file for holding decrypted data"); | ||
| 388 | |||
| 389 | // Read header in from the file | ||
| 390 | memset(header, 0, sizeof(header)); | ||
| 391 | if(read(fd, header, sizeof(header)) < 0) | ||
| 392 | error(1, errno, "read() error while getting the header"); | ||
| 393 | |||
| 394 | // Make sure the header is correct | ||
| 395 | crypto_secretstream_xchacha20poly1305_state nstate; | ||
| 396 | if(crypto_secretstream_xchacha20poly1305_init_pull(&nstate, header, key) != 0) | ||
| 397 | error(1, errno, "ran into a corrupted header"); | ||
| 398 | |||
| 399 | bytesread = -1; unsigned long long mlen = 0; unsigned char tag = 0; | ||
| 400 | buf = xcalloc(CHUNK_SIZE + 1, sizeof(*buf)); | ||
| 401 | cbuf = xcalloc((CHUNK_SIZE + 1) + crypto_secretstream_xchacha20poly1305_ABYTES, sizeof(*cbuf)); | ||
| 402 | while((bytesread = read(fd, cbuf, (CHUNK_SIZE + 1) + crypto_secretstream_xchacha20poly1305_ABYTES)) >= 0) { | ||
| 403 | if(crypto_secretstream_xchacha20poly1305_pull(&nstate, buf, &mlen, &tag, cbuf, bytesread, NULL, 0) != 0) { | ||
| 404 | error(1, errno, "Ran into a corrupted chunk while decrypting"); | ||
| 405 | } | ||
| 406 | |||
| 407 | // Do some error checking | ||
| 408 | if(tag == crypto_secretstream_xchacha20poly1305_TAG_FINAL && bytesread != 0) | ||
| 409 | error(1, errno, "Found an end tag before the end of the file"); | ||
| 410 | if(bytesread == 0 && tag != crypto_secretstream_xchacha20poly1305_TAG_FINAL) | ||
| 411 | error(1, errno, "Hit the end of the file before the end tag"); | ||
| 412 | if(writewholebuffer(tfd, buf, mlen) < 0) | ||
| 413 | error(1, errno, "Ran into a write() error while writing decrypted file contents to the tmp file"); | ||
| 414 | |||
| 415 | |||
| 416 | if(bytesread == 0) | ||
| 417 | break; | ||
| 418 | } | ||
| 419 | if(bytesread < 0) | ||
| 420 | error(1, errno, "read() error while decrypting"); | ||
| 421 | |||
| 422 | // Link the temp file back into the system | ||
| 423 | asprintf(&path, "/proc/self/fd/%d", tfd); | ||
| 424 | if(!path) | ||
| 425 | abort(); | ||
| 426 | remove(dfname); // Make sure an old version isn't sticking around | ||
| 427 | linkat(AT_FDCWD, path, AT_FDCWD, dfname, AT_SYMLINK_FOLLOW); | ||
| 428 | |||
| 429 | //*/// | ||
| 430 | |||
| 431 | |||
| 432 | /*// Sample code to encrypt a file using encrypttotmp | ||
| 433 | |||
| 434 | unsigned char key[crypto_secretstream_xchacha20poly1305_KEYBYTES]; | ||
| 435 | crypto_secretstream_xchacha20poly1305_keygen(key); | ||
| 436 | |||
| 437 | encrypttotmp("main.c", "main.enc", key); | ||
| 438 | |||
| 439 | //*/// | ||
| 440 | |||
| 441 | return 0; | 318 | return 0; |
| 442 | } | 319 | } |
| 443 | 320 | ||
