static int libwrap_ask(struct request_info *r, int fd) { int a; struct sockaddr_storage sin; socklen_t len = sizeof(sin); /* XXX: old FreeBSD didn't fill sockaddr correctly against AF_UNIX */ sin.ss_family = AF_UNIX; /* is this a connection from the local host? */ if (getpeername(fd, (struct sockaddr *) &sin, &len) == 0) { if (((struct sockaddr *)&sin)->sa_family == AF_UNIX) { return 1; } } /* i hope using the sock_* functions are legal; it certainly makes this code very easy! */ request_set(r, RQ_FILE, fd, 0); sock_host(r); a = hosts_access(r); if (!a) { syslog(deny_severity, "refused connection from %s", eval_client(r)); } return a; }
int main( int argc, char *argv[] ) { char *fn = "main()"; char f_randfile[ PATH_MAX ]; int listensd; /* socket descriptor we'll bind to */ int clientsd; /* incoming socket descriptor */ int addrlen; struct sockaddr_in srvaddr; struct sockaddr_in cliaddr; pthread_t ThreadId; /* thread id of each incoming conn */ pthread_t RecycleThread; /* used just for the recycle thread */ pthread_attr_t attr; /* generic thread attribute struct */ int rc, i, fd; unsigned int ui; pid_t pid; /* used just for a fork call */ struct linger lingerstruct; /* for the socket reuse stuff */ int flag; /* for the socket reuse stuff */ ICC_Struct *ICC_tptr; extern char *optarg; extern int optind; char ConfigFile[ MAXPATHLEN ]; /* path to our config file */ #ifdef HAVE_LIBWRAP struct request_info r; /* request struct for libwrap */ #endif flag = 1; ConfigFile[0] = '\0'; /* * Ignore signals we don't want to die from but we don't care enough * about to catch. */ signal( SIGPIPE, SIG_IGN ); signal( SIGHUP, SIG_IGN ); while (( i = getopt( argc, argv, "f:h" ) ) != EOF ) { switch( i ) { case 'f': /* user specified a config filename */ strncpy( ConfigFile, optarg, sizeof ConfigFile -1 ); ConfigFile[ sizeof ConfigFile - 1 ] = '\0'; syslog( LOG_INFO, "%s: Using configuration file '%s'", fn, ConfigFile ); break; case 'h': Usage(); exit( 0 ); case '?': Usage(); exit( 1 ); } } /* * Make sure we know which config file to use and then set our config * options. */ if ( ! ConfigFile[0] ) { strncpy( ConfigFile, DEFAULT_CONFIG_FILE, sizeof ConfigFile -1 ); ConfigFile[ sizeof ConfigFile - 1 ] = '\0'; syslog( LOG_INFO, "%s: Using default configuration file '%s'.", fn, ConfigFile ); } SetConfigOptions( ConfigFile ); SetLogOptions(); /* * Just for logging purposes, are we doing SELECT caching or not? */ if ( PC_Struct.enable_select_cache ) syslog( LOG_INFO, "%s: SELECT caching is enabled", fn ); else syslog( LOG_INFO, "%s: SELECT caching is disabled", fn ); #ifdef HAVE_LIBWRAP /* * Set our tcpd service name */ if (service = strrchr(argv[0], '/')) service++; else service = argv[0]; #endif /* * Initialize some stuff. */ rc = pthread_mutex_init(&mp, NULL); if ( rc ) { syslog(LOG_ERR, "%s: pthread_mutex_init() returned error [%d] initializing main mutex. Exiting.", fn, rc ); exit( 1 ); } rc = pthread_mutex_init(&trace, NULL); if ( rc ) { syslog(LOG_ERR, "%s: pthread_mutex_init() returned error [%d] initializing trace mutex. Exiting.", fn, rc ); exit( 1 ); } TraceUser[0] = '\0'; syslog( LOG_INFO, "%s: Allocating %d IMAP connection structures.", fn, PC_Struct.cache_size ); ICC_free = (ICC_Struct *)malloc( ( sizeof ( ICC_Struct ) ) * PC_Struct.cache_size ); if ( ! ICC_free ) { syslog(LOG_ERR, "%s: malloc() failed to allocate [%d] IMAPConnectionContext structures: %s", fn, PC_Struct.cache_size, strerror( errno ) ); exit( 1 ); } memset( ICC_free, 0, sizeof ( ICC_Struct ) * PC_Struct.cache_size ); ICC_tptr = ICC_free; /* * Bug fixed by Gary Mills <*****@*****.**>. I was pre-incrementing * ICC_tptr and then assigning. I guess gcc evaluates the expression * incorrectly, since I never had a problem with this. Gary had the * problem with cc, so it's fixed here. */ for ( ui = 0; ui < PC_Struct.cache_size - 1; ui++ ) { ICC_tptr->next = ICC_tptr + 1; ICC_tptr++; } memset( ICC_HashTable, 0, sizeof ICC_HashTable ); ServerInit(); /* detach from our parent if necessary */ if (! (getppid() == 1) && ( ! PC_Struct.foreground_mode ) ) { syslog( LOG_INFO, "%s: Configured to run in background mode.", fn ); if ( (pid = fork()) < 0) { syslog(LOG_ERR, "%s: initial call to fork() failed: %s", fn, strerror(errno)); exit( 1 ); } else if ( pid > 0) { exit( 0 ); } if (setsid() == -1) { syslog(LOG_WARNING, "%s: setsid() failed: %s", fn, strerror(errno)); } if ( (pid = fork()) < 0) { syslog(LOG_ERR, "%s: secondary call to fork() failed: %s", fn, strerror(errno)); exit( 1 ); } else if ( pid > 0) { exit( 0 ); } } else { syslog( LOG_INFO, "%s: Configured to run in foreground mode.", fn ); } SetBannerAndCapability(); if ( PC_Struct.login_disabled || PC_Struct.force_tls ) { syslog( LOG_INFO, "%s: Enabling STARTTLS.", fn ); #if HAVE_LIBSSL if ( PC_Struct.support_starttls ) { /* Initialize SSL_CTX */ SSL_library_init(); /* Need to seed PRNG, too! */ if ( RAND_egd( ( RAND_file_name( f_randfile, sizeof( f_randfile ) ) == f_randfile ) ? f_randfile : "/.rnd" ) ) { /* Not an EGD, so read and write it. */ if ( RAND_load_file( f_randfile, -1 ) ) RAND_write_file( f_randfile ); } SSL_load_error_strings(); tls_ctx = SSL_CTX_new( TLSv1_client_method() ); if ( tls_ctx == NULL ) { syslog(LOG_ERR, "%s: Failed to create new SSL_CTX. Exiting.", fn); exit( 1 ); } /* Work around all known bugs */ SSL_CTX_set_options( tls_ctx, SSL_OP_ALL ); if ( ! SSL_CTX_load_verify_locations( tls_ctx, PC_Struct.tls_ca_file, PC_Struct.tls_ca_path ) || ! SSL_CTX_set_default_verify_paths( tls_ctx ) ) { syslog(LOG_ERR, "%s: Failed to load CA data. Exiting.", fn); exit( 1 ); } if ( ! set_cert_stuff( tls_ctx, PC_Struct.tls_cert_file, PC_Struct.tls_key_file ) ) { syslog(LOG_ERR, "%s: Failed to load cert/key data. Exiting.", fn); exit( 1 ); } SSL_CTX_set_verify(tls_ctx, SSL_VERIFY_NONE, verify_callback); } else #endif /* HAVE_LIBSSL */ { /* We're screwed! We won't be able to login without SASL */ syslog(LOG_ERR, "%s: IMAP server has LOGINDISABLED and we can't do STARTTLS. Exiting.", fn); exit( 1 ); } } memset( (char *) &srvaddr, 0, sizeof srvaddr ); srvaddr.sin_family = PF_INET; if ( !PC_Struct.listen_addr ) { srvaddr.sin_addr.s_addr = htonl(INADDR_ANY); } else { srvaddr.sin_addr.s_addr = inet_addr( PC_Struct.listen_addr ); if ( srvaddr.sin_addr.s_addr == -1 ) { syslog( LOG_ERR, "%s: bad bind address: '%s' specified in config file. Exiting.", fn, PC_Struct.listen_addr ); exit( 1 ); } } syslog(LOG_INFO, "%s: Binding to tcp %s:%d", fn, PC_Struct.listen_addr ? PC_Struct.listen_addr : "*", PC_Struct.listen_port ); srvaddr.sin_port = htons(PC_Struct.listen_port); listensd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if ( listensd == -1 ) { syslog(LOG_ERR, "%s: socket() failed: %s", fn, strerror(errno)); exit( 1 ); } setsockopt(listensd, SOL_SOCKET, SO_REUSEADDR, (void *)&flag, sizeof(flag)); lingerstruct.l_onoff = 1; lingerstruct.l_linger = 5; setsockopt(listensd, SOL_SOCKET, SO_LINGER, (void *)&lingerstruct, sizeof(lingerstruct)); if ( PC_Struct.send_tcp_keepalives ) { lingerstruct.l_onoff = 1; syslog( LOG_INFO, "%s: Enabling SO_KEEPALIVE.", fn ); setsockopt( listensd, SOL_SOCKET, SO_KEEPALIVE, (void *)&lingerstruct.l_onoff, sizeof lingerstruct.l_onoff ); } if ( bind(listensd, (struct sockaddr *)&srvaddr, sizeof( srvaddr ) ) < 0 ) { syslog(LOG_ERR, "%s: bind() failed: %s", fn, strerror(errno) ); exit( 1 ); } /* * Create and mmap() our stat file while we're still root. Since it's * configurable, we want to make sure we do this as root so there's the * greatest possibility that we'll have permission to write where we * need to. */ syslog( LOG_INFO, "%s: Using global statistics file '%s'", fn, PC_Struct.stat_filename ); fd = open( PC_Struct.stat_filename, O_RDWR | O_CREAT, S_IREAD | S_IWRITE ); if ( fd == -1 ) { syslog(LOG_ERR, "%s: open() failed for '%s': %s -- Exiting.", fn, PC_Struct.stat_filename, strerror( errno ) ); exit( 1 ); } if ( ( ftruncate( fd, sizeof( IMAPCounter_Struct ) ) ) == -1 ) { syslog(LOG_ERR, "%s: ftruncate() failed: %s -- Exiting.", fn, strerror( errno ) ); exit( 1 ); } IMAPCount = ( IMAPCounter_Struct *)mmap( 0, sizeof( IMAPCounter_Struct ), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0 ); if ( IMAPCount == MAP_FAILED ) { syslog(LOG_ERR, "%s: mmap() failed: %s -- Exiting.", fn, strerror( errno ) ); exit( 1 ); } memset( IMAPCount, 0, sizeof( IMAPCounter_Struct ) ); IMAPCount->StartTime = time( 0 ); IMAPCount->CountTime = time( 0 ); if ( BecomeNonRoot() ) exit( 1 ); /* some misc thread setup */ rc = pthread_attr_init( &attr ); if ( rc ) { syslog(LOG_ERR, "%s: pthread_attr_init() failed: [%d]\n", fn, rc); exit( 1 ); } rc = pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_DETACHED ); if ( rc ) { syslog(LOG_ERR, "%s: pthread_attr_setdetachstate() failed: [%d]\n", fn, rc); exit( 1 ); } /* launch a recycle thread before we loop */ pthread_create( &RecycleThread, &attr, (void *)ICC_Recycle_Loop, NULL ); syslog(LOG_INFO, "%s: Launched ICC recycle thread with id %d", fn, RecycleThread ); /* * Now start listening and accepting connections. */ if ( listen(listensd, MAX_CONN_BACKLOG) < 0) { syslog( LOG_ERR, "%s: listen() failed: %s -- Exiting", fn, strerror(errno)); exit( 1 ); } syslog( LOG_INFO, "%s: Normal server startup.", fn ); /* * Main server loop */ for ( ;; ) { /* * Bug fixed by Gary Mills <*****@*****.**>. I forgot * to initialize addrlen. */ addrlen = sizeof cliaddr; clientsd = accept( listensd, (struct sockaddr *)&cliaddr, &addrlen ); if ( clientsd == -1 ) { syslog(LOG_WARNING, "%s: accept() failed: %s -- retrying", fn, strerror(errno)); sleep( 1 ); continue; } #ifdef HAVE_LIBWRAP request_init(&r, RQ_DAEMON, service, 0); request_set(&r, RQ_FILE, clientsd, 0); sock_host(&r); if (!hosts_access(&r)) { shutdown(clientsd, SHUT_RDWR); close(clientsd); syslog(deny_severity, "refused connection from %s", eval_client(&r)); continue; } #endif IMAPCount->TotalClientConnectionsAccepted++; IMAPCount->CurrentClientConnections++; if ( IMAPCount->CurrentClientConnections > IMAPCount->PeakClientConnections ) IMAPCount->PeakClientConnections = IMAPCount->CurrentClientConnections; pthread_create( &ThreadId, &attr, (void *)HandleRequest, (void *)clientsd ); } }
int main(int argc, char *argv[]) { struct tftphdr *tp; int peer; socklen_t peerlen, len; ssize_t n; int ch; char *chroot_dir = NULL; struct passwd *nobody; const char *chuser = "******"; char recvbuffer[MAXPKTSIZE]; int allow_ro = 1, allow_wo = 1; tzset(); /* syslog in localtime */ acting_as_client = 0; tftp_openlog("tftpd", LOG_PID | LOG_NDELAY, LOG_FTP); while ((ch = getopt(argc, argv, "cCd:F:lnoOp:s:u:U:wW")) != -1) { switch (ch) { case 'c': ipchroot = 1; break; case 'C': ipchroot = 2; break; case 'd': if (atoi(optarg) != 0) debug += atoi(optarg); else debug |= debug_finds(optarg); break; case 'F': newfile_format = optarg; break; case 'l': logging = 1; break; case 'n': suppress_naks = 1; break; case 'o': options_rfc_enabled = 0; break; case 'O': options_extra_enabled = 0; break; case 'p': packetdroppercentage = atoi(optarg); tftp_log(LOG_INFO, "Randomly dropping %d out of 100 packets", packetdroppercentage); break; case 's': chroot_dir = optarg; break; case 'u': chuser = optarg; break; case 'U': mask = strtol(optarg, NULL, 0); break; case 'w': create_new = 1; break; case 'W': create_new = 1; increase_name = 1; break; default: tftp_log(LOG_WARNING, "ignoring unknown option -%c", ch); } } if (optind < argc) { struct dirlist *dirp; /* Get list of directory prefixes. Skip relative pathnames. */ for (dirp = dirs; optind < argc && dirp < &dirs[MAXDIRS]; optind++) { if (argv[optind][0] == '/') { dirp->name = argv[optind]; dirp->len = strlen(dirp->name); dirp++; } } } else if (chroot_dir) { dirs->name = "/"; dirs->len = 1; } if (ipchroot > 0 && chroot_dir == NULL) { tftp_log(LOG_ERR, "-c requires -s"); exit(1); } umask(mask); { int on = 1; if (ioctl(0, FIONBIO, &on) < 0) { tftp_log(LOG_ERR, "ioctl(FIONBIO): %s", strerror(errno)); exit(1); } } /* Find out who we are talking to and what we are going to do */ peerlen = sizeof(peer_sock); n = recvfrom(0, recvbuffer, MAXPKTSIZE, 0, (struct sockaddr *)&peer_sock, &peerlen); if (n < 0) { tftp_log(LOG_ERR, "recvfrom: %s", strerror(errno)); exit(1); } getnameinfo((struct sockaddr *)&peer_sock, peer_sock.ss_len, peername, sizeof(peername), NULL, 0, NI_NUMERICHOST); /* * Now that we have read the message out of the UDP * socket, we fork and exit. Thus, inetd will go back * to listening to the tftp port, and the next request * to come in will start up a new instance of tftpd. * * We do this so that inetd can run tftpd in "wait" mode. * The problem with tftpd running in "nowait" mode is that * inetd may get one or more successful "selects" on the * tftp port before we do our receive, so more than one * instance of tftpd may be started up. Worse, if tftpd * break before doing the above "recvfrom", inetd would * spawn endless instances, clogging the system. */ { int i, pid; for (i = 1; i < 20; i++) { pid = fork(); if (pid < 0) { sleep(i); /* * flush out to most recently sent request. * * This may drop some request, but those * will be resent by the clients when * they timeout. The positive effect of * this flush is to (try to) prevent more * than one tftpd being started up to service * a single request from a single client. */ peerlen = sizeof peer_sock; i = recvfrom(0, recvbuffer, MAXPKTSIZE, 0, (struct sockaddr *)&peer_sock, &peerlen); if (i > 0) { n = i; } } else { break; } } if (pid < 0) { tftp_log(LOG_ERR, "fork: %s", strerror(errno)); exit(1); } else if (pid != 0) { exit(0); } } /* * See if the client is allowed to talk to me. * (This needs to be done before the chroot()) */ { struct request_info req; request_init(&req, RQ_CLIENT_ADDR, peername, 0); request_set(&req, RQ_DAEMON, "tftpd", 0); if (hosts_access(&req) == 0) { if (debug&DEBUG_ACCESS) tftp_log(LOG_WARNING, "Access denied by 'tftpd' entry " "in /etc/hosts.allow"); /* * Full access might be disabled, but maybe the * client is allowed to do read-only access. */ request_set(&req, RQ_DAEMON, "tftpd-ro", 0); allow_ro = hosts_access(&req); request_set(&req, RQ_DAEMON, "tftpd-wo", 0); allow_wo = hosts_access(&req); if (allow_ro == 0 && allow_wo == 0) { tftp_log(LOG_WARNING, "Unauthorized access from %s", peername); exit(1); } if (debug&DEBUG_ACCESS) { if (allow_ro) tftp_log(LOG_WARNING, "But allowed readonly access " "via 'tftpd-ro' entry"); if (allow_wo) tftp_log(LOG_WARNING, "But allowed writeonly access " "via 'tftpd-wo' entry"); } } else if (debug&DEBUG_ACCESS) tftp_log(LOG_WARNING, "Full access allowed" "in /etc/hosts.allow"); } /* * Since we exit here, we should do that only after the above * recvfrom to keep inetd from constantly forking should there * be a problem. See the above comment about system clogging. */ if (chroot_dir) { if (ipchroot > 0) { char *tempchroot; struct stat sb; int statret; struct sockaddr_storage ss; char hbuf[NI_MAXHOST]; statret = -1; memcpy(&ss, &peer_sock, peer_sock.ss_len); unmappedaddr((struct sockaddr_in6 *)&ss); getnameinfo((struct sockaddr *)&ss, ss.ss_len, hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST); asprintf(&tempchroot, "%s/%s", chroot_dir, hbuf); if (ipchroot == 2) statret = stat(tempchroot, &sb); if (ipchroot == 1 || (statret == 0 && (sb.st_mode & S_IFDIR))) chroot_dir = tempchroot; } /* Must get this before chroot because /etc might go away */ if ((nobody = getpwnam(chuser)) == NULL) { tftp_log(LOG_ERR, "%s: no such user", chuser); exit(1); } if (chroot(chroot_dir)) { tftp_log(LOG_ERR, "chroot: %s: %s", chroot_dir, strerror(errno)); exit(1); } chdir("/"); setgroups(1, &nobody->pw_gid); if (setuid(nobody->pw_uid) != 0) { tftp_log(LOG_ERR, "setuid failed"); exit(1); } } len = sizeof(me_sock); if (getsockname(0, (struct sockaddr *)&me_sock, &len) == 0) { switch (me_sock.ss_family) { case AF_INET: ((struct sockaddr_in *)&me_sock)->sin_port = 0; break; case AF_INET6: ((struct sockaddr_in6 *)&me_sock)->sin6_port = 0; break; default: /* unsupported */ break; } } else { memset(&me_sock, 0, sizeof(me_sock)); me_sock.ss_family = peer_sock.ss_family; me_sock.ss_len = peer_sock.ss_len; } close(0); close(1); peer = socket(peer_sock.ss_family, SOCK_DGRAM, 0); if (peer < 0) { tftp_log(LOG_ERR, "socket: %s", strerror(errno)); exit(1); } if (bind(peer, (struct sockaddr *)&me_sock, me_sock.ss_len) < 0) { tftp_log(LOG_ERR, "bind: %s", strerror(errno)); exit(1); } tp = (struct tftphdr *)recvbuffer; tp->th_opcode = ntohs(tp->th_opcode); if (tp->th_opcode == RRQ) { if (allow_ro) tftp_rrq(peer, tp->th_stuff, n - 1); else { tftp_log(LOG_WARNING, "%s read access denied", peername); exit(1); } } if (tp->th_opcode == WRQ) { if (allow_wo) tftp_wrq(peer, tp->th_stuff, n - 1); else { tftp_log(LOG_WARNING, "%s write access denied", peername); exit(1); } } exit(1); }
/* * Parse the protocol and point relevant fields, don't take logic decisions * based on this, just parse to locate things. */ int mk_http_parser(struct mk_http_request *req, struct mk_http_parser *p, char *buffer, int buf_len, struct mk_server *server) { int s; int tmp; int ret; int len; /* lazy test printf("p->i=%i buf_len=%i\n", p->i, buf_len); for (s = p->i; s < buf_len; s++) { if (buffer[s] == '\r') { printf("CR"); } else if (buffer[s] == '\n') { printf("LF"); } else { printf("%c", buffer[s]); } } printf("\n"); */ len = buf_len; for (; p->i < len; p->i++, p->chars++) { /* FIRST LINE LEVEL: Method, URI & Protocol */ if (p->level == REQ_LEVEL_FIRST) { switch (p->status) { case MK_ST_REQ_METHOD: /* HTTP Method */ if (p->chars == -1) { switch (buffer[p->i]) { case 'G': p->method = MK_METHOD_GET; break; case 'P': p->method = MK_METHOD_POST; break; case 'H': p->method = MK_METHOD_HEAD; break; case 'D': p->method = MK_METHOD_DELETE; break; case 'O': p->method = MK_METHOD_OPTIONS; break; } continue; } if (buffer[p->i] == ' ') { mark_end(); p->status = MK_ST_REQ_URI; if (p->end < 2) { return MK_HTTP_PARSER_ERROR; } method_lookup(req, p, buffer); start_next(); } else { if ((p->i - p->start) > 10) { return MK_HTTP_PARSER_ERROR; } } break; case MK_ST_REQ_URI: /* URI */ if (buffer[p->i] == ' ') { mark_end(); p->status = MK_ST_REQ_PROT_VERSION; if (field_len() < 1) { return MK_HTTP_PARSER_ERROR; } request_set(&req->uri, p, buffer); start_next(); } else if (buffer[p->i] == '?') { mark_end(); request_set(&req->uri, p, buffer); p->status = MK_ST_REQ_QUERY_STRING; start_next(); } else if (buffer[p->i] == '\r' || buffer[p->i] == '\n') { mk_http_error(MK_CLIENT_BAD_REQUEST, req->session, req, server); return MK_HTTP_PARSER_ERROR; } break; case MK_ST_REQ_QUERY_STRING: /* Query string */ char_lookup(buffer, ' ', len, p); if (buffer[p->i] == ' ') { mark_end(); request_set(&req->query_string, p, buffer); p->status = MK_ST_REQ_PROT_VERSION; start_next(); } else if (buffer[p->i] == '\r' || buffer[p->i] == '\n') { mk_http_error(MK_CLIENT_BAD_REQUEST, req->session, req, server); return MK_HTTP_PARSER_ERROR; } break; case MK_ST_REQ_PROT_VERSION: /* Protocol Version */ /* * Most of the time we already have the string version in our * buffer, for that case try to match the version and avoid * loop rounds. */ if (p->start + 6 >= p->i) { continue; } tmp = p->start; if (buffer[tmp] == 'H' && buffer[tmp + 1] == 'T' && buffer[tmp + 2] == 'T' && buffer[tmp + 3] == 'P' && buffer[tmp + 4] == '/' && buffer[tmp + 5] == '1' && buffer[tmp + 6] == '.') { request_set(&req->protocol_p, p, buffer); req->protocol_p.len = 8; mk_http_set_minor_version(buffer[tmp + 7]); } else { mk_http_error(MK_SERVER_HTTP_VERSION_UNSUP, req->session, req, server); return MK_HTTP_PARSER_ERROR; } p->status = MK_ST_FIRST_CONTINUE; break; case MK_ST_FIRST_CONTINUE: if (buffer[p->i] == '\r') { p->status = MK_ST_FIRST_FINALIZING; } else { return MK_HTTP_PARSER_ERROR; } break; case MK_ST_FIRST_FINALIZING: /* New Line */ if (buffer[p->i] == '\n') { p->level = REQ_LEVEL_CONTINUE; start_next(); } else { return MK_HTTP_PARSER_ERROR; } break; case MK_ST_BLOCK_END: if (buffer[p->i] == '\n') { return mk_http_parser_ok(req, p, server); } else { return MK_HTTP_PARSER_ERROR; } break; }; } else if (p->level == REQ_LEVEL_CONTINUE) { if (buffer[p->i] == '\r') { p->level = REQ_LEVEL_FIRST; p->status = MK_ST_BLOCK_END; } else { p->level = REQ_LEVEL_HEADERS; p->status = MK_ST_HEADER_KEY; p->chars = 0; } } /* HEADERS: all headers stuff */ if (p->level == REQ_LEVEL_HEADERS) { /* Expect a Header key */ if (p->status == MK_ST_HEADER_KEY) { if (buffer[p->i] == '\r') { if (p->chars == 0) { p->level = REQ_LEVEL_END; start_next(); } else { return MK_HTTP_PARSER_ERROR; } } if (p->chars == 0) { /* * We reach the start of a Header row, lets catch the most * probable header. * * The goal of this 'first row character lookup', is to define a * small range set of probable headers comparison once we catch * a header end. */ s = tolower(buffer[p->i]); switch (s) { case 'a': p->header_min = MK_HEADER_ACCEPT; p->header_max = MK_HEADER_AUTHORIZATION; break; case 'c': p->header_min = MK_HEADER_CACHE_CONTROL; p->header_max = MK_HEADER_CONTENT_TYPE; break; case 'h': p->header_min = MK_HEADER_HOST; p->header_max = MK_HEADER_HTTP2_SETTINGS; break; case 'i': header_scope_eq(p, MK_HEADER_IF_MODIFIED_SINCE); break; case 'l': p->header_min = MK_HEADER_LAST_MODIFIED; p->header_max = MK_HEADER_LAST_MODIFIED_SINCE; break; case 'r': p->header_min = MK_HEADER_RANGE; p->header_max = MK_HEADER_REFERER; break; case 'u': p->header_min = MK_HEADER_UPGRADE; p->header_max = MK_HEADER_USER_AGENT; break; default: p->header_key = -1; p->header_sep = -1; p->header_min = -1; p->header_max = -1; }; p->header_key = p->i; continue; } /* Found key/value separator */ char_lookup(buffer, ':', len, p); if (buffer[p->i] == ':') { /* Set the key/value middle point */ p->header_sep = p->i; /* validate length */ mark_end(); if (field_len() < 1) { return MK_HTTP_PARSER_ERROR; } /* Wait for a value */ p->status = MK_ST_HEADER_VALUE; start_next(); } } /* Parsing the header value */ else if (p->status == MK_ST_HEADER_VALUE) { /* Trim left, set starts only when found something != ' ' */ if (buffer[p->i] == '\r' || buffer[p->i] == '\n') { return MK_HTTP_PARSER_ERROR; } else if (buffer[p->i] != ' ') { p->status = MK_ST_HEADER_VAL_STARTS; p->start = p->header_val = p->i; } continue; } /* New header row starts */ else if (p->status == MK_ST_HEADER_VAL_STARTS) { /* Maybe there is no more headers and we reach the end ? */ if (buffer[p->i] == '\r') { mark_end(); if (field_len() <= 0) { return MK_HTTP_PARSER_ERROR; } /* * A header row has ended, lets lookup the header and populate * our headers table index. */ ret = header_lookup(p, buffer); if (ret != 0) { if (ret < -1) { mk_http_error(-ret, req->session, req, server); } return MK_HTTP_PARSER_ERROR; } /* Try to catch next LF */ if (p->i + 1 < len) { if (buffer[p->i + 1] == '\n') { p->i++; p->status = MK_ST_HEADER_KEY; p->chars = -1; start_next(); } } p->status = MK_ST_HEADER_END; start_next(); } else if (buffer[p->i] == '\n' && buffer[p->i - 1] != '\r') { return MK_HTTP_PARSER_ERROR; } } else if (p->status == MK_ST_HEADER_END) { if (buffer[p->i] == '\n') { p->status = MK_ST_HEADER_KEY; p->chars = -1; start_next(); } else { return MK_HTTP_PARSER_ERROR; } } } else if (p->level == REQ_LEVEL_END) { if (buffer[p->i] == '\n') { if (p->header_content_length > 0) { p->level = REQ_LEVEL_BODY; p->chars = -1; start_next(); } else { return mk_http_parser_ok(req, p, server); } } else { return MK_HTTP_PARSER_ERROR; } } else if (p->level == REQ_LEVEL_BODY) { /* * Reaching this level can means two things: * * - A Pipeline Request * - A Body content (POST/PUT methods) */ if (p->header_content_length > 0) { p->body_received = len - p->start; if ((len - p->start) < p->header_content_length) { return MK_HTTP_PARSER_PENDING; } /* Cut off */ p->i += p->body_received; req->data.len = p->body_received; req->data.data = (buffer + p->start); } return mk_http_parser_ok(req, p, server); } } return MK_HTTP_PARSER_PENDING; }
int main( int argc, char *argv[] ) { const char *fn = "main()"; char f_randfile[ PATH_MAX ]; int listensd; /* socket descriptor we'll bind to */ int clientsd; /* incoming socket descriptor */ int sockaddrlen; struct sockaddr_storage srvaddr; struct sockaddr_storage cliaddr; pthread_t ThreadId; /* thread id of each incoming conn */ pthread_t RecycleThread; /* used just for the recycle thread */ pthread_attr_t attr; /* generic thread attribute struct */ int rc, i, fd; unsigned int ui; struct linger lingerstruct; /* for the socket reuse stuff */ int flag; /* for the socket reuse stuff */ ICC_Struct *ICC_tptr; extern char *optarg; extern int optind; char ConfigFile[ MAXPATHLEN ]; /* path to our config file */ char PidFile[ MAXPATHLEN ]; /* path to our pidfile */ #ifdef HAVE_LIBWRAP struct request_info r; /* request struct for libwrap */ #endif struct addrinfo aihints, *ai; int gaierrnum; flag = 1; ConfigFile[0] = '\0'; strncpy( PidFile, DEFAULT_PID_FILE, sizeof PidFile -1 ); /* * Ignore signals we don't want to die from but we don't care enough * about to catch. */ signal( SIGPIPE, SIG_IGN ); signal( SIGHUP, SIG_IGN ); while (( i = getopt( argc, argv, "f:p:h" ) ) != EOF ) { switch( i ) { case 'f': /* user specified a config filename */ strncpy( ConfigFile, optarg, sizeof ConfigFile -1 ); ConfigFile[ sizeof ConfigFile - 1 ] = '\0'; syslog( LOG_INFO, "%s: Using configuration file '%s'", fn, ConfigFile ); break; case 'p': /* user specified a pidfile */ strncpy( PidFile, optarg, sizeof PidFile -1 ); PidFile[ sizeof PidFile - 1 ] = '\0'; syslog( LOG_INFO, "%s: Using pidfile '%s'", fn, PidFile ); break; case 'h': Usage(); exit( 0 ); case '?': Usage(); exit( 1 ); } } /* * Make sure we know which config file to use and then set our config * options. */ if ( ! ConfigFile[0] ) { strncpy( ConfigFile, DEFAULT_CONFIG_FILE, sizeof ConfigFile -1 ); ConfigFile[ sizeof ConfigFile - 1 ] = '\0'; syslog( LOG_INFO, "%s: Using default configuration file '%s'.", fn, ConfigFile ); } SetDefaultConfigValues(&PC_Struct); SetConfigOptions( ConfigFile ); SetLogOptions(); /* * Just for logging purposes, are we doing SELECT caching or not? */ if ( PC_Struct.enable_select_cache ) syslog( LOG_INFO, "%s: SELECT caching is enabled", fn ); else syslog( LOG_INFO, "%s: SELECT caching is disabled", fn ); /* * Just for logging purposes, are the admin commands enabled or not? */ if ( PC_Struct.enable_admin_commands ) syslog( LOG_INFO, "%s: Internal admin commands are enabled", fn ); else syslog( LOG_INFO, "%s: Internal admin commands are disabled", fn ); #ifdef HAVE_LIBWRAP /* * Set our tcpd service name */ if (service = strrchr(argv[0], '/')) service++; else service = argv[0]; #endif /* * Initialize some stuff. */ rc = pthread_mutex_init(&mp, NULL); if ( rc ) { syslog(LOG_ERR, "%s: pthread_mutex_init() returned error [%d] initializing main mutex. Exiting.", fn, rc ); exit( 1 ); } rc = pthread_mutex_init(&trace, NULL); if ( rc ) { syslog(LOG_ERR, "%s: pthread_mutex_init() returned error [%d] initializing trace mutex. Exiting.", fn, rc ); exit( 1 ); } TraceUser[0] = '\0'; syslog( LOG_INFO, "%s: Allocating %d IMAP connection structures.", fn, PC_Struct.cache_size ); ICC_free = (ICC_Struct *)malloc( ( sizeof ( ICC_Struct ) ) * PC_Struct.cache_size ); if ( ! ICC_free ) { syslog(LOG_ERR, "%s: malloc() failed to allocate [%d] IMAPConnectionContext structures: %s", fn, PC_Struct.cache_size, strerror( errno ) ); exit( 1 ); } memset( ICC_free, 0, sizeof ( ICC_Struct ) * PC_Struct.cache_size ); ICC_tptr = ICC_free; /* * Bug fixed by Gary Mills <*****@*****.**>. I was pre-incrementing * ICC_tptr and then assigning. I guess gcc evaluates the expression * incorrectly, since I never had a problem with this. Gary had the * problem with cc, so it's fixed here. */ for ( ui = 0; ui < PC_Struct.cache_size - 1; ui++ ) { ICC_tptr->next = ICC_tptr + 1; ICC_tptr++; } memset( ICC_HashTable, 0, sizeof ICC_HashTable ); #if HAVE_LIBSSL /* Initialize SSL_CTX */ syslog( LOG_INFO, "%s: Enabling openssl library.", fn ); SSL_library_init(); /* Set up OpenSSL thread protection */ ssl_thread_setup(fn); /* Need to seed PRNG, too! */ if ( RAND_egd( ( RAND_file_name( f_randfile, sizeof( f_randfile ) ) == f_randfile ) ? f_randfile : "/.rnd" ) ) { /* Not an EGD, so read and write it. */ if ( RAND_load_file( f_randfile, -1 ) ) RAND_write_file( f_randfile ); } SSL_load_error_strings(); tls_ctx = SSL_CTX_new( TLSv1_client_method() ); if ( tls_ctx == NULL ) { syslog(LOG_ERR, "%s: Failed to create new SSL_CTX. Exiting.", fn); exit( 1 ); } /* Work around all known bugs */ SSL_CTX_set_options( tls_ctx, SSL_OP_ALL ); if ( PC_Struct.tls_ca_file != NULL || PC_Struct.tls_ca_path != NULL ) { rc = SSL_CTX_load_verify_locations( tls_ctx, PC_Struct.tls_ca_file, PC_Struct.tls_ca_path ); } else { rc = SSL_CTX_set_default_verify_paths( tls_ctx ); } if ( rc == 0 ) { syslog(LOG_ERR, "%s: Failed to load CA data. Exiting.", fn); exit( 1 ); } if ( ! set_cert_stuff( tls_ctx, PC_Struct.tls_cert_file, PC_Struct.tls_key_file ) ) { syslog(LOG_ERR, "%s: Failed to load cert/key data. Exiting.", fn); exit( 1 ); } SSL_CTX_set_verify(tls_ctx, SSL_VERIFY_NONE, verify_callback); #endif /* HAVE_LIBSSL */ ServerInit(); /* Daemonize() would go here */ SetBannerAndCapability(); /* * We don't need to check PC_Struct.support_starttls since we * probably have refetched the capability list after a STARTTLS * if we did one; it won't ever be supported at this point. * * It also makes no difference to check PC_Struct.force_tls now * because we've either done a STARTTLS or we haven't - all that * matters is if we got LOGINDISABLED or not. * * Note that all these things *ARE* tested when checking the * server capabilities (in fact, the following check is probably * a duplicate). */ if ( PC_Struct.login_disabled ) { /* We're screwed! We can't login */ syslog(LOG_ERR, "%s: IMAP server has LOGINDISABLED. Exiting.", fn); exit( 1 ); } memset( &aihints, 0, sizeof aihints ); aihints.ai_family = AF_UNSPEC; aihints.ai_socktype = SOCK_STREAM; aihints.ai_flags = AI_PASSIVE; if ( ( gaierrnum = getaddrinfo( PC_Struct.listen_addr, PC_Struct.listen_port, &aihints, &ai ) ) ) { syslog( LOG_ERR, "%s: bad bind address: '%s' specified in config file. Exiting.", fn, PC_Struct.listen_addr ); exit( 1 ); } syslog( LOG_INFO, "%s: Binding to tcp %s:%s", fn, PC_Struct.listen_addr ? PC_Struct.listen_addr : "*", PC_Struct.listen_port ); for ( ; ai != NULL; ai = ai->ai_next ) { listensd = socket( ai->ai_family, ai->ai_socktype, ai->ai_protocol ); if ( listensd == -1 ) { syslog(LOG_WARNING, "%s: socket() failed: %s", fn, strerror(errno)); continue; } setsockopt(listensd, SOL_SOCKET, SO_REUSEADDR, (void *)&flag, sizeof(flag)); lingerstruct.l_onoff = 1; lingerstruct.l_linger = 5; setsockopt(listensd, SOL_SOCKET, SO_LINGER, (void *)&lingerstruct, sizeof(lingerstruct)); if ( PC_Struct.send_tcp_keepalives ) { lingerstruct.l_onoff = 1; syslog( LOG_INFO, "%s: Enabling SO_KEEPALIVE.", fn ); setsockopt( listensd, SOL_SOCKET, SO_KEEPALIVE, (void *)&lingerstruct.l_onoff, sizeof lingerstruct.l_onoff ); } memcpy( &srvaddr, ai->ai_addr, ai->ai_addrlen ); if ( bind( listensd, (struct sockaddr *)&srvaddr, ai->ai_addrlen ) < 0 ) { syslog(LOG_WARNING, "%s: bind() failed: %s", fn, strerror(errno) ); continue; } else break; } if ( ai == NULL ) { syslog( LOG_ERR, "%s: no useable addresses to bind to", fn ); exit( EXIT_FAILURE); } /* * Create and mmap() our stat file while we're still root. Since it's * configurable, we want to make sure we do this as root so there's the * greatest possibility that we'll have permission to write where we * need to. */ syslog( LOG_INFO, "%s: Using global statistics file '%s'", fn, PC_Struct.stat_filename ); fd = open( PC_Struct.stat_filename, O_RDWR | O_CREAT, S_IREAD | S_IWRITE ); if ( fd == -1 ) { syslog(LOG_ERR, "%s: open() failed for '%s': %s -- Exiting.", fn, PC_Struct.stat_filename, strerror( errno ) ); exit( 1 ); } if ( ( ftruncate( fd, sizeof( IMAPCounter_Struct ) ) ) == -1 ) { syslog(LOG_ERR, "%s: ftruncate() failed: %s -- Exiting.", fn, strerror( errno ) ); exit( 1 ); } IMAPCount = ( IMAPCounter_Struct *)mmap( 0, sizeof( IMAPCounter_Struct ), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0 ); if ( IMAPCount == MAP_FAILED ) { syslog(LOG_ERR, "%s: mmap() failed: %s -- Exiting.", fn, strerror( errno ) ); exit( 1 ); } memset( IMAPCount, 0, sizeof( IMAPCounter_Struct ) ); IMAPCount->StartTime = time( 0 ); IMAPCount->CountTime = time( 0 ); /* * Daemonize as late as possible, so that connection failures can be caught * and startup aborted before dettaching from parent */ Daemonize( PidFile ); if ( BecomeNonRoot() ) exit( 1 ); /* some misc thread setup */ rc = pthread_attr_init( &attr ); if ( rc ) { syslog(LOG_ERR, "%s: pthread_attr_init() failed: [%d]\n", fn, rc); exit( 1 ); } rc = pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_DETACHED ); if ( rc ) { syslog(LOG_ERR, "%s: pthread_attr_setdetachstate() failed: [%d]\n", fn, rc); exit( 1 ); } /* launch a recycle thread before we loop */ pthread_create( &RecycleThread, &attr, (void *)ICC_Recycle_Loop, NULL ); syslog(LOG_INFO, "%s: Launched ICC recycle thread with id %d", fn, (int)RecycleThread ); /* * Now start listening and accepting connections. */ if ( listen(listensd, MAX_CONN_BACKLOG) < 0) { syslog( LOG_ERR, "%s: listen() failed: %s -- Exiting", fn, strerror(errno)); exit( 1 ); } syslog( LOG_INFO, "%s: squirrelmail-imap_proxy version %s normal server startup.", fn, IMAP_PROXY_VERSION ); /* * Main server loop */ for ( ;; ) { /* * Bug fixed by Gary Mills <*****@*****.**>. I forgot * to initialize sockaddrlen. */ sockaddrlen = sizeof cliaddr; clientsd = accept( listensd, (struct sockaddr *)&cliaddr, &sockaddrlen ); if ( clientsd == -1 ) { syslog(LOG_WARNING, "%s: accept() failed: %s -- retrying", fn, strerror(errno)); sleep( 1 ); continue; } #ifdef HAVE_LIBWRAP request_init(&r, RQ_DAEMON, service, 0); request_set(&r, RQ_FILE, clientsd, 0); sock_host(&r); if (!hosts_access(&r)) { shutdown(clientsd, SHUT_RDWR); close(clientsd); syslog(deny_severity, "refused connection from %s", eval_client(&r)); continue; } #endif IMAPCount->TotalClientConnectionsAccepted++; IMAPCount->CurrentClientConnections++; if ( IMAPCount->CurrentClientConnections > IMAPCount->PeakClientConnections ) IMAPCount->PeakClientConnections = IMAPCount->CurrentClientConnections; rc = pthread_create( &ThreadId, &attr, (void *)HandleRequest, (void *)clientsd ); if (rc != 0) { syslog(LOG_ERR, "%s: pthread_create() returned error [%d] for HandleRequest.", fn, rc ); close(clientsd); } } }