Ctrl K
ComunidadGuías…

src/import_imap.c

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/*
 * import_imap.c
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <curl/curl.h>
#include <fcntl.h>
#include <dirent.h>
#include <unistd.h>
#include <time.h>
#include <locale.h>
#include <getopt.h>
#include <syslog.h>
#include <piler.h>

#define SSL_SETUP(curl, data)  \
   if(!strncmp(data->import->server, "imaps://", 8)) {    \
      curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_ALL);  \
      if(data->import->verifyssl == 0) {                \
         curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);    \
         curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);    \
      }                                   \
   }

void free_folder_list(struct FolderList *list){
   if(list){
      for(size_t i=0; i<list->count; i++){
         free(list->folders[i]);
      }

      if(list->folders && list->count){
         free(list->folders);
      }

      list->folders = NULL;
      list->count = 0;
   }
}

static size_t callback(void *contents, size_t size, size_t nmemb, void *userp){
   size_t realsize = size * nmemb;
   struct MemoryStruct *mem = (struct MemoryStruct *)userp;

   if(mem->size < mem->written_size + realsize + 1){
      int howmuch = realsize < BIGBUFSIZE ? BIGBUFSIZE : realsize;
      mem->memory = realloc(mem->memory, mem->size + howmuch + 1);
      if(mem->memory == NULL) {
         fprintf(stderr, "realloc() error: %s:%d\n", __func__, __LINE__);
         return 0;
      }
      mem->size += howmuch;
   }

   memcpy(&(mem->memory[mem->written_size]), contents, realsize);
   mem->written_size += realsize;
   mem->memory[mem->written_size] = 0;

   return realsize;
}


struct FolderList* list_folders_curl(CURL *curl, struct data *data){
   CURLcode res;
   struct MemoryStruct chunk;

   struct FolderList *result = malloc(sizeof(struct FolderList));
   if(!result){
      fprintf(stderr, "malloc() error: %s:%d\n", __func__, __LINE__);
      return NULL;
   }

   result->count = 0;

   chunk.memory = malloc(1);
   chunk.written_size = 0;
   chunk.size = 0;

   curl_easy_reset(curl);
   curl_easy_setopt(curl, CURLOPT_URL, data->import->server);
   curl_easy_setopt(curl, CURLOPT_USERNAME, data->import->username);
   curl_easy_setopt(curl, CURLOPT_PASSWORD, data->import->password);
   curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, callback);
   curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);

   SSL_SETUP(curl, data);

   res = curl_easy_perform(curl);
   if(res != CURLE_OK){
      fprintf(stderr, "Curl perform failed: %s\n", curl_easy_strerror(res));
      goto cleanup3;
   }

   int rc;
   char buf[MAXBUFSIZE];
   char *p = chunk.memory;

   if(data->import->folder_imap){
      char tmpbuf[SMALLBUFSIZE];

      snprintf(tmpbuf, sizeof(tmpbuf)-1, "* LIST (\\HasNoChildren) \".\" \"%s\"\r\n", data->import->folder_imap);
      p = tmpbuf;
   }

   do {
      memset(buf, 0, sizeof(buf));
      p = split(p, '\n', buf, sizeof(buf)-1, &rc);

      if(*buf == '\0') continue;

      int len = strlen(buf);
      if(len < 5) continue;

      // Folder listings look like below
      // * LIST (\HasNoChildren) "." "fol der"
      // * LIST (\HasNoChildren) "." INBOX

      char *q = strchr(buf, '"');
      if(!q) continue;
      q = strchr(q+1, '"');
      if(!q) continue;

      if(result->count == 0){
         result->folders = malloc(sizeof(char*));
      } else {
         result->folders = realloc(result->folders, (result->count + 1) * sizeof(char*));
      }

      if(!result->folders){
         free(result);
         fprintf(stderr, "malloc() error: %s:%d\n", __func__, __LINE__);
         return NULL;
      }

      size_t name_len = strlen(q+2);
      char *folder_name = malloc(name_len + 3); // +3 for potential quotes and null terminator
      if(!folder_name){
         free_folder_list(result);
         fprintf(stderr, "malloc() error: %s:%d\n", __func__, __LINE__);
         return NULL;
      }

      snprintf(folder_name, name_len, "%s", q+2);
      result->folders[result->count++] = folder_name;
   } while(p);

cleanup3:
   free(chunk.memory);
   return result;
}


int examine_imap_folder(CURL *curl, struct data *data, const char *folder){
    CURLcode res;
    struct MemoryStruct chunk;
    long n_messages = 0;

    chunk.memory = malloc(1);
    chunk.written_size = 0;
    chunk.size = 0;

    curl_easy_reset(curl);
    curl_easy_setopt(curl, CURLOPT_URL, data->import->server);
    curl_easy_setopt(curl, CURLOPT_USERNAME, data->import->username);
    curl_easy_setopt(curl, CURLOPT_PASSWORD, data->import->password);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);

    SSL_SETUP(curl, data);

    char command[SMALLBUFSIZE];
    snprintf(command, sizeof(command), "EXAMINE %s", folder);
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, command);

    res = curl_easy_perform(curl);
    if(res != CURLE_OK){
       fprintf(stderr, "Curl perform failed: %s\n", curl_easy_strerror(res));
       goto cleanup2;
    }

    int result;
    char buf[MAXBUFSIZE];
    char *p = chunk.memory;

    do {
       memset(buf, 0, sizeof(buf));
       p = split(p, '\n', buf, sizeof(buf)-1, &result);

       if(*buf == '\0') continue;

       char *q = strcasestr(buf, " exists");
       if(q){
          *q = '\0';
          q = strrchr(buf, ' ');
          if(q) {
             q++;
             n_messages = atol(q);
          }
          break;
       }

    } while(p);

cleanup2:
   free(chunk.memory);
   return n_messages;
}

static CURLcode send_imap_command(CURL *curl, struct data *data, const char *url, struct MemoryStruct *chunk, const char *command){
   curl_easy_reset(curl);
   curl_easy_setopt(curl, CURLOPT_URL, url);
   curl_easy_setopt(curl, CURLOPT_USERNAME, data->import->username);
   curl_easy_setopt(curl, CURLOPT_PASSWORD, data->import->password);
   curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, callback);
   curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)chunk);
   curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, command);

   SSL_SETUP(curl, data);

   return curl_easy_perform(curl);
}

int delete_emails_from_imap(CURL *curl, struct data *data, const char *url, int *seq_nums, int count){
   CURLcode res;
   struct MemoryStruct chunk;
   char command[MAXBUFSIZE];
   int offset = 0;
   int i = 0;

   if(count <= 0) return 0;

   chunk.memory = malloc(1);
   chunk.written_size = 0;
   chunk.size = 0;

   // Build and send STORE commands in batches to avoid buffer overflow
   // Reserve space for "STORE " prefix (6) and " +FLAGS (\Deleted)" suffix (18) plus safety margin
   const int max_offset = sizeof(command) - 32;

   offset = snprintf(command, sizeof(command), "STORE ");

   for(i = 0; i < count; i++){
      int needed = snprintf(NULL, 0, "%s%d", (offset > 6) ? "," : "", seq_nums[i]);

      if(offset + needed >= max_offset){
         // Buffer full, send current batch
         snprintf(command + offset, sizeof(command) - offset, " +FLAGS (\\Deleted)");

         res = send_imap_command(curl, data, url, &chunk, command);
         if(res != CURLE_OK){
            fprintf(stderr, "Failed to mark messages as deleted: %s\n", curl_easy_strerror(res));
            free(chunk.memory);
            return -1;
         }

         // Start new batch
         offset = snprintf(command, sizeof(command), "STORE ");
      }

      // Add sequence number to current batch
      if(offset > 6) offset += snprintf(command + offset, sizeof(command) - offset, ",");
      offset += snprintf(command + offset, sizeof(command) - offset, "%d", seq_nums[i]);
   }

   // Send remaining batch
   if(offset > 6){
      snprintf(command + offset, sizeof(command) - offset, " +FLAGS (\\Deleted)");

      res = send_imap_command(curl, data, url, &chunk, command);
      if(res != CURLE_OK){
         fprintf(stderr, "Failed to mark messages as deleted: %s\n", curl_easy_strerror(res));
         free(chunk.memory);
         return -1;
      }
   }

   // Now expunge to permanently remove the deleted messages
   res = send_imap_command(curl, data, url, &chunk, "EXPUNGE");
   if(res != CURLE_OK){
      fprintf(stderr, "Failed to expunge deleted messages: %s\n", curl_easy_strerror(res));
      free(chunk.memory);
      return -1;
   }

   if(data->quiet == 0) printf("Deleted %d messages from server\n", count);

   free(chunk.memory);
   return 0;
}

int download_email(CURL *curl, struct data *data, const char *url, int seq_num, const char *output_path){
   char seq_url[SMALLBUFSIZE];
   CURLcode res;

   // Prepare URL for specific email
   snprintf(seq_url, sizeof(seq_url), "%s/;UID=%d", url, seq_num);

   struct MemoryStruct fetch_size, message;

   fetch_size.memory = malloc(1);
   fetch_size.written_size = 0;
   fetch_size.size = 0;

   message.memory = malloc(1);
   message.written_size = 0;
   message.size = 0;

   curl_easy_reset(curl);
   curl_easy_setopt(curl, CURLOPT_URL, seq_url);
   curl_easy_setopt(curl, CURLOPT_USERNAME, data->import->username);
   curl_easy_setopt(curl, CURLOPT_PASSWORD, data->import->password);
   curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, callback);
   curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&fetch_size);
   curl_easy_setopt(curl, CURLOPT_HEADERDATA, (void *)&message);

   SSL_SETUP(curl, data);

   // Fetch command to get entire email body
   char command[SMALLBUFSIZE];
   snprintf(command, sizeof(command), "FETCH %d BODY[]", seq_num);
   curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, command);

   res = curl_easy_perform(curl);
   if(res != CURLE_OK){
      fprintf(stderr, "Curl perform failed: %s\n", curl_easy_strerror(res));
      goto cleanup;
   }

   char *body_start = memmem(message.memory, message.written_size, fetch_size.memory, fetch_size.written_size);
   if(body_start){
      body_start += fetch_size.written_size;

      // Parse message size from the 'fetch_size': * 1 FETCH (UID 1 BODY[] {19427}

      ssize_t message_size = 0;
      char *s = strrchr(fetch_size.memory, '{');
      if(s){
         s++;
         char *p = strchr(s, '}');
         if(!p) goto cleanup;

         *p = '\0';
         message_size = strtoull(s, NULL, 10);

         int fd = open(output_path, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR|S_IWUSR|S_IRGRP);
         if(fd != -1){
            ssize_t n = write(fd, body_start, message_size);
            if(n != message_size) fprintf(stderr, "Failed to write %ld bytes\n", message_size);
            fsync(fd);
            close(fd);
         } else {
            fprintf(stderr, "Failed to open %s\n", output_path);
         }
      }
   } else {
      fprintf(stderr, "Cannot find %s in the message\n", fetch_size.memory);
   }

cleanup:
   free(message.memory);
   free(fetch_size.memory);

   return (res == CURLE_OK) ? 0 : -1;
}

int import_from_imap_server(struct session_data *sdata, struct data *data, struct counters *counters, struct config *cfg){
   CURL *curl;
   CURLcode global_init;

   global_init = curl_global_init(CURL_GLOBAL_DEFAULT);
   if(global_init != CURLE_OK){
      fprintf(stderr, "Curl global init failed\n");
      return 1;
   }

   curl = curl_easy_init();
   if(!curl){
      fprintf(stderr, "Curl init failed\n");
      curl_global_cleanup();
      return 1;
   }

   int check_skip_list = 0;
   if(data->import->skiplist && strlen(data->import->skiplist) > 0) check_skip_list = 1;

   struct FolderList *folders = list_folders_curl(curl, data);
   if(folders){
      uint64 total_counter = 0;

      for(size_t i=0; i<folders->count; i++){

         if(check_skip_list){
            char puf[SMALLBUFSIZE];
            snprintf(puf, sizeof(puf)-1, "%s,", folders->folders[i]);
            if(strstr(data->import->skiplist, puf)){
               if(data->quiet == 0) printf("SKIPPING FOLDER: %s\n", folders->folders[i]);
               continue;
            }
         }

         int n = examine_imap_folder(curl, data, folders->folders[i]);
         if(data->quiet == 0) printf("Folder %s has %d messages\n", folders->folders[i], n);
         if(n > 0){
            char url[SMALLBUFSIZE];
            char *q = folders->folders[i];
            if(*q == '"'){
               q++;
               *(q + strlen(q)-1) = '\0';
            }
            char *folder_name = url_encode(q);
            if(!folder_name) continue;
            snprintf(url, sizeof(url), "%s/%s", data->import->server, folder_name);
            free(folder_name);

            // Track downloaded message sequence numbers for deletion
            int *seq_nums_to_delete = NULL;
            int delete_count = 0;
            if(data->import->remove_after_import == 1){
               seq_nums_to_delete = malloc(n * sizeof(int));
               if(!seq_nums_to_delete){
                  fprintf(stderr, "Failed to allocate memory for deletion tracking\n");
               }
            }

            for(int j=1; j<=n; j++) {
               char output_path[SMALLBUFSIZE];
               total_counter++;
               snprintf(output_path, sizeof(output_path), "%llu.eml", total_counter);

               if(download_email(curl, data, url, j, output_path) == 0) {
                  // Track successfully downloaded message for deletion
                  if(seq_nums_to_delete){
                     seq_nums_to_delete[delete_count++] = j;
                  }
               } else {
                  fprintf(stderr, "Failed to download email %d from %s\n", j, folders->folders[i]);
               }

               // Import every downloaded 1000 messages
               if(total_counter % 1000 == 0){
                  import_from_maildir(sdata, data, ".", counters, cfg);
               }
            }

            // Import the rest
            import_from_maildir(sdata, data, ".", counters, cfg);

            // Delete successfully imported messages from server if requested
            if(data->import->remove_after_import == 1 && seq_nums_to_delete && delete_count > 0){
               if(delete_emails_from_imap(curl, data, url, seq_nums_to_delete, delete_count) != 0){
                  fprintf(stderr, "Failed to delete messages from %s\n", folders->folders[i]);
               }
            }

            if(seq_nums_to_delete){
               free(seq_nums_to_delete);
            }
         }
      }

      free_folder_list(folders);
      if(folders){
         free(folders);
      }

   } else {
      fprintf(stderr, "No imap folders found\n");
   }

   curl_easy_cleanup(curl);
   curl_global_cleanup();

   data->import->status = 2;

   return 0;
}