Newer
Older
mailpiler / src / archive.c
@Janos SUTO Janos SUTO on 8 Sep 2020 11 KB Fixed zip structure
/*
 * archive.c, SJ
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <locale.h>
#include <syslog.h>
#include <openssl/blowfish.h>
#include <openssl/evp.h>
#include <zlib.h>
#include <assert.h>
#include <zip.h>
#include <piler.h>



void zerr(int ret){

   fputs("zpipe: ", stderr);

   switch (ret) {
      case Z_ERRNO:
                    fputs("I/O error\n", stderr);
                    break;

      case Z_STREAM_ERROR:
                    fputs("invalid compression level\n", stderr);
                    break;


      case Z_DATA_ERROR:
                    fputs("invalid or incomplete deflate data\n", stderr);
                    break;

      case Z_MEM_ERROR:
                    fputs("out of memory\n", stderr);
                    break;

      case Z_VERSION_ERROR:
                    fputs("zlib version mismatch!\n", stderr);
                    break;

   }
}


int inf(unsigned char *in, int len, int mode, char **buffer, FILE *dest){
   int ret, pos=0;
   z_stream strm;
   char *new_ptr;
   unsigned char out[REALLYBIGBUFSIZE];

   /* allocate inflate state */

   strm.zalloc = Z_NULL;
   strm.zfree = Z_NULL;
   strm.opaque = Z_NULL;
   strm.avail_in = 0;
   strm.next_in = Z_NULL;
   ret = inflateInit(&strm);


   if(ret != Z_OK) return ret;

   strm.avail_in = len;
   strm.next_in = in;

   if(mode == WRITE_TO_BUFFER){
      *buffer = malloc(REALLYBIGBUFSIZE);
      if(!*buffer) return Z_MEM_ERROR;
      memset(*buffer, 0, REALLYBIGBUFSIZE);
   }


   do {
      strm.avail_out = REALLYBIGBUFSIZE;
      strm.next_out = out;
      ret = inflate(&strm, Z_NO_FLUSH);

      assert(ret != Z_STREAM_ERROR);  /* state not clobbered */
      switch (ret) {
         case Z_NEED_DICT:
                ret = Z_DATA_ERROR;     /* and fall through */
         case Z_DATA_ERROR:
         case Z_MEM_ERROR:
                (void)inflateEnd(&strm);
                return ret;
      }

      unsigned have = REALLYBIGBUFSIZE - strm.avail_out;

      /*
       * write the uncompressed result either to stdout
       * or to the buffer
       */

      if(mode == WRITE_TO_STDOUT){
         if(fwrite(out, 1, have, dest) != have){
            (void)inflateEnd(&strm);
            return Z_ERRNO;
         }
      }
      else {
         memcpy(*buffer+pos, out, have);
         pos += have;
         new_ptr = realloc(*buffer, pos+REALLYBIGBUFSIZE);
         if(!new_ptr){
            (void)inflateEnd(&strm);
            return Z_MEM_ERROR;
         }
         *buffer = new_ptr;
         memset(*buffer+pos, 0, REALLYBIGBUFSIZE);
      }


   } while (strm.avail_out == 0);


   (void)inflateEnd(&strm);

   return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR;
}


unsigned char *extract_file_from_zip(char *zipfile, char *filename, zip_uint64_t *size){
   int i=0, errorp;
   unsigned char *p=NULL;
   struct zip *z;
   struct zip_stat sb;
   struct zip_file *zf;
#if LIBZIP_VERSION_MAJOR >= 1 && LIBZIP_VERSION_MINOR >= 1
   int zip_flags = ZIP_RDONLY;
#else
   int zip_flags = 0;
#endif

   z = zip_open(zipfile, zip_flags, &errorp);
   if(!z){
      syslog(LOG_INFO, "%s: error: corrupt zip file=%s, error code=%d", zipfile, filename, errorp);
      return NULL;
   }

   while(zip_stat_index(z, i, 0, &sb) == 0){
      if(sb.size > 0 && sb.comp_size > 0 && strncmp(sb.name, filename, strlen(filename)) == 0){
         *size = sb.comp_size;
         zf = zip_fopen_index(z, i, 0);
         if(zf){
            p = malloc(sb.comp_size);
            if(p){
               if(zip_fread(zf, p, sb.comp_size) == -1){
                  syslog(LOG_PRIORITY, "zip_fread(): Error reading %s from %s", filename, zipfile);
                  free(p);
                  p = NULL;
               }
            }
            zip_fclose(zf);
         }
         else syslog(LOG_PRIORITY, "cannot extract '%s' from '%s'", filename, zipfile);
      }

      i++;
   }

   zip_close(z);

   return p;
}


