/* Spruce * Copyright (C) 1999-2000 Jeffrey Stedfast * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H # include #endif #include "imap.h" #define IMAP_LOGGING /* this is used in the tag before each command */ static guint32 imap_commands = 0; extern gint timeout; extern GList *mime_parts; gint imap_ok (gint tag, gchar *line) { /* returns 1 if OK was found */ gchar find[64]; gint ret; g_snprintf(find, sizeof(find)-1, "A%.5d OK", tag); ret = find_string (line, find); if (ret < 0) return 0; return 1; } gint imap_login_cram_md5 (gint socket, gchar *username, gchar *password) { /* Log in to server using CRAM-MD5 keyed hash. */ gchar buffer[512]; gchar *retstr; gint pos; if (username == NULL || password == NULL) return ERROR; memset(buffer, 0, sizeof(buffer)); if (recvline(socket, buffer, sizeof(buffer)-1) < 0) return ERROR; /* Fetch the OK line from the server */ if (find_string(buffer, "OK") == -1) return ERROR; g_snprintf(buffer, sizeof(buffer)-1, "A%.5d AUTHENTICATE CRAM-MD5\r\n", imap_commands); if (send(socket, buffer, strlen(buffer), 0) < 0) return ERROR; memset(buffer, 0, sizeof(buffer)); if (recvline(socket, buffer, sizeof(buffer)-1) < 0) return ERROR; pos = find_string(buffer, "\r\n"); if (pos != -1) buffer[pos] = '\0'; retstr = cram_md5(username, password, buffer); if (retstr[strlen(retstr)-1] == '\n') retstr[strlen(retstr)-1] = '\0'; g_snprintf(buffer, sizeof(buffer)-1, "%s\r\n", retstr); g_free(retstr); if (send (socket, buffer, strlen(buffer), 0) < 0) return ERROR; if (recvline(socket, buffer, sizeof(buffer)-1) < 0) return ERROR; if (!imap_ok(imap_commands, buffer)) return ERROR; imap_commands++; return SUCCESS; } gint imap_login (gint socket, gchar *username, gchar *password) { /* this logs us in to the server */ gchar buffer[512]; gchar temp[64]; if (username == NULL || password == NULL) return ERROR; g_snprintf(buffer, sizeof(buffer)-1, "A%.5d LOGIN \"%s\" \"%s\"\r\n", imap_commands, username, password); #ifdef IMAP_LOGGING fprintf(stderr, "%s", buffer); #endif if (send (socket, buffer, strlen(buffer), 0) < 0) { return ERROR; } g_snprintf(temp, sizeof(temp)-1, "A%.5d", imap_commands); memset(buffer, 0, sizeof(buffer)); recvline_timeo(socket, buffer, sizeof(buffer)-1, timeout); while (!strstr(buffer, temp)) { memset(buffer, 0, sizeof(buffer)); recvline_timeo(socket, buffer, sizeof(buffer)-1, timeout); } if (!imap_ok(imap_commands, buffer)) return ERROR; imap_commands++; return SUCCESS; } GList *imap_list (gint socket, gchar *namespace) { /* this gets the names of all the mailboxes */ gchar buffer[512]; gchar flags[256]; gchar temp[64], *ptr = NULL, *flagptr = NULL; gchar slashdot = '\0'; GList *list = NULL; gint ret, size = 0, flaglen = 0; if (namespace && *namespace) { if (*namespace && namespace[strlen(namespace)-1] != '/' && namespace[strlen(namespace)-1] != '.') slashdot = '/'; g_snprintf(buffer, sizeof(buffer)-1, "A%.5d LIST \"\" %s%c*\r\n", imap_commands, namespace, slashdot); } else g_snprintf(buffer, sizeof(buffer)-1, "A%.5d LIST \"\" INBOX.*\r\n", imap_commands); #ifdef IMAP_LOGGING fprintf(stderr, "%s", buffer); #endif if (send(socket, buffer, strlen(buffer), 0) < 0) { return NULL; } do { memset(buffer, 0, sizeof(buffer)); ret = recvline(socket, buffer, sizeof(buffer)-1); if (ret > 0) { #ifdef IMAP_LOGGING fprintf(stderr, "received: %s", buffer); #endif if (buffer[0] == '*') { strip(buffer, '\r'); strip(buffer, '\n'); /* skip ahead to the flag section */ ptr = strstr(buffer, "("); /* find the end of the flags section */ flagptr = ptr + 1; ptr = strstr(ptr, ")") + 1; /* eventually we will need to parse this */ memset(flags, 0, sizeof(flags)); flaglen = (gint)(ptr - flagptr) - 1; size = sizeof(flags); strncpy(flags, flagptr, flaglen > size ? size : flaglen); if (!strstrcase(flags, "\\NoSelect")) /* is this a selectable mailbox? */ { /* skip the reference name */ ptr += imap_get_string (ptr, temp, sizeof(temp)-1, ""); /* the rest of the return string is fair play... */ g_strstrip(ptr); /* trim off any extra white space */ unquote(ptr); /* unquote the mailbox name if it is quoted */ if (slashdot) strcut(ptr, 0, strlen(namespace)+1); /* cut out the namespace and the '/' */ else strcut(ptr, 0, strlen(namespace)); /* cut out the namespace */ list = g_list_append (list, g_strdup(ptr)); } } else break; } } while (ret > 0); imap_commands++; return list; } gint imap_select_mailbox (gint socket, gchar *mailbox, gchar *namespace) { /* selects a mailbox, returns the number of messages in that mailbox * or -1 on error */ gchar *cmdbuf, buffer[512], temp[64], *index, mesgs[16]; gchar slashdot = '\0'; gint ret, i; if (mailbox == NULL) return ERROR; if (namespace && strcmp(mailbox, "INBOX")) { if (*namespace && namespace[strlen(namespace)-1] != '/' && namespace[strlen(namespace)-1] != '.') slashdot = '/'; cmdbuf = g_strdup_printf("A%.5d SELECT %s%c%s\r\n", imap_commands, namespace, slashdot, mailbox); } else cmdbuf = g_strdup_printf("A%.5d SELECT %s\r\n", imap_commands, mailbox); #ifdef IMAP_LOGGING fprintf(stderr, "%s", cmdbuf); #endif if (send(socket, cmdbuf, strlen(cmdbuf), 0) < 0) { g_free(cmdbuf); return -1; } g_free(cmdbuf); g_snprintf(temp, sizeof(temp)-1, "A%.5d", imap_commands); memset(buffer, 0, sizeof(buffer)); ret = recvline(socket, buffer, sizeof(buffer)-1); while (ret > 0) { #ifdef IMAP_LOGGING fprintf(stderr, "received: %s", buffer); #endif if (strstr(buffer, temp)) break; if (buffer[0] == '*') { if (strstr(buffer, "EXISTS")) { index = buffer; while (*index != ' ') index++; index++; i = 0; memset(mesgs, 0, sizeof(mesgs)); while (*index != ' ' && i < sizeof(mesgs)-1) { mesgs[i] = *index; index++; i++; } } } memset(buffer, 0, sizeof(buffer)); ret = recvline(socket, buffer, sizeof(buffer)-1); } if (!imap_ok(imap_commands, buffer)) return -1; imap_commands++; return atoi(mesgs); } gint imap_logout (gint socket) { /* logs out */ gchar buffer[256]; g_snprintf(buffer, sizeof(buffer)-1, "A%.5d LOGOUT\r\n", imap_commands); #ifdef IMAP_LOGGING fprintf(stderr, "%s", buffer); #endif if (send(socket, buffer, strlen(buffer), 0) < 0) { return ERROR; } return SUCCESS; } gint imap_mailbox_create (gint socket, gchar *mailbox) { /* creates a new mailbox */ gchar buffer[256]; if (mailbox == NULL) return ERROR; g_snprintf(buffer, sizeof(buffer)-1, "A%.5d CREATE %s\r\n", imap_commands, mailbox); #ifdef IMAP_LOGGING fprintf(stderr, "%s", buffer); #endif if (send(socket, buffer, strlen(buffer), 0) < 0) { return ERROR; } memset(buffer, 0, sizeof(buffer)); if (recvline(socket, buffer, sizeof(buffer)-1) < 0 || !imap_ok(imap_commands, buffer)) { return ERROR; } imap_commands++; return SUCCESS; } gint imap_mailbox_delete (gint socket, gchar *mailbox) { /* deletes a mailbox */ gchar buffer[256]; if (mailbox == NULL) return ERROR; g_snprintf(buffer, sizeof(buffer)-1, "A%.5d DELETE %s\r\n", imap_commands, mailbox); #ifdef IMAP_LOGGING fprintf(stderr, "%s", buffer); #endif if (send(socket, buffer, strlen(buffer), 0) < 0) { return ERROR; } memset(buffer, 0, sizeof(buffer)); if (recvline(socket, buffer, sizeof(buffer)-1) < 0 || !imap_ok(imap_commands, buffer)) { return ERROR; } imap_commands++; return SUCCESS; } /* fetches the specified part of a message, which can be alot of * if you use peek the \Seen flag is not set */ gchar *imap_fetch (gint socket, gint mesgnum, gchar *part, gint *seen) { /* fetches the specified part of the mesg. */ gchar *mesg = NULL; gchar buffer[512], *index; gchar flags[128], size[16], temp[64]; gint i, n, msgsize = 1000; if (mesgnum < 0) return (gchar *)NULL; g_snprintf(buffer, sizeof(buffer)-1, "A%.5d FETCH %d (FLAGS %s)\r\n", imap_commands, mesgnum, part); #ifdef IMAP_LOGGING fprintf(stderr, "%s", buffer); #endif if (send(socket, buffer, strlen(buffer), 0) < 0) { return (gchar *)NULL; } memset(buffer, 0, sizeof(buffer)); n = recvline(socket, buffer, sizeof(buffer)-1); if (buffer[0] != '*' && imap_ok(imap_commands, buffer)) { memset(buffer, 0, sizeof(buffer)); n = recvline(socket, buffer, sizeof(buffer)-1); } if (buffer[0] == '*') /*if (imap_ok(imap_commands, buffer))*/ { index = strstrcase(buffer, "FLAGS"); if (index == NULL) /* hmm */ { fprintf(stderr, _("IMAP server replied using unknown tokens.\n")); return (gchar *)NULL; } else { #ifdef IMAP_LOGGING fprintf(stderr, "received: %s", buffer); #endif /* skip to the FLAGS token */ for ( ; *index && *index != '('; index++); index++; i = 0; memset(flags, 0, sizeof(flags)); while (*index != ')' && i < sizeof(flags)-1) { flags[i] = *index; index++; i++; } flags[i] = '\0'; /* skip to the next significant token */ for (index++; *index && *index != '{'; index++); index++; i = 0; memset(size, 0, sizeof(size)); while (*index != '}' && i < sizeof(size)-1) { size[i] = *index; index++; i++; } size[i] = '\0'; msgsize = atoi(size); } } else { g_snprintf(temp, sizeof(temp)-1, "A%.5d", imap_commands); if (strstr(buffer, temp)) /* this means there's no such message */ { fprintf(stderr, _("IMAP responded with \"no such message\".\n")); return (gchar *)NULL; } } mesg = g_malloc0(msgsize + 50); /* just to be safe */ n = recvline(socket, buffer, sizeof(buffer)-1); while (!(n <= 0) && !imap_ok(imap_commands, buffer)) { strip(buffer, '\r'); /* strip all the \r's */ strcat(mesg, buffer); memset(buffer, 0, sizeof(buffer)); n = recvline(socket, buffer, sizeof(buffer)-1); } if (mesg) mesg[strlen(mesg)-3] = '\0'; /* strip the ending ) */ if (seen != NULL) { if (strstrcase(flags, "\\Seen")) *seen = 1; else *seen = 0; } imap_commands++; return (gchar*)mesg; } gboolean imap_delete(const ImapAccount_t *imap, GList *sorted) { GList *p = sorted; gchar buffer[256]; gchar temp[16]; gint ret; do { gint id = GPOINTER_TO_INT(p->data); g_snprintf(buffer, sizeof(buffer)-1, "A%.5d STORE %d +FLAGS (\\Deleted)\r\n", imap_commands, id); #ifdef IMAP_LOGGING fprintf(stderr, "%s", buffer); #endif if (send(imap->socket, buffer, strlen(buffer), 0) < 0) { return FALSE; } g_snprintf(temp, sizeof(temp)-1, "A%.5d", imap_commands); memset(buffer, 0, sizeof(buffer)); ret = recvline(imap->socket, buffer, sizeof(buffer)-1); while (ret > 0) { if (find_string(buffer, temp) >= 0) break; memset(buffer, 0, sizeof(buffer)); ret = recvline(imap->socket, buffer, sizeof(buffer)-1); } if (!imap_ok(imap_commands, buffer)) { return FALSE; } imap_commands++; } while ((p = g_list_next(p))); g_snprintf(buffer, 255, "A%.5d EXPUNGE\r\n", imap_commands); #ifdef IMAP_LOGGING fprintf(stderr, "%s", buffer); #endif if (send(imap->socket, buffer, strlen(buffer), 0) < 0) { return FALSE; } g_snprintf (temp, 15, "A%.5d", imap_commands); memset(buffer, 0, sizeof(buffer)); ret = recvline(imap->socket, buffer, sizeof(buffer)-1); while (ret > 0) { if (find_string(buffer, temp) >= 0) break; memset(buffer, 0, sizeof(buffer)); ret = recvline(imap->socket, buffer, sizeof(buffer)-1); } if (!imap_ok(imap_commands, buffer)) { return FALSE; } imap_commands++; return TRUE; } gint imap_connect (Server *server) { /* connects to the server and returns the socket or -1 on error */ gchar buffer[512]; gint sock; if (!Resolve(server)) return -1; sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sock < 0) return -1; server->sin.sin_family = AF_INET; server->sin.sin_port = htons(server->port); #ifdef IMAP_LOGGING fprintf(stderr, _("Connecting to IMAP server (%s)..."), server->ip); #endif if (connect_timeo(sock, (struct sockaddr*)&server->sin, sizeof(server->sin), timeout) < 0) { fprintf(stderr, _("failed.\n")); close(sock); return -1; } fprintf(stderr, _("success.\n")); { /* read the connect responce */ memset(buffer, 0, sizeof(buffer)); recvline_timeo(sock, buffer, sizeof(buffer)-1, timeout); } return sock; } gint imap_add_part(gchar *c) { gchar name[64], value[64]; gchar temp[64]; gchar *start = c; struct mime_part *part; part = g_malloc0(sizeof(struct mime_part)); c += imap_get_string (c, part->type, sizeof(part->type)-1, "text"); c += imap_get_string (c, part->subtype, sizeof(part->subtype)-1, "plain"); /* seek to the beginning of the parameter... */ for ( ; *c && *c == ' '; c++); if (*c) { gchar *p = part->parameter; if (*c == '(') { c++; while (*c && *c != ')') { c += imap_get_string (c, name, sizeof(name)-1, ""); c += imap_get_string (c, value, sizeof(value)-1, ""); /* don't buffer overrun */ g_snprintf(p, sizeof(part->parameter)-1, "%s=\"%s\"; ", name, value); p += strlen(p); while (*c && *c == ' ') /* skip any spaces */ c++; } } else { c += imap_get_string (c, name, sizeof(name)-1, ""); strcpy(value, name); *p++ = '\0'; } c++; /* skip over the ')' belonging to the parameter values */ if (*c) { /* ignore id and description */ c += imap_get_string (c, temp, sizeof(temp)-1, ""); c += imap_get_string (c, temp, sizeof(temp)-1, ""); /* encoding */ c += imap_get_string (c, part->encoding, sizeof(part->encoding)-1, ""); /* size */ c += imap_get_number (c, &part->len); /* skip the optional info */ c += imap_skip_section(c); part->pos = 0; /* isn't useful in imap */ #ifdef IMAP_LOGGING fprintf(stderr, "type = %s/%s\n", part->type, part->subtype); fprintf(stderr, "encoding = %s\n", part->encoding); fprintf(stderr, "param = %s\n", part->parameter); #endif mime_parts = g_list_append (mime_parts, part); return (c - start); } } return -1; } gint imap_parts (gint socket, gint mesg_num) { GList *tmp; gchar *buffer = NULL, *c; gint res = 1, cnt; tmp = mime_parts; while (tmp != NULL) { g_free(tmp->data); tmp = tmp->next; } if (mime_parts != NULL) { g_list_free(mime_parts); mime_parts = NULL; } buffer = g_malloc0(sizeof(gchar)*2048); g_snprintf(buffer, 2047, "A%.5d FETCH %d (BODYSTRUCTURE)\r\n", imap_commands, mesg_num); #ifdef IMAP_LOGGING fprintf(stderr, "%s", buffer); #endif if (send(socket, buffer, strlen(buffer), 0) < 0) { g_free(buffer); return 0; } /* get the structure of the body */ memset (buffer, 0, sizeof(gchar)*2048); recvline (socket, buffer, sizeof(gchar)*2048); #ifdef IMAP_LOGGING fprintf(stderr, "received: %s", buffer); #endif c = buffer; /* skip to the BODYSTRUCTURE */ c = strstr(c, "BODYSTRUCTURE"); if (c == NULL) return 0; c += strlen("BODYSTRUCTURE"); if (*c) { /* looks good so far, skip to the parts */ for ( ; *c && *c != '('; c++); if (*c && *(c+1) == '(') { c++; #ifdef IMAP_LOGGING fprintf(stderr, "message is multipart\n"); #endif /* multipart */ while (*c == '(') { cnt = imap_skip_section(c); if (cnt > 1) { c[cnt-1] = '\0'; cnt = imap_add_part(c); if (cnt == -1) { res = 0; break; } c += cnt; } else { res = 0; break; } /* skip to the next mime part */ for ( ; *c && *c == ' '; c++); } } else if (*c) { /* one part */ cnt = imap_add_part(c); res = res != -1; } /* just forget the rest, who cares?? */ } g_free(buffer); return res; } gint imap_get_string (gchar *index, gchar *dest, gint destlen, gchar *def) { /* gets a string ("data" or NIL) , if NIL it copies def instead */ gint i; gchar *start = index; while (*index && *index == ' ') /* skip white space */ index++; if (strncmp(index, "NIL", 3)) { /* progress to the first quote (we should already be there but just in case) */ while (*index && *index != '"') index++; index++; i = 0; while (*index && *index != '"') { if (i < destlen-1) { dest[i] = *index; i++; } index++; } dest[i] = '\0'; } else { /* if there were no data we just copy def */ index += 3; strncpy (dest, def, destlen); } return index - start + 1; } gint imap_get_number (gchar *index, gint *dest) { /* gets a number */ gchar number[32]; gchar *start = index; gint i; /* skip white space **/ while (*index == ' ') index++; i = 0; while (*index != ' ' && i < sizeof(number)-1) { number[i] = *index; index++; i++; } number[i] = '\0'; *dest = atoi(number); return index - start; } gint imap_skip_section(gchar *index) { gint depth = 1; gchar *start = index; while (depth != 0 && *index) { if (*index == '(') depth++; else if ( *index == ')' ) depth--; index++; } return index - start; }