#ifdef HAVE_CONFIG_H # include "config.h" #endif #include "md5.h" #include #include #include #include #include #include #ifdef HAVE_SHADOW_H #include #endif #include #include #include #include #include #ifdef HAVE_CRYPT_H #include #endif #include "strlcpy.h" #include "mysql.h" extern const char * authfile; char real_username[MAXLINE+1]; char real_maildrop[MAXLINE+1]; extern int use_pop3_allow_deny; /* user_in_file() and is_user_allowed() by Lukasz Luzar * modified by Andreas Krennmair */ static int user_in_file(char * user, char * path) { int fd; size_t userlen; size_t len; char * buffer, * ptr; struct stat sb; /* sanity check */ if (user==NULL || path==NULL) { return -1; } userlen = strlen(user); fd = open(path,O_RDONLY); if (fd < 0) { return -1; } if (0!=fstat(fd,&sb)) { close(fd); return -1; } len = (size_t)sb.st_size; buffer = alloca(len+1); /* sanity check */ if (buffer==NULL) { return -1; } if (read(fd,buffer,(size_t)len)!=(ssize_t)len) { close(fd); return -1; } if (0!=close(fd)) { return -1; } buffer[len] = '\0'; ptr = buffer; while (*ptr != '\0') { /* omit whitespaces */ while (*ptr == '\t' || *ptr == ' ') { ptr++; } if (*ptr == '\0') { break; } if ((*ptr!='#') && (strncmp(ptr,user,userlen)==0) && (ptr[userlen] == ' ' || ptr[userlen] == '\t' || ptr[userlen] == '\n')) { return 1; } while (*ptr != '\0' && *ptr != '\n') { ptr++; } if (*ptr == '\n') { ptr++; } } return 0; } static int is_user_allowed(char * user) { int allow, deny; switch (user_in_file(user,"/etc/pop3.allow")>0) { case 0: allow = 0; break; case 1: allow = 1; break; default: /* user_in_file() must not return anything else! */ return 0; } switch (user_in_file(user,"/etc/pop3.deny")) { case 0: deny = 0; break; case 1: deny = 1; break; default: /* dito */ return 0; } if ((allow == 0 && deny == 0) || (allow == 1 && deny == 0)) { return 1; } return 0; } /* returns a static buffer containing the string representation of an * MD5'ified password hash. the string starts with "MD5-". the rest * of the string is the lower case ASCII representation of an MD5 hash * (32 bytes). the hash is calculated from the password followed by a * line feed (ASCII 10), followed by the user name, another line feed, * and the "magic" string "akpop3d". */ static char * calc_password_hash(char * username, char * password) { /* Sverre H. Huseby, 2003-05-09 */ /* this program is not multi threaded, and we know we won't mix two * calls to the function, so let's make it simple using a static * return variable. */ static char ret[4 + 32 + 1]; static char * magic = "akpop3d"; static char * hex_digits = "0123456789abcdef"; char buf[1024]; unsigned char md5[16]; int q; char * p; unsigned char * up; if (strlen(username) + strlen(password) + strlen(magic) + 3 > sizeof(buf)) { syslog(LOG_ERR, "overly long username or password"); return NULL; } snprintf(buf,sizeof(buf),"%s\n%s\n%s",password,username,magic); md5_buffer(buf, strlen(buf), md5); strlcpy(ret, "MD5-", sizeof(ret)); p = ret + 4; up = md5; for (q = 0; q < 16; q++) { *p++ = hex_digits[*up >> 4]; *p++ = hex_digits[*up & 0xf]; ++up; } *p = '\0'; return ret; } static int is_password_match(char * username, char * stored_password, char * given_password) { /* Sverre H. Huseby, 2003-05-09 */ char * hash; char * p1; char * p2; int q; if (strlen(stored_password) == (4 + 32) && strncmp(stored_password, "MD5-", 3) == 0) { /* the password is stored as and MD5 hash */ hash = calc_password_hash(username, given_password); p1 = hash + 4; p2 = stored_password + 4; /* compare MD5 hashes represented as ASCII (32 bytes) , including * the terminating NUL byte (an additional byte). could have used * strcasecmp, but as this function is not supported by all * systems, we do it the hard way. i guess i should have learned * to make those fancy autoconf rules, but i haven't. */ for (q = 0; q < 32 + 1; q++) { /* tolower may be a macro I guess, so we can't increase the * p1/p2 pointers in the following if statement. */ if (tolower(*p1) != tolower(*p2)) return 0; ++p1; ++p2; } /* the hashes are equal. */ return 1; } else { /* the password is stored in the clear. yikes! */ return (strcmp(given_password, stored_password) == 0); } } /* returns 0 if authentication failed, !0 if it succeeded. */ static int authenticate_by_file(char * username, char * password) { char * ptr; char linebuf[MAXLINE+1]; FILE * fptr; if (authfile == NULL) return 0; fptr = fopen(authfile, "r"); if (fptr == NULL) { syslog(LOG_ERR, "%s: %s: %s", "failed to read auth file", authfile, strerror(errno)); return 0; } while (!ferror(fptr) && !feof(fptr)) { linebuf[0] = '\0'; if (fgets(linebuf, sizeof(linebuf), fptr) == NULL) { fclose(fptr); return 0; } ptr = strtok(linebuf, ":"); if (ptr == NULL) continue; if (strcmp(username, ptr) != 0) continue; ptr = strtok(NULL, ":"); if (ptr == NULL) { fclose(fptr); return 0; } if (!is_password_match(username, ptr, password)) { fclose(fptr); return 0; } /* * At this point we've authenticated, but we now need to find out * what Unix username to use and what maildrop file to read */ ptr = strtok(NULL, ":"); if (ptr == NULL) { fclose(fptr); return 0; } strlcpy(real_username, ptr, sizeof(real_username)); ptr = strtok(NULL, ":\n"); if (ptr == NULL) { real_username[0] = '\0'; fclose(fptr); return 0; } fclose(fptr); strlcpy(real_maildrop, ptr, sizeof(real_maildrop)); return 1; } fclose(fptr); return 0; } int authenticate(char * username, char * password) { char user[MAXLINE+1], pass[MAXLINE+1]; char * sys_pw; char * crp; struct passwd * u; #ifdef HAVE_SHADOW_H struct spwd * s; #endif int len; /* sanity checks */ if (username==NULL || password==NULL) { return -1; } real_username[0] = '\0'; real_maildrop[0] = '\0'; /* extract username */ crp = memchr(username,'\r',strlen(username)); if (crp==NULL) { crp = memchr(username,'\n',strlen(username)); if (crp==NULL) { crp = username + strlen(username); } } len = crp - username; if (len+1>sizeof(user)) { len = sizeof(user)-1; } memset(user,0,sizeof(user)); strlcpy(user,username,len+1); /* extract password */ crp = memchr(password,'\r',strlen(password)); if (crp==NULL) { crp = memchr(password,'\n',strlen(password)); if (crp==NULL) { crp = password + strlen(password); } } len = crp - password; if (len+1>sizeof(pass)) { len = sizeof(pass)-1; } memset(pass,0,sizeof(pass)); strlcpy(pass,password,len+1); /* check /etc/pop3.{allow,deny} */ if (0!=use_pop3_allow_deny && 0==is_user_allowed(user)) { return 0; } /* * Text-file authentication */ if (authfile != NULL) { return authenticate_by_file(user, pass); } #ifndef HAVE_LIBMYSQLCLIENT u = getpwnam(user); #else u = getMpwnam( user ); /* getMpwnam first checks getpwnam() */ #endif /* HAVE_LIBMYSQLCLIENT */ if (u == NULL || u->pw_passwd == NULL) { return -1; } /* handle shadowed passwd files */ #if HAVE_SHADOW_H if (strcmp(u->pw_passwd,"x")==0) { #ifndef HAVE_LIBMYSQLCLIENT s = getspnam(user); #else s = getMspnam(user); /* getMspanm first checks getspnam() */ #endif if (s==NULL) { return -1; } sys_pw = s->sp_pwdp; } else { sys_pw = u->pw_passwd; } #else sys_pw = u->pw_passwd; #endif if (strcmp(sys_pw,crypt(pass,sys_pw))==0) { return 1; } return 0; }