/* * ratSender.c -- * * This is the subprocess which handles the actual sending of messages. * * * TkRat software and its included text is Copyright 1996-2002 by * Martin Forssén * * The full text of the legal notice is contained in the file called * COPYRIGHT, included with this distribution. */ #include "ratFolder.h" #include "ratPGP.h" /* * This is a list of outstanding commands */ typedef struct CmdList { char *cmd; struct CmdList *next; } CmdList; static CmdList *cmdList = NULL; /* * Create bodypart s procedure */ static BODY *RatCreateBody(Tcl_Interp *interp, char *handler, ENVELOPE *env, int *errorFlag, Tcl_Obj *files, const char *role); static int RatSenderSend(Tcl_Interp *interp, const char *prefix, Tcl_Obj *usedArraysPtr, Tcl_Obj *files, int *hardError); static void RatSenderStanddown(Tcl_Interp *interp); static int RatParseParameter(Tcl_Interp *interp, const char *src, PARAMETER **dstPtrPtr); /* *---------------------------------------------------------------------- * * RatSender -- * * This routine runs the sending process. * * Results: * None, this routine never returns * * Side effects: * Messages may be sent * * *---------------------------------------------------------------------- */ void RatSender(Tcl_Interp *interp) { Tcl_DString result; CONST84 char **sendlistArgv, **argv; char *buf; CmdList *cmdPtr; int sendlistArgc, objc, i, s, buflen, hardError = 0, argc; Tcl_Obj *usedArraysPtr, **objv, *filesPtr; /* * Clear cached passwords */ ClearPGPPass(NULL); Tcl_DStringInit(&result); buflen = 1024; buf = (char*)ckalloc(buflen); while (1) { if (cmdList) { cmdPtr = cmdList; strlcpy(buf, cmdList->cmd, buflen); cmdList = cmdList->next; ckfree(cmdPtr->cmd); ckfree(cmdPtr); } else { i = 0; while (buf[buflen-2] = '\0', fgets(buf+i, buflen-i, stdin) && buflen-i-1 == strlen(buf+i) && buf[buflen-2] != '\n') { i = buflen-1; buflen += 1024; buf = ckrealloc(buf, buflen); } if (feof(stdin)) { exit(0); } } if (!strncmp(buf, "SEND", 4)) { (void)Tcl_SplitList(interp, buf, &sendlistArgc, &sendlistArgv); for (s=1; s=0; i--) { if ('\n' == Tcl_DStringValue(&result)[i]) { Tcl_DStringValue(&result)[i] = ' '; } } fwrite(Tcl_DStringValue(&result), Tcl_DStringLength(&result)+1, 1, stdout); fflush(stdout); } ckfree(sendlistArgv); RatSenderStanddown(interp); } else if (!strncmp(buf, "RSET", 4)) { hardError = 0; } else { exit(0); } } /* Notreached */ exit(0); } /* *---------------------------------------------------------------------- * * RatSenderSend -- * * Send a specified message * * Results: * A standard tcl result. * * Side effects: * A message is sent. * * *---------------------------------------------------------------------- */ static int RatSenderSend(Tcl_Interp *interp, const char *prefix, Tcl_Obj *usedArraysPtr, Tcl_Obj *filesPtr, int *hardError) { int listObjc, i, errorFlag = 0, requestDSN, verbose, listArgc; char *tmp, buf[1024], *handler, *header = NULL, host[1024], *s; const char *role, *ctmp, *saveTo; CONST84 char **listArgv; SMTPChannel smtpChannel = NULL; Tcl_Obj *oPtr, *rPtr, **listObjv; Tcl_DString ds; ENVELOPE *env; BODY *body; /* * Extract the message */ if (TCL_OK != RatHoldExtract(interp, prefix, usedArraysPtr, filesPtr)) { return TCL_ERROR; } handler = cpystr(Tcl_GetStringResult(interp)); role = Tcl_GetVar2(interp, handler, "role", 0); /* * Check hostname to use for unqualified addresses */ strlcpy(host, RatGetCurrent(interp, RAT_HOST, role), sizeof(host)); /* * Construct the headers */ if (!(oPtr = Tcl_GetVar2Ex(interp, handler, "request_dsn",TCL_GLOBAL_ONLY)) || TCL_OK != Tcl_GetBooleanFromObj(interp, oPtr, &requestDSN)) { requestDSN = 0; } env = mail_newenvelope (); RatGenerateAddresses(interp, role, handler, &env->from, &env->sender); buf[0] = '\0'; tmp = cpystr(Tcl_GetVar2(interp, handler, "to", TCL_GLOBAL_ONLY)); rfc822_parse_adrlist(&env->to, tmp, host); ckfree(tmp); RatEncodeAddresses(interp, env->to); env->remail = cpystr(Tcl_GetVar2(interp,handler,"remail",TCL_GLOBAL_ONLY)); if (NULL == (env->date = (char*)Tcl_GetVar2(interp,handler, "date",TCL_GLOBAL_ONLY))) { rfc822_date(buf); env->date = buf; } env->date = cpystr(env->date); if ((ctmp = Tcl_GetVar2(interp, handler, "reply_to", TCL_GLOBAL_ONLY)) && !RatIsEmpty(ctmp)) { tmp = cpystr(ctmp); rfc822_parse_adrlist(&env->reply_to, tmp, host); ckfree(tmp); RatEncodeAddresses(interp, env->reply_to); } oPtr = Tcl_GetVar2Ex(interp, handler, "subject", TCL_GLOBAL_ONLY); if (oPtr && 0 < Tcl_GetCharLength(oPtr)) { env->subject = cpystr(RatEncodeHeaderLine(interp, oPtr, 9)); } if ((ctmp = Tcl_GetVar2(interp, handler, "cc", TCL_GLOBAL_ONLY)) && !RatIsEmpty(ctmp)) { tmp = cpystr(ctmp); rfc822_parse_adrlist(&env->cc, tmp, host); ckfree(tmp); RatEncodeAddresses(interp, env->cc); } if ((ctmp = Tcl_GetVar2(interp, handler, "bcc", TCL_GLOBAL_ONLY)) && !RatIsEmpty(ctmp)) { tmp = cpystr(ctmp); rfc822_parse_adrlist(&env->bcc, tmp, host); ckfree(tmp); RatEncodeAddresses(interp, env->bcc); } env->in_reply_to = cpystr(Tcl_GetVar2(interp, handler, "in_reply_to", TCL_GLOBAL_ONLY)); env->message_id = cpystr(Tcl_GetVar2(interp, handler, "message_id", TCL_GLOBAL_ONLY)); env->newsgroups = NULL; /* * Construct the body */ if ((ctmp = Tcl_GetVar2(interp, handler, "body", TCL_GLOBAL_ONLY))) { body = RatCreateBody(interp, (char*)ctmp, env, &errorFlag, filesPtr, role); } else { body = mail_newbody (); } if (errorFlag) { goto error; } /* * Send the message */ header = (char*)ckalloc(RatHeaderSize(env, body)); oPtr = Tcl_GetVar2Ex(interp, "option", "smtp_verbose", TCL_GLOBAL_ONLY); Tcl_GetIntFromObj(interp, oPtr, &verbose); if (1 == verbose) { RatLogF(interp, RAT_PARSE, "sending_message", RATLOG_EXPLICIT); } snprintf(buf, sizeof(buf), "%s,sendprot", role); ctmp = Tcl_GetVar2(interp, "option", buf, TCL_GLOBAL_ONLY); if (ctmp && !strcmp(ctmp, "smtp")) { int reuseChannel; oPtr = Tcl_GetVar2Ex(interp, "option", "smtp_reuse", TCL_GLOBAL_ONLY); Tcl_GetBooleanFromObj(interp, oPtr, &reuseChannel); listObjc = 0; snprintf(buf, sizeof(buf), "%s,smtp_hosts", role); if ((oPtr = Tcl_GetVar2Ex(interp, "option", buf, TCL_GLOBAL_ONLY))) { Tcl_ListObjGetElements(interp, oPtr, &listObjc, &listObjv); } if (0 == listObjc) { Tcl_SetResult(interp, "Configuration error; no valid SMTP hosts given", TCL_STATIC); *hardError = 1; goto error; } for (i=0; !smtpChannel && ito; adrPtr; adrPtr = adrPtr->next) { if (RatAddressSize(adrPtr, 0) > sizeof(buf)-1) { Tcl_SetResult(interp, "Ridiculously long address", TCL_STATIC); *hardError = 1; goto error; } else { buf[0] = '\0'; rfc822_address(buf, adrPtr); Tcl_DStringAppendElement(&ds, buf); } } for (adrPtr = env->cc; adrPtr; adrPtr = adrPtr->next) { if (RatAddressSize(adrPtr, 0) > sizeof(buf)-1) { Tcl_SetResult(interp, "Ridiculously long address", TCL_STATIC); *hardError = 1; goto error; } else { buf[0] = '\0'; rfc822_address(buf, adrPtr); Tcl_DStringAppendElement(&ds, buf); } } for (adrPtr = env->bcc; adrPtr; adrPtr = adrPtr->next) { if (RatAddressSize(adrPtr, 0) > sizeof(buf)-1) { Tcl_SetResult(interp, "Ridiculously long address", TCL_STATIC); *hardError = 1; goto error; } else { buf[0] = '\0'; rfc822_address(buf, adrPtr); Tcl_DStringAppendElement(&ds, buf); } } Tcl_ResetResult(interp); if (TCL_OK != Tcl_SplitList(interp, Tcl_DStringValue(&ds), &listArgc, &listArgv) || (NULL == (channel = Tcl_OpenCommandChannel(interp, listArgc, listArgv, TCL_STDIN|TCL_STDOUT|TCL_STDERR)))) { rPtr = Tcl_NewStringObj("Failed to run send program: ", -1); Tcl_AppendToObj(rPtr, Tcl_GetStringResult(interp), -1); Tcl_SetObjResult(interp, rPtr); goto error; } else { Tcl_DStringFree(&ds); ckfree(listArgv); rfc822_output(header, env, body, RatTclPutsSendmail, channel, i); Tcl_Close(interp, channel); } } else { snprintf(buf, sizeof(buf), "Invalid send protocol '%s'", tmp ? tmp : ""); Tcl_SetResult(interp, buf, TCL_VOLATILE); *hardError = 1; goto error; } if (verbose) { RatLog(interp, RAT_PARSE, "", RATLOG_EXPLICIT); } saveTo = Tcl_GetVar2(interp, handler, "save_to", TCL_GLOBAL_ONLY); if (saveTo && *saveTo) { MESSAGECACHE elt; Tcl_DString saveArg, ds; Tcl_Channel channel; char saveFile[1024]; struct tm *tmPtr; time_t now; int perm; Tcl_DStringInit(&saveArg); Tcl_DStringInit(&ds); ctmp = Tcl_GetVar(interp, "rat_tmp", TCL_GLOBAL_ONLY); RatGenId(NULL, interp, 0, NULL); snprintf(saveFile, sizeof(saveFile), "%s/rat.%s", ctmp, Tcl_GetStringResult(interp)); oPtr = Tcl_GetVar2Ex(interp, "option", "permissions", TCL_GLOBAL_ONLY); Tcl_GetIntFromObj(interp, oPtr, &perm); if (NULL == (channel=Tcl_OpenFileChannel(interp,saveFile,"a",perm))){ Tcl_AppendResult(interp, "Failed to save copy of message: ", Tcl_PosixError(interp), (char*) NULL); goto error; } rfc822_output(header, env, body, RatTclPuts, channel, 1); Tcl_Close(interp, channel); Tcl_DStringAppendElement(&saveArg, saveFile); Tcl_DStringAppendElement(&saveArg, saveTo); Tcl_DStringSetLength(&ds, RatAddressSize(env->to, 1)); Tcl_DStringValue(&ds)[0] = '\0'; rfc822_write_address(Tcl_DStringValue(&ds), env->to); Tcl_DStringSetLength(&ds, strlen(Tcl_DStringValue(&ds))); Tcl_DStringAppendElement(&saveArg, RatDecodeHeader(interp, Tcl_DStringValue(&ds), 1)); if (env->from) { Tcl_DStringSetLength(&ds, RatAddressSize(env->from, 1)); Tcl_DStringValue(&ds)[0] = '\0'; rfc822_write_address(Tcl_DStringValue(&ds), env->from); Tcl_DStringSetLength(&ds, strlen(Tcl_DStringValue(&ds))); } else { s = RatGetCurrent(interp, RAT_MAILBOX, role); Tcl_DStringSetLength(&ds, strlen(s) + strlen(host)+2); sprintf(Tcl_DStringValue(&ds), "%s@%s", s, host); } Tcl_DStringAppendElement(&saveArg, RatDecodeHeader(interp, Tcl_DStringValue(&ds), 1)); Tcl_DStringSetLength(&ds, RatAddressSize(env->cc, 1)); Tcl_DStringValue(&ds)[0] = '\0'; rfc822_write_address(Tcl_DStringValue(&ds), env->cc); Tcl_DStringSetLength(&ds, strlen(Tcl_DStringValue(&ds))); Tcl_DStringAppendElement(&saveArg, RatDecodeHeader(interp, Tcl_DStringValue(&ds), 1)); Tcl_DStringAppendElement(&saveArg, env->message_id); Tcl_DStringAppendElement(&saveArg, (env->in_reply_to ? env->in_reply_to : env->references)); Tcl_DStringAppendElement(&saveArg, RatDecodeHeader(interp, env->subject, 0)); Tcl_DStringAppendElement(&saveArg, flag_name[RAT_SEEN].imap_name); now = time(NULL); tmPtr = gmtime(&now); elt.day = tmPtr->tm_mday; elt.month = tmPtr->tm_mon+1; elt.year = tmPtr->tm_year+1900-BASEYEAR; elt.hours = tmPtr->tm_hour; elt.minutes = tmPtr->tm_min; elt.seconds = tmPtr->tm_sec; elt.zoccident = 0; elt.zhours = 0; elt.zminutes = 0; Tcl_DStringAppendElement(&saveArg, mail_date(buf, &elt)); fprintf(stdout, "SAVE %s", Tcl_DStringValue(&saveArg)); fputc('\0', stdout); fflush(stdout); Tcl_DStringFree(&saveArg); } mail_free_envelope(&env); mail_free_body(&body); ckfree(header); return TCL_OK; error: if (verbose) { RatLog(interp, RAT_PARSE, "", RATLOG_EXPLICIT); } ckfree(header); mail_free_envelope(&env); mail_free_body(&body); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * RatSenderStanddown -- * * Closes the open SMTP channel (if open) * * Results: * None * * Side effects: * All open SMTP channels are closed. * * *---------------------------------------------------------------------- */ static void RatSenderStanddown(Tcl_Interp *interp) { int verbose; Tcl_Obj *oPtr; oPtr = Tcl_GetVar2Ex(interp, "option", "smtp_verbose", TCL_GLOBAL_ONLY); Tcl_GetIntFromObj(interp, oPtr, &verbose); RatSMTPCloseAll(interp, verbose); } /* *---------------------------------------------------------------------- * * RatCreateBody -- * * See ../doc/interface * * Results: * The return value is normally TCL_OK and the result can be found * in the result area. If something goes wrong TCL_ERROR is returned * and an error message will be left in the result area. * * Side effects: * None. * * *---------------------------------------------------------------------- */ static BODY* RatCreateBody(Tcl_Interp *interp, char *handler, ENVELOPE *env, int *errorFlag, Tcl_Obj *filesPtr, const char *role) { BODY *body = mail_newbody(); CONST84 char *type; CONST84 char *encoding; CONST84 char *parameter; char buf[1024]; CONST84 char *filename = NULL; CONST84 char *children; int pgp_sign, pgp_encrypt; Tcl_Obj *oPtr; if (NULL == (type = Tcl_GetVar2(interp, handler, "type", TCL_GLOBAL_ONLY))) { Tcl_SetResult(interp, "Internal error, no bhandler(type)", TCL_STATIC); (*errorFlag)++; return body; } if (!strcasecmp(type, "text")) body->type = TYPETEXT; else if (!strcasecmp(type, "multipart")) body->type = TYPEMULTIPART; else if (!strcasecmp(type, "message")) body->type = TYPEMESSAGE; else if (!strcasecmp(type, "application")) body->type = TYPEAPPLICATION; else if (!strcasecmp(type, "audio")) body->type = TYPEAUDIO; else if (!strcasecmp(type, "image")) body->type = TYPEIMAGE; else if (!strcasecmp(type, "video")) body->type = TYPEVIDEO; else body->type = TYPEOTHER; if (NULL == (encoding = Tcl_GetVar2(interp, handler, "encoding", TCL_GLOBAL_ONLY))) { body->encoding = ENC7BIT; } else { if (!strcasecmp(encoding, "7bit")) body->encoding = ENC7BIT; else if (!strcasecmp(encoding, "8bit")) body->encoding = ENC8BIT; else if (!strcasecmp(encoding, "binary")) body->encoding = ENCBINARY; else if (!strcasecmp(encoding, "base64")) body->encoding = ENCBASE64; else if (!strcasecmp(encoding, "quoted-printable")) body->encoding = ENCQUOTEDPRINTABLE; else { snprintf(buf, sizeof(buf), "Unkown encoding %s\n", encoding); Tcl_SetResult(interp, buf, TCL_VOLATILE); (*errorFlag)++; } } /* * Force all non-text and non-message bodyparts to binary * (unless already encoded) */ if (TYPETEXT != body->type && TYPEMESSAGE != body->type && (ENC7BIT == body->encoding || ENC8BIT == body->encoding)) { body->encoding = ENCBINARY; } if (NULL == (body->subtype = (char*)Tcl_GetVar2(interp, handler, "subtype", TCL_GLOBAL_ONLY))) { Tcl_SetResult(interp, "Internal error, no bhandler(subtype)", TCL_STATIC); (*errorFlag)++; return body; } body->subtype = cpystr(body->subtype); if ((parameter = (char*)Tcl_GetVar2(interp, handler, "parameter", TCL_GLOBAL_ONLY))){ (*errorFlag) += RatParseParameter(interp, parameter, &body->parameter); } if ((parameter = Tcl_GetVar2(interp, handler, "disp_parm", TCL_GLOBAL_ONLY))) { (*errorFlag) += RatParseParameter(interp, parameter, &body->disposition.parameter); } body->disposition.type = cpystr(Tcl_GetVar2(interp, handler, "disp_type", TCL_GLOBAL_ONLY)); if (body->disposition.type && !strlen(body->disposition.type)) { body->disposition.type = NULL; } body->id = cpystr(Tcl_GetVar2(interp, handler, "id", TCL_GLOBAL_ONLY)); if (body->id && !strlen(body->id)) { body->id = NULL; } oPtr = Tcl_GetVar2Ex(interp, handler, "description", TCL_GLOBAL_ONLY); if (oPtr) { body->description = cpystr(RatEncodeHeaderLine(interp,oPtr,13)); } if (body->description && !strlen(body->description)) { body->description = NULL; } if (TYPEMULTIPART == body->type && NULL != (children = Tcl_GetVar2(interp, handler, "children", TCL_GLOBAL_ONLY))) { PART **partPtrPtr = &body->nested.part; int childrenArgc, i; CONST84 char **childrenArgv; /* Convert to Tcl_Obj */ Tcl_SplitList(interp, children, &childrenArgc, &childrenArgv); for (i=0; ibody = *RatCreateBody(interp, (char*)childrenArgv[i], env, errorFlag, filesPtr, role); partPtrPtr = &(*partPtrPtr)->next; } } else if (TYPEMESSAGE == body->type) { unsigned char *message; if (NULL == (filename = Tcl_GetVar2(interp, handler,"filename", TCL_GLOBAL_ONLY))) { Tcl_SetResult(interp, "Internal error, no bhandler(filename)", TCL_STATIC); (*errorFlag)++; return body; } message = RatReadFile(interp, filename, &body->contents.text.size, 1); if (NULL == message) { (*errorFlag)++; return body; } body->nested.msg = RatParseMsg(interp, message); body->contents.text.data = (unsigned char*)message; } else { unsigned char *data; if (NULL == (filename = Tcl_GetVar2(interp, handler, "filename", TCL_GLOBAL_ONLY))) { Tcl_SetResult(interp, "Internal error, no bhandler(filename)", TCL_STATIC); (*errorFlag)++; return body; } if (ENCBINARY != body->encoding && TYPETEXT == body->type) { data = RatReadFile(interp, filename, &body->contents.text.size,1); } else { data = RatReadFile(interp, filename, &body->contents.text.size,0); } if (NULL == data) { (*errorFlag)++; return body; } body->contents.text.data = data; body->size.bytes = body->contents.text.size; if (TYPEMULTIPART == body->type) { body->type = TYPEAPPLICATION; ckfree(body->subtype); body->subtype = cpystr("octet-stream"); } } if (filename) { int removeFile = 0; if ((oPtr=Tcl_GetVar2Ex(interp,handler,"removeFile",TCL_GLOBAL_ONLY))){ Tcl_GetBooleanFromObj(interp, oPtr, &removeFile); } if (removeFile && filesPtr) { Tcl_ListObjAppendElement(interp, filesPtr, Tcl_NewStringObj(filename, -1)); } } /* * Do PGP signing and/or encryption */ if (!(oPtr = Tcl_GetVar2Ex(interp, handler, "pgp_sign",TCL_GLOBAL_ONLY)) || TCL_OK != Tcl_GetBooleanFromObj(interp, oPtr, &pgp_sign)) { pgp_sign = 0; } if (!(oPtr = Tcl_GetVar2Ex(interp, handler,"pgp_encrypt",TCL_GLOBAL_ONLY)) || TCL_OK != Tcl_GetBooleanFromObj(interp, oPtr, &pgp_encrypt)) { pgp_encrypt = 0; } if (pgp_sign || pgp_encrypt) { if (pgp_encrypt) { body = RatPGPEncrypt(interp, env, body, pgp_sign); } else { snprintf(buf, sizeof(buf), "%s,pgp_keyid", role); body = RatPGPSign(interp, env, body, Tcl_GetVar2(interp, "option", buf, TCL_GLOBAL_ONLY)); } if (!body) { Tcl_SetResult(interp, "Failed to do PGP operation", TCL_STATIC); (*errorFlag)++; return body; } } return body; } /* *---------------------------------------------------------------------- * * RatParseParameter -- * * Splits a parameter list into elements * * Results: * number of encountered errors * * Side effects: * None. * * *---------------------------------------------------------------------- */ static int RatParseParameter(Tcl_Interp *interp, const char *src, PARAMETER **dstPtrPtr) { int parmListArgc, parmArgc, i, errcount = 0; CONST84 char **parmListArgv, **parmArgv; Tcl_Obj *oPtr; char buf[1024]; /* Convert to Tcl_Obj? */ Tcl_SplitList(interp, src, &parmListArgc, &parmListArgv); for (i=0; iattribute = cpystr(parmArgv[0]); oPtr = Tcl_NewStringObj(parmArgv[1], -1); (*dstPtrPtr)->value = cpystr(RatEncodeHeaderLine(interp, oPtr, 0)); Tcl_DecrRefCount(oPtr); dstPtrPtr = &(*dstPtrPtr)->next; } ckfree(parmArgv); } ckfree(parmListArgv); return errcount; } /* *---------------------------------------------------------------------- * * RatSendPGPCommand -- * * Sends a PGP command to the master and then waits for a reply. * * Results: * A pointer to a static buffer containing the command. * * Side effects: * Other commands that arrive while we are waiting are placed in cmdList * * *---------------------------------------------------------------------- */ char* RatSendPGPCommand(char *cmd) { static char buf[1024]; CmdList **cmdPtrPtr; fwrite(cmd, strlen(cmd)+1, 1, stdout); fflush(stdout); for (cmdPtrPtr = &cmdList; *cmdPtrPtr; cmdPtrPtr = &(*cmdPtrPtr)->next); while(1) { fgets(buf, sizeof(buf), stdin); if (feof(stdin)) { exit(0); } buf[strlen(buf)-1] = '\0'; if (strncmp("PGP ", buf, 4)) { *cmdPtrPtr = (CmdList*)ckalloc(sizeof(CmdList)); (*cmdPtrPtr)->cmd = cpystr(buf); (*cmdPtrPtr)->next = NULL; cmdPtrPtr = &(*cmdPtrPtr)->next; } else { return buf+4; } } }