/* -*- Mode: C -*-
======================================================================
FILE: stow.c
CREATOR: eric 29 April 2000
$Id$
$Locker$
(C) COPYRIGHT 2000 Eric Busboom
http://www.softwarestudio.org
The contents of this file are subject to the Mozilla Public License
Version 1.0 (the "License"); you may not use this file except in
compliance with the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL/
Software distributed under the License is distributed on an "AS IS"
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
the License for the specific language governing rights and
limitations under the License.
The Initial Developer of the Original Code is Eric Busboom
======================================================================*/
#include <stdio.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <limits.h> /* for PATH_MAX */
#include <assert.h>
#include <stdlib.h>
#include <sys/utsname.h> /* for uname */
#include <sys/stat.h> /* for stat */
#include <unistd.h> /* for stat, getpid, getopt */
#include <pwd.h> /* For getpwent */
#include <sys/types.h> /* For getpwent */
#include <ctype.h> /* for tolower */
#include "ical.h"
#include "icalcalendar.h"
#include "icalfileset.h"
#include "icalmime.h"
char* program_name;
#define TMPSIZE 2048
#define SENDMAIL "/usr/lib/sendmail -t"
void usage(char *message);
#ifndef PATH_MAX
#define PATH_MAX 256 /* HACK */
#endif
enum options {
STORE_IN_FILE,
STORE_IN_DB,
INPUT_IS_MIME,
INPUT_IS_ICAL,
INPUT_FROM_STDIN,
INPUT_FROM_FILE,
ERRORS_TO_STDOUT,
ERRORS_TO_ORGANIZER
};
struct options_struct
{
enum options storage;
enum options input_type;
enum options input_source;
enum options errors;
char* input_file;
char* calid;
char* output_file;
};
enum file_type
{
ERROR,
NO_FILE,
DIRECTORY,
REGULAR,
OTHER
};
enum file_type test_file(char *path)
{
struct stat sbuf;
enum file_type type;
errno = 0;
/* Check if the path already exists and if it is a directory*/
if (stat(path,&sbuf) != 0){
/* A file by the given name does not exist, or there was
another error */
if(errno == ENOENT)
{
type = NO_FILE;
} else {
type = ERROR;
}
} else {
/* A file by the given name exists, but is it a directory? */
if (S_ISDIR(sbuf.st_mode)){
type = DIRECTORY;
} else if(S_ISREG(sbuf.st_mode)){
type = REGULAR;
} else {
type = OTHER;
}
}
return type;
}
char* lowercase(const char* str)
{
char* p = 0;
char* new = strdup(str);
if(str ==0){
return 0;
}
for(p = new; *p!=0; p++){
*p = tolower(*p);
}
return new;
}
#if 0
char* get_local_attendee(struct options_struct *opt)
{
char attendee[PATH_MAX];
if(opt->calid){
strncpy(attendee,opt->calid,PATH_MAX);
} else {
char* user = getenv("USER");
struct utsname uts;
uname(&utget_option);
/* HACK nodename may not be a fully qualified domain name */
snprintf(attendee,PATH_MAX,"%s@%s",user,uts.nodename);
}
return lowercase(attendee);
}
#endif
icalcomponent* get_first_real_component(icalcomponent *comp)
{
icalcomponent *c;
for(c = icalcomponent_get_first_component(comp,ICAL_ANY_COMPONENT);
c != 0;
c = icalcomponent_get_next_component(comp,ICAL_ANY_COMPONENT)){
if (icalcomponent_isa(c) == ICAL_VEVENT_COMPONENT ||
icalcomponent_isa(c) == ICAL_VTODO_COMPONENT ||
icalcomponent_isa(c) == ICAL_VJOURNAL_COMPONENT )
{
return c;
}
}
return 0;
}
char* make_mime(char* to, const char* from, const char* subject,
const char* text_message, const char* method,
const char* ical_message)
{
size_t size = strlen(to)+strlen(from)+strlen(subject)+
strlen(text_message)+ strlen(ical_message)+TMPSIZE;
char mime_part_1[TMPSIZE];
char mime_part_2[TMPSIZE];
char content_id[TMPSIZE];
char boundary[TMPSIZE];
struct utsname uts;
char* m;
if ((m = malloc(sizeof(char)*size)) == 0){
fprintf(stderr,"%s: Can't allocate memory: %s\n",program_name,strerror(errno));
exit(1);
}
uname(&uts);
srand(time(0)<<getpid());
sprintf(content_id,"%d-%d@%s",(int)time(0),rand(),uts.nodename);
sprintf(boundary,"%d-%d-%s",(int)time(0),rand(),uts.nodename);
sprintf(mime_part_1,"Content-ID: %s\n\
Content-type: text/plain\n\
Content-Description: Text description of error message\n\n\
%s\n\n--%s",
content_id,text_message,boundary);
if(ical_message != 0 && method != 0){
sprintf(mime_part_2,"Content-ID: %s\n\
Content-type: text/calendar; method=%s\n\
Content-Description: iCal component reply\n\n\
%s\n\n--%s--",
content_id,method,ical_message,boundary);
}
sprintf(m,"To: %s\n\
From: %s\n\
Subject: %s\n\
MIME-Version: 1.0\n\
Content-ID: %s\n\
Content-Type: multipart/mixed; boundary=\"%s\"\n\
\n\
This is a multimedia message in MIME format\n\
\n\
--%s\n\
%s\n\
",
to,from,subject,content_id,boundary,boundary,
mime_part_1);
if(ical_message != 0 && method != 0){
strcat(m, mime_part_2);
} else {
strcat(m,"--\n");
}
return m;
}
/* The incoming component had fatal errors */
void return_failure(icalcomponent* comp, char* message,
struct options_struct *opt)
{
char* local_attendee = opt->calid;
FILE* p;
icalcomponent *inner = get_first_real_component(comp);
icalproperty *organizer_prop = icalcomponent_get_first_property(inner,ICAL_ORGANIZER_PROPERTY);
const char *organizer = icalproperty_get_organizer(organizer_prop);
organizer += 7;
if (opt->errors == ERRORS_TO_ORGANIZER){
p = popen(SENDMAIL,"w");
} else {
p = stdout;
}
if(p == 0){
fprintf(stderr,
"%s: fatal. Could not open pipe to sendmail (\"%s\") \n",
program_name,SENDMAIL);
exit(1);
}
fputs(make_mime(organizer, local_attendee, "iMIP error",
message, "reply",
icalcomponent_as_ical_string(comp)),p);
if (opt->errors == ERRORS_TO_ORGANIZER){
pclose(p);
}
}
/* The program had a fatal error and could not process the incoming component*/
void return_error(icalcomponent* comp, char* message, struct options_struct *opt)
{
fputs(make_mime("Dest", "Source", "iMIP system failure",
message, 0,0),stdout);
}
icalcomponent* make_reply(icalcomponent *comp, icalproperty *return_status,
struct options_struct *opt)
{
icalcomponent *reply, *rinner;
icalcomponent *inner = get_first_real_component(comp);
icalproperty *p=0;
char* local_attendee = opt->calid;
char attendee[TMPSIZE];
char prodid[TMPSIZE];
snprintf(attendee,TMPSIZE,"mailto:%s",local_attendee);
snprintf(prodid,TMPSIZE,"-//Softwarestudio.org//%s version %s//EN",ICAL_PACKAGE,ICAL_VERSION);
/* Create the base component */
reply = icalcomponent_vanew(
ICAL_VCALENDAR_COMPONENT,
icalproperty_new_version(strdup("2.0")),
icalproperty_new_prodid(strdup(prodid)),
icalproperty_new_method(ICAL_METHOD_REPLY),
icalcomponent_vanew(
ICAL_VEVENT_COMPONENT,
icalproperty_new_clone(
icalcomponent_get_first_property(inner,ICAL_DTSTAMP_PROPERTY)),
icalproperty_new_clone(
icalcomponent_get_first_property(inner,ICAL_ORGANIZER_PROPERTY)),
icalproperty_new_clone(
icalcomponent_get_first_property(inner,ICAL_UID_PROPERTY)),
icalproperty_new_attendee(attendee),
0),
0);
/* Convert errors into request-status properties and transfers
them to the reply component */
icalcomponent_convert_errors(comp);
rinner = get_first_real_component(reply);
for(p = icalcomponent_get_first_property(inner,
ICAL_REQUESTSTATUS_PROPERTY);
p != 0;
p = icalcomponent_get_next_property(inner,
ICAL_REQUESTSTATUS_PROPERTY)){
icalcomponent_add_property(rinner,icalproperty_new_clone(p));
}
if(return_status != 0){
icalcomponent_add_property(rinner, return_status);
}
return reply;
}
int check_attendee(icalproperty *p, struct options_struct *opt){
const char* s = icalproperty_get_attendee(p);
char* lower_attendee = lowercase(s);
char* local_attendee = opt->calid;
/* Check that attendee begins with "mailto:" */
if (strncmp(lower_attendee,"mailto:",7) == 0){
/* skip over the mailto: part */
lower_attendee += 7;
if(strcmp(lower_attendee,local_attendee) == 0){
return 1;
}
lower_attendee -= 7;
free(lower_attendee);
}
return 0;
}
char static_component_error_str[PATH_MAX];
char* check_component(icalcomponent* comp, icalproperty **return_status,
struct options_struct *opt)
{
char* component_error_str=0;
icalcomponent* inner;
int errors = 0;
icalproperty *p;
int found_attendee = 0;
*return_status = 0;
/* This do/while loop only executes once because it is being used
to fake exceptions */
do {
/* Check that we actually got a component */
if(comp == 0){
strcpy(static_component_error_str,
"Did not find a component");
component_error_str = static_component_error_str;
break;
}
/* Check that the root component is a VCALENDAR */
if(icalcomponent_isa(comp) != ICAL_VCALENDAR_COMPONENT){
strcpy(static_component_error_str,
"Root component is not a VCALENDAR");
component_error_str = static_component_error_str;
break;
}
/* Check that the component has a METHOD */
if (icalcomponent_get_first_property(comp,ICAL_METHOD_PROPERTY) == 0)
{
strcpy(static_component_error_str,
"Component does not have a METHOD property");
component_error_str = static_component_error_str;
break;
}
inner = get_first_real_component(comp);
/* Check that the compopnent has an organizer */
if(icalcomponent_get_first_property(inner,ICAL_ORGANIZER_PROPERTY) == 0){
fprintf(stderr,"%s: fatal. Component does not have an ORGANIZER property\n",program_name);
exit(1);
}
/* Check for this user as an attendee or organizer */
for(p = icalcomponent_get_first_property(inner,ICAL_ATTENDEE_PROPERTY);
p != 0;
p = icalcomponent_get_next_property(inner,ICAL_ATTENDEE_PROPERTY)){
found_attendee += check_attendee(p,opt);
}
for(p = icalcomponent_get_first_property(inner,ICAL_ORGANIZER_PROPERTY);
p != 0;
p = icalcomponent_get_next_property(inner,ICAL_ORGANIZER_PROPERTY)){
found_attendee += check_attendee(p,opt);
}
if (found_attendee == 0){
struct icalreqstattype rs;
char* rs_string;
memset(static_component_error_str,0,PATH_MAX);
snprintf(static_component_error_str,PATH_MAX,
"This target user (%s) is not listed as an attendee or organizer",
opt->calid );
component_error_str = static_component_error_str;
rs.code = ICAL_3_7_INVCU_STATUS;
rs.desc = 0;
rs.debug = component_error_str;
rs_string = icalreqstattype_as_string(rs);
*return_status = icalproperty_new_requeststatus(rs_string);
break;
}
/* Check that the component passes iTIP restrictions */
errors = icalcomponent_count_errors(comp);
icalrestriction_check(comp);
if(errors != icalcomponent_count_errors(comp)){
snprintf(static_component_error_str,PATH_MAX,
"The component does not conform to iTIP restrictions.\n Here is the original component; look at the X-LIC-ERROR properties\nfor details\n\n%s",icalcomponent_as_ical_string(comp));
component_error_str = static_component_error_str;
break;
}
} while(0);
return component_error_str;
}
void usage(char *message)
{
fprintf(stderr,"Usage: %s [-emdcn] [-i inputfile] [-o outputfile] [-u calid]\n",program_name);
fprintf(stderr,"-e\tInput data is encapsulated in a MIME Message \n\
-m\tInput is raw iCal \n\
-i\tSpecify input file. Otherwise, input comes from stdin\n\
-o\tSpecify file to save incoming message to\n\
-d\tSpecify database to send data to\n\
-u\tSet the calid to store the data to\n\
-n\tSend errors to stdout instead of organizer\n\
");
}
void get_options(int argc, char* argv[], struct options_struct *opt)
{
int c;
extern char *optarg;
extern int optind, optopt;
int errflg=0;
opt->storage = STORE_IN_FILE;
opt->input_source = INPUT_FROM_STDIN;
opt->input_type = INPUT_IS_ICAL;
opt->input_file = 0;
opt->errors = ERRORS_TO_ORGANIZER;
opt->calid = 0;
opt->output_file = 0;
while ((c = getopt(argc, argv, "nemu:o:d:b:c:i:")) != -1) {
switch (c) {
case 'e': { /* Input data is MIME encapsulated */
opt->input_type = INPUT_IS_MIME;
break;
}
case 'm': { /* Input is iCal. Default*/
opt->input_type = INPUT_IS_ICAL;
break;
}
case 'i': { /* Input comes from named file */
opt->input_source = INPUT_FROM_FILE;
opt->input_file = strdup(optarg);
break;
}
case 'o': { /* Output goes to named file. Default*/
opt->output_file = strdup(optarg);
opt->storage = STORE_IN_FILE;
break;
}
case 'd': { /* Output goes to database */
fprintf(stderr,"%s: option -d is unimplmented\n",program_name);
opt->storage = STORE_IN_DB;
errflg++;
break;
}
case 'c': {
break;
}
case 'u': { /* Set the calid for the output database or
file. Default is user name of user running
program */
opt->calid = strdup(optarg);
break;
}
case 'n': { /* Dump error to stdout. Default is to
send error to the organizer specified
in the iCal data */
opt->errors = ERRORS_TO_STDOUT;
break;
}
case ':': {/* Option given without an operand */
fprintf(stderr,
"%s: Option -%c requires an operand\n",
program_name,optopt);
errflg++;
break;
}
case '?': {
errflg++;
}
}
if (errflg >0){
usage("");
exit(1);
}
}
if(opt->calid == 0){
/* If no calid specified, use username */
char attendee[PATH_MAX];
char* user = getenv("USER");
struct utsname uts;
uname(&uts);
/* HACK nodename may not be a fully qualified domain name */
snprintf(attendee,PATH_MAX,"%s@%s",user,uts.nodename);
opt->calid = lowercase(attendee);
}
if(opt->storage == STORE_IN_FILE &&
opt->output_file ==0){
char file[PATH_MAX];
char* user = getenv("USER");
struct passwd *pw;
if(!user){
fprintf(stderr,"%s: Can't get username. Try explicitly specifing the output file with -o", program_name);
exit(1);
}
/* Find password entry for user */
while( (pw = getpwent())!=0){
if(strcmp(user,pw->pw_name)==0){
break;
}
}
if(pw==0){
fprintf(stderr,"%s: Can't get get password entry for user \"%s\" Try explicitly specifing the output file with -o",
program_name,user);
exit(1);
}
if(pw->pw_dir==0){
fprintf(stderr,"%s: User \"%s\" has no home directory. Try explicitly specifing the output file with -o",
program_name, user);
exit(1);
}
snprintf(file,PATH_MAX,"%s/.facs/%s",pw->pw_dir,opt->calid);
opt->output_file = strdup(file);
}
/* Now try to create the calendar directory if it does
not exist */
if(opt->storage == STORE_IN_FILE ) {
char * p;
char* facspath = strdup(opt->output_file);
enum file_type type;
/* Cut off the last slash to make it just a directoy */
p = strrchr(facspath,'/');
if (p == 0){
fprintf(stderr,"%s: Invalid calendar filename \"%s\"",
program_name,facspath);
exit(1);
}
*p='\0';
type = test_file(facspath);
errno = 0;
if (type == NO_FILE){
if(mkdir(facspath,0775) != 0){
fprintf(stderr,
"%s: Failed to create calendar directory %s: %s\n",
program_name,facspath, strerror(errno));
exit(1);
} else {
fprintf(stderr,"%s: Creating calendar directory %s\n",
program_name,facspath);
}
} else if(type==REGULAR || type == ERROR){
fprintf(stderr,"%s: Cannot create calendar directory %s\n",
program_name,facspath);
exit(1);
}
}
}
char* check_options(struct options_struct *opt)
{
return 0;
}
void store_component(icalcomponent *comp, struct options_struct *opt)
{
icalerrorenum error;
if(opt->storage == STORE_IN_FILE){
icalfileset *fs = icalfileset_new(opt->output_file);
if (fs == 0){
fprintf(stderr,
"%s: Failed to get incoming component directory: %s\n",
program_name, icalerror_strerror(icalerrno));
exit(1);
}
error = icalfileset_add_component(fs,comp);
if (error != ICAL_NO_ERROR){
fprintf(stderr,"%s: Failed to write incoming component: %s\n",
program_name, icalerror_strerror(icalerrno));
exit(1);
}
error = icalfileset_commit(fs);
if (error != ICAL_NO_ERROR){
fprintf(stderr,"%s: Failed to commit incoming cluster: %s\n",
program_name, icalerror_strerror(icalerrno));
exit(1);
}
icalfileset_free(fs);
return;
} else {
assert(0);
}
}
char* read_stream(char *s, size_t size, void *d)
{
char *c = fgets(s,size, (FILE*)d);
return c;
}
icalcomponent* read_nonmime_component(struct options_struct *opt)
{
FILE *stream;
icalcomponent *comp;
icalparser* parser = icalparser_new();
char* line;
if(opt->input_source == INPUT_FROM_FILE){
stream = fopen(opt->input_file,"r");
if (stream == 0){
perror("Can't open input file");
exit(1);
}
} else {
stream = stdin;
}
assert(stream != 0);
icalparser_set_gen_data(parser,stream);
do {
line = icalparser_get_line(parser,read_stream);
comp = icalparser_add_line(parser,line);
if (comp != 0){
return comp;
}
} while ( line != 0);
if(opt->input_source == INPUT_FROM_FILE){
fclose(stream);
}
return comp;
}
icalcomponent* find_vcalendar(icalcomponent* comp)
{
icalcomponent *c,*rtrn;
for(c = icalcomponent_get_first_component(comp,ICAL_ANY_COMPONENT);
c != 0;
c = icalcomponent_get_next_component(comp,ICAL_ANY_COMPONENT)){
if(icalcomponent_isa(c) == ICAL_VCALENDAR_COMPONENT){
icalcomponent_remove_component(comp,c);
return c;
}
if((rtrn=find_vcalendar(c)) != 0){
return rtrn;
}
}
return 0;
}
icalcomponent* read_mime_component(struct options_struct *opt)
{
icalcomponent *comp,*mimecomp;
FILE* stream;
if(opt->input_source == INPUT_FROM_FILE){
stream = fopen(opt->input_file,"r");
if (stream == 0){
perror("Can't open input file");
exit(1);
}
} else {
stream = stdin;
}
assert(stream != 0);
mimecomp = icalmime_parse(read_stream,(void*)stream);
/* now find the iCal component embedded within the mime component */
comp = find_vcalendar(mimecomp);
if(comp == 0){
return 0;
}
return comp;
}
icalcomponent* read_component(struct options_struct *opt)
{
if(opt->input_type == INPUT_IS_MIME){
return read_mime_component(opt);
} else if (opt->input_type == INPUT_IS_ICAL){
return read_nonmime_component(opt);
} else {
fprintf(stderr,"%s: Internal Error; unknown option for input_type\n",
program_name);
exit(1);
}
}
int main(int argc, char* argv[] )
{
char* options_error_str;
char* component_error_str;
icalcomponent* comp, *reply;
struct options_struct opt;
icalproperty *return_status;
program_name = strrchr(argv[0],'/');
get_options(argc, argv, &opt);
if ( (options_error_str = check_options(&opt)) != 0 ){
usage(options_error_str);
exit(1);
}
comp = read_component(&opt);
if ( (component_error_str =
check_component(comp,&return_status,&opt)) != 0){
reply = make_reply(comp,return_status,&opt);
return_failure(reply, component_error_str, &opt);
icalcomponent_free(reply);
exit(0);
}
store_component(comp,&opt);
/* Don't free the component comp, since it is now part of the
store, and will be freed there */
exit(0);
}