int retrieve_file_from_archive(char *filename, int mode, char **buffer, FILE *dest, struct config *cfg){
   int rc=0, n, olen, tlen, len, fd=-1;
   char *relfilename;
   unsigned char *s=NULL, *addr=NULL, *zipbuf=NULL, inbuf[REALLYBIGBUFSIZE];
   struct stat st;
   zip_uint64_t zipped_size=0;
#if OPENSSL_VERSION_NUMBER < 0x10100000L
   EVP_CIPHER_CTX ctx;
#else
   EVP_CIPHER_CTX *ctx=NULL;
#endif

   if(filename == NULL) return 1;

   relfilename = strchr(filename, ' ');
   if(relfilename){
      // We'll read the given file from a zip file
      // filename is decomposed to <zipfile> and <relative filename>
      *relfilename = '\0';
      relfilename++;

      zipbuf = extract_file_from_zip(filename, relfilename, &zipped_size);
      if(!zipped_size){
         syslog(LOG_PRIORITY, "%s not found in %s", relfilename, filename);
         return 1;
      }

      len = zipped_size+EVP_MAX_BLOCK_LENGTH;
   }
   else {
      // We have a distinct .m file or an attachment to read from
      fd = open(filename, O_RDONLY);
      if(fd == -1){
         syslog(LOG_PRIORITY, "%s: cannot open()", filename);
         return 1;
      }

      if(fstat(fd, &st)){
         syslog(LOG_PRIORITY, "%s: cannot fstat()", filename);
         close(fd);
         return 1;
      }

      len = st.st_size+EVP_MAX_BLOCK_LENGTH;
   }

   if(cfg->encrypt_messages == 1){
   #if OPENSSL_VERSION_NUMBER < 0x10100000L
      EVP_CIPHER_CTX_init(&ctx);
      EVP_DecryptInit_ex(&ctx, EVP_bf_cbc(), NULL, cfg->key, cfg->iv);
   #else
      ctx = EVP_CIPHER_CTX_new();
      if(!ctx) goto CLEANUP;

      EVP_CIPHER_CTX_init(ctx);
      EVP_DecryptInit_ex(ctx, EVP_bf_cbc(), NULL, cfg->key, cfg->iv);
   #endif

      s = malloc(len);

      if(!s){
         printf("malloc()\n");
         goto CLEANUP;
      }

      tlen = 0;

      if(!relfilename){
         while((n = read(fd, inbuf, sizeof(inbuf)))){

         #if OPENSSL_VERSION_NUMBER < 0x10100000L
            if(!EVP_DecryptUpdate(&ctx, s+tlen, &olen, inbuf, n)){
         #else
            if(!EVP_DecryptUpdate(ctx, s+tlen, &olen, inbuf, n)){
         #endif
               syslog(LOG_PRIORITY, "%s: EVP_DecryptUpdate()", filename);
               goto CLEANUP;
            }

            tlen += olen;
         }
      } else {
         if(!EVP_DecryptUpdate(ctx, s+tlen, &olen, zipbuf, zipped_size)){
            printf("error in EVP_DecryptUpdate\n");
            goto CLEANUP;
         }
         tlen += olen;
      }

   #if OPENSSL_VERSION_NUMBER < 0x10100000L
      if(EVP_DecryptFinal(&ctx, s + tlen, &olen) != 1){
   #else
      if(EVP_DecryptFinal(ctx, s + tlen, &olen) != 1){
   #endif
         syslog(LOG_PRIORITY, "%s: EVP_DecryptFinal()", filename);
         printf("error in EVP_DecryptFinal\n");
         goto CLEANUP;
      }


      tlen += olen;
      rc = inf(s, tlen, mode, buffer, dest);
   }
   else {
      addr = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
      rc = inf(addr, st.st_size, mode, buffer, dest);
      munmap(addr, st.st_size);
   }


   if(rc != Z_OK) zerr(rc);


CLEANUP:
   if(fd != -1) close(fd);
   if(s) free(s);
   if(zipbuf) free(zipbuf);
   if(cfg->encrypt_messages == 1)
   #if OPENSSL_VERSION_NUMBER < 0x10100000L
      EVP_CIPHER_CTX_cleanup(&ctx);
   #else
      if(ctx) EVP_CIPHER_CTX_free(ctx);
   #endif

   return 0;
}


void assemble_filename(char *filename, int len, char *s, struct config *cfg){
   char zipfilename[SMALLBUFSIZE];
   struct stat st;

   // /var/piler/00/5f5/00/5f5_00.zip
   snprintf(zipfilename, sizeof(zipfilename)-1, "%s/%02x/%c%c%c/%c%c/%c%c%c_%c%c.zip", cfg->queuedir, cfg->server_id, s[8], s[9], s[10], s[RND_STR_LEN-4], s[RND_STR_LEN-3], s[8], s[9], s[10], s[RND_STR_LEN-4], s[RND_STR_LEN-3]);

   if(cfg->consolidated_store == 1 && stat(zipfilename, &st) == 0){
      // If the zip file exists, then fix the filename to be '<zipfile> <relative filename>'
      snprintf(filename, len-1, "%s %c%c/%s.m", zipfilename, s[RND_STR_LEN-2], s[RND_STR_LEN-1], s);
   }
   else {
      snprintf(filename, len-1, "%s/%02x/%c%c%c/%c%c/%c%c/%s.m",
         cfg->queuedir, cfg->server_id, s[8], s[9], s[10], s[RND_STR_LEN-4], s[RND_STR_LEN-3], s[RND_STR_LEN-2], s[RND_STR_LEN-1], s);
   #ifdef HAVE_SUPPORT_FOR_COMPAT_STORAGE_LAYOUT
      if(stat(filename, &st)){
         snprintf(filename, len-1, "%s/%02x/%c%c/%c%c/%c%c/%s.m",
            cfg->queuedir, cfg->server_id, s[RND_STR_LEN-6], s[RND_STR_LEN-5], s[RND_STR_LEN-4], s[RND_STR_LEN-3], s[RND_STR_LEN-2], s[RND_STR_LEN-1], s);
      }
   #endif
   }
}


void assemble_attachment_filename(char *filename, int len, char *s, int attachment_id, struct config *cfg){
   char zipfilename[SMALLBUFSIZE];
   struct stat st;

   snprintf(zipfilename, sizeof(zipfilename)-1, "%s/%02x/%c%c%c/%c%c/%c%c%c_%c%c.zip", cfg->queuedir, cfg->server_id, s[8], s[9], s[10], s[RND_STR_LEN-4], s[RND_STR_LEN-3], s[8], s[9], s[10], s[RND_STR_LEN-4], s[RND_STR_LEN-3]);

   if(cfg->consolidated_store == 1 && stat(zipfilename, &st) == 0){
      snprintf(filename, len-1, "%s %c%c/%s.a%d", zipfilename, s[RND_STR_LEN-2], s[RND_STR_LEN-1], s, attachment_id);
   }
   else {
      snprintf(filename, len-1, "%s/%02x/%c%c%c/%c%c/%c%c/%s.a%d", cfg->queuedir, cfg->server_id, s[8], s[9], s[10], s[RND_STR_LEN-4], s[RND_STR_LEN-3], s[RND_STR_LEN-2], s[RND_STR_LEN-1], s, attachment_id);

   #ifdef HAVE_SUPPORT_FOR_COMPAT_STORAGE_LAYOUT
      if(stat(filename, &st)){
         snprintf(filename, len-1, "%s/%02x/%c%c/%c%c/%c%c/%s.a%d",
            cfg->queuedir, cfg->server_id, s[RND_STR_LEN-6], s[RND_STR_LEN-5], s[RND_STR_LEN-4], s[RND_STR_LEN-3], s[RND_STR_LEN-2], s[RND_STR_LEN-1], s, attachment_id);
      }
   #endif
   }
}


int retrieve_email_from_archive(struct session_data *sdata, FILE *dest, struct config *cfg){
   int attachments;
   char *buffer=NULL, *saved_buffer, *p, filename[SMALLBUFSIZE];
   struct ptr_array ptr_arr[MAX_ATTACHMENTS];

   if(strlen(sdata->ttmpfile) != RND_STR_LEN){
      printf("invalid piler-id: %s\n", sdata->ttmpfile);
      return 1;
   }

   attachments = query_attachments(sdata, &ptr_arr[0]);

   if(attachments == -1){
      printf("problem querying the attachment of %s\n", sdata->ttmpfile);
      return 1;
   }


   assemble_filename(filename, sizeof(filename), sdata->ttmpfile, cfg);

   if(attachments == 0){
      retrieve_file_from_archive(filename, WRITE_TO_STDOUT, &buffer, dest, cfg);
   }
   else {
      retrieve_file_from_archive(filename, WRITE_TO_BUFFER, &buffer, dest, cfg);

      if(buffer){
         saved_buffer = buffer;

         for(int i=1; i<=attachments; i++){
            char pointer[SMALLBUFSIZE];
            snprintf(pointer, sizeof(pointer)-1, "ATTACHMENT_POINTER_%s.a%d_XXX_PILER", sdata->ttmpfile, i);

            p = strstr(buffer, pointer);
            if(p){
               *p = '\0';
               fwrite(buffer, 1, p - buffer, dest);
               buffer = p + strlen(pointer);

               if(strlen(ptr_arr[i].piler_id) == RND_STR_LEN){
                  assemble_attachment_filename(filename, sizeof(filename), ptr_arr[i].piler_id, ptr_arr[i].attachment_id, cfg);
                  retrieve_file_from_archive(filename, WRITE_TO_STDOUT, NULL, dest, cfg);
               }
            }

         }

         if(buffer){
            fwrite(buffer, 1, strlen(buffer), dest);
         }

         buffer = saved_buffer;
         free(buffer);
      }
   }

   return 0;
}