#include "lua.h" #include "lauxlib.h" #include "lualib.h" #include "c-client.h" #include "imap4r1.h" #include #include extern int errno; /* just in case */ #define M_LOG(a,...) syslog (LOG_NOTICE,"limap@[%s: %d]: " a "\n", __FILE__, \ __LINE__, ##__VA_ARGS__) #define M_WARN(a,...) syslog (LOG_WARNING, "limap@[%s: %d]: " a "\n", __FILE__, \ __LINE__, ##__VA_ARGS__) #define M_ERROR(a,...) syslog (LOG_ERR, "limap@[%s: %d]: " a "\n", __FILE__, \ __LINE__, ##__VA_ARGS__) #define add_property_string(L,k,v) \ lua_pushstring(L,k); \ lua_pushstring(L,v); \ lua_settable(L,-3); #define add_property_long(L,k,v) \ lua_pushstring(L,k); \ lua_pushnumber(L,v); \ lua_settable(L,-3); unsigned long find_rightmost_bit(unsigned long *valptr); static size_t strlcat(char *dst, const char *src, size_t n) { char *p = dst; while (n != 0 && *p != '\0') { p++; n--; } if (n != 0) { for (; --n != 0; p++, src++) { if ((*p = *src) == '\0') return p - dst; } *p = '\0'; } return (p - dst) + strlen(src); } static void str_trim(char* str, const char delim) { if(!str || strlen(str) == 0) return; char * p = str; int l = strlen(p); while(l > 0 && p[l - 1] == delim) p[--l] = 0; while(* p && (* p) == delim ) ++p, --l; memmove(str, p, l + 1); } /* Co-routines from MAIL library */ /* Message matches a search * Accepts: MAIL stream * message number */ void mm_searched (MAILSTREAM *stream,unsigned long msgno) { /* dummy routine */ } /* Message exists (i.e. there are that many messages in the mailbox) * Accepts: MAIL stream * message number */ void mm_exists (MAILSTREAM *stream,unsigned long number) { M_LOG("New emails found in %s: %d", stream->mailbox, number); int * n = stream->sparep; if(!n) return; *n = number; } /* Message expunged * Accepts: MAIL stream * message number */ void mm_expunged (MAILSTREAM *stream,unsigned long number) { /* dummy routine */ } /* Message flags update seen * Accepts: MAIL stream * message number */ void mm_flags (MAILSTREAM *stream,unsigned long number) { /* dummy routine */ } /* Mailbox found * Accepts: MAIL stream * hierarchy delimiter * mailbox name * mailbox attributes */ void mm_list (MAILSTREAM *stream,int delimiter,char *name,long attributes) { lua_State *L = stream->sparep; if(!L) return; if(attributes & LATT_NOSELECT) { return; } size_t cnt = lua_rawlen(L,-1) + 1; lua_pushnumber(L,cnt); lua_newtable(L); char delim[2]; delim[0] = (char) delimiter; delim[1] = '\0'; if(!name) return; lua_pushstring(L, "delimiter"); lua_pushstring(L, delim); lua_settable(L, -3); lua_pushstring(L, "name"); lua_pushstring(L, name); lua_settable(L, -3); lua_pushstring(L, "LATT_NOINFERIORS"); lua_pushboolean(L, attributes & LATT_NOINFERIORS); lua_settable(L, -3); lua_pushstring(L, "LATT_NOSELECT"); lua_pushboolean(L, attributes & LATT_NOSELECT); lua_settable(L, -3); lua_pushstring(L, "LATT_MARKED"); lua_pushboolean(L, attributes & LATT_MARKED); lua_settable(L, -3); lua_pushstring(L, "LATT_UNMARKED"); lua_pushboolean(L, attributes & LATT_UNMARKED); lua_settable(L, -3); lua_settable(L, -3); } /* Subscribe mailbox found * Accepts: MAIL stream * hierarchy delimiter * mailbox name * mailbox attributes */ void mm_lsub (MAILSTREAM *stream,int delimiter,char *name,long attributes) { /* dummy routine */ } /* Mailbox status * Accepts: MAIL stream * mailbox name * mailbox status */ void mm_status (MAILSTREAM *stream,char *mailbox,MAILSTATUS *status) { if (status->recent || status->unseen) printf ("%lu new message(s) (%lu unseen),",status->recent,status->unseen); else fputs ("No new messages,",stdout); printf (" %lu total in %s\n",status->messages,mailbox); } /* Notification event * Accepts: MAIL stream * string to log * error flag */ void mm_notify (MAILSTREAM *stream,char *string,long errflg) { if (!errflg && (string[0] == '[') && ((string[1] == 'T') || (string[1] == 't')) && ((string[2] == 'R') || (string[2] == 'r')) && ((string[3] == 'Y') || (string[3] == 'y')) && ((string[4] == 'C') || (string[4] == 'c')) && ((string[5] == 'R') || (string[5] == 'r')) && ((string[6] == 'E') || (string[6] == 'e')) && ((string[7] == 'A') || (string[7] == 'a')) && ((string[8] == 'T') || (string[8] == 't')) && ((string[9] == 'E') || (string[9] == 'e')) && (string[10] == ']')) mm_log (string,errflg); /* just do mm_log action */ } /* Log an event for the user to see * Accepts: string to log * error flag */ void mm_log (char *string,long errflg) { switch (errflg) { case BYE: case NIL: /* no error */ M_LOG ("[%s]",string); break; case PARSE: /* parsing problem */ case WARN: /* warning */ M_WARN("warning: %s",string); break; case ERROR: /* error */ default: M_ERROR ("%s",string); break; } } /* Log an event to debugging telemetry * Accepts: string to log */ void mm_dlog (char *string) { M_LOG ("%s",string); } /* Get user name and password for this host * Accepts: parse of network mailbox name * where to return user name * where to return password * trial count */ void mm_login (NETMBX *mb,char *username,char *password,long trial) { if(mb->user) { char * tmp = mb->user; (void)strsep(&tmp, ";"); strncpy(username, mb->user, NETMAXUSER); if(tmp) { strncpy (password,tmp,MAILTMPLEN); } M_LOG("host: %s", mb->host); M_LOG("orighost: %s", mb->orighost); M_LOG("user: %s", mb->user); M_LOG("mailbox: %s", mb->mailbox); M_LOG("service: %s", mb->service); } } /* About to enter critical code * Accepts: stream */ void mm_critical (MAILSTREAM *stream) { /* note in critical code */ } /* About to exit critical code * Accepts: stream */ void mm_nocritical (MAILSTREAM *stream) { /* note not in critical code */ } /* Disk error found * Accepts: stream * system error code * flag indicating that mailbox may be clobbered * Returns: T if user wants to abort */ long mm_diskerror (MAILSTREAM *stream,long errcode,long serious) { return T; } /* Log a fatal error event * Accepts: string to log */ void mm_fatal (char *string) { M_ERROR ("FATAL: %s",string); } static int l_mail_open(lua_State *L) { const char* mailbox = luaL_checkstring(L,1); long flag = 0; if( lua_toboolean(L,2)) { /*half open*/ flag = flag | OP_HALFOPEN; } if( lua_toboolean(L,3)) { /*debug*/ flag = flag | OP_DEBUG; } MAILSTREAM *stream = mail_open (NIL,(char*)mailbox,flag); if(!stream) { lua_pushnil(L); return 1; } //strncpy(stream->original_mailbox, stream->mailbox, strlen(stream->mailbox)); if(stream->original_mailbox) { free(stream->original_mailbox); } stream->original_mailbox = strdup(stream->mailbox); M_LOG("mailbox: %s", stream->mailbox); M_LOG("original_mailbox: %s", stream->original_mailbox); lua_pushlightuserdata(L, stream); lua_pushstring(L,stream->mailbox); return 2; } static int l_mail_close(lua_State *L) { MAILSTREAM *stream = (MAILSTREAM *) lua_touserdata(L, 1); if(!stream) { lua_pushboolean(L,0); } else { (void) mail_close(stream); lua_pushboolean(L,1); } return 1; } static int l_get_mailboxes(lua_State *L) { MAILSTREAM *stream = (MAILSTREAM *) lua_touserdata(L, 1); const char* reference = luaL_checkstring(L,2); const char* pattern = luaL_checkstring(L,3); if(!stream) { lua_pushboolean(L,0); return 1; } stream->sparep = (void*)L; lua_newtable(L); mail_list(stream,(char*)reference, (char*)pattern); return 1; } static int l_mail_ping(lua_State *L) { MAILSTREAM *stream = (MAILSTREAM *) lua_touserdata(L, 1); int new_mail = 0; stream->sparep = (void*)&new_mail; if(!stream || mail_ping(stream) == NIL) { lua_pushboolean(L,0); } else { lua_pushboolean(L,1); } lua_pushnumber(L, new_mail); return 2; } static int l_mail_check(lua_State *L) { MAILSTREAM *stream = (MAILSTREAM *) lua_touserdata(L, 1); if(!stream) { return 0; } mail_check(stream); return 0; } static int l_mail_get_raw_header(lua_State* L) { MAILSTREAM *stream = (MAILSTREAM *) lua_touserdata(L, 1); if(!stream) { lua_pushnil(L); return 1; } const int id = luaL_checknumber(L,2); char* msg_header = mail_fetchheader_full(stream, id, NIL, NIL, 0); lua_pushstring(L, msg_header); return 1; } static int l_mail_getnumber(lua_State* L) { MAILSTREAM *stream = (MAILSTREAM *) lua_touserdata(L, 1); if(!stream) { lua_pushnumber(L, -1); return 1; } lua_pushnumber(L, stream->nmsgs); return 1; } static void _imap_parse_address (lua_State* L, ADDRESS *addresslist) { ADDRESS *addresstmp; addresstmp = addresslist; int count = 1; lua_newtable(L); do { lua_pushnumber(L,count); lua_newtable(L); if (addresstmp->personal) { add_property_string(L, "personal", addresstmp->personal); } if (addresstmp->adl) { add_property_string(L, "adl", addresstmp->adl); } if (addresstmp->mailbox) { add_property_string(L, "mailbox", addresstmp->mailbox); } if (addresstmp->host) { add_property_string(L, "host", addresstmp->host); } lua_settable(L, -3); count++; } while ((addresstmp = addresstmp->next)); } static void _make_header_object(lua_State* L, ENVELOPE *en) { lua_newtable(L); if (en->remail) { add_property_string(L, "remail", en->remail); } if(en->date) { add_property_string(L, "date", (char*)en->date); } if (en->subject) { add_property_string(L, "subject", en->subject); } if (en->in_reply_to) { add_property_string(L, "in_reply_to", en->in_reply_to); } if (en->message_id) { add_property_string(L, "message_id", en->message_id); } if (en->newsgroups) { add_property_string(L, "newsgroups", en->newsgroups); } if (en->followup_to) { add_property_string(L, "followup_to", en->followup_to); } if (en->references) { add_property_string(L, "references", en->references); } if (en->to) { lua_pushstring(L, "to"); _imap_parse_address(L, en->to); lua_settable(L, -3); } if (en->from) { lua_pushstring(L, "from"); _imap_parse_address(L, en->from); lua_settable(L, -3); } if (en->cc) { lua_pushstring(L, "cc"); _imap_parse_address(L, en->cc); lua_settable(L, -3); } if (en->bcc) { lua_pushstring(L, "bcc"); _imap_parse_address(L, en->bcc); lua_settable(L, -3); } if (en->reply_to) { lua_pushstring(L, "reply_to"); _imap_parse_address(L, en->reply_to); lua_settable(L, -3); } if (en->sender) { lua_pushstring(L, "sender"); _imap_parse_address(L, en->sender); lua_settable(L, -3); } if (en->return_path) { lua_pushstring(L, "return_path"); _imap_parse_address(L, en->return_path); lua_settable(L, -3); } } static int l_rfc822_parse_header(lua_State* L) { ENVELOPE *en = NULL; const char* headers = luaL_checkstring(L,1); rfc822_parse_msg(&en, NULL, (char*)headers, strlen(headers), NULL, "UNKNOWN", NIL); if(en) { _make_header_object(L, en); mail_free_envelope(&en); } else { lua_pushnil(L); } return 1; } static int l_reopen(lua_State *L) { MAILSTREAM *stream = (MAILSTREAM *) lua_touserdata(L, 1); const char* mailbox = luaL_checkstring(L,2); long flag = 0; if( lua_toboolean(L,3)) { /*half open*/ flag = flag | OP_HALFOPEN; } if( lua_toboolean(L,4)) { /*debug*/ flag = flag | OP_DEBUG; } if(!stream) { lua_pushboolean(L,0); return 1; } stream = mail_open (stream,(char*)mailbox,flag); if(!stream) { lua_pushboolean(L,0); return 1; } if(stream->original_mailbox) { free(stream->original_mailbox); } stream->original_mailbox = strdup(stream->mailbox); M_LOG("mailbox: %s", stream->mailbox); M_LOG("original_mailbox: %s", stream->original_mailbox); lua_pushboolean(L,1); lua_pushstring(L,stream->mailbox); return 2; } static int l_mail_get_header(lua_State* L) { char tmp[MAILTMPLEN + 1]; MAILSTREAM *stream = (MAILSTREAM *) lua_touserdata(L, 1); if(!stream) { lua_pushnil(L); return 1; } const int msgno = luaL_checknumber(L,2); MESSAGECACHE *cache; ENVELOPE *en; if (mail_fetchstructure(stream, msgno, NIL)) { cache = mail_elt(stream, msgno); } else { lua_pushnil(L); return 1; } en = mail_fetchenvelope(stream, msgno); /* call a function to parse all the text, so that we can use the same function to parse text from other sources */ _make_header_object(L, en); //free(en); /* now run through properties that are only going to be returned from a server, not text headers */ add_property_string(L, "recent", cache->recent ? (cache->seen ? "R": "N") : " "); add_property_string(L, "unseen", (cache->recent | cache->seen) ? " " : "U"); add_property_string(L, "flagged", cache->flagged ? "F" : " "); add_property_string(L, "answered", cache->answered ? "A" : " "); add_property_string(L, "deleted", cache->deleted ? "D" : " "); add_property_string(L, "draft", cache->draft ? "X" : " "); add_property_long(L, "msgno", cache->msgno); mail_date(tmp, cache); add_property_string(L, "mail_date", tmp); add_property_long(L, "size", cache->rfc822_size); add_property_long(L, "udate", mail_longdate(cache)); if (en->from) { tmp[0] = 0x00; mail_fetchfrom(tmp, stream, msgno, sizeof(tmp)); str_trim(tmp, ' '); add_property_string(L, "fetchfrom", tmp); } if (en->subject) { tmp[0] = 0x00; mail_fetchsubject(tmp, stream, msgno, sizeof(tmp)); str_trim(tmp, ' '); add_property_string(L, "fetchsubject", tmp); } return 1; } static int l_mail_get_headers(lua_State* L) { MAILSTREAM *stream = (MAILSTREAM *) lua_touserdata(L, 1); if(!stream) { lua_pushnil(L); return 1; } char tmp[MAILTMPLEN]; unsigned int msgno; char *t; unsigned long i; lua_newtable(L); for (msgno = 1; msgno <= stream->nmsgs; msgno++) { lua_pushnumber(L, msgno); MESSAGECACHE * cache = mail_elt (stream, msgno); mail_fetchstructure(stream, msgno, NIL); tmp[0] = cache->recent ? (cache->seen ? 'R': 'N') : ' '; tmp[1] = (cache->recent | cache->seen) ? ' ' : 'U'; tmp[2] = cache->flagged ? 'F' : ' '; tmp[3] = cache->answered ? 'A' : ' '; tmp[4] = cache->deleted ? 'D' : ' '; tmp[5] = cache->draft ? 'X' : ' '; snprintf(tmp + 6, sizeof(tmp) - 6, "%4ld) ", cache->msgno); mail_date(tmp+11, cache); tmp[22] = ' '; tmp[23] = '\0'; mail_fetchfrom(tmp+23, stream, msgno, (long)20); strcat(tmp, " "); if ((i = cache->user_flags)) { strcat(tmp, "{"); while (i) { strlcat(tmp, stream->user_flags[find_rightmost_bit (&i)], sizeof(tmp)); if (i) strlcat(tmp, " ", sizeof(tmp)); } strlcat(tmp, "} ", sizeof(tmp)); } mail_fetchsubject(t = tmp + strlen(tmp), stream, msgno, (long)25); snprintf(t += strlen(t), sizeof(tmp) - strlen(tmp), " (%ld chars)", cache->rfc822_size); lua_pushstring(L, tmp); lua_settable(L,-3); } return 1; } static int l_get_raw_body(lua_State* L) { char tmp[MAILTMPLEN + 1]; MAILSTREAM *stream = (MAILSTREAM *) lua_touserdata(L, 1); if(!stream) { lua_pushnil(L); return 1; } unsigned int flags = 0; const int msgno = luaL_checknumber(L,2); if(lua_toboolean(L,3)) { flags |= FT_PEEK; } char *body; unsigned long body_len = 0; body = mail_fetchtext_full(stream, msgno, &body_len, flags); if (body_len == 0) { lua_pushnil(L); } else { lua_pushstring(L, body); } return 1; } static void _imap_add_body(lua_State* L, BODY *body) { PARAMETER *par, *dpar; PART *part; int count = 1; lua_newtable(L); if (body->type <= TYPEMAX) { add_property_long(L, "type", body->type); switch(body->type) { case TYPETEXT: /* unformatted text */ add_property_string(L, "type_text", "TEXT"); break; case TYPEMULTIPART: /* multiple part */ add_property_string(L, "type_text", "MULTIPART"); break; case TYPEMESSAGE: /* encapsulated message */ add_property_string(L, "type_text", "MESSAGE"); break; case TYPEAPPLICATION: /* application data */ add_property_string(L, "type_text", "APPLICATION"); break; case TYPEAUDIO: /* audio */ add_property_string(L, "type_text", "AUDIO"); break; case TYPEIMAGE: /* static image */ add_property_string(L, "type_text", "IMAGE"); break; case TYPEVIDEO: /* video */ add_property_string(L, "type_text", "VIDEO"); break; case TYPEMODEL: /* model */ add_property_string(L, "type_text", "MODEL"); break; default: add_property_string(L, "type_text", "OTHER"); break; } } if (body->encoding <= ENCMAX) { add_property_long(L, "encoding", body->encoding); switch(body->encoding) { case ENC7BIT: /* 7 bit SMTP semantic data */ add_property_string(L, "encoding_text", "7BIT"); break; case ENC8BIT: /* 8 bit SMTP semantic data */ add_property_string(L, "encoding_text", "8BIT"); break; case ENCBINARY: /* 8 bit binary data */ add_property_string(L, "encoding_text", "BINARY"); break; case ENCBASE64: /* base-64 encoded data */ add_property_string(L, "encoding_text", "BASE64"); break; case ENCQUOTEDPRINTABLE: /* human-readable 8-as-7 bit data */ add_property_string(L, "encoding_text", "QUOTEDPRINTABLE"); break; default: add_property_string(L, "encoding_text", "OTHER"); break; } } if (body->subtype) { add_property_string(L, "subtype", body->subtype); } if (body->description) { add_property_string(L, "description", body->description); } if (body->id) { add_property_string(L, "id", body->id); } if (body->size.lines) { add_property_long(L, "lines", body->size.lines); } if (body->size.bytes) { add_property_long(L, "bytes", body->size.bytes); } if (body->md5) { add_property_string(L, "md5", body->md5); } if (body->location) { add_property_string(L, "location", body->location); } if (body->disposition.type) { add_property_string(L, "disposition", body->disposition.type); } if (body->disposition.parameter) { dpar = body->disposition.parameter; lua_pushstring(L, "dparameters"); lua_newtable(L); //count = 1; do { //lua_pushnumber(L, count); //lua_newtable(L); if(dpar->attribute && dpar->value) { add_property_string(L, dpar->attribute, dpar->value); } //lua_settable(L,-3); //count++; } while ((dpar = dpar->next)); lua_settable(L, -3); } if ((par = body->parameter)) { lua_pushstring(L, "parameters"); lua_newtable(L); //count = 1; do { //lua_pushnumber(L, count); //lua_newtable(L); if (par->attribute && par->value) { add_property_string(L, par->attribute, par->value); } //lua_settable(L,-3); //count++; } while ((par = par->next)); lua_settable(L, -3); } /* multipart message ? */ if (body->type == TYPEMULTIPART) { lua_pushstring(L, "parts"); lua_newtable(L); count = 1; for (part = body->nested.part; part; part = part->next) { lua_pushnumber(L, count); _imap_add_body(L, &part->body); lua_settable(L, -3); count++; } lua_settable(L, -3); } /* encapsulated message ? */ if ((body->type == TYPEMESSAGE) && (!strcasecmp(body->subtype, "rfc822"))) { lua_pushstring(L, "message"); lua_newtable(L); lua_pushstring(L, "header"); _make_header_object(L,body->nested.msg->env); lua_settable(L, -3); lua_pushstring(L, "body"); _imap_add_body(L, body->nested.msg->body); lua_settable(L, -3); lua_settable(L, -3); } } static int l_get_structure(lua_State* L) { MAILSTREAM *stream = (MAILSTREAM *) lua_touserdata(L, 1); BODY *body; if(!stream) { lua_pushnil(L); return 1; } const int msgno = luaL_checknumber(L,2); mail_fetchstructure_full(stream, msgno, &body , 0); if (!body) { lua_pushnil(L); return 1; } _imap_add_body(L, body); return 1; } static int l_get_body_part(lua_State* L) { MAILSTREAM *stream = (MAILSTREAM *) lua_touserdata(L, 1); if(!stream) { lua_pushnil(L); return 1; } const int msgno = luaL_checknumber(L,2); const char* part_name = luaL_checkstring(L,3); unsigned int flags = 0; if(lua_toboolean(L,3)) { flags |= FT_PEEK; } char *body; unsigned long len; body = mail_fetchbody_full(stream, msgno, (char*)part_name, &len, flags); if (!body) { lua_pushnil(L); return 1; } lua_pushstring(L, body); return 1; } static const struct luaL_Reg _lib [] = { {"open", l_mail_open}, {"close", l_mail_close}, {"get_mail_boxes", l_get_mailboxes}, {"reopen", l_reopen}, {"ping", l_mail_ping}, {"check", l_mail_check}, {"get_raw_header", l_mail_get_raw_header}, {"get_nmail", l_mail_getnumber}, {"get_headers", l_mail_get_headers}, {"get_header", l_mail_get_header}, {"rfc822_parse_header", l_rfc822_parse_header}, {"get_raw_body", l_get_raw_body}, {"get_structure", l_get_structure}, {"get_body_part", l_get_body_part}, {NULL,NULL} }; int luaopen_limap(lua_State *L) { #include "linkage.c" luaL_newlib(L, _lib); return 1; }