imapext-2007

annotate src/osdep/amiga/env_ami.c @ 0:ada5e610ab86

imap-2007e
author yuuji@gentei.org
date Mon, 14 Sep 2009 15:17:45 +0900
parents
children
rev   line source
yuuji@0 1 /* ========================================================================
yuuji@0 2 * Copyright 1988-2008 University of Washington
yuuji@0 3 *
yuuji@0 4 * Licensed under the Apache License, Version 2.0 (the "License");
yuuji@0 5 * you may not use this file except in compliance with the License.
yuuji@0 6 * You may obtain a copy of the License at
yuuji@0 7 *
yuuji@0 8 * http://www.apache.org/licenses/LICENSE-2.0
yuuji@0 9 *
yuuji@0 10 *
yuuji@0 11 * ========================================================================
yuuji@0 12 */
yuuji@0 13
yuuji@0 14 /*
yuuji@0 15 * Program: Amiga environment routines
yuuji@0 16 *
yuuji@0 17 * Author: Mark Crispin
yuuji@0 18 * UW Technology
yuuji@0 19 * Seattle, WA 98195
yuuji@0 20 * Internet: MRC@Washington.EDU
yuuji@0 21 *
yuuji@0 22 * Date: 1 August 1988
yuuji@0 23 * Last Edited: 15 May 2008
yuuji@0 24 */
yuuji@0 25
yuuji@0 26 #include <grp.h>
yuuji@0 27 #include <signal.h>
yuuji@0 28 #include <sys/wait.h>
yuuji@0 29
yuuji@0 30 /* c-client environment parameters */
yuuji@0 31
yuuji@0 32 static char *myUserName = NIL; /* user name */
yuuji@0 33 static char *myHomeDir = NIL; /* home directory name */
yuuji@0 34 static char *myMailboxDir = NIL;/* mailbox directory name */
yuuji@0 35 static char *myLocalHost = NIL; /* local host name */
yuuji@0 36 static char *myNewsrc = NIL; /* newsrc file name */
yuuji@0 37 static char *mailsubdir = NIL; /* mail subdirectory name */
yuuji@0 38 static char *sysInbox = NIL; /* system inbox name */
yuuji@0 39 static char *newsActive = NIL; /* news active file */
yuuji@0 40 static char *newsSpool = NIL; /* news spool */
yuuji@0 41 /* anonymous home directory */
yuuji@0 42 static char *anonymousHome = NIL;
yuuji@0 43 static char *ftpHome = NIL; /* ftp export home directory */
yuuji@0 44 static char *publicHome = NIL; /* public home directory */
yuuji@0 45 static char *sharedHome = NIL; /* shared home directory */
yuuji@0 46 static short anonymous = NIL; /* is anonymous */
yuuji@0 47 static short restrictBox = NIL; /* is a restricted box */
yuuji@0 48 static short has_no_life = NIL; /* is a cretin with no life */
yuuji@0 49 /* block environment init */
yuuji@0 50 static short block_env_init = NIL;
yuuji@0 51 static short hideDotFiles = NIL;/* hide files whose names start with . */
yuuji@0 52 /* advertise filesystem root */
yuuji@0 53 static short advertisetheworld = NIL;
yuuji@0 54 /* disable automatic shared namespaces */
yuuji@0 55 static short noautomaticsharedns = NIL;
yuuji@0 56 static short no822tztext = NIL; /* disable RFC [2]822 timezone text */
yuuji@0 57 static short netfsstatbug = NIL;/* compensate for broken stat() on network
yuuji@0 58 * filesystems (AFS and old NFS). Don't do
yuuji@0 59 * this unless you really have to!
yuuji@0 60 */
yuuji@0 61 /* 1 = disable plaintext, 2 = if not SSL */
yuuji@0 62 static long disablePlaintext = NIL;
yuuji@0 63 static long list_max_level = 20;/* maximum level of list recursion */
yuuji@0 64 /* default file protection */
yuuji@0 65 static long mbx_protection = 0600;
yuuji@0 66 /* default directory protection */
yuuji@0 67 static long dir_protection = 0700;
yuuji@0 68 /* default lock file protection */
yuuji@0 69 static long lock_protection = MANDATORYLOCKPROT;
yuuji@0 70 /* default ftp file protection */
yuuji@0 71 static long ftp_protection = 0644;
yuuji@0 72 static long ftp_dir_protection = 0755;
yuuji@0 73 /* default public file protection */
yuuji@0 74 static long public_protection = 0666;
yuuji@0 75 static long public_dir_protection = 0777;
yuuji@0 76 /* default shared file protection */
yuuji@0 77 static long shared_protection = 0660;
yuuji@0 78 static long shared_dir_protection = 0770;
yuuji@0 79 static long locktimeout = 5; /* default lock timeout */
yuuji@0 80 /* default prototypes */
yuuji@0 81 static MAILSTREAM *createProto = NIL;
yuuji@0 82 static MAILSTREAM *appendProto = NIL;
yuuji@0 83 /* default user flags */
yuuji@0 84 static char *userFlags[NUSERFLAGS] = {NIL};
yuuji@0 85 static NAMESPACE *nslist[3]; /* namespace list */
yuuji@0 86 static int logtry = 3; /* number of server login tries */
yuuji@0 87 /* block notification */
yuuji@0 88 static blocknotify_t mailblocknotify = mm_blocknotify;
yuuji@0 89
yuuji@0 90 /* Amiga namespaces */
yuuji@0 91
yuuji@0 92 /* personal mh namespace */
yuuji@0 93 static NAMESPACE nsmhf = {"#mh/",'/',NIL,NIL};
yuuji@0 94 static NAMESPACE nsmh = {"#mhinbox",NIL,NIL,&nsmhf};
yuuji@0 95 /* home namespace */
yuuji@0 96 static NAMESPACE nshome = {"",'/',NIL,&nsmh};
yuuji@0 97 /* Amiga other user namespace */
yuuji@0 98 static NAMESPACE nsamigaother = {"~",'/',NIL,NIL};
yuuji@0 99 /* public (anonymous OK) namespace */
yuuji@0 100 static NAMESPACE nspublic = {"#public/",'/',NIL,NIL};
yuuji@0 101 /* netnews namespace */
yuuji@0 102 static NAMESPACE nsnews = {"#news.",'.',NIL,&nspublic};
yuuji@0 103 /* FTP export namespace */
yuuji@0 104 static NAMESPACE nsftp = {"#ftp/",'/',NIL,&nsnews};
yuuji@0 105 /* shared (no anonymous) namespace */
yuuji@0 106 static NAMESPACE nsshared = {"#shared/",'/',NIL,&nsftp};
yuuji@0 107 /* world namespace */
yuuji@0 108 static NAMESPACE nsworld = {"/",'/',NIL,&nsshared};
yuuji@0 109
yuuji@0 110 #include "write.c" /* include safe writing routines */
yuuji@0 111 #include "pmatch.c" /* include wildcard pattern matcher */
yuuji@0 112
yuuji@0 113 /* Get all authenticators */
yuuji@0 114
yuuji@0 115 #include "auths.c"
yuuji@0 116
yuuji@0 117 /* Environment manipulate parameters
yuuji@0 118 * Accepts: function code
yuuji@0 119 * function-dependent value
yuuji@0 120 * Returns: function-dependent return value
yuuji@0 121 */
yuuji@0 122
yuuji@0 123 void *env_parameters (long function,void *value)
yuuji@0 124 {
yuuji@0 125 void *ret = NIL;
yuuji@0 126 switch ((int) function) {
yuuji@0 127 case GET_NAMESPACE:
yuuji@0 128 ret = (void *) nslist;
yuuji@0 129 break;
yuuji@0 130 case SET_USERNAME:
yuuji@0 131 if (myUserName) fs_give ((void **) &myUserName);
yuuji@0 132 myUserName = cpystr ((char *) value);
yuuji@0 133 case GET_USERNAME:
yuuji@0 134 ret = (void *) myUserName;
yuuji@0 135 break;
yuuji@0 136 case SET_HOMEDIR:
yuuji@0 137 if (myHomeDir) fs_give ((void **) &myHomeDir);
yuuji@0 138 myHomeDir = cpystr ((char *) value);
yuuji@0 139 case GET_HOMEDIR:
yuuji@0 140 ret = (void *) myHomeDir;
yuuji@0 141 break;
yuuji@0 142 case SET_LOCALHOST:
yuuji@0 143 if (myLocalHost) fs_give ((void **) &myLocalHost);
yuuji@0 144 myLocalHost = cpystr ((char *) value);
yuuji@0 145 case GET_LOCALHOST:
yuuji@0 146 ret = (void *) myLocalHost;
yuuji@0 147 break;
yuuji@0 148 case SET_NEWSRC:
yuuji@0 149 if (myNewsrc) fs_give ((void **) &myNewsrc);
yuuji@0 150 myNewsrc = cpystr ((char *) value);
yuuji@0 151 case GET_NEWSRC:
yuuji@0 152 ret = (void *) myNewsrc;
yuuji@0 153 break;
yuuji@0 154 case SET_NEWSACTIVE:
yuuji@0 155 if (newsActive) fs_give ((void **) &newsActive);
yuuji@0 156 newsActive = cpystr ((char *) value);
yuuji@0 157 case GET_NEWSACTIVE:
yuuji@0 158 ret = (void *) newsActive;
yuuji@0 159 break;
yuuji@0 160 case SET_NEWSSPOOL:
yuuji@0 161 if (newsSpool) fs_give ((void **) &newsSpool);
yuuji@0 162 newsSpool = cpystr ((char *) value);
yuuji@0 163 case GET_NEWSSPOOL:
yuuji@0 164 ret = (void *) newsSpool;
yuuji@0 165 break;
yuuji@0 166
yuuji@0 167 case SET_ANONYMOUSHOME:
yuuji@0 168 if (anonymousHome) fs_give ((void **) &anonymousHome);
yuuji@0 169 anonymousHome = cpystr ((char *) value);
yuuji@0 170 case GET_ANONYMOUSHOME:
yuuji@0 171 if (!anonymousHome) anonymousHome = cpystr (ANONYMOUSHOME);
yuuji@0 172 ret = (void *) anonymousHome;
yuuji@0 173 break;
yuuji@0 174 case SET_FTPHOME:
yuuji@0 175 if (ftpHome) fs_give ((void **) &ftpHome);
yuuji@0 176 ftpHome = cpystr ((char *) value);
yuuji@0 177 case GET_FTPHOME:
yuuji@0 178 ret = (void *) ftpHome;
yuuji@0 179 break;
yuuji@0 180 case SET_PUBLICHOME:
yuuji@0 181 if (publicHome) fs_give ((void **) &publicHome);
yuuji@0 182 publicHome = cpystr ((char *) value);
yuuji@0 183 case GET_PUBLICHOME:
yuuji@0 184 ret = (void *) publicHome;
yuuji@0 185 break;
yuuji@0 186 case SET_SHAREDHOME:
yuuji@0 187 if (sharedHome) fs_give ((void **) &sharedHome);
yuuji@0 188 sharedHome = cpystr ((char *) value);
yuuji@0 189 case GET_SHAREDHOME:
yuuji@0 190 ret = (void *) sharedHome;
yuuji@0 191 break;
yuuji@0 192 case SET_SYSINBOX:
yuuji@0 193 if (sysInbox) fs_give ((void **) &sysInbox);
yuuji@0 194 sysInbox = cpystr ((char *) value);
yuuji@0 195 case GET_SYSINBOX:
yuuji@0 196 ret = (void *) sysInbox;
yuuji@0 197 break;
yuuji@0 198 case SET_LISTMAXLEVEL:
yuuji@0 199 list_max_level = (long) value;
yuuji@0 200 case GET_LISTMAXLEVEL:
yuuji@0 201 ret = (void *) list_max_level;
yuuji@0 202 break;
yuuji@0 203
yuuji@0 204 case SET_MBXPROTECTION:
yuuji@0 205 mbx_protection = (long) value;
yuuji@0 206 case GET_MBXPROTECTION:
yuuji@0 207 ret = (void *) mbx_protection;
yuuji@0 208 break;
yuuji@0 209 case SET_DIRPROTECTION:
yuuji@0 210 dir_protection = (long) value;
yuuji@0 211 case GET_DIRPROTECTION:
yuuji@0 212 ret = (void *) dir_protection;
yuuji@0 213 break;
yuuji@0 214 case SET_LOCKPROTECTION:
yuuji@0 215 lock_protection = (long) value;
yuuji@0 216 case GET_LOCKPROTECTION:
yuuji@0 217 ret = (void *) lock_protection;
yuuji@0 218 break;
yuuji@0 219 case SET_FTPPROTECTION:
yuuji@0 220 ftp_protection = (long) value;
yuuji@0 221 case GET_FTPPROTECTION:
yuuji@0 222 ret = (void *) ftp_protection;
yuuji@0 223 break;
yuuji@0 224 case SET_PUBLICPROTECTION:
yuuji@0 225 public_protection = (long) value;
yuuji@0 226 case GET_PUBLICPROTECTION:
yuuji@0 227 ret = (void *) public_protection;
yuuji@0 228 break;
yuuji@0 229 case SET_SHAREDPROTECTION:
yuuji@0 230 shared_protection = (long) value;
yuuji@0 231 case GET_SHAREDPROTECTION:
yuuji@0 232 ret = (void *) shared_protection;
yuuji@0 233 break;
yuuji@0 234 case SET_FTPDIRPROTECTION:
yuuji@0 235 ftp_dir_protection = (long) value;
yuuji@0 236 case GET_FTPDIRPROTECTION:
yuuji@0 237 ret = (void *) ftp_dir_protection;
yuuji@0 238 break;
yuuji@0 239 case SET_PUBLICDIRPROTECTION:
yuuji@0 240 public_dir_protection = (long) value;
yuuji@0 241 case GET_PUBLICDIRPROTECTION:
yuuji@0 242 ret = (void *) public_dir_protection;
yuuji@0 243 break;
yuuji@0 244 case SET_SHAREDDIRPROTECTION:
yuuji@0 245 shared_dir_protection = (long) value;
yuuji@0 246 case GET_SHAREDDIRPROTECTION:
yuuji@0 247 ret = (void *) shared_dir_protection;
yuuji@0 248 break;
yuuji@0 249
yuuji@0 250 case SET_LOCKTIMEOUT:
yuuji@0 251 locktimeout = (long) value;
yuuji@0 252 case GET_LOCKTIMEOUT:
yuuji@0 253 ret = (void *) locktimeout;
yuuji@0 254 break;
yuuji@0 255 case SET_HIDEDOTFILES:
yuuji@0 256 hideDotFiles = value ? T : NIL;
yuuji@0 257 case GET_HIDEDOTFILES:
yuuji@0 258 ret = (void *) (hideDotFiles ? VOIDT : NIL);
yuuji@0 259 break;
yuuji@0 260 case SET_DISABLEPLAINTEXT:
yuuji@0 261 disablePlaintext = (long) value;
yuuji@0 262 case GET_DISABLEPLAINTEXT:
yuuji@0 263 ret = (void *) disablePlaintext;
yuuji@0 264 break;
yuuji@0 265 case SET_ADVERTISETHEWORLD:
yuuji@0 266 advertisetheworld = value ? T : NIL;
yuuji@0 267 case GET_ADVERTISETHEWORLD:
yuuji@0 268 ret = (void *) (advertisetheworld ? VOIDT : NIL);
yuuji@0 269 break;
yuuji@0 270 case SET_DISABLEAUTOSHAREDNS:
yuuji@0 271 noautomaticsharedns = value ? T : NIL;
yuuji@0 272 case GET_DISABLEAUTOSHAREDNS:
yuuji@0 273 ret = (void *) (noautomaticsharedns ? VOIDT : NIL);
yuuji@0 274 break;
yuuji@0 275 case SET_DISABLE822TZTEXT:
yuuji@0 276 no822tztext = value ? T : NIL;
yuuji@0 277 case GET_DISABLE822TZTEXT:
yuuji@0 278 ret = (void *) (no822tztext ? VOIDT : NIL);
yuuji@0 279 break;
yuuji@0 280 case SET_USERHASNOLIFE:
yuuji@0 281 has_no_life = value ? T : NIL;
yuuji@0 282 case GET_USERHASNOLIFE:
yuuji@0 283 ret = (void *) (has_no_life ? VOIDT : NIL);
yuuji@0 284 break;
yuuji@0 285 case SET_NETFSSTATBUG:
yuuji@0 286 netfsstatbug = value ? T : NIL;
yuuji@0 287 case GET_NETFSSTATBUG:
yuuji@0 288 ret = (void *) (netfsstatbug ? VOIDT : NIL);
yuuji@0 289 break;
yuuji@0 290 case SET_BLOCKENVINIT:
yuuji@0 291 block_env_init = value ? T : NIL;
yuuji@0 292 case GET_BLOCKENVINIT:
yuuji@0 293 ret = (void *) (block_env_init ? VOIDT : NIL);
yuuji@0 294 break;
yuuji@0 295 case SET_BLOCKNOTIFY:
yuuji@0 296 mailblocknotify = (blocknotify_t) value;
yuuji@0 297 case GET_BLOCKNOTIFY:
yuuji@0 298 ret = (void *) mailblocknotify;
yuuji@0 299 break;
yuuji@0 300 }
yuuji@0 301 return ret;
yuuji@0 302 }
yuuji@0 303
yuuji@0 304 /* Write current time
yuuji@0 305 * Accepts: destination string
yuuji@0 306 * optional format of day-of-week prefix
yuuji@0 307 * format of date and time
yuuji@0 308 * flag whether to append symbolic timezone
yuuji@0 309 */
yuuji@0 310
yuuji@0 311 static void do_date (char *date,char *prefix,char *fmt,int suffix)
yuuji@0 312 {
yuuji@0 313 time_t tn = time (0);
yuuji@0 314 struct tm *t = gmtime (&tn);
yuuji@0 315 int zone = t->tm_hour * 60 + t->tm_min;
yuuji@0 316 int julian = t->tm_yday;
yuuji@0 317 t = localtime (&tn); /* get local time now */
yuuji@0 318 /* minus UTC minutes since midnight */
yuuji@0 319 zone = t->tm_hour * 60 + t->tm_min - zone;
yuuji@0 320 /* julian can be one of:
yuuji@0 321 * 36x local time is December 31, UTC is January 1, offset -24 hours
yuuji@0 322 * 1 local time is 1 day ahead of UTC, offset +24 hours
yuuji@0 323 * 0 local time is same day as UTC, no offset
yuuji@0 324 * -1 local time is 1 day behind UTC, offset -24 hours
yuuji@0 325 * -36x local time is January 1, UTC is December 31, offset +24 hours
yuuji@0 326 */
yuuji@0 327 if (julian = t->tm_yday -julian)
yuuji@0 328 zone += ((julian < 0) == (abs (julian) == 1)) ? -24*60 : 24*60;
yuuji@0 329 if (prefix) { /* want day of week? */
yuuji@0 330 sprintf (date,prefix,days[t->tm_wday]);
yuuji@0 331 date += strlen (date); /* make next sprintf append */
yuuji@0 332 }
yuuji@0 333 /* output the date */
yuuji@0 334 sprintf (date,fmt,t->tm_mday,months[t->tm_mon],t->tm_year+1900,
yuuji@0 335 t->tm_hour,t->tm_min,t->tm_sec,zone/60,abs (zone) % 60);
yuuji@0 336 /* append timezone suffix if desired */
yuuji@0 337 if (suffix) rfc822_timezone (date,(void *) t);
yuuji@0 338 }
yuuji@0 339
yuuji@0 340 /* Write current time in RFC 822 format
yuuji@0 341 * Accepts: destination string
yuuji@0 342 */
yuuji@0 343
yuuji@0 344 void rfc822_date (char *date)
yuuji@0 345 {
yuuji@0 346 do_date (date,"%s, ","%d %s %d %02d:%02d:%02d %+03d%02d",
yuuji@0 347 no822tztext ? NIL : T);
yuuji@0 348 }
yuuji@0 349
yuuji@0 350
yuuji@0 351 /* Write current time in fixed-width RFC 822 format
yuuji@0 352 * Accepts: destination string
yuuji@0 353 */
yuuji@0 354
yuuji@0 355 void rfc822_fixed_date (char *date)
yuuji@0 356 {
yuuji@0 357 do_date (date,NIL,"%02d %s %4d %02d:%02d:%02d %+03d%02d",NIL);
yuuji@0 358 }
yuuji@0 359
yuuji@0 360
yuuji@0 361 /* Write current time in internal format
yuuji@0 362 * Accepts: destination string
yuuji@0 363 */
yuuji@0 364
yuuji@0 365 void internal_date (char *date)
yuuji@0 366 {
yuuji@0 367 do_date (date,NIL,"%02d-%s-%d %02d:%02d:%02d %+03d%02d",NIL);
yuuji@0 368 }
yuuji@0 369
yuuji@0 370 /* Initialize server
yuuji@0 371 * Accepts: server name for syslog or NIL
yuuji@0 372 * /etc/services service name or NIL
yuuji@0 373 * alternate /etc/services service name or NIL
yuuji@0 374 * clock interrupt handler
yuuji@0 375 * kiss-of-death interrupt handler
yuuji@0 376 * hangup interrupt handler
yuuji@0 377 * termination interrupt handler
yuuji@0 378 */
yuuji@0 379
yuuji@0 380 void server_init (char *server,char *service,char *sslservice,
yuuji@0 381 void *clkint,void *kodint,void *hupint,void *trmint,
yuuji@0 382 void *staint)
yuuji@0 383 {
yuuji@0 384 int onceonly = server && service && sslservice;
yuuji@0 385 if (onceonly) { /* set server name in syslog */
yuuji@0 386 int mask;
yuuji@0 387 openlog (myServerName = cpystr (server),LOG_PID,syslog_facility);
yuuji@0 388 fclose (stderr); /* possibly save a process ID */
yuuji@0 389
yuuji@0 390 switch (mask = umask (022)){/* check old umask */
yuuji@0 391 case 0: /* definitely unreasonable */
yuuji@0 392 case 022: /* don't need to change it */
yuuji@0 393 break;
yuuji@0 394 default: /* already was a reasonable value */
yuuji@0 395 umask (mask); /* so change it back */
yuuji@0 396 }
yuuji@0 397 }
yuuji@0 398 arm_signal (SIGALRM,clkint); /* prepare for clock interrupt */
yuuji@0 399 arm_signal (SIGUSR2,kodint); /* prepare for Kiss Of Death */
yuuji@0 400 arm_signal (SIGHUP,hupint); /* prepare for hangup */
yuuji@0 401 arm_signal (SIGPIPE,hupint); /* alternative hangup */
yuuji@0 402 arm_signal (SIGTERM,trmint); /* prepare for termination */
yuuji@0 403 /* status dump */
yuuji@0 404 if (staint) arm_signal (SIGUSR1,staint);
yuuji@0 405 if (onceonly) { /* set up network and maybe SSL */
yuuji@0 406 long port;
yuuji@0 407 struct servent *sv;
yuuji@0 408 /* Use SSL if SSL service, or if server starts with "s" and not service */
yuuji@0 409 if (((port = tcp_serverport ()) >= 0)) {
yuuji@0 410 if ((sv = getservbyname (service,"tcp")) && (port == ntohs (sv->s_port)))
yuuji@0 411 syslog (LOG_DEBUG,"%s service init from %s",service,tcp_clientaddr ());
yuuji@0 412 else if ((sv = getservbyname (sslservice,"tcp")) &&
yuuji@0 413 (port == ntohs (sv->s_port))) {
yuuji@0 414 syslog (LOG_DEBUG,"%s SSL service init from %s",sslservice,
yuuji@0 415 tcp_clientaddr ());
yuuji@0 416 ssl_server_init (server);
yuuji@0 417 }
yuuji@0 418 else { /* not service or SSL service port */
yuuji@0 419 syslog (LOG_DEBUG,"port %ld service init from %s",port,
yuuji@0 420 tcp_clientaddr ());
yuuji@0 421 if (*server == 's') ssl_server_init (server);
yuuji@0 422 }
yuuji@0 423 }
yuuji@0 424 }
yuuji@0 425 }
yuuji@0 426
yuuji@0 427 /* Wait for stdin input
yuuji@0 428 * Accepts: timeout in seconds
yuuji@0 429 * Returns: T if have input on stdin, else NIL
yuuji@0 430 */
yuuji@0 431
yuuji@0 432 long server_input_wait (long seconds)
yuuji@0 433 {
yuuji@0 434 fd_set rfd,efd;
yuuji@0 435 struct timeval tmo;
yuuji@0 436 FD_ZERO (&rfd);
yuuji@0 437 FD_ZERO (&efd);
yuuji@0 438 FD_SET (0,&rfd);
yuuji@0 439 FD_SET (0,&efd);
yuuji@0 440 tmo.tv_sec = seconds; tmo.tv_usec = 0;
yuuji@0 441 return select (1,&rfd,0,&efd,&tmo) ? LONGT : NIL;
yuuji@0 442 }
yuuji@0 443
yuuji@0 444 /* Return Amiga password entry for user name
yuuji@0 445 * Accepts: user name string
yuuji@0 446 * Returns: password entry
yuuji@0 447 *
yuuji@0 448 * Tries all-lowercase form of user name if given user name fails
yuuji@0 449 */
yuuji@0 450
yuuji@0 451 static struct passwd *pwuser (unsigned char *user)
yuuji@0 452 {
yuuji@0 453 unsigned char *s;
yuuji@0 454 struct passwd *pw = getpwnam (user);
yuuji@0 455 if (!pw) { /* failed, see if any uppercase characters */
yuuji@0 456 for (s = user; *s && ((*s < 'A') || (*s > 'Z')); s++);
yuuji@0 457 if (*s) { /* yes, try all lowercase form */
yuuji@0 458 pw = getpwnam (s = lcase (cpystr (user)));
yuuji@0 459 fs_give ((void **) &s);
yuuji@0 460 }
yuuji@0 461 }
yuuji@0 462 return pw;
yuuji@0 463 }
yuuji@0 464
yuuji@0 465
yuuji@0 466 /* Validate password for user name
yuuji@0 467 * Accepts: user name string
yuuji@0 468 * password string
yuuji@0 469 * argument count
yuuji@0 470 * argument vector
yuuji@0 471 * Returns: password entry if validated
yuuji@0 472 *
yuuji@0 473 * Tries password+1 if password fails and starts with space
yuuji@0 474 */
yuuji@0 475
yuuji@0 476 static struct passwd *valpwd (char *user,char *pwd,int argc,char *argv[])
yuuji@0 477 {
yuuji@0 478 char *s;
yuuji@0 479 struct passwd *pw;
yuuji@0 480 struct passwd *ret = NIL;
yuuji@0 481 if (auth_md5.server) { /* using CRAM-MD5 authentication? */
yuuji@0 482 if (s = auth_md5_pwd (user)) {
yuuji@0 483 if (!strcmp (s,pwd) || ((*pwd == ' ') && pwd[1] && !strcmp (s,pwd+1)))
yuuji@0 484 ret = pwuser (user); /* validated, get passwd entry for user */
yuuji@0 485 memset (s,0,strlen (s)); /* erase sensitive information */
yuuji@0 486 fs_give ((void **) &s);
yuuji@0 487 }
yuuji@0 488 }
yuuji@0 489 else if (pw = pwuser (user)) {/* can get user? */
yuuji@0 490 s = cpystr (pw->pw_name); /* copy returned name in case we need it */
yuuji@0 491 if (*pwd && !(ret = checkpw (pw,pwd,argc,argv)) &&
yuuji@0 492 (*pwd == ' ') && pwd[1] && (ret = pwuser (s)))
yuuji@0 493 ret = checkpw (pw,pwd+1,argc,argv);
yuuji@0 494 fs_give ((void **) &s); /* don't need copy of name any more */
yuuji@0 495 }
yuuji@0 496 return ret;
yuuji@0 497 }
yuuji@0 498
yuuji@0 499 /* Server log in
yuuji@0 500 * Accepts: user name string
yuuji@0 501 * password string
yuuji@0 502 * authenticating user name string
yuuji@0 503 * argument count
yuuji@0 504 * argument vector
yuuji@0 505 * Returns: T if password validated, NIL otherwise
yuuji@0 506 */
yuuji@0 507
yuuji@0 508 long server_login (char *user,char *pwd,char *authuser,int argc,char *argv[])
yuuji@0 509 {
yuuji@0 510 struct passwd *pw = NIL;
yuuji@0 511 int level = LOG_NOTICE;
yuuji@0 512 char *err = "failed";
yuuji@0 513 /* cretins still haven't given up */
yuuji@0 514 if ((strlen (user) >= NETMAXUSER) ||
yuuji@0 515 (authuser && (strlen (authuser) >= NETMAXUSER))) {
yuuji@0 516 level = LOG_ALERT; /* escalate this alert */
yuuji@0 517 err = "SYSTEM BREAK-IN ATTEMPT";
yuuji@0 518 logtry = 0; /* render this session useless */
yuuji@0 519 }
yuuji@0 520 else if (logtry-- <= 0) err = "excessive login failures";
yuuji@0 521 else if (disablePlaintext) err = "disabled";
yuuji@0 522 else if (!(authuser && *authuser)) pw = valpwd (user,pwd,argc,argv);
yuuji@0 523 else if (valpwd (authuser,pwd,argc,argv)) pw = pwuser (user);
yuuji@0 524 if (pw && pw_login (pw,authuser,pw->pw_name,NIL,argc,argv)) return T;
yuuji@0 525 syslog (level|LOG_AUTH,"Login %s user=%.64s auth=%.64s host=%.80s",err,
yuuji@0 526 user,(authuser && *authuser) ? authuser : user,tcp_clienthost ());
yuuji@0 527 sleep (3); /* slow down possible cracker */
yuuji@0 528 return NIL;
yuuji@0 529 }
yuuji@0 530
yuuji@0 531 /* Authenticated server log in
yuuji@0 532 * Accepts: user name string
yuuji@0 533 * authenticating user name string
yuuji@0 534 * argument count
yuuji@0 535 * argument vector
yuuji@0 536 * Returns: T if password validated, NIL otherwise
yuuji@0 537 */
yuuji@0 538
yuuji@0 539 long authserver_login (char *user,char *authuser,int argc,char *argv[])
yuuji@0 540 {
yuuji@0 541 return pw_login (pwuser (user),authuser,user,NIL,argc,argv);
yuuji@0 542 }
yuuji@0 543
yuuji@0 544
yuuji@0 545 /* Log in as anonymous daemon
yuuji@0 546 * Accepts: argument count
yuuji@0 547 * argument vector
yuuji@0 548 * Returns: T if successful, NIL if error
yuuji@0 549 */
yuuji@0 550
yuuji@0 551 long anonymous_login (int argc,char *argv[])
yuuji@0 552 {
yuuji@0 553 /* log in Mr. A. N. Onymous */
yuuji@0 554 return pw_login (getpwnam (ANONYMOUSUSER),NIL,NIL,
yuuji@0 555 (char *) mail_parameters (NIL,GET_ANONYMOUSHOME,NIL),
yuuji@0 556 argc,argv);
yuuji@0 557 }
yuuji@0 558
yuuji@0 559 /* Finish log in and environment initialization
yuuji@0 560 * Accepts: passwd struct for loginpw()
yuuji@0 561 * optional authentication user name
yuuji@0 562 * user name (NIL for anonymous)
yuuji@0 563 * home directory (NIL to use directory from passwd struct)
yuuji@0 564 * argument count
yuuji@0 565 * argument vector
yuuji@0 566 * Returns: T if successful, NIL if error
yuuji@0 567 */
yuuji@0 568
yuuji@0 569 long pw_login (struct passwd *pw,char *auser,char *user,char *home,int argc,
yuuji@0 570 char *argv[])
yuuji@0 571 {
yuuji@0 572 struct group *gr;
yuuji@0 573 char **t;
yuuji@0 574 long ret = NIL;
yuuji@0 575 if (pw && pw->pw_uid) { /* must have passwd struct for non-UID 0 */
yuuji@0 576 /* make safe copies of user and home */
yuuji@0 577 if (user) user = cpystr (pw->pw_name);
yuuji@0 578 home = cpystr (home ? home : pw->pw_dir);
yuuji@0 579 /* authorization ID .NE. authentication ID? */
yuuji@0 580 if (user && auser && *auser && compare_cstring (auser,user)) {
yuuji@0 581 /* scan list of mail administrators */
yuuji@0 582 if ((gr = getgrnam (ADMINGROUP)) && (t = gr->gr_mem)) while (*t && !ret)
yuuji@0 583 if (!compare_cstring (auser,*t++))
yuuji@0 584 ret = pw_login (pw,NIL,user,home,argc,argv);
yuuji@0 585 syslog (LOG_NOTICE|LOG_AUTH,"%s %.80s override of user=%.80s host=%.80s",
yuuji@0 586 ret ? "Admin" : "Failed",auser,user,tcp_clienthost ());
yuuji@0 587 }
yuuji@0 588 /* normal login */
yuuji@0 589 else if (((pw->pw_uid == geteuid ()) || loginpw (pw,argc,argv)) &&
yuuji@0 590 (ret = env_init (user,home))) chdir (myhomedir ());
yuuji@0 591 fs_give ((void **) &home); /* clean up */
yuuji@0 592 if (user) fs_give ((void **) &user);
yuuji@0 593 }
yuuji@0 594 return ret; /* return status */
yuuji@0 595 }
yuuji@0 596
yuuji@0 597 /* Initialize environment
yuuji@0 598 * Accepts: user name (NIL for anonymous)
yuuji@0 599 * home directory name
yuuji@0 600 * Returns: T, always
yuuji@0 601 */
yuuji@0 602
yuuji@0 603 long env_init (char *user,char *home)
yuuji@0 604 {
yuuji@0 605 extern MAILSTREAM CREATEPROTO;
yuuji@0 606 extern MAILSTREAM EMPTYPROTO;
yuuji@0 607 struct passwd *pw;
yuuji@0 608 struct stat sbuf;
yuuji@0 609 char tmp[MAILTMPLEN];
yuuji@0 610 /* don't init if blocked */
yuuji@0 611 if (block_env_init) return LONGT;
yuuji@0 612 if (myUserName) fatal ("env_init called twice!");
yuuji@0 613 /* set up user name */
yuuji@0 614 myUserName = cpystr (user ? user : ANONYMOUSUSER);
yuuji@0 615 if (user) { /* remember user name and home directory */
yuuji@0 616 nslist[0] = &nshome; /* home namespace */
yuuji@0 617 nslist[1] = &nsamigaother;
yuuji@0 618 nslist[2] = advertisetheworld ? &nsworld : &nsshared;
yuuji@0 619 }
yuuji@0 620 else { /* anonymous user */
yuuji@0 621 nslist[0] = nslist[1] = NIL,nslist[2] = &nsftp;
yuuji@0 622 sprintf (tmp,"%s/INBOX",
yuuji@0 623 home = (char *) mail_parameters (NIL,GET_ANONYMOUSHOME,NIL));
yuuji@0 624 sysInbox = cpystr (tmp); /* make system INBOX */
yuuji@0 625 anonymous = T; /* flag as anonymous */
yuuji@0 626 }
yuuji@0 627 myHomeDir = cpystr (home); /* set home directory */
yuuji@0 628 if (!noautomaticsharedns) {
yuuji@0 629 /* #ftp namespace */
yuuji@0 630 if (!ftpHome && (pw = getpwnam ("ftp"))) ftpHome = cpystr (pw->pw_dir);
yuuji@0 631 /* #public namespace */
yuuji@0 632 if (!publicHome && (pw = getpwnam ("imappublic")))
yuuji@0 633 publicHome = cpystr (pw->pw_dir);
yuuji@0 634 /* #shared namespace */
yuuji@0 635 if (!anonymous && !sharedHome && (pw = getpwnam ("imapshared")))
yuuji@0 636 sharedHome = cpystr (pw->pw_dir);
yuuji@0 637 }
yuuji@0 638 if (!myLocalHost) mylocalhost ();
yuuji@0 639 if (!myNewsrc) myNewsrc = cpystr(strcat (strcpy (tmp,myHomeDir),"/.newsrc"));
yuuji@0 640 if (!newsActive) newsActive = cpystr (ACTIVEFILE);
yuuji@0 641 if (!newsSpool) newsSpool = cpystr (NEWSSPOOL);
yuuji@0 642 /* force default prototype to be set */
yuuji@0 643 if (!createProto) createProto = &CREATEPROTO;
yuuji@0 644 if (!appendProto) appendProto = &EMPTYPROTO;
yuuji@0 645 /* re-do open action to get flags */
yuuji@0 646 (*createProto->dtb->open) (NIL);
yuuji@0 647 endpwent (); /* close pw database */
yuuji@0 648 return T;
yuuji@0 649 }
yuuji@0 650
yuuji@0 651 /* Return my user name
yuuji@0 652 * Accepts: pointer to optional flags
yuuji@0 653 * Returns: my user name
yuuji@0 654 */
yuuji@0 655
yuuji@0 656 char *myusername_full (unsigned long *flags)
yuuji@0 657 {
yuuji@0 658 struct passwd *pw;
yuuji@0 659 struct stat sbuf;
yuuji@0 660 char *s;
yuuji@0 661 unsigned long euid;
yuuji@0 662 char *ret = UNLOGGEDUSER;
yuuji@0 663 /* no user name yet and not root? */
yuuji@0 664 if (!myUserName && (euid = geteuid ())) {
yuuji@0 665 /* yes, look up getlogin() user name or EUID */
yuuji@0 666 if (((s = (char *) getlogin ()) && *s && (strlen (s) < NETMAXUSER) &&
yuuji@0 667 (pw = getpwnam (s)) && (pw->pw_uid == euid)) ||
yuuji@0 668 (pw = getpwuid (euid))) {
yuuji@0 669 if (block_env_init) { /* don't env_init if blocked */
yuuji@0 670 if (flags) *flags = MU_LOGGEDIN;
yuuji@0 671 return pw->pw_name;
yuuji@0 672 }
yuuji@0 673 env_init (pw->pw_name,
yuuji@0 674 ((s = getenv ("HOME")) && *s && (strlen (s) < NETMAXMBX) &&
yuuji@0 675 !stat (s,&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFDIR)) ?
yuuji@0 676 s : pw->pw_dir);
yuuji@0 677 }
yuuji@0 678 else fatal ("Unable to look up user name");
yuuji@0 679 }
yuuji@0 680 if (myUserName) { /* logged in? */
yuuji@0 681 if (flags) *flags = anonymous ? MU_ANONYMOUS : MU_LOGGEDIN;
yuuji@0 682 ret = myUserName; /* return user name */
yuuji@0 683 }
yuuji@0 684 else if (flags) *flags = MU_NOTLOGGEDIN;
yuuji@0 685 return ret;
yuuji@0 686 }
yuuji@0 687 /* Return my local host name
yuuji@0 688 * Returns: my local host name
yuuji@0 689 */
yuuji@0 690
yuuji@0 691 char *mylocalhost ()
yuuji@0 692 {
yuuji@0 693 char tmp[MAILTMPLEN];
yuuji@0 694 struct hostent *host_name;
yuuji@0 695 if (!myLocalHost) myLocalHost = cpystr (gethostname (tmp,MAILTMPLEN-1) ?
yuuji@0 696 "random-pc" : tcp_canonical (tmp));
yuuji@0 697 return myLocalHost;
yuuji@0 698 }
yuuji@0 699
yuuji@0 700 /* Return my home directory name
yuuji@0 701 * Returns: my home directory name
yuuji@0 702 */
yuuji@0 703
yuuji@0 704 char *myhomedir ()
yuuji@0 705 {
yuuji@0 706 if (!myHomeDir) myusername ();/* initialize if first time */
yuuji@0 707 return myHomeDir ? myHomeDir : "";
yuuji@0 708 }
yuuji@0 709
yuuji@0 710
yuuji@0 711 /* Return my home mailbox name
yuuji@0 712 * Returns: my home directory name
yuuji@0 713 */
yuuji@0 714
yuuji@0 715 static char *mymailboxdir ()
yuuji@0 716 {
yuuji@0 717 char *home = myhomedir ();
yuuji@0 718 if (!myMailboxDir && home) { /* initialize if first time */
yuuji@0 719 if (mailsubdir) {
yuuji@0 720 char tmp[MAILTMPLEN];
yuuji@0 721 sprintf (tmp,"%s/%s",home,mailsubdir);
yuuji@0 722 myMailboxDir = cpystr (tmp);/* use pre-defined subdirectory of home */
yuuji@0 723 }
yuuji@0 724 else myMailboxDir = cpystr (home);
yuuji@0 725 }
yuuji@0 726 return myMailboxDir ? myMailboxDir : "";
yuuji@0 727 }
yuuji@0 728
yuuji@0 729
yuuji@0 730 /* Return system standard INBOX
yuuji@0 731 * Accepts: buffer string
yuuji@0 732 */
yuuji@0 733
yuuji@0 734 char *sysinbox ()
yuuji@0 735 {
yuuji@0 736 char tmp[MAILTMPLEN];
yuuji@0 737 if (!sysInbox) { /* initialize if first time */
yuuji@0 738 sprintf (tmp,"%s/%s",MAILSPOOL,myusername ());
yuuji@0 739 sysInbox = cpystr (tmp); /* system inbox is from mail spool */
yuuji@0 740 }
yuuji@0 741 return sysInbox;
yuuji@0 742 }
yuuji@0 743
yuuji@0 744 /* Return mailbox directory name
yuuji@0 745 * Accepts: destination buffer
yuuji@0 746 * directory prefix
yuuji@0 747 * name in directory
yuuji@0 748 * Returns: file name or NIL if error
yuuji@0 749 */
yuuji@0 750
yuuji@0 751 char *mailboxdir (char *dst,char *dir,char *name)
yuuji@0 752 {
yuuji@0 753 char tmp[MAILTMPLEN];
yuuji@0 754 if (dir || name) { /* if either argument provided */
yuuji@0 755 if (dir) {
yuuji@0 756 if (strlen (dir) > NETMAXMBX) return NIL;
yuuji@0 757 strcpy (tmp,dir); /* write directory prefix */
yuuji@0 758 }
yuuji@0 759 else tmp[0] = '\0'; /* otherwise null string */
yuuji@0 760 if (name) {
yuuji@0 761 if (strlen (name) > NETMAXMBX) return NIL;
yuuji@0 762 strcat (tmp,name); /* write name in directory */
yuuji@0 763 }
yuuji@0 764 /* validate name, return its name */
yuuji@0 765 if (!mailboxfile (dst,tmp)) return NIL;
yuuji@0 766 }
yuuji@0 767 /* no arguments, wants mailbox directory */
yuuji@0 768 else strcpy (dst,mymailboxdir ());
yuuji@0 769 return dst; /* return the name */
yuuji@0 770 }
yuuji@0 771
yuuji@0 772 /* Return mailbox file name
yuuji@0 773 * Accepts: destination buffer
yuuji@0 774 * mailbox name
yuuji@0 775 * Returns: file name or empty string for driver-selected INBOX or NIL if error
yuuji@0 776 */
yuuji@0 777
yuuji@0 778 char *mailboxfile (char *dst,char *name)
yuuji@0 779 {
yuuji@0 780 struct passwd *pw;
yuuji@0 781 char *s;
yuuji@0 782 if (!name || !*name || (*name == '{') || (strlen (name) > NETMAXMBX) ||
yuuji@0 783 ((anonymous || restrictBox || (*name == '#')) &&
yuuji@0 784 (strstr (name,"..") || strstr (name,"//") || strstr (name,"/~"))))
yuuji@0 785 dst = NIL; /* invalid name */
yuuji@0 786 else switch (*name) { /* determine mailbox type based upon name */
yuuji@0 787 case '#': /* namespace name */
yuuji@0 788 /* #ftp/ namespace */
yuuji@0 789 if (((name[1] == 'f') || (name[1] == 'F')) &&
yuuji@0 790 ((name[2] == 't') || (name[2] == 'T')) &&
yuuji@0 791 ((name[3] == 'p') || (name[3] == 'P')) &&
yuuji@0 792 (name[4] == '/') && ftpHome) sprintf (dst,"%s/%s",ftpHome,name+5);
yuuji@0 793 /* #public/ and #shared/ namespaces */
yuuji@0 794 else if ((((name[1] == 'p') || (name[1] == 'P')) &&
yuuji@0 795 ((name[2] == 'u') || (name[2] == 'U')) &&
yuuji@0 796 ((name[3] == 'b') || (name[3] == 'B')) &&
yuuji@0 797 ((name[4] == 'l') || (name[4] == 'L')) &&
yuuji@0 798 ((name[5] == 'i') || (name[5] == 'I')) &&
yuuji@0 799 ((name[6] == 'c') || (name[6] == 'C')) &&
yuuji@0 800 (name[7] == '/') && (s = publicHome)) ||
yuuji@0 801 (!anonymous && ((name[1] == 's') || (name[1] == 'S')) &&
yuuji@0 802 ((name[2] == 'h') || (name[2] == 'H')) &&
yuuji@0 803 ((name[3] == 'a') || (name[3] == 'A')) &&
yuuji@0 804 ((name[4] == 'r') || (name[4] == 'R')) &&
yuuji@0 805 ((name[5] == 'e') || (name[5] == 'E')) &&
yuuji@0 806 ((name[6] == 'd') || (name[6] == 'D')) &&
yuuji@0 807 (name[7] == '/') && (s = sharedHome)))
yuuji@0 808 sprintf (dst,"%s/%s",s,compare_cstring (name,"INBOX") ? name : "INBOX");
yuuji@0 809 else dst = NIL; /* unknown namespace */
yuuji@0 810 break;
yuuji@0 811
yuuji@0 812 case '/': /* root access */
yuuji@0 813 if (anonymous) dst = NIL; /* anonymous forbidden to do this */
yuuji@0 814 else if ((restrictBox & RESTRICTROOT) && strcmp (name,sysinbox ()))
yuuji@0 815 dst = NIL; /* restricted and not access to sysinbox */
yuuji@0 816 else strcpy (dst,name); /* unrestricted, copy root name */
yuuji@0 817 break;
yuuji@0 818 case '~': /* other user access */
yuuji@0 819 /* bad syntax or anonymous can't win */
yuuji@0 820 if (!*++name || anonymous) dst = NIL;
yuuji@0 821 /* ~/ equivalent to ordinary name */
yuuji@0 822 else if (*name == '/') sprintf (dst,"%s/%s",mymailboxdir (),name+1);
yuuji@0 823 /* other user forbidden if restricted */
yuuji@0 824 else if (restrictBox & RESTRICTOTHERUSER) dst = NIL;
yuuji@0 825 else { /* clear box other user */
yuuji@0 826 /* copy user name */
yuuji@0 827 for (s = dst; *name && (*name != '/'); *s++ = *name++);
yuuji@0 828 *s++ = '\0'; /* tie off user name, look up in passwd file */
yuuji@0 829 if ((pw = getpwnam (dst)) && pw->pw_dir) {
yuuji@0 830 if (*name) name++; /* skip past the slash */
yuuji@0 831 /* canonicalize case of INBOX */
yuuji@0 832 if (!compare_cstring (name,"INBOX")) name = "INBOX";
yuuji@0 833 /* remove trailing / from directory */
yuuji@0 834 if ((s = strrchr (pw->pw_dir,'/')) && !s[1]) *s = '\0';
yuuji@0 835 /* don't allow ~root/ if restricted root */
yuuji@0 836 if ((restrictBox & RESTRICTROOT) && !*pw->pw_dir) dst = NIL;
yuuji@0 837 /* build final name w/ subdir if needed */
yuuji@0 838 else if (mailsubdir) sprintf (dst,"%s/%s/%s",pw->pw_dir,mailsubdir,name);
yuuji@0 839 else sprintf (dst,"%s/%s",pw->pw_dir,name);
yuuji@0 840 }
yuuji@0 841 else dst = NIL; /* no such user */
yuuji@0 842 }
yuuji@0 843 break;
yuuji@0 844 case 'I': case 'i': /* possible INBOX */
yuuji@0 845 if (!compare_cstring (name+1,"NBOX")) {
yuuji@0 846 /* if anonymous, use INBOX in mailbox dir */
yuuji@0 847 if (anonymous) sprintf (dst,"%s/INBOX",mymailboxdir ());
yuuji@0 848 else *dst = '\0'; /* otherwise driver selects the name */
yuuji@0 849 break;
yuuji@0 850 }
yuuji@0 851 /* drop into to ordinary name case */
yuuji@0 852 default: /* ordinary name is easy */
yuuji@0 853 sprintf (dst,"%s/%s",mymailboxdir (),name);
yuuji@0 854 break;
yuuji@0 855 }
yuuji@0 856 return dst; /* return final name */
yuuji@0 857 }
yuuji@0 858
yuuji@0 859 /* Dot-lock file locker
yuuji@0 860 * Accepts: file name to lock
yuuji@0 861 * destination buffer for lock file name
yuuji@0 862 * open file description on file name to lock
yuuji@0 863 * Returns: T if success, NIL if failure
yuuji@0 864 */
yuuji@0 865
yuuji@0 866 long dotlock_lock (char *file,DOTLOCK *base,int fd)
yuuji@0 867 {
yuuji@0 868 int i = locktimeout * 60;
yuuji@0 869 int j,mask,retry,pi[2],po[2];
yuuji@0 870 char *s,tmp[MAILTMPLEN];
yuuji@0 871 struct stat sb;
yuuji@0 872 /* flush absurd file name */
yuuji@0 873 if (strlen (file) > 512) return NIL;
yuuji@0 874 /* build lock filename */
yuuji@0 875 sprintf (base->lock,"%s.lock",file);
yuuji@0 876 /* assume no pipe */
yuuji@0 877 base->pipei = base->pipeo = -1;
yuuji@0 878 do { /* make sure not symlink */
yuuji@0 879 if (!(j = chk_notsymlink (base->lock,&sb))) return NIL;
yuuji@0 880 /* time out if file older than 5 minutes */
yuuji@0 881 if ((j > 0) && ((time (0)) >= (sb.st_ctime + locktimeout * 60))) i = 0;
yuuji@0 882 /* try to create the lock */
yuuji@0 883 if ((j = open (name,O_WRONLY|O_CREAT|O_EXCL,(int) lock_protection)) >= 0) {
yuuji@0 884 close (i); /* make the file, now close it */
yuuji@0 885 chmod (base->lock,(int) lock_protection);
yuuji@0 886 return LONGT;
yuuji@0 887 }
yuuji@0 888 if (errno == EEXIST) { /* already locked? */
yuuji@0 889 retry = -1; /* can try again */
yuuji@0 890 if (!(i%15)) { /* time to notify? */
yuuji@0 891 sprintf (tmp,"Mailbox %.80s is locked, will override in %d seconds...",
yuuji@0 892 file,i);
yuuji@0 893 mm_log (tmp,WARN);
yuuji@0 894 }
yuuji@0 895 sleep (1); /* wait 1 second before next try */
yuuji@0 896 }
yuuji@0 897 else retry = i = 0; /* hard failure, no more retries */
yuuji@0 898 } while (i--); /* until out of retries */
yuuji@0 899 if (retry < 0) { /* still returning retry after locktimeout? */
yuuji@0 900 if (!(j = chk_notsymlink (base->lock,&sb))) return NIL;
yuuji@0 901 if ((j > 0) && ((time (0)) < (sb.st_ctime + locktimeout * 60))) {
yuuji@0 902 sprintf (tmp,"Mailbox vulnerable - seizing %ld second old lock",
yuuji@0 903 (long) (time (0) - sb.st_ctime));
yuuji@0 904 mm_log (tmp,WARN);
yuuji@0 905 }
yuuji@0 906 mask = umask (0);
yuuji@0 907 unlink (base->lock); /* try to remove the old file */
yuuji@0 908 /* seize the lock */
yuuji@0 909 if ((i = open (base->lock,O_WRONLY|O_CREAT,(int) lock_protection)) >= 0) {
yuuji@0 910 close (i); /* don't need descriptor any more */
yuuji@0 911 sprintf (tmp,"Mailbox %.80s lock overridden",file);
yuuji@0 912 mm_log (tmp,NIL);
yuuji@0 913 chmod (base->lock,(int) lock_protection);
yuuji@0 914 umask (mask) /* restore old umask */
yuuji@0 915 return LONGT;
yuuji@0 916 }
yuuji@0 917 umask (mask) /* restore old umask */
yuuji@0 918 }
yuuji@0 919
yuuji@0 920 if (fd >= 0) switch (errno) {
yuuji@0 921 case EACCES: /* protection failure? */
yuuji@0 922 /* make command pipes */
yuuji@0 923 if (!stat (LOCKPGM,&sb) && (pipe (pi) >= 0)) {
yuuji@0 924 /* if input pipes usable create output pipes */
yuuji@0 925 if ((pi[0] < FD_SETSIZE) && (pi[1] < FD_SETSIZE) && (pipe (po) >= 0)) {
yuuji@0 926 /* make sure output pipes are usable */
yuuji@0 927 if ((po[0] >= FD_SETSIZE) || (po[1] >= FD_SETSIZE));
yuuji@0 928 /* all is good, make inferior process */
yuuji@0 929 else if (!(j = fork ())) {
yuuji@0 930 if (!fork ()) { /* make grandchild so it's inherited by init */
yuuji@0 931 long cf; /* don't change caller vars in case vfork() */
yuuji@0 932 char *argv[4],arg[20];
yuuji@0 933 /* prepare argument vector */
yuuji@0 934 sprintf (arg,"%d",fd);
yuuji@0 935 argv[0] = LOCKPGM; argv[1] = arg;
yuuji@0 936 argv[2] = file; argv[3] = NIL;
yuuji@0 937 /* set parent's I/O to my O/I */
yuuji@0 938 dup2 (pi[1],1); dup2 (pi[1],2); dup2 (po[0],0);
yuuji@0 939 /* close all unnecessary descriptors */
yuuji@0 940 for (cf = max (20,max (max (pi[0],pi[1]),max(po[0],po[1])));
yuuji@0 941 cf >= 3; --cf) if (cf != fd) close (cf);
yuuji@0 942 /* be our own process group */
yuuji@0 943 setpgrp (0,getpid ());
yuuji@0 944 /* now run it */
yuuji@0 945 _exit (execv (argv[0],argv));
yuuji@0 946 }
yuuji@0 947 _exit (1); /* child is done */
yuuji@0 948 }
yuuji@0 949 else if (j > 0) { /* parent process */
yuuji@0 950 fd_set rfd;
yuuji@0 951 struct timeval tmo;
yuuji@0 952 FD_ZERO (&rfd);
yuuji@0 953 FD_SET (pi[0],&rfd);
yuuji@0 954 tmo.tv_sec = locktimeout * 60;
yuuji@0 955 grim_pid_reap (j,NIL);/* reap child; grandchild now owned by init */
yuuji@0 956 /* read response from locking program */
yuuji@0 957 if (select (pi[0]+1,&rfd,0,0,&tmo) &&
yuuji@0 958 (read (pi[0],tmp,1) == 1) && (tmp[0] == '+')) {
yuuji@0 959 /* success, record pipes */
yuuji@0 960 base->pipei = pi[0]; base->pipeo = po[1];
yuuji@0 961 /* close child's side of the pipes */
yuuji@0 962 close (pi[1]); close (po[0]);
yuuji@0 963 return LONGT;
yuuji@0 964 }
yuuji@0 965 }
yuuji@0 966 close (po[0]); close (po[1]);
yuuji@0 967 }
yuuji@0 968 close (pi[0]); close (pi[1]);
yuuji@0 969 }
yuuji@0 970 /* find directory/file delimiter */
yuuji@0 971 if (s = strrchr (base->lock,'/')) {
yuuji@0 972 *s = '\0'; /* tie off at directory */
yuuji@0 973 sprintf(tmp, /* generate default message */
yuuji@0 974 "Mailbox vulnerable - directory %.80s must have 1777 protection",
yuuji@0 975 base->lock);
yuuji@0 976 /* definitely not 1777 if can't stat */
yuuji@0 977 mask = stat (base->lock,&sb) ? 0 : (sb.st_mode & 1777);
yuuji@0 978 *s = '/'; /* restore lock name */
yuuji@0 979 if (mask != 1777) { /* default warning if not 1777 */
yuuji@0 980 MM_LOG (tmp,WARN);
yuuji@0 981 break;
yuuji@0 982 }
yuuji@0 983 }
yuuji@0 984 default:
yuuji@0 985 sprintf (tmp,"Mailbox vulnerable - error creating %.80s: %s",
yuuji@0 986 base->lock,strerror (errno));
yuuji@0 987 mm_log (tmp,WARN); /* this is probably not good */
yuuji@0 988 break;
yuuji@0 989 }
yuuji@0 990 base->lock[0] = '\0'; /* don't use lock files */
yuuji@0 991 return NIL;
yuuji@0 992 }
yuuji@0 993
yuuji@0 994 /* Dot-lock file unlocker
yuuji@0 995 * Accepts: lock file name
yuuji@0 996 * Returns: T if success, NIL if failure
yuuji@0 997 */
yuuji@0 998
yuuji@0 999 long dotlock_unlock (DOTLOCK *base)
yuuji@0 1000 {
yuuji@0 1001 long ret = LONGT;
yuuji@0 1002 if (base && base->lock[0]) {
yuuji@0 1003 if (base->pipei >= 0) { /* if running through a pipe unlocker */
yuuji@0 1004 ret = (write (base->pipeo,"+",1) == 1);
yuuji@0 1005 /* nuke the pipes */
yuuji@0 1006 close (base->pipei); close (base->pipeo);
yuuji@0 1007 }
yuuji@0 1008 else ret = !unlink (base->lock);
yuuji@0 1009 }
yuuji@0 1010 return ret;
yuuji@0 1011 }
yuuji@0 1012
yuuji@0 1013 /* Lock file name
yuuji@0 1014 * Accepts: scratch buffer
yuuji@0 1015 * file name
yuuji@0 1016 * type of locking operation (LOCK_SH or LOCK_EX)
yuuji@0 1017 * pointer to return PID of locker
yuuji@0 1018 * Returns: file descriptor of lock or negative if error
yuuji@0 1019 */
yuuji@0 1020
yuuji@0 1021 int lockname (char *lock,char *fname,int op,long *pid)
yuuji@0 1022 {
yuuji@0 1023 struct stat sbuf;
yuuji@0 1024 *pid = 0; /* no locker PID */
yuuji@0 1025 return stat (fname,&sbuf) ? -1 : lock_work (lock,&sbuf,op,pid);
yuuji@0 1026 }
yuuji@0 1027
yuuji@0 1028
yuuji@0 1029 /* Lock file descriptor
yuuji@0 1030 * Accepts: file descriptor
yuuji@0 1031 * lock file name buffer
yuuji@0 1032 * type of locking operation (LOCK_SH or LOCK_EX)
yuuji@0 1033 * Returns: file descriptor of lock or negative if error
yuuji@0 1034 */
yuuji@0 1035
yuuji@0 1036 int lockfd (int fd,char *lock,int op)
yuuji@0 1037 {
yuuji@0 1038 struct stat sbuf;
yuuji@0 1039 return fstat (fd,&sbuf) ? -1 : lock_work (lock,&sbuf,op,NIL);
yuuji@0 1040 }
yuuji@0 1041
yuuji@0 1042 /* Lock file name worker
yuuji@0 1043 * Accepts: lock file name
yuuji@0 1044 * pointer to stat() buffer
yuuji@0 1045 * type of locking operation (LOCK_SH or LOCK_EX)
yuuji@0 1046 * pointer to return PID of locker
yuuji@0 1047 * Returns: file descriptor of lock or negative if error
yuuji@0 1048 */
yuuji@0 1049
yuuji@0 1050 int lock_work (char *lock,void *sb,int op,long *pid)
yuuji@0 1051 {
yuuji@0 1052 struct stat lsb,fsb;
yuuji@0 1053 struct stat *sbuf = (struct stat *) sb;
yuuji@0 1054 char tmp[MAILTMPLEN];
yuuji@0 1055 long i;
yuuji@0 1056 int fd;
yuuji@0 1057 int mask = umask (0);
yuuji@0 1058 if (pid) *pid = 0; /* initialize return PID */
yuuji@0 1059 /* make temporary lock file name */
yuuji@0 1060 sprintf (lock,"%s/.%lx.%lx","/tmp",
yuuji@0 1061 (unsigned long) sbuf->st_dev,(unsigned long) sbuf->st_ino);
yuuji@0 1062 while (T) { /* until get a good lock */
yuuji@0 1063 do switch ((int) chk_notsymlink (lock,&lsb)) {
yuuji@0 1064 case 1: /* exists just once */
yuuji@0 1065 if (((fd = open (lock,O_RDWR,lock_protection)) >= 0) ||
yuuji@0 1066 (errno != ENOENT) || (chk_notsymlink (lock,&lsb) >= 0)) break;
yuuji@0 1067 case -1: /* name doesn't exist */
yuuji@0 1068 fd = open (lock,O_RDWR|O_CREAT|O_EXCL,lock_protection);
yuuji@0 1069 break;
yuuji@0 1070 default: /* multiple hard links */
yuuji@0 1071 mm_log ("hard link to lock name",ERROR);
yuuji@0 1072 syslog (LOG_CRIT,"SECURITY PROBLEM: hard link to lock name: %.80s",lock);
yuuji@0 1073 case 0: /* symlink (already did syslog) */
yuuji@0 1074 umask (mask); /* restore old mask */
yuuji@0 1075 return -1; /* fail: no lock file */
yuuji@0 1076 } while ((fd < 0) && (errno == EEXIST));
yuuji@0 1077 if (fd < 0) { /* failed to get file descriptor */
yuuji@0 1078 syslog (LOG_INFO,"Mailbox lock file %s open failure: %s",lock,
yuuji@0 1079 strerror (errno));
yuuji@0 1080 if (stat ("/tmp",&lsb))
yuuji@0 1081 syslog (LOG_CRIT,"SYSTEM ERROR: no /tmp: %s",strerror (errno));
yuuji@0 1082 else if ((lsb.st_mode & 01777) != 01777)
yuuji@0 1083 mm_log ("Can't lock for write: /tmp must have 1777 protection",WARN);
yuuji@0 1084 umask (mask); /* restore old mask */
yuuji@0 1085 return -1; /* fail: can't open lock file */
yuuji@0 1086 }
yuuji@0 1087
yuuji@0 1088 /* non-blocking form */
yuuji@0 1089 if (op & LOCK_NB) i = flock (fd,op);
yuuji@0 1090 else { /* blocking form */
yuuji@0 1091 (*mailblocknotify) (BLOCK_FILELOCK,NIL);
yuuji@0 1092 i = flock (fd,op);
yuuji@0 1093 (*mailblocknotify) (BLOCK_NONE,NIL);
yuuji@0 1094 }
yuuji@0 1095 if (i) { /* failed, get other process' PID */
yuuji@0 1096 if (pid && !fstat (fd,&fsb) && (i = min (fsb.st_size,MAILTMPLEN-1)) &&
yuuji@0 1097 (read (fd,tmp,i) == i) && !(tmp[i] = 0) && ((i = atol (tmp)) > 0))
yuuji@0 1098 *pid = i;
yuuji@0 1099 close (fd); /* failed, give up on lock */
yuuji@0 1100 umask (mask); /* restore old mask */
yuuji@0 1101 return -1; /* fail: can't lock */
yuuji@0 1102 }
yuuji@0 1103 /* make sure this lock is good for us */
yuuji@0 1104 if (!lstat (lock,&lsb) && ((lsb.st_mode & S_IFMT) != S_IFLNK) &&
yuuji@0 1105 !fstat (fd,&fsb) && (lsb.st_dev == fsb.st_dev) &&
yuuji@0 1106 (lsb.st_ino == fsb.st_ino) && (fsb.st_nlink == 1)) break;
yuuji@0 1107 close (fd); /* lock not right, drop fd and try again */
yuuji@0 1108 }
yuuji@0 1109 /* make sure mode OK (don't use fchmod()) */
yuuji@0 1110 chmod (lock,(int) lock_protection);
yuuji@0 1111 umask (mask); /* restore old mask */
yuuji@0 1112 return fd; /* success */
yuuji@0 1113 }
yuuji@0 1114
yuuji@0 1115 /* Check to make sure not a symlink
yuuji@0 1116 * Accepts: file name
yuuji@0 1117 * stat buffer
yuuji@0 1118 * Returns: -1 if doesn't exist, NIL if symlink, else number of hard links
yuuji@0 1119 */
yuuji@0 1120
yuuji@0 1121 long chk_notsymlink (char *name,void *sb)
yuuji@0 1122 {
yuuji@0 1123 struct stat *sbuf = (struct stat *) sb;
yuuji@0 1124 /* name exists? */
yuuji@0 1125 if (lstat (name,sbuf)) return -1;
yuuji@0 1126 /* forbid symbolic link */
yuuji@0 1127 if ((sbuf->st_mode & S_IFMT) == S_IFLNK) {
yuuji@0 1128 mm_log ("symbolic link on lock name",ERROR);
yuuji@0 1129 syslog (LOG_CRIT,"SECURITY PROBLEM: symbolic link on lock name: %.80s",
yuuji@0 1130 name);
yuuji@0 1131 return NIL;
yuuji@0 1132 }
yuuji@0 1133 return (long) sbuf->st_nlink; /* return number of hard links */
yuuji@0 1134 }
yuuji@0 1135
yuuji@0 1136
yuuji@0 1137 /* Unlock file descriptor
yuuji@0 1138 * Accepts: file descriptor
yuuji@0 1139 * lock file name from lockfd()
yuuji@0 1140 */
yuuji@0 1141
yuuji@0 1142 void unlockfd (int fd,char *lock)
yuuji@0 1143 {
yuuji@0 1144 /* delete the file if no sharers */
yuuji@0 1145 if (!flock (fd,LOCK_EX|LOCK_NB)) unlink (lock);
yuuji@0 1146 flock (fd,LOCK_UN); /* unlock it */
yuuji@0 1147 close (fd); /* close it */
yuuji@0 1148 }
yuuji@0 1149
yuuji@0 1150 /* Set proper file protection for mailbox
yuuji@0 1151 * Accepts: mailbox name
yuuji@0 1152 * actual file path name
yuuji@0 1153 * Returns: T, always
yuuji@0 1154 */
yuuji@0 1155
yuuji@0 1156 long set_mbx_protections (char *mailbox,char *path)
yuuji@0 1157 {
yuuji@0 1158 struct stat sbuf;
yuuji@0 1159 int mode = (int) mbx_protection;
yuuji@0 1160 if (*mailbox == '#') { /* possible namespace? */
yuuji@0 1161 if (((mailbox[1] == 'f') || (mailbox[1] == 'F')) &&
yuuji@0 1162 ((mailbox[2] == 't') || (mailbox[2] == 'T')) &&
yuuji@0 1163 ((mailbox[3] == 'p') || (mailbox[3] == 'P')) &&
yuuji@0 1164 (mailbox[4] == '/')) mode = (int) ftp_protection;
yuuji@0 1165 else if (((mailbox[1] == 'p') || (mailbox[1] == 'P')) &&
yuuji@0 1166 ((mailbox[2] == 'u') || (mailbox[2] == 'U')) &&
yuuji@0 1167 ((mailbox[3] == 'b') || (mailbox[3] == 'B')) &&
yuuji@0 1168 ((mailbox[4] == 'l') || (mailbox[4] == 'L')) &&
yuuji@0 1169 ((mailbox[5] == 'i') || (mailbox[5] == 'I')) &&
yuuji@0 1170 ((mailbox[6] == 'c') || (mailbox[6] == 'C')) &&
yuuji@0 1171 (mailbox[7] == '/')) mode = (int) public_protection;
yuuji@0 1172 else if (((mailbox[1] == 's') || (mailbox[1] == 'S')) &&
yuuji@0 1173 ((mailbox[2] == 'h') || (mailbox[2] == 'H')) &&
yuuji@0 1174 ((mailbox[3] == 'a') || (mailbox[3] == 'A')) &&
yuuji@0 1175 ((mailbox[4] == 'r') || (mailbox[4] == 'R')) &&
yuuji@0 1176 ((mailbox[5] == 'e') || (mailbox[5] == 'E')) &&
yuuji@0 1177 ((mailbox[6] == 'd') || (mailbox[6] == 'D')) &&
yuuji@0 1178 (mailbox[7] == '/')) mode = (int) shared_protection;
yuuji@0 1179 }
yuuji@0 1180 /* if a directory */
yuuji@0 1181 if (!stat (path,&sbuf) && ((sbuf.st_mode & S_IFMT) == S_IFDIR)) {
yuuji@0 1182 /* set owner search if allow read or write */
yuuji@0 1183 if (mode & 0600) mode |= 0100;
yuuji@0 1184 if (mode & 060) mode |= 010;/* set group search if allow read or write */
yuuji@0 1185 if (mode & 06) mode |= 01; /* set world search if allow read or write */
yuuji@0 1186 /* preserve directory SGID bit */
yuuji@0 1187 if (sbuf.st_mode & S_ISGID) mode |= S_ISGID;
yuuji@0 1188 }
yuuji@0 1189 chmod (path,mode); /* set the new protection, ignore failure */
yuuji@0 1190 return LONGT;
yuuji@0 1191 }
yuuji@0 1192
yuuji@0 1193 /* Get proper directory protection
yuuji@0 1194 * Accepts: mailbox name
yuuji@0 1195 * Returns: directory mode, always
yuuji@0 1196 */
yuuji@0 1197
yuuji@0 1198 long get_dir_protection (char *mailbox)
yuuji@0 1199 {
yuuji@0 1200 if (*mailbox == '#') { /* possible namespace? */
yuuji@0 1201 if (((mailbox[1] == 'f') || (mailbox[1] == 'F')) &&
yuuji@0 1202 ((mailbox[2] == 't') || (mailbox[2] == 'T')) &&
yuuji@0 1203 ((mailbox[3] == 'p') || (mailbox[3] == 'P')) &&
yuuji@0 1204 (mailbox[4] == '/')) return ftp_dir_protection;
yuuji@0 1205 else if (((mailbox[1] == 'p') || (mailbox[1] == 'P')) &&
yuuji@0 1206 ((mailbox[2] == 'u') || (mailbox[2] == 'U')) &&
yuuji@0 1207 ((mailbox[3] == 'b') || (mailbox[3] == 'B')) &&
yuuji@0 1208 ((mailbox[4] == 'l') || (mailbox[4] == 'L')) &&
yuuji@0 1209 ((mailbox[5] == 'i') || (mailbox[5] == 'I')) &&
yuuji@0 1210 ((mailbox[6] == 'c') || (mailbox[6] == 'C')) &&
yuuji@0 1211 (mailbox[7] == '/')) return public_dir_protection;
yuuji@0 1212 else if (((mailbox[1] == 's') || (mailbox[1] == 'S')) &&
yuuji@0 1213 ((mailbox[2] == 'h') || (mailbox[2] == 'H')) &&
yuuji@0 1214 ((mailbox[3] == 'a') || (mailbox[3] == 'A')) &&
yuuji@0 1215 ((mailbox[4] == 'r') || (mailbox[4] == 'R')) &&
yuuji@0 1216 ((mailbox[5] == 'e') || (mailbox[5] == 'E')) &&
yuuji@0 1217 ((mailbox[6] == 'd') || (mailbox[6] == 'D')) &&
yuuji@0 1218 (mailbox[7] == '/')) return shared_dir_protection;
yuuji@0 1219 }
yuuji@0 1220 return dir_protection;
yuuji@0 1221 }
yuuji@0 1222
yuuji@0 1223 /* Determine default prototype stream to user
yuuji@0 1224 * Accepts: type (NIL for create, T for append)
yuuji@0 1225 * Returns: default prototype stream
yuuji@0 1226 */
yuuji@0 1227
yuuji@0 1228 MAILSTREAM *default_proto (long type)
yuuji@0 1229 {
yuuji@0 1230 myusername (); /* make sure initialized */
yuuji@0 1231 /* return default driver's prototype */
yuuji@0 1232 return type ? appendProto : createProto;
yuuji@0 1233 }
yuuji@0 1234
yuuji@0 1235
yuuji@0 1236 /* Set up user flags for stream
yuuji@0 1237 * Accepts: MAIL stream
yuuji@0 1238 * Returns: MAIL stream with user flags set up
yuuji@0 1239 */
yuuji@0 1240
yuuji@0 1241 MAILSTREAM *user_flags (MAILSTREAM *stream)
yuuji@0 1242 {
yuuji@0 1243 int i;
yuuji@0 1244 myusername (); /* make sure initialized */
yuuji@0 1245 for (i = 0; i < NUSERFLAGS && userFlags[i]; ++i)
yuuji@0 1246 if (!stream->user_flags[i]) stream->user_flags[i] = cpystr (userFlags[i]);
yuuji@0 1247 return stream;
yuuji@0 1248 }
yuuji@0 1249
yuuji@0 1250
yuuji@0 1251 /* Return nth user flag
yuuji@0 1252 * Accepts: user flag number
yuuji@0 1253 * Returns: flag
yuuji@0 1254 */
yuuji@0 1255
yuuji@0 1256 char *default_user_flag (unsigned long i)
yuuji@0 1257 {
yuuji@0 1258 myusername (); /* make sure initialized */
yuuji@0 1259 return userFlags[i];
yuuji@0 1260 }
yuuji@0 1261
yuuji@0 1262 /* Default block notify routine
yuuji@0 1263 * Accepts: reason for calling
yuuji@0 1264 * data
yuuji@0 1265 * Returns: data
yuuji@0 1266 */
yuuji@0 1267
yuuji@0 1268 void *mm_blocknotify (int reason,void *data)
yuuji@0 1269 {
yuuji@0 1270 void *ret = data;
yuuji@0 1271 switch (reason) {
yuuji@0 1272 case BLOCK_SENSITIVE: /* entering sensitive code */
yuuji@0 1273 ret = (void *) alarm (0);
yuuji@0 1274 break;
yuuji@0 1275 case BLOCK_NONSENSITIVE: /* exiting sensitive code */
yuuji@0 1276 if ((unsigned int) data) alarm ((unsigned int) data);
yuuji@0 1277 break;
yuuji@0 1278 default: /* ignore all other reasons */
yuuji@0 1279 break;
yuuji@0 1280 }
yuuji@0 1281 return ret;
yuuji@0 1282 }

UW-IMAP'd extensions by yuuji