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