ctm_smail.c revision 6290
1/* 2 * Send a compressed CTM delta to a recipient mailing list by encoding it 3 * in safe ASCII characters, in mailer-friendly chunks, and passing it 4 * to sendmail. The encoding is almost the same as MIME BASE64, and is 5 * protected by a simple checksum. 6 * 7 * Author: Stephen McKay 8 * 9 * NOTICE: This is free software. I hope you get some use from this program. 10 * In return you should think about all the nice people who give away software. 11 * Maybe you should write some free software too. 12 */ 13 14#include <stdio.h> 15#include <string.h> 16#include <unistd.h> 17#include <sys/types.h> 18#include <sys/stat.h> 19#include <errno.h> 20#include <paths.h> 21#include "error.h" 22#include "options.h" 23 24#define DEF_MAX_MSG 64000 /* Default maximum mail msg minus headers. */ 25 26#define LINE_LENGTH 76 /* Chars per encode line. Divisible by 4. */ 27 28void chop_and_send(char *delta, off_t ctm_size, long max_msg_size, 29 char *mail_alias); 30unsigned encode_body(FILE *sm_fp, FILE *delta_fp, long msg_size); 31void write_header(FILE *sfp, char *mail_alias, char *delta, int pce, 32 int npieces); 33void write_trailer(FILE *sfp, unsigned sum); 34void apologise(char *delta, off_t ctm_size, long max_ctm_size, 35 char *mail_alias); 36FILE *open_sendmail(void); 37int close_sendmail(FILE *fp); 38 39int 40main(int argc, char **argv) 41 { 42 char *delta_file; 43 char *mail_alias; 44 long max_msg_size = DEF_MAX_MSG; 45 long max_ctm_size = 0; 46 char *log_file = NULL; 47 struct stat sb; 48 49 err_prog_name(argv[0]); 50 51 OPTIONS("[-l log] [-m maxmsgsize] [-c maxctmsize] ctm-delta mail-alias") 52 NUMBER('m', max_msg_size) 53 NUMBER('c', max_ctm_size) 54 STRING('l', log_file) 55 ENDOPTS 56 57 if (argc != 3) 58 usage(); 59 60 if (log_file != NULL) 61 err_set_log(log_file); 62 63 delta_file = argv[1]; 64 mail_alias = argv[2]; 65 66 if (stat(delta_file, &sb) < 0) 67 { 68 err("%s: %s", delta_file, strerror(errno)); 69 exit(1); 70 } 71 72 if (max_ctm_size != 0 && sb.st_size > max_ctm_size) 73 apologise(delta_file, sb.st_size, max_ctm_size, mail_alias); 74 else 75 chop_and_send(delta_file, sb.st_size, max_msg_size, mail_alias); 76 77 return 0; 78 } 79 80 81/* 82 * Carve our CTM delta into pieces, encode them, and send them. 83 */ 84void 85chop_and_send(char *delta, off_t ctm_size, long max_msg_size, char *mail_alias) 86 { 87 int npieces; 88 long msg_size; 89 long exp_size; 90 int pce; 91 FILE *sfp; 92 FILE *dfp; 93 unsigned sum; 94 95#define howmany(x,y) (((x)+((y)-1))/(y)) 96 97 /* 98 * Work out how many pieces we need, bearing in mind that each piece 99 * grows by 4/3 when encoded. We count the newlines too, but ignore 100 * all mail headers and piece headers. They are a "small" (almost 101 * constant) per message overhead that we make the user worry about. :-) 102 */ 103 exp_size = ctm_size * 4 / 3; 104 exp_size += howmany(exp_size, LINE_LENGTH); 105 npieces = howmany(exp_size, max_msg_size); 106 msg_size = howmany(ctm_size, npieces); 107 108#undef howmany 109 110 if ((dfp = fopen(delta, "r")) == NULL) 111 { 112 err("cannot open '%s' for reading.", delta); 113 exit(1); 114 } 115 116 for (pce = 1; pce <= npieces; pce++) 117 { 118 sfp = open_sendmail(); 119 if (sfp == NULL) 120 exit(1); 121 write_header(sfp, mail_alias, delta, pce, npieces); 122 sum = encode_body(sfp, dfp, msg_size); 123 write_trailer(sfp, sum); 124 if (!close_sendmail(sfp)) 125 exit(1); 126 err("%s %d/%d sent to %s", delta, pce, npieces, mail_alias); 127 } 128 129 fclose(dfp); 130 } 131 132 133/* 134 * MIME BASE64 encode table. 135 */ 136static char to_b64[0x40] = 137 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 138 139/* 140 * This cheap plastic checksum effectively rotates our checksum-so-far 141 * left one, then adds the character. We only want 16 bits of it, and 142 * don't care what happens to the rest. It ain't much, but it's small. 143 */ 144#define add_ck(sum,x) \ 145 ((sum) += ((x)&0xff) + (sum) + (((sum)&0x8000) ? 1 : 0)) 146 147/* 148 * Encode the body. Use an encoding almost the same as MIME BASE64. 149 * 150 * Characters are read from delta_fp and encoded characters are written 151 * to sm_fp. At most 'msg_size' characters should be read from delta_fp. 152 * 153 * The body consists of lines of up to LINE_LENGTH characters. Each group 154 * of 4 characters encodes 3 input characters. Each output character encodes 155 * 6 bits. Thus 64 different characters are needed in this representation. 156 */ 157unsigned 158encode_body(FILE *sm_fp, FILE *delta_fp, long msg_size) 159 { 160 unsigned short cksum = 0xffff; 161 unsigned char *ip; 162 char *op; 163 int want, n, i; 164 unsigned char inbuf[LINE_LENGTH*3/4]; 165 char outbuf[LINE_LENGTH+1]; 166 167 /* 168 * Round up to the nearest line boundary, for the tiniest of gains, 169 * and lots of neatness. :-) 170 */ 171 msg_size += (LINE_LENGTH*3/4) - 1; 172 msg_size -= msg_size % (LINE_LENGTH*3/4); 173 174 while (msg_size > 0) 175 { 176 want = (msg_size < sizeof(inbuf)) ? msg_size : sizeof(inbuf); 177 if ((n = fread(inbuf, sizeof(char), want, delta_fp)) == 0) 178 break; 179 msg_size -= n; 180 181 for (i = 0; i < n; i++) 182 add_ck(cksum, inbuf[i]); 183 184 /* 185 * Produce a line of encoded data. Every line length will be a 186 * multiple of 4, except for, perhaps, the last line. 187 */ 188 ip = inbuf; 189 op = outbuf; 190 while (n >= 3) 191 { 192 *op++ = to_b64[ip[0] >> 2]; 193 *op++ = to_b64[(ip[0] << 4 & 0x3f) | ip[1] >> 4]; 194 *op++ = to_b64[(ip[1] << 2 & 0x3f) | ip[2] >> 6]; 195 *op++ = to_b64[ip[2] & 0x3f]; 196 ip += 3; 197 n -= 3; 198 } 199 if (n > 0) 200 { 201 *op++ = to_b64[ip[0] >> 2]; 202 *op++ = to_b64[(ip[0] << 4 & 0x3f) | ip[1] >> 4]; 203 if (n >= 2) 204 *op++ = to_b64[ip[1] << 2 & 0x3f]; 205 } 206 *op++ = '\n'; 207 fwrite(outbuf, sizeof(char), op - outbuf, sm_fp); 208 } 209 210 if (ferror(delta_fp)) 211 { 212 err("error reading input file."); 213 exit(1); 214 } 215 216 if (ferror(sm_fp)) 217 { 218 err("error writing to sendmail"); 219 exit(1); 220 } 221 222 return cksum; 223 } 224 225 226/* 227 * Write the mail header and data header. 228 */ 229void 230write_header(FILE *sfp, char *mail_alias, char *delta, int pce, int npieces) 231 { 232 char *sn; 233 234 if ((sn = strrchr(delta, '/')) == NULL) 235 sn = delta; 236 else 237 sn++; 238 239 fprintf(sfp, "From: %s-owner\n", mail_alias); 240 fprintf(sfp, "To: %s\n", mail_alias); 241 fprintf(sfp, "Subject: ctm-mail %s %d/%d\n\n", sn, pce, npieces); 242 243 fprintf(sfp, "CTM_MAIL BEGIN %s %d %d\n", sn, pce, npieces); 244 } 245 246 247/* 248 * Write the data trailer. 249 */ 250void 251write_trailer(FILE *sfp, unsigned sum) 252 { 253 fprintf(sfp, "CTM_MAIL END %ld\n", (long)sum); 254 } 255 256 257/* 258 * We're terribly sorry, but the delta is too big to send. 259 */ 260void 261apologise(char *delta, off_t ctm_size, long max_ctm_size, char *mail_alias) 262 { 263 FILE *sfp; 264 char *sn; 265 266 sfp = open_sendmail(); 267 if (sfp == NULL) 268 exit(1); 269 270 if ((sn = strrchr(delta, '/')) == NULL) 271 sn = delta; 272 else 273 sn++; 274 275 fprintf(sfp, "From: %s-owner\n", mail_alias); 276 fprintf(sfp, "To: %s\n", mail_alias); 277 fprintf(sfp, "Subject: ctm-notice %s\n\n", sn); 278 279 fprintf(sfp, "%s is %ld bytes. The limit is %ld bytes.\n\n", sn, 280 (long)ctm_size, max_ctm_size); 281 fprintf(sfp, "You can retrieve this delta via ftpmail, or your good mate at the university.\n"); 282 283 if (!close_sendmail(sfp)) 284 exit(1); 285 } 286 287 288/* 289 * Start a pipe to sendmail. Sendmail will decode the destination 290 * from the message contents. 291 */ 292FILE * 293open_sendmail() 294 { 295 FILE *fp; 296 char buf[100]; 297 298 sprintf(buf, "%s -t", _PATH_SENDMAIL); 299 if ((fp = popen(buf, "w")) == NULL) 300 err("cannot start sendmail"); 301 return fp; 302 } 303 304 305/* 306 * Close a pipe to sendmail. Sendmail will then do its bit. 307 * Return 1 on success, 0 on failure. 308 */ 309int 310close_sendmail(FILE *fp) 311 { 312 int status; 313 314 fflush(fp); 315 if (ferror(fp)) 316 { 317 err("error writing to sendmail"); 318 return 0; 319 } 320 321 if ((status = pclose(fp)) != 0) 322 err("sendmail failed with status %d", status); 323 324 return (status == 0); 325 } 326