citadel-9.01/0000755000000000000000000000000012507024063011545 5ustar rootrootcitadel-9.01/serv_extensions.h0000644000000000000000000000564312507024051015161 0ustar rootroot #ifndef SERV_EXTENSIONS_H #define SERV_EXTENSIONS_H #include "server.h" /* * This is where we declare all of the server extensions we have. * We'll probably start moving these to a more sane location in the near * future. For now, this just shuts up the compiler. */ //void serv_calendar_destroy(void); //char *serv_test_init(void); //char *serv_postfix_tcpdict(void); /* */ typedef void (*CtdlDbgFunction) (const int); extern int DebugModules; #define MDBGLOG(LEVEL) if ((LEVEL != LOG_DEBUG) || (DebugModules != 0)) #define MOD_syslog(LEVEL, FORMAT, ...) \ MDBGLOG(LEVEL) syslog(LEVEL, \ "%s Modules: " FORMAT, IOSTR, __VA_ARGS__) #define MODM_syslog(LEVEL, FORMAT) \ MDBGLOG(LEVEL) syslog(LEVEL, \ "%s Modules: " FORMAT, IOSTR); /* * ServiceFunctionHook extensions are used for hooks which implement various * protocols (either on TCP or on unix domain sockets) directly in the Citadel server. */ typedef struct ServiceFunctionHook ServiceFunctionHook; struct ServiceFunctionHook { ServiceFunctionHook *next; int tcp_port; char *sockpath; void (*h_greeting_function) (void) ; void (*h_command_function) (void) ; void (*h_async_function) (void) ; int msock; const char* ServiceName; /* this is just for debugging and logging purposes. */ }; extern ServiceFunctionHook *ServiceHookTable; typedef struct CleanupFunctionHook CleanupFunctionHook; struct CleanupFunctionHook { CleanupFunctionHook *next; void (*h_function_pointer) (void); }; extern CleanupFunctionHook *CleanupHookTable; typedef struct __LogDebugEntry { CtdlDbgFunction F; const char *Name; long Len; const int *LogP; } LogDebugEntry; extern HashList *LogDebugEntryTable; void initialize_server_extensions(void); int DLoader_Exec_Cmd(char *cmdbuf); char *Dynamic_Module_Init(void); void CtdlDestroySessionHooks(void); void PerformSessionHooks(int EventType); int CheckTDAPVeto (int DBType, StrBuf *ErrMsg); void CtdlDestroyTDAPVetoHooks(void); void CtdlDestroyUserHooks(void); void PerformUserHooks(struct ctdluser *usbuf, int EventType); int PerformXmsgHooks(char *, char *, char *, char *); void CtdlDestroyXmsgHooks(void); void CtdlDestroyMessageHook(void); int PerformMessageHooks(struct CtdlMessage *, recptypes *recps, int EventType); void CtdlDestroyNetprocHooks(void); int PerformNetprocHooks(struct CtdlMessage *, char *); void CtdlDestroyRoomHooks(void); int PerformRoomHooks(struct ctdlroom *); void CtdlDestroyDeleteHooks(void); void PerformDeleteHooks(char *, long); void CtdlDestroyCleanupHooks(void); void CtdlDestroyProtoHooks(void); void CtdlDestroyServiceHook(void); void CtdlDestroySearchHooks(void); void CtdlDestroyFixedOutputHooks(void); int PerformFixedOutputHooks(char *, char *, int); void CtdlRegisterDebugFlagHook(const char *Name, long len, CtdlDbgFunction F, const int *); void CtdlSetDebugLogFacilities(const char **Str, long n); void CtdlDestroyDebugTable(void); #endif /* SERV_EXTENSIONS_H */ citadel-9.01/event_client.c0000644000000000000000000007347512507024051014405 0ustar rootroot/* * Copyright (c) 1998-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "sysdep.h" #include #include #include #include #include #include #include #if HAVE_BACKTRACE #include #endif #include #include "ctdl_module.h" #include "event_client.h" #include "citserver.h" ConstStr IOStates[] = { {HKEY("DB Queue")}, {HKEY("DB Q Next")}, {HKEY("DB Attach")}, {HKEY("DB Next")}, {HKEY("DB Stop")}, {HKEY("DB Exit")}, {HKEY("DB Terminate")}, {HKEY("IO Queue")}, {HKEY("IO Attach")}, {HKEY("IO Connect Socket")}, {HKEY("IO Abort")}, {HKEY("IO Timeout")}, {HKEY("IO ConnFail")}, {HKEY("IO ConnFail Now")}, {HKEY("IO Conn Now")}, {HKEY("IO Conn Wait")}, {HKEY("Curl Q")}, {HKEY("Curl Start")}, {HKEY("Curl Shotdown")}, {HKEY("Curl More IO")}, {HKEY("Curl Got IO")}, {HKEY("Curl Got Data")}, {HKEY("Curl Got Status")}, {HKEY("C-Ares Start")}, {HKEY("C-Ares IO Done")}, {HKEY("C-Ares Finished")}, {HKEY("C-Ares exit")}, {HKEY("Killing")}, {HKEY("Exit")} }; void SetEVState(AsyncIO *IO, eIOState State) { CitContext* CCC = IO->CitContext; if (CCC != NULL) memcpy(CCC->lastcmdname, IOStates[State].Key, IOStates[State].len + 1); } eNextState QueueAnEventContext(AsyncIO *IO); static void IO_Timeout_callback(struct ev_loop *loop, ev_timer *watcher, int revents); static void IO_abort_shutdown_callback(struct ev_loop *loop, ev_cleanup *watcher, int revents); /*------------------------------------------------------------------------------ * Server DB IO *----------------------------------------------------------------------------*/ extern int evdb_count; extern pthread_mutex_t DBEventQueueMutex; extern pthread_mutex_t DBEventExitQueueMutex; extern HashList *DBInboundEventQueue; extern struct ev_loop *event_db; extern ev_async DBAddJob; extern ev_async DBExitEventLoop; eNextState QueueAnDBOperation(AsyncIO *IO) { IOAddHandler *h; int i; SetEVState(IO, eDBQ); h = (IOAddHandler*)malloc(sizeof(IOAddHandler)); h->IO = IO; assert(IO->ReAttachCB != NULL); h->EvAttch = IO->ReAttachCB; ev_cleanup_init(&IO->db_abort_by_shutdown, IO_abort_shutdown_callback); IO->db_abort_by_shutdown.data = IO; pthread_mutex_lock(&DBEventQueueMutex); if (DBInboundEventQueue == NULL) { /* shutting down... */ free(h); EVM_syslog(LOG_DEBUG, "DBEVENT Q exiting.\n"); pthread_mutex_unlock(&DBEventQueueMutex); return eAbort; } EVM_syslog(LOG_DEBUG, "DBEVENT Q\n"); i = ++evdb_count ; Put(DBInboundEventQueue, IKEY(i), h, NULL); pthread_mutex_unlock(&DBEventQueueMutex); pthread_mutex_lock(&DBEventExitQueueMutex); if (event_db == NULL) { pthread_mutex_unlock(&DBEventExitQueueMutex); return eAbort; } ev_async_send (event_db, &DBAddJob); pthread_mutex_unlock(&DBEventExitQueueMutex); EVQM_syslog(LOG_DEBUG, "DBEVENT Q Done.\n"); return eDBQuery; } void StopDBWatchers(AsyncIO *IO) { SetEVState(IO, eDBStop); ev_cleanup_stop(event_db, &IO->db_abort_by_shutdown); ev_idle_stop(event_db, &IO->db_unwind_stack); } void ShutDownDBCLient(AsyncIO *IO) { CitContext *Ctx =IO->CitContext; become_session(Ctx); SetEVState(IO, eDBTerm); EVM_syslog(LOG_DEBUG, "DBEVENT Terminating.\n"); StopDBWatchers(IO); assert(IO->DBTerminate); IO->DBTerminate(IO); } void DB_PerformNext(struct ev_loop *loop, ev_idle *watcher, int revents) { AsyncIO *IO = watcher->data; SetEVState(IO, eDBNext); IO->Now = ev_now(event_db); EV_syslog(LOG_DEBUG, "%s()", __FUNCTION__); become_session(IO->CitContext); ev_idle_stop(event_db, &IO->db_unwind_stack); assert(IO->NextDBOperation); switch (IO->NextDBOperation(IO)) { case eSendReply: ev_cleanup_stop(loop, &IO->db_abort_by_shutdown); QueueAnEventContext(IO); break; case eDBQuery: break; case eSendDNSQuery: case eReadDNSReply: case eConnect: case eSendMore: case eSendFile: case eReadMessage: case eReadMore: case eReadPayload: case eReadFile: ev_cleanup_stop(loop, &IO->db_abort_by_shutdown); break; case eTerminateConnection: case eAbort: ev_idle_stop(event_db, &IO->db_unwind_stack); ev_cleanup_stop(loop, &IO->db_abort_by_shutdown); ShutDownDBCLient(IO); } } eNextState NextDBOperation(AsyncIO *IO, IO_CallBack CB) { SetEVState(IO, eQDBNext); IO->NextDBOperation = CB; ev_idle_init(&IO->db_unwind_stack, DB_PerformNext); IO->db_unwind_stack.data = IO; ev_idle_start(event_db, &IO->db_unwind_stack); return eDBQuery; } /*------------------------------------------------------------------------------ * Client IO *----------------------------------------------------------------------------*/ extern int evbase_count; extern pthread_mutex_t EventQueueMutex; extern pthread_mutex_t EventExitQueueMutex; extern HashList *InboundEventQueue; extern struct ev_loop *event_base; extern ev_async AddJob; extern ev_async ExitEventLoop; static void IO_abort_shutdown_callback(struct ev_loop *loop, ev_cleanup *watcher, int revents) { AsyncIO *IO = watcher->data; SetEVState(IO, eIOAbort); EV_syslog(LOG_DEBUG, "EVENT Q: %s\n", __FUNCTION__); IO->Now = ev_now(event_base); assert(IO->ShutdownAbort); IO->ShutdownAbort(IO); } eNextState QueueAnEventContext(AsyncIO *IO) { IOAddHandler *h; int i; SetEVState(IO, eIOQ); h = (IOAddHandler*)malloc(sizeof(IOAddHandler)); h->IO = IO; assert(IO->ReAttachCB != NULL); h->EvAttch = IO->ReAttachCB; ev_cleanup_init(&IO->abort_by_shutdown, IO_abort_shutdown_callback); IO->abort_by_shutdown.data = IO; pthread_mutex_lock(&EventQueueMutex); if (InboundEventQueue == NULL) { free(h); /* shutting down... */ EVM_syslog(LOG_DEBUG, "EVENT Q exiting.\n"); pthread_mutex_unlock(&EventQueueMutex); return eAbort; } EVM_syslog(LOG_DEBUG, "EVENT Q\n"); i = ++evbase_count; Put(InboundEventQueue, IKEY(i), h, NULL); pthread_mutex_unlock(&EventQueueMutex); pthread_mutex_lock(&EventExitQueueMutex); if (event_base == NULL) { pthread_mutex_unlock(&EventExitQueueMutex); return eAbort; } ev_async_send (event_base, &AddJob); pthread_mutex_unlock(&EventExitQueueMutex); EVM_syslog(LOG_DEBUG, "EVENT Q Done.\n"); return eSendReply; } eNextState EventQueueDBOperation(AsyncIO *IO, IO_CallBack CB, int CloseFDs) { StopClientWatchers(IO, CloseFDs); IO->ReAttachCB = CB; return eDBQuery; } eNextState DBQueueEventContext(AsyncIO *IO, IO_CallBack CB) { StopDBWatchers(IO); IO->ReAttachCB = CB; return eSendReply; } eNextState QueueEventContext(AsyncIO *IO, IO_CallBack CB) { IO->ReAttachCB = CB; return QueueAnEventContext(IO); } extern eNextState evcurl_handle_start(AsyncIO *IO); eNextState QueueCurlContext(AsyncIO *IO) { IOAddHandler *h; int i; SetEVState(IO, eCurlQ); h = (IOAddHandler*)malloc(sizeof(IOAddHandler)); h->IO = IO; h->EvAttch = evcurl_handle_start; pthread_mutex_lock(&EventQueueMutex); if (InboundEventQueue == NULL) { /* shutting down... */ free(h); EVM_syslog(LOG_DEBUG, "EVENT Q exiting.\n"); pthread_mutex_unlock(&EventQueueMutex); return eAbort; } EVM_syslog(LOG_DEBUG, "EVENT Q\n"); i = ++evbase_count; Put(InboundEventQueue, IKEY(i), h, NULL); pthread_mutex_unlock(&EventQueueMutex); pthread_mutex_lock(&EventExitQueueMutex); if (event_base == NULL) { pthread_mutex_unlock(&EventExitQueueMutex); return eAbort; } ev_async_send (event_base, &AddJob); pthread_mutex_unlock(&EventExitQueueMutex); EVM_syslog(LOG_DEBUG, "EVENT Q Done.\n"); return eSendReply; } eNextState CurlQueueDBOperation(AsyncIO *IO, IO_CallBack CB) { StopCurlWatchers(IO); IO->ReAttachCB = CB; return eDBQuery; } void FreeAsyncIOContents(AsyncIO *IO) { CitContext *Ctx = IO->CitContext; FreeStrBuf(&IO->IOBuf); FreeStrBuf(&IO->SendBuf.Buf); FreeStrBuf(&IO->RecvBuf.Buf); FreeURL(&IO->ConnectMe); FreeStrBuf(&IO->HttpReq.ReplyData); if (Ctx) { Ctx->state = CON_IDLE; Ctx->kill_me = 1; IO->CitContext = NULL; } } void DestructCAres(AsyncIO *IO); void StopClientWatchers(AsyncIO *IO, int CloseFD) { EVM_syslog(LOG_DEBUG, "EVENT StopClientWatchers"); DestructCAres(IO); ev_timer_stop (event_base, &IO->rw_timeout); ev_timer_stop(event_base, &IO->conn_fail); ev_idle_stop(event_base, &IO->unwind_stack); ev_cleanup_stop(event_base, &IO->abort_by_shutdown); ev_io_stop(event_base, &IO->conn_event); ev_io_stop(event_base, &IO->send_event); ev_io_stop(event_base, &IO->recv_event); if (CloseFD && (IO->SendBuf.fd > 0)) { close(IO->SendBuf.fd); IO->SendBuf.fd = 0; IO->RecvBuf.fd = 0; } } void StopCurlWatchers(AsyncIO *IO) { EVM_syslog(LOG_DEBUG, "EVENT StopCurlWatchers \n"); ev_timer_stop (event_base, &IO->rw_timeout); ev_timer_stop(event_base, &IO->conn_fail); ev_idle_stop(event_base, &IO->unwind_stack); ev_cleanup_stop(event_base, &IO->abort_by_shutdown); ev_io_stop(event_base, &IO->conn_event); ev_io_stop(event_base, &IO->send_event); ev_io_stop(event_base, &IO->recv_event); curl_easy_cleanup(IO->HttpReq.chnd); IO->HttpReq.chnd = NULL; if (IO->SendBuf.fd != 0) { close(IO->SendBuf.fd); } IO->SendBuf.fd = 0; IO->RecvBuf.fd = 0; } eNextState ShutDownCLient(AsyncIO *IO) { CitContext *Ctx =IO->CitContext; SetEVState(IO, eExit); become_session(Ctx); EVM_syslog(LOG_DEBUG, "EVENT Terminating \n"); StopClientWatchers(IO, 1); if (IO->DNS.Channel != NULL) { ares_destroy(IO->DNS.Channel); EV_DNS_LOG_STOP(DNS.recv_event); EV_DNS_LOG_STOP(DNS.send_event); ev_io_stop(event_base, &IO->DNS.recv_event); ev_io_stop(event_base, &IO->DNS.send_event); IO->DNS.Channel = NULL; } assert(IO->Terminate); return IO->Terminate(IO); } void PostInbound(AsyncIO *IO) { switch (IO->NextState) { case eSendFile: ev_io_start(event_base, &IO->send_event); break; case eSendReply: case eSendMore: assert(IO->SendDone); IO->NextState = IO->SendDone(IO); switch (IO->NextState) { case eSendFile: case eSendReply: case eSendMore: case eReadMessage: case eReadPayload: case eReadMore: case eReadFile: ev_io_start(event_base, &IO->send_event); break; case eDBQuery: StopClientWatchers(IO, 0); QueueAnDBOperation(IO); default: break; } break; case eReadPayload: case eReadMore: case eReadFile: ev_io_start(event_base, &IO->recv_event); break; case eTerminateConnection: case eAbort: if (ShutDownCLient(IO) == eDBQuery) { QueueAnDBOperation(IO); } break; case eSendDNSQuery: case eReadDNSReply: case eConnect: case eReadMessage: break; case eDBQuery: QueueAnDBOperation(IO); } } eReadState HandleInbound(AsyncIO *IO) { const char *Err = NULL; eReadState Finished = eBufferNotEmpty; become_session(IO->CitContext); while ((Finished == eBufferNotEmpty) && ((IO->NextState == eReadMessage)|| (IO->NextState == eReadMore)|| (IO->NextState == eReadFile)|| (IO->NextState == eReadPayload))) { /* Reading lines... * lex line reply in callback, * or do it ourselves. * i.e. as nnn-blabla means continue reading in SMTP */ if ((IO->NextState == eReadFile) && (Finished == eBufferNotEmpty)) { Finished = WriteIOBAlreadyRead(&IO->IOB, &Err); if (Finished == eReadSuccess) { IO->NextState = eSendReply; } } else if (IO->LineReader) Finished = IO->LineReader(IO); else Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf); switch (Finished) { case eMustReadMore: /// read new from socket... break; case eBufferNotEmpty: /* shouldn't happen... */ case eReadSuccess: /// done for now... break; case eReadFail: /// WHUT? ///todo: shut down! break; } if (Finished != eMustReadMore) { ev_io_stop(event_base, &IO->recv_event); IO->NextState = IO->ReadDone(IO); if (IO->NextState == eDBQuery) { if (QueueAnDBOperation(IO) == eAbort) return eReadFail; else return eReadSuccess; } else { Finished = StrBufCheckBuffer(&IO->RecvBuf); } } } PostInbound(IO); return Finished; } static void IO_send_callback(struct ev_loop *loop, ev_io *watcher, int revents) { int rc; AsyncIO *IO = watcher->data; const char *errmsg = NULL; IO->Now = ev_now(event_base); become_session(IO->CitContext); #ifdef BIGBAD_IODBG { int rv = 0; char fn [SIZ]; FILE *fd; const char *pch = ChrPtr(IO->SendBuf.Buf); const char *pchh = IO->SendBuf.ReadWritePointer; long nbytes; if (pchh == NULL) pchh = pch; nbytes = StrLength(IO->SendBuf.Buf) - (pchh - pch); snprintf(fn, SIZ, "/tmp/foolog_ev_%s.%d", ((CitContext*)(IO->CitContext))->ServiceName, IO->SendBuf.fd); fd = fopen(fn, "a+"); if (fd == NULL) { syslog(LOG_EMERG, "failed to open file %s: %s", fn, strerror(errno)); cit_backtrace(); exit(1); } fprintf(fd, "Send: BufSize: %ld BufContent: [", nbytes); rv = fwrite(pchh, nbytes, 1, fd); if (!rv) printf("failed to write debug to %s!\n", fn); fprintf(fd, "]\n"); #endif switch (IO->NextState) { case eSendFile: rc = FileSendChunked(&IO->IOB, &errmsg); if (rc < 0) StrBufPlain(IO->ErrMsg, errmsg, -1); break; default: rc = StrBuf_write_one_chunk_callback(IO->SendBuf.fd, 0, &IO->SendBuf); } #ifdef BIGBAD_IODBG fprintf(fd, "Sent: BufSize: %d bytes.\n", rc); fclose(fd); } #endif if (rc == 0) { ev_io_stop(event_base, &IO->send_event); switch (IO->NextState) { case eSendMore: assert(IO->SendDone); IO->NextState = IO->SendDone(IO); if ((IO->NextState == eTerminateConnection) || (IO->NextState == eAbort) ) ShutDownCLient(IO); else { ev_io_start(event_base, &IO->send_event); } break; case eSendFile: if (IO->IOB.ChunkSendRemain > 0) { ev_io_start(event_base, &IO->recv_event); SetNextTimeout(IO, 100.0); } else { assert(IO->ReadDone); IO->NextState = IO->ReadDone(IO); switch(IO->NextState) { case eSendDNSQuery: case eReadDNSReply: case eDBQuery: case eConnect: break; case eSendReply: case eSendMore: case eSendFile: ev_io_start(event_base, &IO->send_event); break; case eReadMessage: case eReadMore: case eReadPayload: case eReadFile: break; case eTerminateConnection: case eAbort: break; } } break; case eSendReply: if (StrBufCheckBuffer(&IO->SendBuf) != eReadSuccess) break; IO->NextState = eReadMore; case eReadMore: case eReadMessage: case eReadPayload: case eReadFile: if (StrBufCheckBuffer(&IO->RecvBuf) == eBufferNotEmpty) { HandleInbound(IO); } else { ev_io_start(event_base, &IO->recv_event); } break; case eDBQuery: /* * we now live in another queue, * so we have to unregister. */ ev_cleanup_stop(loop, &IO->abort_by_shutdown); break; case eSendDNSQuery: case eReadDNSReply: case eConnect: case eTerminateConnection: case eAbort: break; } } else if (rc < 0) { if (errno != EAGAIN) { StopClientWatchers(IO, 1); EV_syslog(LOG_DEBUG, "IO_send_callback(): Socket Invalid! [%d] [%s] [%d]\n", errno, strerror(errno), IO->SendBuf.fd); StrBufPrintf(IO->ErrMsg, "Socket Invalid! [%s]", strerror(errno)); SetNextTimeout(IO, 0.01); } } /* else : must write more. */ } static void set_start_callback(struct ev_loop *loop, AsyncIO *IO, int revents) { ev_timer_stop(event_base, &IO->conn_fail); ev_timer_start(event_base, &IO->rw_timeout); switch(IO->NextState) { case eReadMore: case eReadMessage: case eReadFile: StrBufAppendBufPlain(IO->ErrMsg, HKEY("[while waiting for greeting]"), 0); ev_io_start(event_base, &IO->recv_event); break; case eSendReply: case eSendMore: case eReadPayload: case eSendFile: become_session(IO->CitContext); IO_send_callback(loop, &IO->send_event, revents); break; case eDBQuery: case eSendDNSQuery: case eReadDNSReply: case eConnect: case eTerminateConnection: case eAbort: /// TODO: WHUT? break; } } static void IO_Timeout_callback(struct ev_loop *loop, ev_timer *watcher, int revents) { AsyncIO *IO = watcher->data; SetEVState(IO, eIOTimeout); IO->Now = ev_now(event_base); ev_timer_stop (event_base, &IO->rw_timeout); become_session(IO->CitContext); if (IO->SendBuf.fd != 0) { ev_io_stop(event_base, &IO->send_event); ev_io_stop(event_base, &IO->recv_event); ev_timer_stop (event_base, &IO->rw_timeout); close(IO->SendBuf.fd); IO->SendBuf.fd = IO->RecvBuf.fd = 0; } assert(IO->Timeout); switch (IO->Timeout(IO)) { case eAbort: ShutDownCLient(IO); default: break; } } static void IO_connfail_callback(struct ev_loop *loop, ev_timer *watcher, int revents) { AsyncIO *IO = watcher->data; SetEVState(IO, eIOConnfail); IO->Now = ev_now(event_base); ev_timer_stop (event_base, &IO->conn_fail); if (IO->SendBuf.fd != 0) { ev_io_stop(loop, &IO->conn_event); ev_io_stop(event_base, &IO->send_event); ev_io_stop(event_base, &IO->recv_event); ev_timer_stop (event_base, &IO->rw_timeout); close(IO->SendBuf.fd); IO->SendBuf.fd = IO->RecvBuf.fd = 0; } become_session(IO->CitContext); assert(IO->ConnFail); switch (IO->ConnFail(IO)) { case eAbort: ShutDownCLient(IO); default: break; } } static void IO_connfailimmediate_callback(struct ev_loop *loop, ev_idle *watcher, int revents) { AsyncIO *IO = watcher->data; SetEVState(IO, eIOConnfailNow); IO->Now = ev_now(event_base); ev_idle_stop (event_base, &IO->conn_fail_immediate); if (IO->SendBuf.fd != 0) { close(IO->SendBuf.fd); IO->SendBuf.fd = IO->RecvBuf.fd = 0; } become_session(IO->CitContext); assert(IO->ConnFail); switch (IO->ConnFail(IO)) { case eAbort: ShutDownCLient(IO); default: break; } } static void IO_connestd_callback(struct ev_loop *loop, ev_io *watcher, int revents) { AsyncIO *IO = watcher->data; int so_err = 0; socklen_t lon = sizeof(so_err); int err; SetEVState(IO, eIOConnNow); IO->Now = ev_now(event_base); EVM_syslog(LOG_DEBUG, "connect() succeeded.\n"); ev_io_stop(loop, &IO->conn_event); ev_timer_stop(event_base, &IO->conn_fail); err = getsockopt(IO->SendBuf.fd, SOL_SOCKET, SO_ERROR, (void*)&so_err, &lon); if ((err == 0) && (so_err != 0)) { EV_syslog(LOG_DEBUG, "connect() failed [%d][%s]\n", so_err, strerror(so_err)); IO_connfail_callback(loop, &IO->conn_fail, revents); } else { EVM_syslog(LOG_DEBUG, "connect() succeeded\n"); set_start_callback(loop, IO, revents); } } static void IO_recv_callback(struct ev_loop *loop, ev_io *watcher, int revents) { const char *errmsg; ssize_t nbytes; AsyncIO *IO = watcher->data; IO->Now = ev_now(event_base); switch (IO->NextState) { case eReadFile: nbytes = FileRecvChunked(&IO->IOB, &errmsg); if (nbytes < 0) StrBufPlain(IO->ErrMsg, errmsg, -1); else { if (IO->IOB.ChunkSendRemain == 0) { IO->NextState = eSendReply; assert(IO->ReadDone); ev_io_stop(event_base, &IO->recv_event); PostInbound(IO); return; } else return; } break; default: nbytes = StrBuf_read_one_chunk_callback(IO->RecvBuf.fd, 0, &IO->RecvBuf); break; } #ifdef BIGBAD_IODBG { long nbytes; int rv = 0; char fn [SIZ]; FILE *fd; const char *pch = ChrPtr(IO->RecvBuf.Buf); const char *pchh = IO->RecvBuf.ReadWritePointer; if (pchh == NULL) pchh = pch; nbytes = StrLength(IO->RecvBuf.Buf) - (pchh - pch); snprintf(fn, SIZ, "/tmp/foolog_ev_%s.%d", ((CitContext*)(IO->CitContext))->ServiceName, IO->SendBuf.fd); fd = fopen(fn, "a+"); if (fd == NULL) { syslog(LOG_EMERG, "failed to open file %s: %s", fn, strerror(errno)); cit_backtrace(); exit(1); } fprintf(fd, "Read: BufSize: %ld BufContent: [", nbytes); rv = fwrite(pchh, nbytes, 1, fd); if (!rv) printf("failed to write debug to %s!\n", fn); fprintf(fd, "]\n"); fclose(fd); } #endif if (nbytes > 0) { HandleInbound(IO); } else if (nbytes == 0) { StopClientWatchers(IO, 1); SetNextTimeout(IO, 0.01); return; } else if (nbytes == -1) { if (errno != EAGAIN) { // FD is gone. kick it. StopClientWatchers(IO, 1); EV_syslog(LOG_DEBUG, "IO_recv_callback(): Socket Invalid! [%d] [%s] [%d]\n", errno, strerror(errno), IO->SendBuf.fd); StrBufPrintf(IO->ErrMsg, "Socket Invalid! [%s]", strerror(errno)); SetNextTimeout(IO, 0.01); } return; } } void IO_postdns_callback(struct ev_loop *loop, ev_idle *watcher, int revents) { AsyncIO *IO = watcher->data; SetEVState(IO, eCaresFinished); IO->Now = ev_now(event_base); EV_syslog(LOG_DEBUG, "event: %s\n", __FUNCTION__); become_session(IO->CitContext); assert(IO->DNS.Query->PostDNS); switch (IO->DNS.Query->PostDNS(IO)) { case eAbort: assert(IO->DNS.Fail); switch (IO->DNS.Fail(IO)) { case eAbort: //// StopClientWatchers(IO); ShutDownCLient(IO); break; case eDBQuery: StopClientWatchers(IO, 0); QueueAnDBOperation(IO); break; default: break; } case eDBQuery: StopClientWatchers(IO, 0); QueueAnDBOperation(IO); break; default: break; } } eNextState EvConnectSock(AsyncIO *IO, double conn_timeout, double first_rw_timeout, int ReadFirst) { struct sockaddr_in egress_sin; int fdflags; int rc = -1; SetEVState(IO, eIOConnectSock); become_session(IO->CitContext); if (ReadFirst) { IO->NextState = eReadMessage; } else { IO->NextState = eSendReply; } IO->SendBuf.fd = IO->RecvBuf.fd = socket( (IO->ConnectMe->IPv6)?PF_INET6:PF_INET, SOCK_STREAM, IPPROTO_TCP); if (IO->SendBuf.fd < 0) { EV_syslog(LOG_ERR, "EVENT: socket() failed: %s\n", strerror(errno)); StrBufPrintf(IO->ErrMsg, "Failed to create socket: %s", strerror(errno)); IO->SendBuf.fd = IO->RecvBuf.fd = 0; return eAbort; } fdflags = fcntl(IO->SendBuf.fd, F_GETFL); if (fdflags < 0) { EV_syslog(LOG_ERR, "EVENT: unable to get socket %d flags! %s \n", IO->SendBuf.fd, strerror(errno)); StrBufPrintf(IO->ErrMsg, "Failed to get socket %d flags: %s", IO->SendBuf.fd, strerror(errno)); close(IO->SendBuf.fd); IO->SendBuf.fd = IO->RecvBuf.fd = 0; return eAbort; } fdflags = fdflags | O_NONBLOCK; if (fcntl(IO->SendBuf.fd, F_SETFL, fdflags) < 0) { EV_syslog( LOG_ERR, "EVENT: unable to set socket %d nonblocking flags! %s \n", IO->SendBuf.fd, strerror(errno)); StrBufPrintf(IO->ErrMsg, "Failed to set socket flags: %s", strerror(errno)); close(IO->SendBuf.fd); IO->SendBuf.fd = IO->RecvBuf.fd = 0; return eAbort; } /* TODO: maye we could use offsetof() to calc the position of data... * http://doc.dvgu.ru/devel/ev.html#associating_custom_data_with_a_watcher */ ev_io_init(&IO->recv_event, IO_recv_callback, IO->RecvBuf.fd, EV_READ); IO->recv_event.data = IO; ev_io_init(&IO->send_event, IO_send_callback, IO->SendBuf.fd, EV_WRITE); IO->send_event.data = IO; ev_timer_init(&IO->conn_fail, IO_connfail_callback, conn_timeout, 0); IO->conn_fail.data = IO; ev_timer_init(&IO->rw_timeout, IO_Timeout_callback, first_rw_timeout,0); IO->rw_timeout.data = IO; /* for debugging you may bypass it like this: * IO->Addr.sin_addr.s_addr = inet_addr("127.0.0.1"); * ((struct sockaddr_in)IO->ConnectMe->Addr).sin_addr.s_addr = * inet_addr("127.0.0.1"); */ if (IO->ConnectMe->IPv6) { rc = connect(IO->SendBuf.fd, &IO->ConnectMe->Addr, sizeof(struct sockaddr_in6)); } else { /* If citserver is bound to a specific IP address on the host, make * sure we use that address for outbound connections. */ memset(&egress_sin, 0, sizeof(egress_sin)); egress_sin.sin_family = AF_INET; if (!IsEmptyStr(config.c_ip_addr)) { egress_sin.sin_addr.s_addr = inet_addr(config.c_ip_addr); if (egress_sin.sin_addr.s_addr == !INADDR_ANY) { egress_sin.sin_addr.s_addr = INADDR_ANY; } /* If this bind fails, no problem; we can still use INADDR_ANY */ bind(IO->SendBuf.fd, (struct sockaddr *)&egress_sin, sizeof(egress_sin)); } rc = connect(IO->SendBuf.fd, (struct sockaddr_in *)&IO->ConnectMe->Addr, sizeof(struct sockaddr_in)); } if (rc >= 0){ SetEVState(IO, eIOConnNow); EV_syslog(LOG_DEBUG, "connect() = %d immediate success.\n", IO->SendBuf.fd); set_start_callback(event_base, IO, 0); return IO->NextState; } else if (errno == EINPROGRESS) { SetEVState(IO, eIOConnWait); EV_syslog(LOG_DEBUG, "connect() = %d have to wait now.\n", IO->SendBuf.fd); ev_io_init(&IO->conn_event, IO_connestd_callback, IO->SendBuf.fd, EV_READ|EV_WRITE); IO->conn_event.data = IO; ev_io_start(event_base, &IO->conn_event); ev_timer_start(event_base, &IO->conn_fail); return IO->NextState; } else { SetEVState(IO, eIOConnfail); ev_idle_init(&IO->conn_fail_immediate, IO_connfailimmediate_callback); IO->conn_fail_immediate.data = IO; ev_idle_start(event_base, &IO->conn_fail_immediate); EV_syslog(LOG_ERR, "connect() = %d failed: %s\n", IO->SendBuf.fd, strerror(errno)); StrBufPrintf(IO->ErrMsg, "Failed to connect: %s", strerror(errno)); return IO->NextState; } return IO->NextState; } void SetNextTimeout(AsyncIO *IO, double timeout) { IO->rw_timeout.repeat = timeout; ev_timer_again (event_base, &IO->rw_timeout); } eNextState ReAttachIO(AsyncIO *IO, void *pData, int ReadFirst) { SetEVState(IO, eIOAttach); IO->Data = pData; become_session(IO->CitContext); ev_cleanup_start(event_base, &IO->abort_by_shutdown); if (ReadFirst) { IO->NextState = eReadMessage; } else { IO->NextState = eSendReply; } set_start_callback(event_base, IO, 0); return IO->NextState; } void InitIOStruct(AsyncIO *IO, void *Data, eNextState NextState, IO_LineReaderCallback LineReader, IO_CallBack DNS_Fail, IO_CallBack SendDone, IO_CallBack ReadDone, IO_CallBack Terminate, IO_CallBack DBTerminate, IO_CallBack ConnFail, IO_CallBack Timeout, IO_CallBack ShutdownAbort) { IO->Data = Data; IO->CitContext = CloneContext(CC); IO->CitContext->session_specific_data = Data; IO->CitContext->IO = IO; IO->NextState = NextState; IO->SendDone = SendDone; IO->ReadDone = ReadDone; IO->Terminate = Terminate; IO->DBTerminate = DBTerminate; IO->LineReader = LineReader; IO->ConnFail = ConnFail; IO->Timeout = Timeout; IO->ShutdownAbort = ShutdownAbort; IO->DNS.Fail = DNS_Fail; IO->SendBuf.Buf = NewStrBufPlain(NULL, 1024); IO->RecvBuf.Buf = NewStrBufPlain(NULL, 1024); IO->IOBuf = NewStrBuf(); EV_syslog(LOG_DEBUG, "EVENT: Session lives at %p IO at %p \n", Data, IO); } extern int evcurl_init(AsyncIO *IO); int InitcURLIOStruct(AsyncIO *IO, void *Data, const char* Desc, IO_CallBack SendDone, IO_CallBack Terminate, IO_CallBack DBTerminate, IO_CallBack ShutdownAbort) { IO->Data = Data; IO->CitContext = CloneContext(CC); IO->CitContext->session_specific_data = Data; IO->CitContext->IO = IO; IO->SendDone = SendDone; IO->Terminate = Terminate; IO->DBTerminate = DBTerminate; IO->ShutdownAbort = ShutdownAbort; strcpy(IO->HttpReq.errdesc, Desc); return evcurl_init(IO); } typedef struct KillOtherSessionContext { AsyncIO IO; AsyncIO *OtherOne; }KillOtherSessionContext; eNextState KillTerminate(AsyncIO *IO) { long id; KillOtherSessionContext *Ctx = (KillOtherSessionContext*)IO->Data; EV_syslog(LOG_DEBUG, "%s Exit\n", __FUNCTION__); id = IO->ID; FreeAsyncIOContents(IO); memset(Ctx, 0, sizeof(KillOtherSessionContext)); IO->ID = id; /* just for the case we want to analyze it in a coredump */ free(Ctx); return eAbort; } eNextState KillShutdown(AsyncIO *IO) { return eTerminateConnection; } eNextState KillOtherContextNow(AsyncIO *IO) { KillOtherSessionContext *Ctx = IO->Data; SetEVState(IO, eKill); if (Ctx->OtherOne->ShutdownAbort != NULL) { Ctx->OtherOne->NextState = eAbort; if (Ctx->OtherOne->ShutdownAbort(Ctx->OtherOne) == eDBQuery) { StopClientWatchers(Ctx->OtherOne, 0); QueueAnDBOperation(Ctx->OtherOne); } } return eTerminateConnection; } void KillAsyncIOContext(AsyncIO *IO) { KillOtherSessionContext *Ctx; Ctx = (KillOtherSessionContext*) malloc(sizeof(KillOtherSessionContext)); memset(Ctx, 0, sizeof(KillOtherSessionContext)); InitIOStruct(&Ctx->IO, Ctx, eReadMessage, NULL, NULL, NULL, NULL, KillTerminate, NULL, NULL, NULL, KillShutdown); Ctx->OtherOne = IO; switch(IO->NextState) { case eSendDNSQuery: case eReadDNSReply: case eConnect: case eSendReply: case eSendMore: case eSendFile: case eReadMessage: case eReadMore: case eReadPayload: case eReadFile: Ctx->IO.ReAttachCB = KillOtherContextNow; QueueAnEventContext(&Ctx->IO); break; case eDBQuery: Ctx->IO.ReAttachCB = KillOtherContextNow; QueueAnDBOperation(&Ctx->IO); break; case eTerminateConnection: case eAbort: /*hm, its already dying, dunno which Queue its in... */ free(Ctx); } } extern int DebugEventLoopBacktrace; void EV_backtrace(AsyncIO *IO) { #ifdef HAVE_BACKTRACE void *stack_frames[50]; size_t size, i; char **strings; if ((IO == NULL) || (DebugEventLoopBacktrace == 0)) return; size = backtrace(stack_frames, sizeof(stack_frames) / sizeof(void*)); strings = backtrace_symbols(stack_frames, size); for (i = 0; i < size; i++) { if (strings != NULL) { EV_syslog(LOG_ALERT, " BT %s\n", strings[i]); } else { EV_syslog(LOG_ALERT, " BT %p\n", stack_frames[i]); } } free(strings); #endif } ev_tstamp ctdl_ev_now (void) { return ev_now(event_base); } citadel-9.01/typesize.h0000644000000000000000000000404312507024051013570 0ustar rootroot /* This file defines typedefs for 8, 16, and 32 bit integers. They are: cit_int8_t default 8-bit int cit_int16_t default 16-bit int cit_int32_t default 32-bit int cit_int64_t default 64-bit int (not implemented yet) cit_sint8_t signed 8-bit int cit_sint16_t signed 16-bit int cit_sint32_t signed 32-bit int cit_sint64_t signed 64-bit int (not implemented yet) cit_uint8_t unsigned 8-bit int cit_uint16_t unsigned 16-bit int cit_uint32_t unsigned 32-bit int cit_uint64_t unsigned 64-bit int (not implemented yet) The sizes are determined during the configure process; see the AC_CHECK_SIZEOF macros in configure.in. In no way do we assume that any given datatype is any particular width, e.g. we don't assume short is two bytes; we check for it specifically. This might seem excessively paranoid, but I've seen some WEIRD systems and some bizarre compilers (Domain/OS for instance) in my time. */ #ifndef _CITADEL_UX_TYPESIZE_H #define _CITADEL_UX_TYPESIZE_H /* Include sysdep.h if not already included */ #ifndef CTDLDIR # include "sysdep.h" #endif /* 8-bit - If this fails, your compiler is broken */ #if SIZEOF_CHAR == 1 typedef char cit_int8_t; typedef signed char cit_sint8_t; typedef unsigned char cit_uint8_t; #else # error Unable to find an 8-bit integer datatype #endif /* 16-bit - If this fails, your compiler is broken */ #if SIZEOF_SHORT == 2 typedef short cit_int16_t; typedef signed short cit_sint16_t; typedef unsigned short cit_uint16_t; #elif SIZEOF_INT == 2 typedef int cit_int16_t; typedef signed int cit_sint16_t; typedef unsigned int cit_uint16_t; #else # error Unable to find a 16-bit integer datatype #endif /* 32-bit - If this fails, your compiler is broken */ #if SIZEOF_INT == 4 typedef int cit_int32_t; typedef signed int cit_sint32_t; typedef unsigned int cit_uint32_t; #elif SIZEOF_LONG == 4 typedef long cit_int32_t; typedef signed long cit_sint32_t; typedef unsigned long cit_uint32_t; #else # error Unable to find a 32-bit integer datatype #endif #endif /* _CITADEL_UX_TYPESIZE_H */ citadel-9.01/contrib/0000755000000000000000000000000012507024051013202 5ustar rootrootcitadel-9.01/contrib/getdoku.sh0000755000000000000000000000277112507024051015212 0ustar rootroot#!/bin/bash BASE_SITE=http://www.citadel.org #retrieves an index document from the citadel.org website, and filters it # 1: URL # 2: outfile where to put the filtered content at GetIndex() { cd /tmp/; wget -q "${BASE_SITE}/${1}" cat "/tmp/${1}" | \ grep /doku.php/ | \ grep -v "do=" | \ sed -e "s;.*href=\";;" \ -e "s;\" .*;;" \ -e "s;doku.php/;doku.php?id=;"| \ grep "^/doku" > \ "/tmp/$2" } rm -f /tmp/mainindex /tmp/doku.php* GetIndex "doku.php?id=faq:start" mainindex for i in `cat /tmp/mainindex`; do TMPNAME=`echo $i|sed "s;.*=;;"` echo $i $TMPNAME mkdir /tmp/$TMPNAME GetIndex "$i" "$TMPNAME/$TMPNAME" for j in `cat /tmp/$TMPNAME/$TMPNAME`; do echo "-----------$j----------------" cd /tmp/$TMPNAME/; DOCUMENT_NAME=`echo $j|sed -e "s;/doku.php?id=.*:;;"` PLAIN_NAME=`grep "$DOCUMENT_NAME" /tmp/doku*$TMPNAME |head -n1 |sed -e "s;','/doku.*;;" -e "s;.*';;"` echo "********** retrieving $DOCUMENT_NAME ************" wget -q "${BASE_SITE}/${j}&do=export_xhtmlbody" mv "/tmp/$TMPNAME/${j}&do=export_xhtmlbody" /tmp/$TMPNAME/$DOCUMENT_NAME echo "
  • $PLAIN_NAME
  • " >>collect_index echo "" >>collect_bodies cat $DOCUMENT_NAME>>collect_bodies done ( echo "$TMPNAME
      " cat "/tmp/$TMPNAME/collect_index" echo "
    " cat "/tmp/$TMPNAME/collect_bodies" echo "" ) >/tmp/`echo $TMPNAME|sed "s;:;_;g"`.html donecitadel-9.01/euidindex.h0000644000000000000000000000060012507024051013665 0ustar rootroot/* * Index messages by EUID per room. */ int DoesThisRoomNeedEuidIndexing(struct ctdlroom *qrbuf); /* locate_message_by_euid is deprecated. Use CtdlLocateMessageByEuid instead */ long locate_message_by_euid(char *euid, struct ctdlroom *qrbuf) __attribute__ ((deprecated)); void index_message_by_euid(char *euid, struct ctdlroom *qrbuf, long msgnum); void rebuild_euid_index(void); citadel-9.01/modules_upgrade.c0000644000000000000000000000113512507024063015070 0ustar rootroot/* * /var/www/easyinstall/citadel/citadel/modules_upgrade.c * Auto generated by mk_modules_init.sh DO NOT EDIT THIS FILE */ #include "sysdep.h" #include #include #include #include #include #include #include #include #include "citadel.h" #include "modules_init.h" #include "sysdep_decls.h" #include "serv_extensions.h" void upgrade_modules (void) { const char *pMod; MODM_syslog(LOG_INFO, "Upgrade modules.\n"); pMod = CTDL_UPGRADE_CALL(upgrade); MOD_syslog(LOG_INFO, "%s\n", pMod); } citadel-9.01/msgbase.c0000644000000000000000000032274512507024051013344 0ustar rootroot/* * Implements the message store. * * Copyright (c) 1987-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include "md5.h" #include "ctdl_module.h" #include "citserver.h" #include "control.h" #include "clientsocket.h" #include "genstamp.h" #include "room_ops.h" #include "user_ops.h" #include "internet_addressing.h" #include "euidindex.h" #include "msgbase.h" #include "journaling.h" struct addresses_to_be_filed *atbf = NULL; /* This temp file holds the queue of operations for AdjRefCount() */ static FILE *arcfp = NULL; void AdjRefCountList(long *msgnum, long nmsg, int incr); int MessageDebugEnabled = 0; /* * These are the four-character field headers we use when outputting * messages in Citadel format (as opposed to RFC822 format). */ char *msgkeys[] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "from", /* A */ NULL, /* B */ NULL, /* C */ NULL, /* D */ "exti", /* E */ "rfca", /* F */ NULL, /* G */ "hnod", /* H */ "msgn", /* I */ "jrnl", /* J */ "rep2", /* K */ "list", /* L */ "text", /* M */ "node", /* N */ "room", /* O */ "path", /* P */ NULL, /* Q */ "rcpt", /* R */ "spec", /* S */ "time", /* T */ "subj", /* U */ "nvto", /* V */ "wefw", /* W */ NULL, /* X */ "cccc", /* Y */ NULL /* Z */ }; eMsgField FieldOrder[] = { /* Important fields */ emessageId , eMessagePath , eTimestamp , eAuthor , erFc822Addr , eOriginalRoom, eNodeName , eHumanNode , eRecipient , eDestination , /* Semi-important fields */ eBig_message , eRemoteRoom , eExclusiveID , eWeferences , eJournal , /* G is not used yet, may become virus signature*/ eReplyTo , eListID , /* Q is not used yet */ eSpecialField, eenVelopeTo , /* X is not used yet */ /* Z is not used yet */ eCarbonCopY , eMsgSubject , /* internal only */ eErrorMsg , eSuppressIdx , eExtnotify , /* Message text (MUST be last) */ eMesageText /* Not saved to disk: eVltMsgNum */ }; static const long NDiskFields = sizeof(FieldOrder) / sizeof(eMsgField); int CM_IsEmpty(struct CtdlMessage *Msg, eMsgField which) { return !((Msg->cm_fields[which] != NULL) && (Msg->cm_fields[which][0] != '\0')); } void CM_SetField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length) { if (Msg->cm_fields[which] != NULL) free (Msg->cm_fields[which]); Msg->cm_fields[which] = malloc(length + 1); memcpy(Msg->cm_fields[which], buf, length); Msg->cm_fields[which][length] = '\0'; Msg->cm_lengths[which] = length; } void CM_SetFieldLONG(struct CtdlMessage *Msg, eMsgField which, long lvalue) { char buf[128]; long len; len = snprintf(buf, sizeof(buf), "%ld", lvalue); CM_SetField(Msg, which, buf, len); } void CM_CutFieldAt(struct CtdlMessage *Msg, eMsgField WhichToCut, long maxlen) { if (Msg->cm_fields[WhichToCut] == NULL) return; if (Msg->cm_lengths[WhichToCut] > maxlen) { Msg->cm_fields[WhichToCut][maxlen] = '\0'; Msg->cm_lengths[WhichToCut] = maxlen; } } void CM_FlushField(struct CtdlMessage *Msg, eMsgField which) { if (Msg->cm_fields[which] != NULL) free (Msg->cm_fields[which]); Msg->cm_fields[which] = NULL; Msg->cm_lengths[which] = 0; } void CM_Flush(struct CtdlMessage *Msg) { int i; if (CM_IsValidMsg(Msg) == 0) return; for (i = 0; i < 256; ++i) { CM_FlushField(Msg, i); } } void CM_CopyField(struct CtdlMessage *Msg, eMsgField WhichToPutTo, eMsgField WhichtToCopy) { long len; if (Msg->cm_fields[WhichToPutTo] != NULL) free (Msg->cm_fields[WhichToPutTo]); if (Msg->cm_fields[WhichtToCopy] != NULL) { len = Msg->cm_lengths[WhichtToCopy]; Msg->cm_fields[WhichToPutTo] = malloc(len + 1); memcpy(Msg->cm_fields[WhichToPutTo], Msg->cm_fields[WhichtToCopy], len); Msg->cm_fields[WhichToPutTo][len] = '\0'; Msg->cm_lengths[WhichToPutTo] = len; } else { Msg->cm_fields[WhichToPutTo] = NULL; Msg->cm_lengths[WhichToPutTo] = 0; } } void CM_PrependToField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length) { if (Msg->cm_fields[which] != NULL) { long oldmsgsize; long newmsgsize; char *new; oldmsgsize = Msg->cm_lengths[which] + 1; newmsgsize = length + oldmsgsize; new = malloc(newmsgsize); memcpy(new, buf, length); memcpy(new + length, Msg->cm_fields[which], oldmsgsize); free(Msg->cm_fields[which]); Msg->cm_fields[which] = new; Msg->cm_lengths[which] = newmsgsize - 1; } else { Msg->cm_fields[which] = malloc(length + 1); memcpy(Msg->cm_fields[which], buf, length); Msg->cm_fields[which][length] = '\0'; Msg->cm_lengths[which] = length; } } void CM_SetAsField(struct CtdlMessage *Msg, eMsgField which, char **buf, long length) { if (Msg->cm_fields[which] != NULL) free (Msg->cm_fields[which]); Msg->cm_fields[which] = *buf; *buf = NULL; Msg->cm_lengths[which] = length; } void CM_SetAsFieldSB(struct CtdlMessage *Msg, eMsgField which, StrBuf **buf) { if (Msg->cm_fields[which] != NULL) free (Msg->cm_fields[which]); Msg->cm_lengths[which] = StrLength(*buf); Msg->cm_fields[which] = SmashStrBuf(buf); } void CM_GetAsField(struct CtdlMessage *Msg, eMsgField which, char **ret, long *retlen) { if (Msg->cm_fields[which] != NULL) { *retlen = Msg->cm_lengths[which]; *ret = Msg->cm_fields[which]; Msg->cm_fields[which] = NULL; Msg->cm_lengths[which] = 0; } else { *ret = NULL; *retlen = 0; } } /* * Returns 1 if the supplied pointer points to a valid Citadel message. * If the pointer is NULL or the magic number check fails, returns 0. */ int CM_IsValidMsg(struct CtdlMessage *msg) { if (msg == NULL) return 0; if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) { struct CitContext *CCC = CC; MSGM_syslog(LOG_WARNING, "CM_IsValidMsg() -- self-check failed\n"); return 0; } return 1; } void CM_FreeContents(struct CtdlMessage *msg) { int i; for (i = 0; i < 256; ++i) if (msg->cm_fields[i] != NULL) { free(msg->cm_fields[i]); msg->cm_lengths[i] = 0; } msg->cm_magic = 0; /* just in case */ } /* * 'Destructor' for struct CtdlMessage */ void CM_Free(struct CtdlMessage *msg) { if (CM_IsValidMsg(msg) == 0) { if (msg != NULL) free (msg); return; } CM_FreeContents(msg); free(msg); } int CM_DupField(eMsgField i, struct CtdlMessage *OrgMsg, struct CtdlMessage *NewMsg) { long len; len = OrgMsg->cm_lengths[i]; NewMsg->cm_fields[i] = malloc(len + 1); if (NewMsg->cm_fields[i] == NULL) return 0; memcpy(NewMsg->cm_fields[i], OrgMsg->cm_fields[i], len); NewMsg->cm_fields[i][len] = '\0'; NewMsg->cm_lengths[i] = len; return 1; } struct CtdlMessage * CM_Duplicate(struct CtdlMessage *OrgMsg) { int i; struct CtdlMessage *NewMsg; if (CM_IsValidMsg(OrgMsg) == 0) return NULL; NewMsg = (struct CtdlMessage *)malloc(sizeof(struct CtdlMessage)); if (NewMsg == NULL) return NULL; memcpy(NewMsg, OrgMsg, sizeof(struct CtdlMessage)); memset(&NewMsg->cm_fields, 0, sizeof(char*) * 256); for (i = 0; i < 256; ++i) { if (OrgMsg->cm_fields[i] != NULL) { if (!CM_DupField(i, OrgMsg, NewMsg)) { CM_Free(NewMsg); return NULL; } } } return NewMsg; } /* Determine if a given message matches the fields in a message template. * Return 0 for a successful match. */ int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) { int i; /* If there aren't any fields in the template, all messages will * match. */ if (template == NULL) return(0); /* Null messages are bogus. */ if (msg == NULL) return(1); for (i='A'; i<='Z'; ++i) { if (template->cm_fields[i] != NULL) { if (msg->cm_fields[i] == NULL) { /* Considered equal if temmplate is empty string */ if (IsEmptyStr(template->cm_fields[i])) continue; return 1; } if ((template->cm_lengths[i] != msg->cm_lengths[i]) || (strcasecmp(msg->cm_fields[i], template->cm_fields[i]))) return 1; } } /* All compares succeeded: we have a match! */ return 0; } /* * Retrieve the "seen" message list for the current room. */ void CtdlGetSeen(char *buf, int which_set) { struct CitContext *CCC = CC; visit vbuf; /* Learn about the user and room in question */ CtdlGetRelationship(&vbuf, &CCC->user, &CCC->room); if (which_set == ctdlsetseen_seen) safestrncpy(buf, vbuf.v_seen, SIZ); if (which_set == ctdlsetseen_answered) safestrncpy(buf, vbuf.v_answered, SIZ); } /* * Manipulate the "seen msgs" string (or other message set strings) */ void CtdlSetSeen(long *target_msgnums, int num_target_msgnums, int target_setting, int which_set, struct ctdluser *which_user, struct ctdlroom *which_room) { struct CitContext *CCC = CC; struct cdbdata *cdbfr; int i, k; int is_seen = 0; int was_seen = 0; long lo = (-1L); long hi = (-1L); /// TODO: we just write here. y? visit vbuf; long *msglist; int num_msgs = 0; StrBuf *vset; StrBuf *setstr; StrBuf *lostr; StrBuf *histr; const char *pvset; char *is_set; /* actually an array of booleans */ /* Don't bother doing *anything* if we were passed a list of zero messages */ if (num_target_msgnums < 1) { return; } /* If no room was specified, we go with the current room. */ if (!which_room) { which_room = &CCC->room; } /* If no user was specified, we go with the current user. */ if (!which_user) { which_user = &CCC->user; } MSG_syslog(LOG_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>\n", num_target_msgnums, target_msgnums[0], (target_setting ? "SET" : "CLEAR"), which_set, which_room->QRname); /* Learn about the user and room in question */ CtdlGetRelationship(&vbuf, which_user, which_room); /* Load the message list */ cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long)); if (cdbfr != NULL) { msglist = (long *) cdbfr->ptr; cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */ num_msgs = cdbfr->len / sizeof(long); cdb_free(cdbfr); } else { return; /* No messages at all? No further action. */ } is_set = malloc(num_msgs * sizeof(char)); memset(is_set, 0, (num_msgs * sizeof(char)) ); /* Decide which message set we're manipulating */ switch(which_set) { case ctdlsetseen_seen: vset = NewStrBufPlain(vbuf.v_seen, -1); break; case ctdlsetseen_answered: vset = NewStrBufPlain(vbuf.v_answered, -1); break; default: vset = NewStrBuf(); } #if 0 /* This is a special diagnostic section. Do not allow it to run during normal operation. */ MSG_syslog(LOG_DEBUG, "There are %d messages in the room.\n", num_msgs); for (i=0; i 0) && (msglist[i] <= msglist[i-1])) abort(); } MSG_syslog(LOG_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums); for (k=0; k 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort(); } #endif MSG_syslog(LOG_DEBUG, "before update: %s\n", ChrPtr(vset)); /* Translate the existing sequence set into an array of booleans */ setstr = NewStrBuf(); lostr = NewStrBuf(); histr = NewStrBuf(); pvset = NULL; while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) { StrBufExtract_token(lostr, setstr, 0, ':'); if (StrBufNum_tokens(setstr, ':') >= 2) { StrBufExtract_token(histr, setstr, 1, ':'); } else { FlushStrBuf(histr); StrBufAppendBuf(histr, lostr, 0); } lo = StrTol(lostr); if (!strcmp(ChrPtr(histr), "*")) { hi = LONG_MAX; } else { hi = StrTol(histr); } for (i = 0; i < num_msgs; ++i) { if ((msglist[i] >= lo) && (msglist[i] <= hi)) { is_set[i] = 1; } } } FreeStrBuf(&setstr); FreeStrBuf(&lostr); FreeStrBuf(&histr); /* Now translate the array of booleans back into a sequence set */ FlushStrBuf(vset); was_seen = 0; lo = (-1); hi = (-1); for (i=0; i 0) { StrBufAppendBufPlain(vset, HKEY(","), 0); } if (lo == hi) { StrBufAppendPrintf(vset, "%ld", hi); } else { StrBufAppendPrintf(vset, "%ld:%ld", lo, hi); } } if ((is_seen) && (i == num_msgs - 1)) { if (StrLength(vset) > 0) { StrBufAppendBufPlain(vset, HKEY(","), 0); } if ((i==0) || (was_seen == 0)) { StrBufAppendPrintf(vset, "%ld", msglist[i]); } else { StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]); } } was_seen = is_seen; } /* * We will have to stuff this string back into a 4096 byte buffer, so if it's * larger than that now, truncate it by removing tokens from the beginning. * The limit of 100 iterations is there to prevent an infinite loop in case * something unexpected happens. */ int number_of_truncations = 0; while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) { StrBufRemove_token(vset, 0, ','); ++number_of_truncations; } /* * If we're truncating the sequence set of messages marked with the 'seen' flag, * we want the earliest messages (the truncated ones) to be marked, not unmarked. * Otherwise messages at the beginning will suddenly appear to be 'unseen'. */ if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) { StrBuf *first_tok; first_tok = NewStrBuf(); StrBufExtract_token(first_tok, vset, 0, ','); StrBufRemove_token(vset, 0, ','); if (StrBufNum_tokens(first_tok, ':') > 1) { StrBufRemove_token(first_tok, 0, ':'); } StrBuf *new_set; new_set = NewStrBuf(); StrBufAppendBufPlain(new_set, HKEY("1:"), 0); StrBufAppendBuf(new_set, first_tok, 0); StrBufAppendBufPlain(new_set, HKEY(":"), 0); StrBufAppendBuf(new_set, vset, 0); FreeStrBuf(&vset); FreeStrBuf(&first_tok); vset = new_set; } MSG_syslog(LOG_DEBUG, " after update: %s\n", ChrPtr(vset)); /* Decide which message set we're manipulating */ switch (which_set) { case ctdlsetseen_seen: safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen); break; case ctdlsetseen_answered: safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered); break; } free(is_set); free(msglist); CtdlSetRelationship(&vbuf, which_user, which_room); FreeStrBuf(&vset); } /* * API function to perform an operation for each qualifying message in the * current room. (Returns the number of messages processed.) */ int CtdlForEachMessage(int mode, long ref, char *search_string, char *content_type, struct CtdlMessage *compare, ForEachMsgCallback CallBack, void *userdata) { struct CitContext *CCC = CC; int a, i, j; visit vbuf; struct cdbdata *cdbfr; long *msglist = NULL; int num_msgs = 0; int num_processed = 0; long thismsg; struct MetaData smi; struct CtdlMessage *msg = NULL; int is_seen = 0; long lastold = 0L; int printed_lastold = 0; int num_search_msgs = 0; long *search_msgs = NULL; regex_t re; int need_to_free_re = 0; regmatch_t pm; if ((content_type) && (!IsEmptyStr(content_type))) { regcomp(&re, content_type, 0); need_to_free_re = 1; } /* Learn about the user and room in question */ if (server_shutting_down) { if (need_to_free_re) regfree(&re); return -1; } CtdlGetUser(&CCC->user, CCC->curr_user); if (server_shutting_down) { if (need_to_free_re) regfree(&re); return -1; } CtdlGetRelationship(&vbuf, &CCC->user, &CCC->room); if (server_shutting_down) { if (need_to_free_re) regfree(&re); return -1; } /* Load the message list */ cdbfr = cdb_fetch(CDB_MSGLISTS, &CCC->room.QRnumber, sizeof(long)); if (cdbfr == NULL) { if (need_to_free_re) regfree(&re); return 0; /* No messages at all? No further action. */ } msglist = (long *) cdbfr->ptr; num_msgs = cdbfr->len / sizeof(long); cdbfr->ptr = NULL; /* clear this so that cdb_free() doesn't free it */ cdb_free(cdbfr); /* we own this memory now */ /* * Now begin the traversal. */ if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) { /* If the caller is looking for a specific MIME type, filter * out all messages which are not of the type requested. */ if ((content_type != NULL) && (!IsEmptyStr(content_type))) { /* This call to GetMetaData() sits inside this loop * so that we only do the extra database read per msg * if we need to. Doing the extra read all the time * really kills the server. If we ever need to use * metadata for another search criterion, we need to * move the read somewhere else -- but still be smart * enough to only do the read if the caller has * specified something that will need it. */ if (server_shutting_down) { if (need_to_free_re) regfree(&re); free(msglist); return -1; } GetMetaData(&smi, msglist[a]); /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */ if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) { msglist[a] = 0L; } } } num_msgs = sort_msglist(msglist, num_msgs); /* If a template was supplied, filter out the messages which * don't match. (This could induce some delays!) */ if (num_msgs > 0) { if (compare != NULL) { for (a = 0; a < num_msgs; ++a) { if (server_shutting_down) { if (need_to_free_re) regfree(&re); free(msglist); return -1; } msg = CtdlFetchMessage(msglist[a], 1); if (msg != NULL) { if (CtdlMsgCmp(msg, compare)) { msglist[a] = 0L; } CM_Free(msg); } } } } /* If a search string was specified, get a message list from * the full text index and remove messages which aren't on both * lists. * * How this works: * Since the lists are sorted and strictly ascending, and the * output list is guaranteed to be shorter than or equal to the * input list, we overwrite the bottom of the input list. This * eliminates the need to memmove big chunks of the list over and * over again. */ if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) { /* Call search module via hook mechanism. * NULL means use any search function available. * otherwise replace with a char * to name of search routine */ CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext"); if (num_search_msgs > 0) { int orig_num_msgs; orig_num_msgs = num_msgs; num_msgs = 0; for (i=0; i 0) for (a = 0; a < num_msgs; ++a) { if (server_shutting_down) { if (need_to_free_re) regfree(&re); free(msglist); return num_processed; } thismsg = msglist[a]; if (mode == MSGS_ALL) { is_seen = 0; } else { is_seen = is_msg_in_sequence_set( vbuf.v_seen, thismsg); if (is_seen) lastold = thismsg; } if ((thismsg > 0L) && ( (mode == MSGS_ALL) || ((mode == MSGS_OLD) && (is_seen)) || ((mode == MSGS_NEW) && (!is_seen)) || ((mode == MSGS_LAST) && (a >= (num_msgs - ref))) || ((mode == MSGS_FIRST) && (a < ref)) || ((mode == MSGS_GT) && (thismsg > ref)) || ((mode == MSGS_LT) && (thismsg < ref)) || ((mode == MSGS_EQ) && (thismsg == ref)) ) ) { if ((mode == MSGS_NEW) && (CCC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) { if (CallBack) CallBack(lastold, userdata); printed_lastold = 1; ++num_processed; } if (CallBack) CallBack(thismsg, userdata); ++num_processed; } } if (need_to_free_re) regfree(&re); /* * We cache the most recent msglist in order to do security checks later */ if (CCC->client_socket > 0) { if (CCC->cached_msglist != NULL) { free(CCC->cached_msglist); } CCC->cached_msglist = msglist; CCC->cached_num_msgs = num_msgs; } else { free(msglist); } return num_processed; } /* * memfmout() - Citadel text formatter and paginator. * Although the original purpose of this routine was to format * text to the reader's screen width, all we're really using it * for here is to format text out to 80 columns before sending it * to the client. The client software may reformat it again. */ void memfmout( char *mptr, /* where are we going to get our text from? */ const char *nl /* string to terminate lines with */ ) { struct CitContext *CCC = CC; int column = 0; unsigned char ch = 0; char outbuf[1024]; int len = 0; int nllen = 0; if (!mptr) return; nllen = strlen(nl); while (ch=*(mptr++), ch != 0) { if (ch == '\n') { if (client_write(outbuf, len) == -1) { MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n"); return; } len = 0; if (client_write(nl, nllen) == -1) { MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n"); return; } column = 0; } else if (ch == '\r') { /* Ignore carriage returns. Newlines are always LF or CRLF but never CR. */ } else if (isspace(ch)) { if (column > 72) { /* Beyond 72 columns, break on the next space */ if (client_write(outbuf, len) == -1) { MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n"); return; } len = 0; if (client_write(nl, nllen) == -1) { MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n"); return; } column = 0; } else { outbuf[len++] = ch; ++column; } } else { outbuf[len++] = ch; ++column; if (column > 1000) { /* Beyond 1000 columns, break anywhere */ if (client_write(outbuf, len) == -1) { MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n"); return; } len = 0; if (client_write(nl, nllen) == -1) { MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n"); return; } column = 0; } } } if (len) { if (client_write(outbuf, len) == -1) { MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n"); return; } client_write(nl, nllen); column = 0; } } /* * Callback function for mime parser that simply lists the part */ void list_this_part(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata) { struct ma_info *ma; ma = (struct ma_info *)cbuserdata; if (ma->is_ma == 0) { cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n", name, filename, partnum, disp, cbtype, (long)length, cbid, cbcharset); } } /* * Callback function for multipart prefix */ void list_this_pref(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata) { struct ma_info *ma; ma = (struct ma_info *)cbuserdata; if (!strcasecmp(cbtype, "multipart/alternative")) { ++ma->is_ma; } if (ma->is_ma == 0) { cprintf("pref=%s|%s\n", partnum, cbtype); } } /* * Callback function for multipart sufffix */ void list_this_suff(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata) { struct ma_info *ma; ma = (struct ma_info *)cbuserdata; if (ma->is_ma == 0) { cprintf("suff=%s|%s\n", partnum, cbtype); } if (!strcasecmp(cbtype, "multipart/alternative")) { --ma->is_ma; } } /* * Callback function for mime parser that opens a section for downloading * we use serv_files function here: */ extern void OpenCmdResult(char *filename, const char *mime_type); void mime_download(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata) { int rv = 0; CitContext *CCC = MyContext(); /* Silently go away if there's already a download open. */ if (CCC->download_fp != NULL) return; if ( (!IsEmptyStr(partnum) && (!strcasecmp(CCC->download_desired_section, partnum))) || (!IsEmptyStr(cbid) && (!strcasecmp(CCC->download_desired_section, cbid))) ) { CCC->download_fp = tmpfile(); if (CCC->download_fp == NULL) { MSG_syslog(LOG_EMERG, "mime_download(): Couldn't write: %s\n", strerror(errno)); cprintf("%d cannot open temporary file: %s\n", ERROR + INTERNAL_ERROR, strerror(errno)); return; } rv = fwrite(content, length, 1, CCC->download_fp); if (rv <= 0) { MSG_syslog(LOG_EMERG, "mime_download(): Couldn't write: %s\n", strerror(errno)); cprintf("%d unable to write tempfile.\n", ERROR + TOO_BIG); fclose(CCC->download_fp); CCC->download_fp = NULL; return; } fflush(CCC->download_fp); rewind(CCC->download_fp); OpenCmdResult(filename, cbtype); } } /* * Callback function for mime parser that outputs a section all at once. * We can specify the desired section by part number *or* content-id. */ void mime_spew_section(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata) { int *found_it = (int *)cbuserdata; if ( (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum))) || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid))) ) { *found_it = 1; cprintf("%d %d|-1|%s|%s|%s\n", BINARY_FOLLOWS, (int)length, filename, cbtype, cbcharset ); client_write(content, length); } } /* * Load a message from disk into memory. * This is used by CtdlOutputMsg() and other fetch functions. * * NOTE: Caller is responsible for freeing the returned CtdlMessage struct * using the CtdlMessageFree() function. */ struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body) { struct CitContext *CCC = CC; struct cdbdata *dmsgtext; struct CtdlMessage *ret = NULL; char *mptr; char *upper_bound; cit_uint8_t ch; cit_uint8_t field_header; eMsgField which; MSG_syslog(LOG_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body); dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long)); if (dmsgtext == NULL) { MSG_syslog(LOG_ERR, "CtdlFetchMessage(%ld, %d) Failed!\n", msgnum, with_body); return NULL; } mptr = dmsgtext->ptr; upper_bound = mptr + dmsgtext->len; /* Parse the three bytes that begin EVERY message on disk. * The first is always 0xFF, the on-disk magic number. * The second is the anonymous/public type byte. * The third is the format type byte (vari, fixed, or MIME). */ ch = *mptr++; if (ch != 255) { MSG_syslog(LOG_ERR, "Message %ld appears to be corrupted.\n", msgnum); cdb_free(dmsgtext); return NULL; } ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage)); memset(ret, 0, sizeof(struct CtdlMessage)); ret->cm_magic = CTDLMESSAGE_MAGIC; ret->cm_anon_type = *mptr++; /* Anon type byte */ ret->cm_format_type = *mptr++; /* Format type byte */ if (dmsgtext->ptr[dmsgtext->len - 1] != '\0') { MSG_syslog(LOG_ERR, "CtdlFetchMessage(%ld, %d) Forcefully terminating message!!\n", msgnum, with_body); dmsgtext->ptr[dmsgtext->len - 1] = '\0'; } /* * The rest is zero or more arbitrary fields. Load them in. * We're done when we encounter either a zero-length field or * have just processed the 'M' (message text) field. */ do { field_header = '\0'; long len; /* work around possibly buggy messages: */ while (field_header == '\0') { if (mptr >= upper_bound) { break; } field_header = *mptr++; } if (mptr >= upper_bound) { break; } which = field_header; len = strlen(mptr); CM_SetField(ret, which, mptr, len); mptr += len + 1; /* advance to next field */ } while ((mptr < upper_bound) && (field_header != 'M')); cdb_free(dmsgtext); /* Always make sure there's something in the msg text field. If * it's NULL, the message text is most likely stored separately, * so go ahead and fetch that. Failing that, just set a dummy * body so other code doesn't barf. */ if ( (CM_IsEmpty(ret, eMesageText)) && (with_body) ) { dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long)); if (dmsgtext != NULL) { CM_SetAsField(ret, eMesageText, &dmsgtext->ptr, dmsgtext->len - 1); cdb_free(dmsgtext); } } if (CM_IsEmpty(ret, eMesageText)) { CM_SetField(ret, eMesageText, HKEY("\r\n\r\n (no text)\r\n")); } /* Perform "before read" hooks (aborting if any return nonzero) */ if (PerformMessageHooks(ret, NULL, EVT_BEFOREREAD) > 0) { CM_Free(ret); return NULL; } return (ret); } /* * Pre callback function for multipart/alternative * * NOTE: this differs from the standard behavior for a reason. Normally when * displaying multipart/alternative you want to show the _last_ usable * format in the message. Here we show the _first_ one, because it's * usually text/plain. Since this set of functions is designed for text * output to non-MIME-aware clients, this is the desired behavior. * */ void fixed_output_pre(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata) { struct CitContext *CCC = CC; struct ma_info *ma; ma = (struct ma_info *)cbuserdata; MSG_syslog(LOG_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype); if (!strcasecmp(cbtype, "multipart/alternative")) { ++ma->is_ma; ma->did_print = 0; } if (!strcasecmp(cbtype, "message/rfc822")) { ++ma->freeze; } } /* * Post callback function for multipart/alternative */ void fixed_output_post(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata) { struct CitContext *CCC = CC; struct ma_info *ma; ma = (struct ma_info *)cbuserdata; MSG_syslog(LOG_DEBUG, "fixed_output_post() type=<%s>\n", cbtype); if (!strcasecmp(cbtype, "multipart/alternative")) { --ma->is_ma; ma->did_print = 0; } if (!strcasecmp(cbtype, "message/rfc822")) { --ma->freeze; } } /* * Inline callback function for mime parser that wants to display text */ void fixed_output(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata) { struct CitContext *CCC = CC; char *ptr; char *wptr; size_t wlen; struct ma_info *ma; ma = (struct ma_info *)cbuserdata; MSG_syslog(LOG_DEBUG, "fixed_output() part %s: %s (%s) (%ld bytes)\n", partnum, filename, cbtype, (long)length); /* * If we're in the middle of a multipart/alternative scope and * we've already printed another section, skip this one. */ if ( (ma->is_ma) && (ma->did_print) ) { MSG_syslog(LOG_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype); return; } ma->did_print = 1; if ( (!strcasecmp(cbtype, "text/plain")) || (IsEmptyStr(cbtype)) ) { wptr = content; if (length > 0) { client_write(wptr, length); if (wptr[length-1] != '\n') { cprintf("\n"); } } return; } if (!strcasecmp(cbtype, "text/html")) { ptr = html_to_ascii(content, length, 80, 0); wlen = strlen(ptr); client_write(ptr, wlen); if ((wlen > 0) && (ptr[wlen-1] != '\n')) { cprintf("\n"); } free(ptr); return; } if (ma->use_fo_hooks) { if (PerformFixedOutputHooks(cbtype, content, length)) { /* above function returns nonzero if it handled the part */ return; } } if (strncasecmp(cbtype, "multipart/", 10)) { cprintf("Part %s: %s (%s) (%ld bytes)\r\n", partnum, filename, cbtype, (long)length); return; } } /* * The client is elegant and sophisticated and wants to be choosy about * MIME content types, so figure out which multipart/alternative part * we're going to send. * * We use a system of weights. When we find a part that matches one of the * MIME types we've declared as preferential, we can store it in ma->chosen_part * and then set ma->chosen_pref to that MIME type's position in our preference * list. If we then hit another match, we only replace the first match if * the preference value is lower. */ void choose_preferred(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata) { struct CitContext *CCC = CC; char buf[1024]; int i; struct ma_info *ma; ma = (struct ma_info *)cbuserdata; // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220 // http://bugzilla.citadel.org/show_bug.cgi?id=220 // I don't know if there are any side effects! Please TEST TEST TEST //if (ma->is_ma > 0) { for (i=0; ipreferred_formats, '|'); ++i) { extract_token(buf, CCC->preferred_formats, i, '|', sizeof buf); if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) { if (i < ma->chosen_pref) { MSG_syslog(LOG_DEBUG, "Setting chosen part: <%s>\n", partnum); safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part); ma->chosen_pref = i; } } } } /* * Now that we've chosen our preferred part, output it. */ void output_preferred(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata) { struct CitContext *CCC = CC; int i; char buf[128]; int add_newline = 0; char *text_content; struct ma_info *ma; char *decoded = NULL; size_t bytes_decoded; int rc = 0; ma = (struct ma_info *)cbuserdata; /* This is not the MIME part you're looking for... */ if (strcasecmp(partnum, ma->chosen_part)) return; /* If the content-type of this part is in our preferred formats * list, we can simply output it verbatim. */ for (i=0; ipreferred_formats, '|'); ++i) { extract_token(buf, CCC->preferred_formats, i, '|', sizeof buf); if (!strcasecmp(buf, cbtype)) { /* Yeah! Go! W00t!! */ if (ma->dont_decode == 0) rc = mime_decode_now (content, length, encoding, &decoded, &bytes_decoded); if (rc < 0) break; /* Give us the chance, maybe theres another one. */ if (rc == 0) text_content = (char *)content; else { text_content = decoded; length = bytes_decoded; } if (text_content[length-1] != '\n') { ++add_newline; } cprintf("Content-type: %s", cbtype); if (!IsEmptyStr(cbcharset)) { cprintf("; charset=%s", cbcharset); } cprintf("\nContent-length: %d\n", (int)(length + add_newline) ); if (!IsEmptyStr(encoding)) { cprintf("Content-transfer-encoding: %s\n", encoding); } else { cprintf("Content-transfer-encoding: 7bit\n"); } cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum); cprintf("\n"); if (client_write(text_content, length) == -1) { MSGM_syslog(LOG_ERR, "output_preferred(): aborting due to write failure.\n"); return; } if (add_newline) cprintf("\n"); if (decoded != NULL) free(decoded); return; } } /* No translations required or possible: output as text/plain */ cprintf("Content-type: text/plain\n\n"); rc = 0; if (ma->dont_decode == 0) rc = mime_decode_now (content, length, encoding, &decoded, &bytes_decoded); if (rc < 0) return; /* Give us the chance, maybe theres another one. */ if (rc == 0) text_content = (char *)content; else { text_content = decoded; length = bytes_decoded; } fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset, length, encoding, cbid, cbuserdata); if (decoded != NULL) free(decoded); } struct encapmsg { char desired_section[64]; char *msg; size_t msglen; }; /* * Callback function for */ void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata) { struct encapmsg *encap; encap = (struct encapmsg *)cbuserdata; /* Only proceed if this is the desired section... */ if (!strcasecmp(encap->desired_section, partnum)) { encap->msglen = length; encap->msg = malloc(length + 2); memcpy(encap->msg, content, length); return; } } /* * Determine whether the specified message exists in the cached_msglist * (This is a security check) */ int check_cached_msglist(long msgnum) { struct CitContext *CCC = CC; /* cases in which we skip the check */ if (!CCC) return om_ok; /* not a session */ if (CCC->client_socket <= 0) return om_ok; /* not a client session */ if (CCC->cached_msglist == NULL) return om_access_denied; /* no msglist fetched */ if (CCC->cached_num_msgs == 0) return om_access_denied; /* nothing to check */ /* Do a binary search within the cached_msglist for the requested msgnum */ int min = 0; int max = (CC->cached_num_msgs - 1); while (max >= min) { int middle = min + (max-min) / 2 ; if (msgnum == CCC->cached_msglist[middle]) { return om_ok; } if (msgnum > CC->cached_msglist[middle]) { min = middle + 1; } else { max = middle - 1; } } return om_access_denied; } /* * Get a message off disk. (returns om_* values found in msgbase.h) * */ int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */ int mode, /* how would you like that message? */ int headers_only, /* eschew the message body? */ int do_proto, /* do Citadel protocol responses? */ int crlf, /* Use CRLF newlines instead of LF? */ char *section, /* NULL or a message/rfc822 section */ int flags, /* various flags; see msgbase.h */ char **Author, char **Address, char **MessageID ) { struct CitContext *CCC = CC; struct CtdlMessage *TheMessage = NULL; int retcode = CIT_OK; struct encapmsg encap; int r; MSG_syslog(LOG_DEBUG, "CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)\n", msg_num, mode, (section ? section : "<>") ); r = CtdlDoIHavePermissionToReadMessagesInThisRoom(); if (r != om_ok) { if (do_proto) { if (r == om_not_logged_in) { cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN); } else { cprintf("%d An unknown error has occurred.\n", ERROR); } } return(r); } /* * Check to make sure the message is actually IN this room */ r = check_cached_msglist(msg_num); if (r == om_access_denied) { /* Not in the cache? We get ONE shot to check it again. */ CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, NULL, NULL); r = check_cached_msglist(msg_num); } if (r != om_ok) { MSG_syslog(LOG_DEBUG, "Security check fail: message %ld is not in %s\n", msg_num, CCC->room.QRname ); if (do_proto) { if (r == om_access_denied) { cprintf("%d message %ld was not found in this room\n", ERROR + HIGHER_ACCESS_REQUIRED, msg_num ); } } return(r); } /* * Fetch the message from disk. If we're in HEADERS_FAST mode, * request that we don't even bother loading the body into memory. */ if (headers_only == HEADERS_FAST) { TheMessage = CtdlFetchMessage(msg_num, 0); } else { TheMessage = CtdlFetchMessage(msg_num, 1); } if (TheMessage == NULL) { if (do_proto) cprintf("%d Can't locate msg %ld on disk\n", ERROR + MESSAGE_NOT_FOUND, msg_num); return(om_no_such_msg); } /* Here is the weird form of this command, to process only an * encapsulated message/rfc822 section. */ if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) { memset(&encap, 0, sizeof encap); safestrncpy(encap.desired_section, section, sizeof encap.desired_section); mime_parser(CM_RANGE(TheMessage, eMesageText), *extract_encapsulated_message, NULL, NULL, (void *)&encap, 0 ); if ((Author != NULL) && (*Author == NULL)) { long len; CM_GetAsField(TheMessage, eAuthor, Author, &len); } if ((Address != NULL) && (*Address == NULL)) { long len; CM_GetAsField(TheMessage, erFc822Addr, Address, &len); } if ((MessageID != NULL) && (*MessageID == NULL)) { long len; CM_GetAsField(TheMessage, emessageId, MessageID, &len); } CM_Free(TheMessage); TheMessage = NULL; if (encap.msg) { encap.msg[encap.msglen] = 0; TheMessage = convert_internet_message(encap.msg); encap.msg = NULL; /* no free() here, TheMessage owns it now */ /* Now we let it fall through to the bottom of this * function, because TheMessage now contains the * encapsulated message instead of the top-level * message. Isn't that neat? */ } else { if (do_proto) { cprintf("%d msg %ld has no part %s\n", ERROR + MESSAGE_NOT_FOUND, msg_num, section); } retcode = om_no_such_msg; } } /* Ok, output the message now */ if (retcode == CIT_OK) retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags); if ((Author != NULL) && (*Author == NULL)) { long len; CM_GetAsField(TheMessage, eAuthor, Author, &len); } if ((Address != NULL) && (*Address == NULL)) { long len; CM_GetAsField(TheMessage, erFc822Addr, Address, &len); } if ((MessageID != NULL) && (*MessageID == NULL)) { long len; CM_GetAsField(TheMessage, emessageId, MessageID, &len); } CM_Free(TheMessage); return(retcode); } void OutputCtdlMsgHeaders( struct CtdlMessage *TheMessage, int do_proto) /* do Citadel protocol responses? */ { int i; int suppress_f = 0; char buf[SIZ]; char display_name[256]; /* begin header processing loop for Citadel message format */ safestrncpy(display_name, "", sizeof display_name); if (!CM_IsEmpty(TheMessage, eAuthor)) { strcpy(buf, TheMessage->cm_fields[eAuthor]); if (TheMessage->cm_anon_type == MES_ANONONLY) { safestrncpy(display_name, "****", sizeof display_name); } else if (TheMessage->cm_anon_type == MES_ANONOPT) { safestrncpy(display_name, "anonymous", sizeof display_name); } else { safestrncpy(display_name, buf, sizeof display_name); } if ((is_room_aide()) && ((TheMessage->cm_anon_type == MES_ANONONLY) || (TheMessage->cm_anon_type == MES_ANONOPT))) { size_t tmp = strlen(display_name); snprintf(&display_name[tmp], sizeof display_name - tmp, " [%s]", buf); } } /* Don't show Internet address for users on the * local Citadel network. */ suppress_f = 0; if (!CM_IsEmpty(TheMessage, eNodeName) && (haschar(TheMessage->cm_fields[eNodeName], '.') == 0)) { suppress_f = 1; } /* Now spew the header fields in the order we like them. */ for (i=0; i< NDiskFields; ++i) { eMsgField Field; Field = FieldOrder[i]; if (Field != eMesageText) { if ( (!CM_IsEmpty(TheMessage, Field)) && (msgkeys[Field] != NULL) ) { if ((Field == eenVelopeTo) || (Field == eRecipient) || (Field == eCarbonCopY)) { sanitize_truncated_recipient(TheMessage->cm_fields[Field]); } if (Field == eAuthor) { if (do_proto) cprintf("%s=%s\n", msgkeys[Field], display_name); } else if ((Field == erFc822Addr) && (suppress_f)) { /* do nothing */ } /* Masquerade display name if needed */ else { if (do_proto) cprintf("%s=%s\n", msgkeys[Field], TheMessage->cm_fields[Field] ); } } } } } void OutputRFC822MsgHeaders( struct CtdlMessage *TheMessage, int flags, /* should the bessage be exported clean */ const char *nl, char *mid, long sizeof_mid, char *suser, long sizeof_suser, char *luser, long sizeof_luser, char *fuser, long sizeof_fuser, char *snode, long sizeof_snode) { char datestamp[100]; int subject_found = 0; char buf[SIZ]; int i, j, k; char *mptr = NULL; char *mpptr = NULL; char *hptr; for (i = 0; i < NDiskFields; ++i) { if (TheMessage->cm_fields[FieldOrder[i]]) { mptr = mpptr = TheMessage->cm_fields[FieldOrder[i]]; switch (FieldOrder[i]) { case eAuthor: safestrncpy(luser, mptr, sizeof_luser); safestrncpy(suser, mptr, sizeof_suser); break; case eCarbonCopY: if ((flags & QP_EADDR) != 0) { mptr = qp_encode_email_addrs(mptr); } sanitize_truncated_recipient(mptr); cprintf("CC: %s%s", mptr, nl); break; case eMessagePath: cprintf("Return-Path: %s%s", mptr, nl); break; case eListID: cprintf("List-ID: %s%s", mptr, nl); break; case eenVelopeTo: if ((flags & QP_EADDR) != 0) mptr = qp_encode_email_addrs(mptr); hptr = mptr; while ((*hptr != '\0') && isspace(*hptr)) hptr ++; if (!IsEmptyStr(hptr)) cprintf("Envelope-To: %s%s", hptr, nl); break; case eMsgSubject: cprintf("Subject: %s%s", mptr, nl); subject_found = 1; break; case emessageId: safestrncpy(mid, mptr, sizeof_mid); /// TODO: detect @ here and copy @nodename in if not found. break; case erFc822Addr: safestrncpy(fuser, mptr, sizeof_fuser); /* case eOriginalRoom: cprintf("X-Citadel-Room: %s%s", mptr, nl) break; ; */ case eNodeName: safestrncpy(snode, mptr, sizeof_snode); break; case eRecipient: if (haschar(mptr, '@') == 0) { sanitize_truncated_recipient(mptr); cprintf("To: %s@%s", mptr, config.c_fqdn); cprintf("%s", nl); } else { if ((flags & QP_EADDR) != 0) { mptr = qp_encode_email_addrs(mptr); } sanitize_truncated_recipient(mptr); cprintf("To: %s", mptr); cprintf("%s", nl); } break; case eTimestamp: datestring(datestamp, sizeof datestamp, atol(mptr), DATESTRING_RFC822); cprintf("Date: %s%s", datestamp, nl); break; case eWeferences: cprintf("References: "); k = num_tokens(mptr, '|'); for (j=0; j", buf); if (j == (k-1)) { cprintf("%s", nl); } else { cprintf(" "); } } break; case eReplyTo: hptr = mptr; while ((*hptr != '\0') && isspace(*hptr)) hptr ++; if (!IsEmptyStr(hptr)) cprintf("Reply-To: %s%s", mptr, nl); break; case eRemoteRoom: case eDestination: case eExclusiveID: case eHumanNode: case eJournal: case eMesageText: case eBig_message: case eOriginalRoom: case eSpecialField: case eErrorMsg: case eSuppressIdx: case eExtnotify: case eVltMsgNum: /* these don't map to mime message headers. */ break; } if (mptr != mpptr) free (mptr); } } if (subject_found == 0) { cprintf("Subject: (no subject)%s", nl); } } void Dump_RFC822HeadersBody( struct CtdlMessage *TheMessage, int headers_only, /* eschew the message body? */ int flags, /* should the bessage be exported clean? */ const char *nl) { cit_uint8_t prev_ch; int eoh = 0; const char *StartOfText = StrBufNOTNULL; char outbuf[1024]; int outlen = 0; int nllen = strlen(nl); char *mptr; mptr = TheMessage->cm_fields[eMesageText]; prev_ch = '\0'; while (*mptr != '\0') { if (*mptr == '\r') { /* do nothing */ } else { if ((!eoh) && (*mptr == '\n')) { eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n'); if (!eoh) eoh = *(mptr+1) == '\n'; if (eoh) { StartOfText = mptr; StartOfText = strchr(StartOfText, '\n'); StartOfText = strchr(StartOfText, '\n'); } } if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) || ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY)) ) { if (*mptr == '\n') { memcpy(&outbuf[outlen], nl, nllen); outlen += nllen; outbuf[outlen] = '\0'; } else { outbuf[outlen++] = *mptr; } } } if (flags & ESC_DOT) { if ((prev_ch == '\n') && (*mptr == '.') && ((*(mptr+1) == '\r') || (*(mptr+1) == '\n'))) { outbuf[outlen++] = '.'; } prev_ch = *mptr; } ++mptr; if (outlen > 1000) { if (client_write(outbuf, outlen) == -1) { struct CitContext *CCC = CC; MSGM_syslog(LOG_ERR, "Dump_RFC822HeadersBody(): aborting due to write failure.\n"); return; } outlen = 0; } } if (outlen > 0) { client_write(outbuf, outlen); } } /* If the format type on disk is 1 (fixed-format), then we want * everything to be output completely literally ... regardless of * what message transfer format is in use. */ void DumpFormatFixed( struct CtdlMessage *TheMessage, int mode, /* how would you like that message? */ const char *nl) { cit_uint8_t ch; char buf[SIZ]; int buflen; int xlline = 0; int nllen = strlen (nl); char *mptr; mptr = TheMessage->cm_fields[eMesageText]; if (mode == MT_MIME) { cprintf("Content-type: text/plain\n\n"); } *buf = '\0'; buflen = 0; while (ch = *mptr++, ch > 0) { if (ch == '\n') ch = '\r'; if ((buflen > 250) && (!xlline)){ int tbuflen; tbuflen = buflen; while ((buflen > 0) && (!isspace(buf[buflen]))) buflen --; if (buflen == 0) { xlline = 1; } else { mptr -= tbuflen - buflen; buf[buflen] = '\0'; ch = '\r'; } } /* if we reach the outer bounds of our buffer, abort without respect what whe purge. */ if (xlline && ((isspace(ch)) || (buflen > SIZ - nllen - 2))) ch = '\r'; if (ch == '\r') { memcpy (&buf[buflen], nl, nllen); buflen += nllen; buf[buflen] = '\0'; if (client_write(buf, buflen) == -1) { struct CitContext *CCC = CC; MSGM_syslog(LOG_ERR, "DumpFormatFixed(): aborting due to write failure.\n"); return; } *buf = '\0'; buflen = 0; xlline = 0; } else { buf[buflen] = ch; buflen++; } } buf[buflen] = '\0'; if (!IsEmptyStr(buf)) cprintf("%s%s", buf, nl); } /* * Get a message off disk. (returns om_* values found in msgbase.h) */ int CtdlOutputPreLoadedMsg( struct CtdlMessage *TheMessage, int mode, /* how would you like that message? */ int headers_only, /* eschew the message body? */ int do_proto, /* do Citadel protocol responses? */ int crlf, /* Use CRLF newlines instead of LF? */ int flags /* should the bessage be exported clean? */ ) { struct CitContext *CCC = CC; int i; const char *nl; /* newline string */ struct ma_info ma; /* Buffers needed for RFC822 translation. These are all filled * using functions that are bounds-checked, and therefore we can * make them substantially smaller than SIZ. */ char suser[100]; char luser[100]; char fuser[100]; char snode[100]; char mid[100]; MSG_syslog(LOG_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n", ((TheMessage == NULL) ? "NULL" : "not null"), mode, headers_only, do_proto, crlf); strcpy(mid, "unknown"); nl = (crlf ? "\r\n" : "\n"); if (!CM_IsValidMsg(TheMessage)) { MSGM_syslog(LOG_ERR, "ERROR: invalid preloaded message for output\n"); cit_backtrace (); return(om_no_such_msg); } /* Suppress envelope recipients if required to avoid disclosing BCC addresses. * Pad it with spaces in order to avoid changing the RFC822 length of the message. */ if ( (flags & SUPPRESS_ENV_TO) && (!CM_IsEmpty(TheMessage, eenVelopeTo)) ) { memset(TheMessage->cm_fields[eenVelopeTo], ' ', TheMessage->cm_lengths[eenVelopeTo]); } /* Are we downloading a MIME component? */ if (mode == MT_DOWNLOAD) { if (TheMessage->cm_format_type != FMT_RFC822) { if (do_proto) cprintf("%d This is not a MIME message.\n", ERROR + ILLEGAL_VALUE); } else if (CCC->download_fp != NULL) { if (do_proto) cprintf( "%d You already have a download open.\n", ERROR + RESOURCE_BUSY); } else { /* Parse the message text component */ mime_parser(CM_RANGE(TheMessage, eMesageText), *mime_download, NULL, NULL, NULL, 0); /* If there's no file open by this time, the requested * section wasn't found, so print an error */ if (CCC->download_fp == NULL) { if (do_proto) cprintf( "%d Section %s not found.\n", ERROR + FILE_NOT_FOUND, CCC->download_desired_section); } } return((CCC->download_fp != NULL) ? om_ok : om_mime_error); } /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part * in a single server operation instead of opening a download file. */ if (mode == MT_SPEW_SECTION) { if (TheMessage->cm_format_type != FMT_RFC822) { if (do_proto) cprintf("%d This is not a MIME message.\n", ERROR + ILLEGAL_VALUE); } else { /* Parse the message text component */ int found_it = 0; mime_parser(CM_RANGE(TheMessage, eMesageText), *mime_spew_section, NULL, NULL, (void *)&found_it, 0); /* If section wasn't found, print an error */ if (!found_it) { if (do_proto) cprintf( "%d Section %s not found.\n", ERROR + FILE_NOT_FOUND, CCC->download_desired_section); } } return((CCC->download_fp != NULL) ? om_ok : om_mime_error); } /* now for the user-mode message reading loops */ if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS); /* Does the caller want to skip the headers? */ if (headers_only == HEADERS_NONE) goto START_TEXT; /* Tell the client which format type we're using. */ if ( (mode == MT_CITADEL) && (do_proto) ) { cprintf("type=%d\n", TheMessage->cm_format_type); } /* nhdr=yes means that we're only displaying headers, no body */ if ( (TheMessage->cm_anon_type == MES_ANONONLY) && ((mode == MT_CITADEL) || (mode == MT_MIME)) && (do_proto) ) { cprintf("nhdr=yes\n"); } if ((mode == MT_CITADEL) || (mode == MT_MIME)) OutputCtdlMsgHeaders(TheMessage, do_proto); /* begin header processing loop for RFC822 transfer format */ strcpy(suser, ""); strcpy(luser, ""); strcpy(fuser, ""); memcpy(snode, CFG_KEY(c_nodename) + 1); if (mode == MT_RFC822) OutputRFC822MsgHeaders( TheMessage, flags, nl, mid, sizeof(mid), suser, sizeof(suser), luser, sizeof(luser), fuser, sizeof(fuser), snode, sizeof(snode) ); for (i=0; !IsEmptyStr(&suser[i]); ++i) { suser[i] = tolower(suser[i]); if (!isalnum(suser[i])) suser[i]='_'; } if (mode == MT_RFC822) { if (!strcasecmp(snode, NODENAME)) { safestrncpy(snode, FQDN, sizeof snode); } /* Construct a fun message id */ cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails. if (strchr(mid, '@')==NULL) { cprintf("@%s", snode); } cprintf(">%s", nl); if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) { cprintf("From: \"----\" %s", nl); } else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) { cprintf("From: \"anonymous\" %s", nl); } else if (!IsEmptyStr(fuser)) { cprintf("From: \"%s\" <%s>%s", luser, fuser, nl); } else { cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl); } /* Blank line signifying RFC822 end-of-headers */ if (TheMessage->cm_format_type != FMT_RFC822) { cprintf("%s", nl); } } /* end header processing loop ... at this point, we're in the text */ START_TEXT: if (headers_only == HEADERS_FAST) goto DONE; /* Tell the client about the MIME parts in this message */ if (TheMessage->cm_format_type == FMT_RFC822) { if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) { memset(&ma, 0, sizeof(struct ma_info)); mime_parser(CM_RANGE(TheMessage, eMesageText), (do_proto ? *list_this_part : NULL), (do_proto ? *list_this_pref : NULL), (do_proto ? *list_this_suff : NULL), (void *)&ma, 1); } else if (mode == MT_RFC822) { /* unparsed RFC822 dump */ Dump_RFC822HeadersBody( TheMessage, headers_only, flags, nl); goto DONE; } } if (headers_only == HEADERS_ONLY) { goto DONE; } /* signify start of msg text */ if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) { if (do_proto) cprintf("text\n"); } if (TheMessage->cm_format_type == FMT_FIXED) DumpFormatFixed( TheMessage, mode, /* how would you like that message? */ nl); /* If the message on disk is format 0 (Citadel vari-format), we * output using the formatter at 80 columns. This is the final output * form if the transfer format is RFC822, but if the transfer format * is Citadel proprietary, it'll still work, because the indentation * for new paragraphs is correct and the client will reformat the * message to the reader's screen width. */ if (TheMessage->cm_format_type == FMT_CITADEL) { if (mode == MT_MIME) { cprintf("Content-type: text/x-citadel-variformat\n\n"); } memfmout(TheMessage->cm_fields[eMesageText], nl); } /* If the message on disk is format 4 (MIME), we've gotta hand it * off to the MIME parser. The client has already been told that * this message is format 1 (fixed format), so the callback function * we use will display those parts as-is. */ if (TheMessage->cm_format_type == FMT_RFC822) { memset(&ma, 0, sizeof(struct ma_info)); if (mode == MT_MIME) { ma.use_fo_hooks = 0; strcpy(ma.chosen_part, "1"); ma.chosen_pref = 9999; ma.dont_decode = CCC->msg4_dont_decode; mime_parser(CM_RANGE(TheMessage, eMesageText), *choose_preferred, *fixed_output_pre, *fixed_output_post, (void *)&ma, 1); mime_parser(CM_RANGE(TheMessage, eMesageText), *output_preferred, NULL, NULL, (void *)&ma, 1); } else { ma.use_fo_hooks = 1; mime_parser(CM_RANGE(TheMessage, eMesageText), *fixed_output, *fixed_output_pre, *fixed_output_post, (void *)&ma, 0); } } DONE: /* now we're done */ if (do_proto) cprintf("000\n"); return(om_ok); } /* * Save one or more message pointers into a specified room * (Returns 0 for success, nonzero for failure) * roomname may be NULL to use the current room * * Note that the 'supplied_msg' field may be set to NULL, in which case * the message will be fetched from disk, by number, if we need to perform * replication checks. This adds an additional database read, so if the * caller already has the message in memory then it should be supplied. (Obviously * this mode of operation only works if we're saving a single message.) */ int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs, int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj ) { struct CitContext *CCC = CC; int i, j, unique; char hold_rm[ROOMNAMELEN]; struct cdbdata *cdbfr; int num_msgs; long *msglist; long highest_msg = 0L; long msgid = 0; struct CtdlMessage *msg = NULL; long *msgs_to_be_merged = NULL; int num_msgs_to_be_merged = 0; MSG_syslog(LOG_DEBUG, "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)\n", roomname, num_newmsgs, do_repl_check, suppress_refcount_adj ); strcpy(hold_rm, CCC->room.QRname); /* Sanity checks */ if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR); if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR); if (num_newmsgs > 1) supplied_msg = NULL; /* Now the regular stuff */ if (CtdlGetRoomLock(&CCC->room, ((roomname != NULL) ? roomname : CCC->room.QRname) ) != 0) { MSG_syslog(LOG_ERR, "No such room <%s>\n", roomname); return(ERROR + ROOM_NOT_FOUND); } msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs); num_msgs_to_be_merged = 0; cdbfr = cdb_fetch(CDB_MSGLISTS, &CCC->room.QRnumber, sizeof(long)); if (cdbfr == NULL) { msglist = NULL; num_msgs = 0; } else { msglist = (long *) cdbfr->ptr; cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */ num_msgs = cdbfr->len / sizeof(long); cdb_free(cdbfr); } /* Create a list of msgid's which were supplied by the caller, but do * not already exist in the target room. It is absolutely taboo to * have more than one reference to the same message in a room. */ for (i=0; i 0) for (j=0; jroom.QRnumber, (int)sizeof(long), msglist, (int)(num_msgs * sizeof(long))); /* Free up the memory we used. */ free(msglist); /* Update the highest-message pointer and unlock the room. */ CCC->room.QRhighest = highest_msg; CtdlPutRoomLock(&CCC->room); /* Perform replication checks if necessary */ if ( (DoesThisRoomNeedEuidIndexing(&CCC->room)) && (do_repl_check) ) { MSGM_syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n"); for (i=0; icm_fields[eExclusiveID], &CCC->room, msgid); } /* Free up the memory we may have allocated */ if (msg != supplied_msg) { CM_Free(msg); } } } } else { MSGM_syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n"); } /* Submit this room for processing by hooks */ PerformRoomHooks(&CCC->room); /* Go back to the room we were in before we wandered here... */ CtdlGetRoom(&CCC->room, hold_rm); /* Bump the reference count for all messages which were merged */ if (!suppress_refcount_adj) { AdjRefCountList(msgs_to_be_merged, num_msgs_to_be_merged, +1); } /* Free up memory... */ if (msgs_to_be_merged != NULL) { free(msgs_to_be_merged); } /* Return success. */ return (0); } /* * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts * a single message. */ int CtdlSaveMsgPointerInRoom(char *roomname, long msgid, int do_repl_check, struct CtdlMessage *supplied_msg) { return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0); } /* * Message base operation to save a new message to the message store * (returns new message number) * * This is the back end for CtdlSubmitMsg() and should not be directly * called by server-side modules. * */ long send_message(struct CtdlMessage *msg) { struct CitContext *CCC = CC; long newmsgid; long retval; char msgidbuf[256]; long msgidbuflen; struct ser_ret smr; int is_bigmsg = 0; char *holdM = NULL; long holdMLen = 0; /* Get a new message number */ newmsgid = get_new_message_number(); msgidbuflen = snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s", (long unsigned int) time(NULL), (long unsigned int) newmsgid, config.c_fqdn ); /* Generate an ID if we don't have one already */ if (CM_IsEmpty(msg, emessageId)) { CM_SetField(msg, emessageId, msgidbuf, msgidbuflen); } /* If the message is big, set its body aside for storage elsewhere */ if (!CM_IsEmpty(msg, eMesageText)) { if (msg->cm_lengths[eMesageText] > BIGMSG) { is_bigmsg = 1; holdM = msg->cm_fields[eMesageText]; msg->cm_fields[eMesageText] = NULL; holdMLen = msg->cm_lengths[eMesageText]; msg->cm_lengths[eMesageText] = 0; } } /* Serialize our data structure for storage in the database */ CtdlSerializeMessage(&smr, msg); if (is_bigmsg) { msg->cm_fields[eMesageText] = holdM; msg->cm_lengths[eMesageText] = holdMLen; } if (smr.len == 0) { cprintf("%d Unable to serialize message\n", ERROR + INTERNAL_ERROR); return (-1L); } /* Write our little bundle of joy into the message base */ if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long), smr.ser, smr.len) < 0) { MSGM_syslog(LOG_ERR, "Can't store message\n"); retval = 0L; } else { if (is_bigmsg) { cdb_store(CDB_BIGMSGS, &newmsgid, (int)sizeof(long), holdM, (holdMLen + 1) ); } retval = newmsgid; } /* Free the memory we used for the serialized message */ free(smr.ser); /* Return the *local* message ID to the caller * (even if we're storing an incoming network message) */ return(retval); } /* * Serialize a struct CtdlMessage into the format used on disk and network. * * This function loads up a "struct ser_ret" (defined in server.h) which * contains the length of the serialized message and a pointer to the * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER. */ void CtdlSerializeMessage(struct ser_ret *ret, /* return values */ struct CtdlMessage *msg) /* unserialized msg */ { struct CitContext *CCC = CC; size_t wlen; int i; /* * Check for valid message format */ if (CM_IsValidMsg(msg) == 0) { MSGM_syslog(LOG_ERR, "CtdlSerializeMessage() aborting due to invalid message\n"); ret->len = 0; ret->ser = NULL; return; } ret->len = 3; for (i=0; i < NDiskFields; ++i) if (msg->cm_fields[FieldOrder[i]] != NULL) ret->len += msg->cm_lengths[FieldOrder[i]] + 2; ret->ser = malloc(ret->len); if (ret->ser == NULL) { MSG_syslog(LOG_ERR, "CtdlSerializeMessage() malloc(%ld) failed: %s\n", (long)ret->len, strerror(errno)); ret->len = 0; ret->ser = NULL; return; } ret->ser[0] = 0xFF; ret->ser[1] = msg->cm_anon_type; ret->ser[2] = msg->cm_format_type; wlen = 3; for (i=0; i < NDiskFields; ++i) if (msg->cm_fields[FieldOrder[i]] != NULL) { ret->ser[wlen++] = (char)FieldOrder[i]; memcpy(&ret->ser[wlen], msg->cm_fields[FieldOrder[i]], msg->cm_lengths[FieldOrder[i]] + 1); wlen = wlen + msg->cm_lengths[FieldOrder[i]] + 1; } if (ret->len != wlen) { MSG_syslog(LOG_ERR, "ERROR: len=%ld wlen=%ld\n", (long)ret->len, (long)wlen); } return; } /* * Check to see if any messages already exist in the current room which * carry the same Exclusive ID as this one. If any are found, delete them. */ void ReplicationChecks(struct CtdlMessage *msg) { struct CitContext *CCC = CC; long old_msgnum = (-1L); if (DoesThisRoomNeedEuidIndexing(&CCC->room) == 0) return; MSG_syslog(LOG_DEBUG, "Performing replication checks in <%s>\n", CCC->room.QRname); /* No exclusive id? Don't do anything. */ if (msg == NULL) return; if (CM_IsEmpty(msg, eExclusiveID)) return; /*MSG_syslog(LOG_DEBUG, "Exclusive ID: <%s> for room <%s>\n", msg->cm_fields[eExclusiveID], CCC->room.QRname);*/ old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CCC->room); if (old_msgnum > 0L) { MSG_syslog(LOG_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum); CtdlDeleteMessages(CCC->room.QRname, &old_msgnum, 1, ""); } } /* * Save a message to disk and submit it into the delivery system. */ long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */ recptypes *recps, /* recipients (if mail) */ const char *force, /* force a particular room? */ int flags /* should the message be exported clean? */ ) { char hold_rm[ROOMNAMELEN]; char actual_rm[ROOMNAMELEN]; char force_room[ROOMNAMELEN]; char content_type[SIZ]; /* We have to learn this */ char recipient[SIZ]; char bounce_to[1024]; const char *room; long newmsgid; const char *mptr = NULL; struct ctdluser userbuf; int a, i; struct MetaData smi; char *collected_addresses = NULL; struct addresses_to_be_filed *aptr = NULL; StrBuf *saved_rfc822_version = NULL; int qualified_for_journaling = 0; CitContext *CCC = MyContext(); MSGM_syslog(LOG_DEBUG, "CtdlSubmitMsg() called\n"); if (CM_IsValidMsg(msg) == 0) return(-1); /* self check */ /* If this message has no timestamp, we take the liberty of * giving it one, right now. */ if (CM_IsEmpty(msg, eTimestamp)) { CM_SetFieldLONG(msg, eTimestamp, time(NULL)); } /* If this message has no path, we generate one. */ if (CM_IsEmpty(msg, eMessagePath)) { if (!CM_IsEmpty(msg, eAuthor)) { CM_CopyField(msg, eMessagePath, eAuthor); for (a=0; !IsEmptyStr(&msg->cm_fields[eMessagePath][a]); ++a) { if (isspace(msg->cm_fields[eMessagePath][a])) { msg->cm_fields[eMessagePath][a] = ' '; } } } else { CM_SetField(msg, eMessagePath, HKEY("unknown")); } } if (force == NULL) { force_room[0] = '\0'; } else { strcpy(force_room, force); } /* Learn about what's inside, because it's what's inside that counts */ if (CM_IsEmpty(msg, eMesageText)) { MSGM_syslog(LOG_ERR, "ERROR: attempt to save message with NULL body\n"); return(-2); } switch (msg->cm_format_type) { case 0: strcpy(content_type, "text/x-citadel-variformat"); break; case 1: strcpy(content_type, "text/plain"); break; case 4: strcpy(content_type, "text/plain"); mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:"); if (mptr != NULL) { char *aptr; safestrncpy(content_type, &mptr[13], sizeof content_type); striplt(content_type); aptr = content_type; while (!IsEmptyStr(aptr)) { if ((*aptr == ';') || (*aptr == ' ') || (*aptr == 13) || (*aptr == 10)) { *aptr = 0; } else aptr++; } } } /* Goto the correct room */ room = (recps) ? CCC->room.QRname : SENTITEMS; MSG_syslog(LOG_DEBUG, "Selected room %s\n", room); strcpy(hold_rm, CCC->room.QRname); strcpy(actual_rm, CCC->room.QRname); if (recps != NULL) { strcpy(actual_rm, SENTITEMS); } /* If the user is a twit, move to the twit room for posting */ if (TWITDETECT) { if (CCC->user.axlevel == AxProbU) { strcpy(hold_rm, actual_rm); strcpy(actual_rm, config.c_twitroom); MSGM_syslog(LOG_DEBUG, "Diverting to twit room\n"); } } /* ...or if this message is destined for Aide> then go there. */ if (!IsEmptyStr(force_room)) { strcpy(actual_rm, force_room); } MSG_syslog(LOG_INFO, "Final selection: %s (%s)\n", actual_rm, room); if (strcasecmp(actual_rm, CCC->room.QRname)) { /* CtdlGetRoom(&CCC->room, actual_rm); */ CtdlUserGoto(actual_rm, 0, 1, NULL, NULL, NULL, NULL); } /* * If this message has no O (room) field, generate one. */ if (CM_IsEmpty(msg, eOriginalRoom)) { CM_SetField(msg, eOriginalRoom, CCC->room.QRname, strlen(CCC->room.QRname)); } /* Perform "before save" hooks (aborting if any return nonzero) */ MSGM_syslog(LOG_DEBUG, "Performing before-save hooks\n"); if (PerformMessageHooks(msg, recps, EVT_BEFORESAVE) > 0) return(-3); /* * If this message has an Exclusive ID, and the room is replication * checking enabled, then do replication checks. */ if (DoesThisRoomNeedEuidIndexing(&CCC->room)) { ReplicationChecks(msg); } /* Save it to disk */ MSGM_syslog(LOG_DEBUG, "Saving to disk\n"); newmsgid = send_message(msg); if (newmsgid <= 0L) return(-5); /* Write a supplemental message info record. This doesn't have to * be a critical section because nobody else knows about this message * yet. */ MSGM_syslog(LOG_DEBUG, "Creating MetaData record\n"); memset(&smi, 0, sizeof(struct MetaData)); smi.meta_msgnum = newmsgid; smi.meta_refcount = 0; safestrncpy(smi.meta_content_type, content_type, sizeof smi.meta_content_type); /* * Measure how big this message will be when rendered as RFC822. * We do this for two reasons: * 1. We need the RFC822 length for the new metadata record, so the * POP and IMAP services don't have to calculate message lengths * while the user is waiting (multiplied by potentially hundreds * or thousands of messages). * 2. If journaling is enabled, we will need an RFC822 version of the * message to attach to the journalized copy. */ if (CCC->redirect_buffer != NULL) { MSGM_syslog(LOG_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n"); abort(); } CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ); CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR); smi.meta_rfc822_length = StrLength(CCC->redirect_buffer); saved_rfc822_version = CCC->redirect_buffer; CCC->redirect_buffer = NULL; PutMetaData(&smi); /* Now figure out where to store the pointers */ MSGM_syslog(LOG_DEBUG, "Storing pointers\n"); /* If this is being done by the networker delivering a private * message, we want to BYPASS saving the sender's copy (because there * is no local sender; it would otherwise go to the Trashcan). */ if ((!CCC->internal_pgm) || (recps == NULL)) { if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) { MSGM_syslog(LOG_ERR, "ERROR saving message pointer!\n"); CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg); } } /* For internet mail, drop a copy in the outbound queue room */ if ((recps != NULL) && (recps->num_internet > 0)) { CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg); } /* If other rooms are specified, drop them there too. */ if ((recps != NULL) && (recps->num_room > 0)) for (i=0; irecp_room, '|'); ++i) { extract_token(recipient, recps->recp_room, i, '|', sizeof recipient); MSG_syslog(LOG_DEBUG, "Delivering to room <%s>\n", recipient);///// xxxx CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg); } /* Bump this user's messages posted counter. */ MSGM_syslog(LOG_DEBUG, "Updating user\n"); CtdlLockGetCurrentUser(); CCC->user.posted = CCC->user.posted + 1; CtdlPutCurrentUserLock(); /* Decide where bounces need to be delivered */ if ((recps != NULL) && (recps->bounce_to == NULL)) { if (CCC->logged_in) snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename); else snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields[eAuthor], msg->cm_fields[eNodeName]); recps->bounce_to = bounce_to; } CM_SetFieldLONG(msg, eVltMsgNum, newmsgid); /* If this is private, local mail, make a copy in the * recipient's mailbox and bump the reference count. */ if ((recps != NULL) && (recps->num_local > 0)) { char *pch; int ntokens; pch = recps->recp_local; recps->recp_local = recipient; ntokens = num_tokens(pch, '|'); for (i=0; i\n", recipient); if (CtdlGetUser(&userbuf, recipient) == 0) { CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM); CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg); CtdlBumpNewMailCounter(userbuf.usernum); PerformMessageHooks(msg, recps, EVT_AFTERUSRMBOXSAVE); } else { MSG_syslog(LOG_DEBUG, "No user <%s>\n", recipient); CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg); } } recps->recp_local = pch; } /* Perform "after save" hooks */ MSGM_syslog(LOG_DEBUG, "Performing after-save hooks\n"); PerformMessageHooks(msg, recps, EVT_AFTERSAVE); CM_FlushField(msg, eVltMsgNum); /* Go back to the room we started from */ MSG_syslog(LOG_DEBUG, "Returning to original room %s\n", hold_rm); if (strcasecmp(hold_rm, CCC->room.QRname)) CtdlUserGoto(hold_rm, 0, 1, NULL, NULL, NULL, NULL); /* * Any addresses to harvest for someone's address book? */ if ( (CCC->logged_in) && (recps != NULL) ) { collected_addresses = harvest_collected_addresses(msg); } if (collected_addresses != NULL) { aptr = (struct addresses_to_be_filed *) malloc(sizeof(struct addresses_to_be_filed)); CtdlMailboxName(actual_rm, sizeof actual_rm, &CCC->user, USERCONTACTSROOM); aptr->roomname = strdup(actual_rm); aptr->collected_addresses = collected_addresses; begin_critical_section(S_ATBF); aptr->next = atbf; atbf = aptr; end_critical_section(S_ATBF); } /* * Determine whether this message qualifies for journaling. */ if (!CM_IsEmpty(msg, eJournal)) { qualified_for_journaling = 0; } else { if (recps == NULL) { qualified_for_journaling = config.c_journal_pubmsgs; } else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) { qualified_for_journaling = config.c_journal_email; } else { qualified_for_journaling = config.c_journal_pubmsgs; } } /* * Do we have to perform journaling? If so, hand off the saved * RFC822 version will be handed off to the journaler for background * submit. Otherwise, we have to free the memory ourselves. */ if (saved_rfc822_version != NULL) { if (qualified_for_journaling) { JournalBackgroundSubmit(msg, saved_rfc822_version, recps); } else { FreeStrBuf(&saved_rfc822_version); } } if ((recps != NULL) && (recps->bounce_to == bounce_to)) recps->bounce_to = NULL; /* Done. */ return(newmsgid); } /* * Convenience function for generating small administrative messages. */ void quickie_message(const char *from, const char *fromaddr, const char *to, char *room, const char *text, int format_type, const char *subject) { struct CtdlMessage *msg; recptypes *recp = NULL; msg = malloc(sizeof(struct CtdlMessage)); memset(msg, 0, sizeof(struct CtdlMessage)); msg->cm_magic = CTDLMESSAGE_MAGIC; msg->cm_anon_type = MES_NORMAL; msg->cm_format_type = format_type; if (from != NULL) { CM_SetField(msg, eAuthor, from, strlen(from)); } else if (fromaddr != NULL) { char *pAt; CM_SetField(msg, eAuthor, fromaddr, strlen(fromaddr)); pAt = strchr(msg->cm_fields[eAuthor], '@'); if (pAt != NULL) { CM_CutFieldAt(msg, eAuthor, pAt - msg->cm_fields[eAuthor]); } } else { msg->cm_fields[eAuthor] = strdup("Citadel"); } if (fromaddr != NULL) CM_SetField(msg, erFc822Addr, fromaddr, strlen(fromaddr)); if (room != NULL) CM_SetField(msg, eOriginalRoom, room, strlen(room)); CM_SetField(msg, eNodeName, CFG_KEY(c_nodename)); if (to != NULL) { CM_SetField(msg, eRecipient, to, strlen(to)); recp = validate_recipients(to, NULL, 0); } if (subject != NULL) { CM_SetField(msg, eMsgSubject, subject, strlen(subject)); } CM_SetField(msg, eMesageText, text, strlen(text)); CtdlSubmitMsg(msg, recp, room, 0); CM_Free(msg); if (recp != NULL) free_recipients(recp); } void flood_protect_quickie_message(const char *from, const char *fromaddr, const char *to, char *room, const char *text, int format_type, const char *subject, int nCriterions, const char **CritStr, const long *CritStrLen, long ccid, long ioid, time_t NOW) { int i; u_char rawdigest[MD5_DIGEST_LEN]; struct MD5Context md5context; StrBuf *guid; char timestamp[64]; long tslen; static const time_t tsday = (8*60*60); /* just care for a day... */ time_t seenstamp; tslen = snprintf(timestamp, sizeof(timestamp), "%ld", tsday); MD5Init(&md5context); for (i = 0; i < nCriterions; i++) MD5Update(&md5context, (const unsigned char*)CritStr[i], CritStrLen[i]); MD5Update(&md5context, (const unsigned char*)timestamp, tslen); MD5Final(rawdigest, &md5context); guid = NewStrBufPlain(NULL, MD5_DIGEST_LEN * 2 + 12); StrBufHexEscAppend(guid, NULL, rawdigest, MD5_DIGEST_LEN); StrBufAppendBufPlain(guid, HKEY("_fldpt"), 0); if (StrLength(guid) > 40) StrBufCutAt(guid, 40, NULL); seenstamp = CheckIfAlreadySeen("FPAideMessage", guid, NOW, tsday, eUpdate, ccid, ioid); if ((seenstamp > 0) && (seenstamp < tsday)) { FreeStrBuf(&guid); /* yes, we did. flood protection kicks in. */ syslog(LOG_DEBUG, "not sending message again - %ld < %ld \n", seenstamp, tsday); return; } else { syslog(LOG_DEBUG, "sending message. %ld >= %ld", seenstamp, tsday); FreeStrBuf(&guid); /* no, this message isn't sent recently; go ahead. */ quickie_message(from, fromaddr, to, room, text, format_type, subject); } } /* * Back end function used by CtdlMakeMessage() and similar functions */ StrBuf *CtdlReadMessageBodyBuf(char *terminator, /* token signalling EOT */ long tlen, size_t maxlen, /* maximum message length */ StrBuf *exist, /* if non-null, append to it; exist is ALWAYS freed */ int crlf, /* CRLF newlines instead of LF */ int *sock /* socket handle or 0 for this session's client socket */ ) { StrBuf *Message; StrBuf *LineBuf; int flushing = 0; int finished = 0; int dotdot = 0; LineBuf = NewStrBufPlain(NULL, SIZ); if (exist == NULL) { Message = NewStrBufPlain(NULL, 4 * SIZ); } else { Message = NewStrBufDup(exist); } /* Do we need to change leading ".." to "." for SMTP escaping? */ if ((tlen == 1) && (*terminator == '.')) { dotdot = 1; } /* read in the lines of message text one by one */ do { if (sock != NULL) { if ((CtdlSockGetLine(sock, LineBuf, 5) < 0) || (*sock == -1)) finished = 1; } else { if (CtdlClientGetLine(LineBuf) < 0) finished = 1; } if ((StrLength(LineBuf) == tlen) && (!strcmp(ChrPtr(LineBuf), terminator))) finished = 1; if ( (!flushing) && (!finished) ) { if (crlf) { StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0); } else { StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0); } /* Unescape SMTP-style input of two dots at the beginning of the line */ if ((dotdot) && (StrLength(LineBuf) == 2) && (!strcmp(ChrPtr(LineBuf), ".."))) { StrBufCutLeft(LineBuf, 1); } StrBufAppendBuf(Message, LineBuf, 0); } /* if we've hit the max msg length, flush the rest */ if (StrLength(Message) >= maxlen) flushing = 1; } while (!finished); FreeStrBuf(&LineBuf); return Message; } void DeleteAsyncMsg(ReadAsyncMsg **Msg) { if (*Msg == NULL) return; FreeStrBuf(&(*Msg)->MsgBuf); free(*Msg); *Msg = NULL; } ReadAsyncMsg *NewAsyncMsg(const char *terminator, /* token signalling EOT */ long tlen, size_t maxlen, /* maximum message length */ size_t expectlen, /* if we expect a message, how long should it be? */ StrBuf *exist, /* if non-null, append to it; exist is ALWAYS freed */ long eLen, /* length of exist */ int crlf /* CRLF newlines instead of LF */ ) { ReadAsyncMsg *NewMsg; NewMsg = (ReadAsyncMsg *)malloc(sizeof(ReadAsyncMsg)); memset(NewMsg, 0, sizeof(ReadAsyncMsg)); if (exist == NULL) { long len; if (expectlen == 0) { len = 4 * SIZ; } else { len = expectlen + 10; } NewMsg->MsgBuf = NewStrBufPlain(NULL, len); } else { NewMsg->MsgBuf = NewStrBufDup(exist); } /* Do we need to change leading ".." to "." for SMTP escaping? */ if ((tlen == 1) && (*terminator == '.')) { NewMsg->dodot = 1; } NewMsg->terminator = terminator; NewMsg->tlen = tlen; NewMsg->maxlen = maxlen; NewMsg->crlf = crlf; return NewMsg; } /* * Back end function used by CtdlMakeMessage() and similar functions */ eReadState CtdlReadMessageBodyAsync(AsyncIO *IO) { ReadAsyncMsg *ReadMsg; int MsgFinished = 0; eReadState Finished = eMustReadMore; #ifdef BIGBAD_IODBG char fn [SIZ]; FILE *fd; const char *pch = ChrPtr(IO->SendBuf.Buf); const char *pchh = IO->SendBuf.ReadWritePointer; long nbytes; if (pchh == NULL) pchh = pch; nbytes = StrLength(IO->SendBuf.Buf) - (pchh - pch); snprintf(fn, SIZ, "/tmp/foolog_ev_%s.%d", ((CitContext*)(IO->CitContext))->ServiceName, IO->SendBuf.fd); fd = fopen(fn, "a+"); if (fd == NULL) { syslog(LOG_EMERG, "failed to open file %s: %s", fn, strerror(errno)); cit_backtrace(); exit(1); } #endif ReadMsg = IO->ReadMsg; /* read in the lines of message text one by one */ do { Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf); switch (Finished) { case eMustReadMore: /// read new from socket... #ifdef BIGBAD_IODBG if (IO->RecvBuf.ReadWritePointer != NULL) { nbytes = StrLength(IO->RecvBuf.Buf) - (IO->RecvBuf.ReadWritePointer - ChrPtr(IO->RecvBuf.Buf)); fprintf(fd, "Read; Line unfinished: %ld Bytes still in buffer [", nbytes); fwrite(IO->RecvBuf.ReadWritePointer, nbytes, 1, fd); fprintf(fd, "]\n"); } else { fprintf(fd, "BufferEmpty! \n"); } fclose(fd); #endif return Finished; break; case eBufferNotEmpty: /* shouldn't happen... */ case eReadSuccess: /// done for now... break; case eReadFail: /// WHUT? ///todo: shut down! break; } if ((StrLength(IO->IOBuf) == ReadMsg->tlen) && (!strcmp(ChrPtr(IO->IOBuf), ReadMsg->terminator))) { MsgFinished = 1; #ifdef BIGBAD_IODBG fprintf(fd, "found Terminator; Message Size: %d\n", StrLength(ReadMsg->MsgBuf)); #endif } else if (!ReadMsg->flushing) { #ifdef BIGBAD_IODBG fprintf(fd, "Read Line: [%d][%s]\n", StrLength(IO->IOBuf), ChrPtr(IO->IOBuf)); #endif /* Unescape SMTP-style input of two dots at the beginning of the line */ if ((ReadMsg->dodot) && (StrLength(IO->IOBuf) == 2) && /* TODO: do we just unescape lines with two dots or any line? */ (!strcmp(ChrPtr(IO->IOBuf), ".."))) { #ifdef BIGBAD_IODBG fprintf(fd, "UnEscaped!\n"); #endif StrBufCutLeft(IO->IOBuf, 1); } if (ReadMsg->crlf) { StrBufAppendBufPlain(IO->IOBuf, HKEY("\r\n"), 0); } else { StrBufAppendBufPlain(IO->IOBuf, HKEY("\n"), 0); } StrBufAppendBuf(ReadMsg->MsgBuf, IO->IOBuf, 0); } /* if we've hit the max msg length, flush the rest */ if (StrLength(ReadMsg->MsgBuf) >= ReadMsg->maxlen) ReadMsg->flushing = 1; } while (!MsgFinished); #ifdef BIGBAD_IODBG fprintf(fd, "Done with reading; %s.\n, ", (MsgFinished)?"Message Finished": "FAILED"); fclose(fd); #endif if (MsgFinished) return eReadSuccess; else return eReadFail; } /* * Back end function used by CtdlMakeMessage() and similar functions */ char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */ long tlen, size_t maxlen, /* maximum message length */ StrBuf *exist, /* if non-null, append to it; exist is ALWAYS freed */ int crlf, /* CRLF newlines instead of LF */ int *sock /* socket handle or 0 for this session's client socket */ ) { StrBuf *Message; Message = CtdlReadMessageBodyBuf(terminator, tlen, maxlen, exist, crlf, sock); if (Message == NULL) return NULL; else return SmashStrBuf(&Message); } struct CtdlMessage *CtdlMakeMessage( struct ctdluser *author, /* author's user structure */ char *recipient, /* NULL if it's not mail */ char *recp_cc, /* NULL if it's not mail */ char *room, /* room where it's going */ int type, /* see MES_ types in header file */ int format_type, /* variformat, plain text, MIME... */ char *fake_name, /* who we're masquerading as */ char *my_email, /* which of my email addresses to use (empty is ok) */ char *subject, /* Subject (optional) */ char *supplied_euid, /* ...or NULL if this is irrelevant */ char *preformatted_text, /* ...or NULL to read text from client */ char *references /* Thread references */ ) { return CtdlMakeMessageLen( author, /* author's user structure */ recipient, /* NULL if it's not mail */ (recipient)?strlen(recipient) : 0, recp_cc, /* NULL if it's not mail */ (recp_cc)?strlen(recp_cc): 0, room, /* room where it's going */ (room)?strlen(room): 0, type, /* see MES_ types in header file */ format_type, /* variformat, plain text, MIME... */ fake_name, /* who we're masquerading as */ (fake_name)?strlen(fake_name): 0, my_email, /* which of my email addresses to use (empty is ok) */ (my_email)?strlen(my_email): 0, subject, /* Subject (optional) */ (subject)?strlen(subject): 0, supplied_euid, /* ...or NULL if this is irrelevant */ (supplied_euid)?strlen(supplied_euid):0, preformatted_text, /* ...or NULL to read text from client */ (preformatted_text)?strlen(preformatted_text) : 0, references, /* Thread references */ (references)?strlen(references):0); } /* * Build a binary message to be saved on disk. * (NOTE: if you supply 'preformatted_text', the buffer you give it * will become part of the message. This means you are no longer * responsible for managing that memory -- it will be freed along with * the rest of the fields when CM_Free() is called.) */ struct CtdlMessage *CtdlMakeMessageLen( struct ctdluser *author, /* author's user structure */ char *recipient, /* NULL if it's not mail */ long rcplen, char *recp_cc, /* NULL if it's not mail */ long cclen, char *room, /* room where it's going */ long roomlen, int type, /* see MES_ types in header file */ int format_type, /* variformat, plain text, MIME... */ char *fake_name, /* who we're masquerading as */ long fnlen, char *my_email, /* which of my email addresses to use (empty is ok) */ long myelen, char *subject, /* Subject (optional) */ long subjlen, char *supplied_euid, /* ...or NULL if this is irrelevant */ long euidlen, char *preformatted_text, /* ...or NULL to read text from client */ long textlen, char *references, /* Thread references */ long reflen ) { struct CitContext *CCC = CC; /* Don't confuse the poor folks if it's not routed mail. * / char dest_node[256] = "";*/ long blen; char buf[1024]; struct CtdlMessage *msg; StrBuf *FakeAuthor; StrBuf *FakeEncAuthor = NULL; msg = malloc(sizeof(struct CtdlMessage)); memset(msg, 0, sizeof(struct CtdlMessage)); msg->cm_magic = CTDLMESSAGE_MAGIC; msg->cm_anon_type = type; msg->cm_format_type = format_type; if (recipient != NULL) rcplen = striplt(recipient); if (recp_cc != NULL) cclen = striplt(recp_cc); /* Path or Return-Path */ if (myelen > 0) { CM_SetField(msg, eMessagePath, my_email, myelen); } else { CM_SetField(msg, eMessagePath, author->fullname, strlen(author->fullname)); } convert_spaces_to_underscores(msg->cm_fields[eMessagePath]); blen = snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); CM_SetField(msg, eTimestamp, buf, blen); if (fnlen > 0) { FakeAuthor = NewStrBufPlain (fake_name, fnlen); } else { FakeAuthor = NewStrBufPlain (author->fullname, -1); } StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor); CM_SetAsFieldSB(msg, eAuthor, &FakeEncAuthor); FreeStrBuf(&FakeAuthor); if (CCC->room.QRflags & QR_MAILBOX) { /* room */ CM_SetField(msg, eOriginalRoom, &CCC->room.QRname[11], strlen(&CCC->room.QRname[11])); } else { CM_SetField(msg, eOriginalRoom, CCC->room.QRname, strlen(CCC->room.QRname)); } CM_SetField(msg, eNodeName, CFG_KEY(c_nodename)); CM_SetField(msg, eHumanNode, CFG_KEY(c_humannode)); if (rcplen > 0) { CM_SetField(msg, eRecipient, recipient, rcplen); } if (cclen > 0) { CM_SetField(msg, eCarbonCopY, recp_cc, cclen); } if (myelen > 0) { CM_SetField(msg, erFc822Addr, my_email, myelen); } else if ( (author == &CCC->user) && (!IsEmptyStr(CCC->cs_inet_email)) ) { CM_SetField(msg, erFc822Addr, CCC->cs_inet_email, strlen(CCC->cs_inet_email)); } if (subject != NULL) { long length; length = striplt(subject); if (length > 0) { long i; long IsAscii; IsAscii = -1; i = 0; while ((subject[i] != '\0') && (IsAscii = isascii(subject[i]) != 0 )) i++; if (IsAscii != 0) CM_SetField(msg, eMsgSubject, subject, subjlen); else /* ok, we've got utf8 in the string. */ { char *rfc2047Subj; rfc2047Subj = rfc2047encode(subject, length); CM_SetAsField(msg, eMsgSubject, &rfc2047Subj, strlen(rfc2047Subj)); } } } if (euidlen > 0) { CM_SetField(msg, eExclusiveID, supplied_euid, euidlen); } if (reflen > 0) { CM_SetField(msg, eWeferences, references, reflen); } if (preformatted_text != NULL) { CM_SetField(msg, eMesageText, preformatted_text, textlen); } else { StrBuf *MsgBody; MsgBody = CtdlReadMessageBodyBuf(HKEY("000"), config.c_maxmsglen, NULL, 0, 0); if (MsgBody != NULL) { CM_SetAsFieldSB(msg, eMesageText, &MsgBody); } } return(msg); } /* * API function to delete messages which match a set of criteria * (returns the actual number of messages deleted) */ int CtdlDeleteMessages(char *room_name, /* which room */ long *dmsgnums, /* array of msg numbers to be deleted */ int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */ char *content_type /* or "" for any. regular expressions expected. */ ) { struct CitContext *CCC = CC; struct ctdlroom qrbuf; struct cdbdata *cdbfr; long *msglist = NULL; long *dellist = NULL; int num_msgs = 0; int i, j; int num_deleted = 0; int delete_this; struct MetaData smi; regex_t re; regmatch_t pm; int need_to_free_re = 0; if (content_type) if (!IsEmptyStr(content_type)) { regcomp(&re, content_type, 0); need_to_free_re = 1; } MSG_syslog(LOG_DEBUG, " CtdlDeleteMessages(%s, %d msgs, %s)\n", room_name, num_dmsgnums, content_type); /* get room record, obtaining a lock... */ if (CtdlGetRoomLock(&qrbuf, room_name) != 0) { MSG_syslog(LOG_ERR, " CtdlDeleteMessages(): Room <%s> not found\n", room_name); if (need_to_free_re) regfree(&re); return (0); /* room not found */ } cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long)); if (cdbfr != NULL) { dellist = malloc(cdbfr->len); msglist = (long *) cdbfr->ptr; cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */ num_msgs = cdbfr->len / sizeof(long); cdb_free(cdbfr); } if (num_msgs > 0) { int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type); int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL); int have_more_del = 1; num_msgs = sort_msglist(msglist, num_msgs); if (num_dmsgnums > 1) num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums); /* { StrBuf *dbg = NewStrBuf(); for (i = 0; i < num_dmsgnums; i++) StrBufAppendPrintf(dbg, ", %ld", dmsgnums[i]); MSG_syslog(LOG_DEBUG, " Deleting before: %s", ChrPtr(dbg)); FreeStrBuf(&dbg); } */ i = 0; j = 0; while ((i < num_msgs) && (have_more_del)) { delete_this = 0x00; /* Set/clear a bit for each criterion */ /* 0 messages in the list or a null list means that we are * interested in deleting any messages which meet the other criteria. */ if (have_delmsgs) { delete_this |= 0x01; } else { while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++; if (i >= num_msgs) continue; if (msglist[i] == dmsgnums[j]) { delete_this |= 0x01; } j++; have_more_del = (j < num_dmsgnums); } if (have_contenttype) { GetMetaData(&smi, msglist[i]); if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) { delete_this |= 0x02; } } else { delete_this |= 0x02; } /* Delete message only if all bits are set */ if (delete_this == 0x03) { dellist[num_deleted++] = msglist[i]; msglist[i] = 0L; } i++; } /* { StrBuf *dbg = NewStrBuf(); for (i = 0; i < num_deleted; i++) StrBufAppendPrintf(dbg, ", %ld", dellist[i]); MSG_syslog(LOG_DEBUG, " Deleting: %s", ChrPtr(dbg)); FreeStrBuf(&dbg); } */ num_msgs = sort_msglist(msglist, num_msgs); cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long), msglist, (int)(num_msgs * sizeof(long))); if (num_msgs > 0) qrbuf.QRhighest = msglist[num_msgs - 1]; else qrbuf.QRhighest = 0; } CtdlPutRoomLock(&qrbuf); /* Go through the messages we pulled out of the index, and decrement * their reference counts by 1. If this is the only room the message * was in, the reference count will reach zero and the message will * automatically be deleted from the database. We do this in a * separate pass because there might be plug-in hooks getting called, * and we don't want that happening during an S_ROOMS critical * section. */ if (num_deleted) { for (i=0; imeta_msgnum = msgnum; smibuf->meta_refcount = 1; /* Default reference count is 1 */ /* Use the negative of the message number for its supp record index */ TheIndex = (0L - msgnum); cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long)); if (cdbsmi == NULL) { return; /* record not found; go with defaults */ } memcpy(smibuf, cdbsmi->ptr, ((cdbsmi->len > sizeof(struct MetaData)) ? sizeof(struct MetaData) : cdbsmi->len)); cdb_free(cdbsmi); return; } /* * PutMetaData() - (re)write supplementary record for a message */ void PutMetaData(struct MetaData *smibuf) { long TheIndex; /* Use the negative of the message number for the metadata db index */ TheIndex = (0L - smibuf->meta_msgnum); cdb_store(CDB_MSGMAIN, &TheIndex, (int)sizeof(long), smibuf, (int)sizeof(struct MetaData)); } /* * AdjRefCount - submit an adjustment to the reference count for a message. * (These are just queued -- we actually process them later.) */ void AdjRefCount(long msgnum, int incr) { struct CitContext *CCC = CC; struct arcq new_arcq; int rv = 0; MSG_syslog(LOG_DEBUG, "AdjRefCount() msg %ld ref count delta %+d\n", msgnum, incr); begin_critical_section(S_SUPPMSGMAIN); if (arcfp == NULL) { arcfp = fopen(file_arcq, "ab+"); chown(file_arcq, CTDLUID, (-1)); chmod(file_arcq, 0600); } end_critical_section(S_SUPPMSGMAIN); /* msgnum < 0 means that we're trying to close the file */ if (msgnum < 0) { MSGM_syslog(LOG_DEBUG, "Closing the AdjRefCount queue file\n"); begin_critical_section(S_SUPPMSGMAIN); if (arcfp != NULL) { fclose(arcfp); arcfp = NULL; } end_critical_section(S_SUPPMSGMAIN); return; } /* * If we can't open the queue, perform the operation synchronously. */ if (arcfp == NULL) { TDAP_AdjRefCount(msgnum, incr); return; } new_arcq.arcq_msgnum = msgnum; new_arcq.arcq_delta = incr; rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp); if (rv == -1) { MSG_syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n", file_arcq, strerror(errno)); } fflush(arcfp); return; } void AdjRefCountList(long *msgnum, long nmsg, int incr) { struct CitContext *CCC = CC; long i, the_size, offset; struct arcq *new_arcq; int rv = 0; MSG_syslog(LOG_DEBUG, "AdjRefCountList() msg %ld ref count delta %+d\n", nmsg, incr); begin_critical_section(S_SUPPMSGMAIN); if (arcfp == NULL) { arcfp = fopen(file_arcq, "ab+"); chown(file_arcq, CTDLUID, (-1)); chmod(file_arcq, 0600); } end_critical_section(S_SUPPMSGMAIN); /* * If we can't open the queue, perform the operation synchronously. */ if (arcfp == NULL) { for (i = 0; i < nmsg; i++) TDAP_AdjRefCount(msgnum[i], incr); return; } the_size = sizeof(struct arcq) * nmsg; new_arcq = malloc(the_size); for (i = 0; i < nmsg; i++) { new_arcq[i].arcq_msgnum = msgnum[i]; new_arcq[i].arcq_delta = incr; } rv = 0; offset = 0; while ((rv >= 0) && (offset < the_size)) { rv = fwrite(new_arcq + offset, 1, the_size - offset, arcfp); if (rv == -1) { MSG_syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n", file_arcq, strerror(errno)); } else { offset += rv; } } free(new_arcq); fflush(arcfp); return; } /* * TDAP_ProcessAdjRefCountQueue() * * Process the queue of message count adjustments that was created by calls * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount() * for each one. This should be an "off hours" operation. */ int TDAP_ProcessAdjRefCountQueue(void) { struct CitContext *CCC = CC; char file_arcq_temp[PATH_MAX]; int r; FILE *fp; struct arcq arcq_rec; int num_records_processed = 0; snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand()); begin_critical_section(S_SUPPMSGMAIN); if (arcfp != NULL) { fclose(arcfp); arcfp = NULL; } r = link(file_arcq, file_arcq_temp); if (r != 0) { MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno)); end_critical_section(S_SUPPMSGMAIN); return(num_records_processed); } unlink(file_arcq); end_critical_section(S_SUPPMSGMAIN); fp = fopen(file_arcq_temp, "rb"); if (fp == NULL) { MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno)); return(num_records_processed); } while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) { TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta); ++num_records_processed; } fclose(fp); r = unlink(file_arcq_temp); if (r != 0) { MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno)); } return(num_records_processed); } /* * TDAP_AdjRefCount - adjust the reference count for a message. * This one does it "for real" because it's called by * the autopurger function that processes the queue * created by AdjRefCount(). If a message's reference * count becomes zero, we also delete the message from * disk and de-index it. */ void TDAP_AdjRefCount(long msgnum, int incr) { struct CitContext *CCC = CC; struct MetaData smi; long delnum; /* This is a *tight* critical section; please keep it that way, as * it may get called while nested in other critical sections. * Complicating this any further will surely cause deadlock! */ begin_critical_section(S_SUPPMSGMAIN); GetMetaData(&smi, msgnum); smi.meta_refcount += incr; PutMetaData(&smi); end_critical_section(S_SUPPMSGMAIN); MSG_syslog(LOG_DEBUG, "TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d\n", msgnum, incr, smi.meta_refcount ); /* If the reference count is now zero, delete the message * (and its supplementary record as well). */ if (smi.meta_refcount == 0) { MSG_syslog(LOG_DEBUG, "Deleting message <%ld>\n", msgnum); /* Call delete hooks with NULL room to show it has gone altogether */ PerformDeleteHooks(NULL, msgnum); /* Remove from message base */ delnum = msgnum; cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long)); cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long)); /* Remove metadata record */ delnum = (0L - msgnum); cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long)); } } /* * Write a generic object to this room * * Note: this could be much more efficient. Right now we use two temporary * files, and still pull the message into memory as with all others. */ void CtdlWriteObject(char *req_room, /* Room to stuff it in */ char *content_type, /* MIME type of this object */ char *raw_message, /* Data to be written */ off_t raw_length, /* Size of raw_message */ struct ctdluser *is_mailbox, /* Mailbox room? */ int is_binary, /* Is encoding necessary? */ int is_unique, /* Del others of this type? */ unsigned int flags /* Internal save flags */ ) { struct CitContext *CCC = CC; struct ctdlroom qrbuf; char roomname[ROOMNAMELEN]; struct CtdlMessage *msg; StrBuf *encoded_message = NULL; if (is_mailbox != NULL) { CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room); } else { safestrncpy(roomname, req_room, sizeof(roomname)); } MSG_syslog(LOG_DEBUG, "Raw length is %ld\n", (long)raw_length); if (is_binary) { encoded_message = NewStrBufPlain(NULL, (size_t) (((raw_length * 134) / 100) + 4096 ) ); } else { encoded_message = NewStrBufPlain(NULL, (size_t)(raw_length + 4096)); } StrBufAppendBufPlain(encoded_message, HKEY("Content-type: "), 0); StrBufAppendBufPlain(encoded_message, content_type, -1, 0); StrBufAppendBufPlain(encoded_message, HKEY("\n"), 0); if (is_binary) { StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: base64\n\n"), 0); } else { StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: 7bit\n\n"), 0); } if (is_binary) { StrBufBase64Append(encoded_message, NULL, raw_message, raw_length, 0); } else { StrBufAppendBufPlain(encoded_message, raw_message, raw_length, 0); } MSGM_syslog(LOG_DEBUG, "Allocating\n"); msg = malloc(sizeof(struct CtdlMessage)); memset(msg, 0, sizeof(struct CtdlMessage)); msg->cm_magic = CTDLMESSAGE_MAGIC; msg->cm_anon_type = MES_NORMAL; msg->cm_format_type = 4; CM_SetField(msg, eAuthor, CCC->user.fullname, strlen(CCC->user.fullname)); CM_SetField(msg, eOriginalRoom, req_room, strlen(req_room)); CM_SetField(msg, eNodeName, CFG_KEY(c_nodename)); CM_SetField(msg, eHumanNode, CFG_KEY(c_humannode)); msg->cm_flags = flags; CM_SetAsFieldSB(msg, eMesageText, &encoded_message); /* Create the requested room if we have to. */ if (CtdlGetRoom(&qrbuf, roomname) != 0) { CtdlCreateRoom(roomname, ( (is_mailbox != NULL) ? 5 : 3 ), "", 0, 1, 0, VIEW_BBS); } /* If the caller specified this object as unique, delete all * other objects of this type that are currently in the room. */ if (is_unique) { MSG_syslog(LOG_DEBUG, "Deleted %d other msgs of this type\n", CtdlDeleteMessages(roomname, NULL, 0, content_type) ); } /* Now write the data */ CtdlSubmitMsg(msg, NULL, roomname, 0); CM_Free(msg); } /*****************************************************************************/ /* MODULE INITIALIZATION STUFF */ /*****************************************************************************/ void SetMessageDebugEnabled(const int n) { MessageDebugEnabled = n; } CTDL_MODULE_INIT(msgbase) { if (!threading) { CtdlRegisterDebugFlagHook(HKEY("messages"), SetMessageDebugEnabled, &MessageDebugEnabled); } /* return our Subversion id for the Log */ return "msgbase"; } citadel-9.01/md5.h0000644000000000000000000000132612507024051012402 0ustar rootroot #ifndef MD5_H #define MD5_H #include "sysdep.h" #include "typesize.h" struct MD5Context { cit_uint32_t buf[4]; cit_uint32_t bits[2]; cit_uint32_t in[16]; }; void MD5Init(struct MD5Context *context); void MD5Update(struct MD5Context *context, unsigned char const *buf, unsigned len); void MD5Final(unsigned char digest[16], struct MD5Context *context); void MD5Transform(cit_uint32_t buf[4], cit_uint32_t const in[16]); char *make_apop_string(char *realpass, char *nonce, char *buffer, size_t n); /* * This is needed to make RSAREF happy on some MS-DOS compilers. */ #ifndef HAVE_OPENSSL typedef struct MD5Context MD5_CTX; #endif #define MD5_DIGEST_LEN 16 #define MD5_HEXSTRING_SIZE 33 #endif /* !MD5_H */ citadel-9.01/parsedate.h0000644000000000000000000000004112507024051013656 0ustar rootroot time_t parsedate(const char *); citadel-9.01/ical_dezonify.h0000644000000000000000000000012612507024051014531 0ustar rootrootvoid ical_dezonify(icalcomponent *cal); icaltimezone *get_default_icaltimezone(void); citadel-9.01/sysdep_decls.h0000644000000000000000000000377212507024051014405 0ustar rootroot #ifndef SYSDEP_DECLS_H #define SYSDEP_DECLS_H #include #include "sysdep.h" #ifdef HAVE_PTHREAD_H #include #endif #ifdef HAVE_DB_H #include #elif defined(HAVE_DB4_DB_H) #include #else #error Neither nor was found by configure. Install db4-devel. #endif #if DB_VERSION_MAJOR < 4 || DB_VERSION_MINOR < 1 #error Citadel requires Berkeley DB v4.1 or newer. Please upgrade. #endif #include "server.h" #include "database.h" #if SIZEOF_SIZE_T == SIZEOF_INT #define SIZE_T_FMT "%d" #else #define SIZE_T_FMT "%ld" #endif #if SIZEOF_LOFF_T == SIZEOF_LONG #define LOFF_T_FMT "%ld" #else #define LOFF_T_FMT "%lld" #endif void cputbuf(const StrBuf *Buf); #ifdef __GNUC__ void cprintf (const char *format, ...) __attribute__((__format__(__printf__,1,2))); #else void cprintf (const char *format, ...); #endif void init_sysdep (void); int ctdl_tcp_server(char *ip_addr, int port_number, int queue_len, char *errormessage); int ctdl_uds_server(char *sockpath, int queue_len, char *errormessage); void buffer_output(void); void unbuffer_output(void); void flush_output(void); int client_write (const char *buf, int nbytes); int client_read_to (char *buf, int bytes, int timeout); int client_read (char *buf, int bytes); int client_getln (char *buf, int maxbytes); int CtdlClientGetLine(StrBuf *Target); int client_read_blob(StrBuf *Target, int bytes, int timeout); void client_set_inbound_buf(long N); int client_read_random_blob(StrBuf *Target, int timeout); void client_close(void); void sysdep_master_cleanup (void); void kill_session (int session_to_kill); void start_daemon (int do_close_stdio); void checkcrash(void); int convert_login (char *NameToConvert); void init_master_fdset(void); void *worker_thread(void *); extern volatile int exit_signal; extern volatile int shutdown_and_halt; extern volatile int running_as_daemon; extern volatile int restart_server; extern int verbosity; extern int rescan[]; extern int SyslogFacility(char *name); #endif /* SYSDEP_DECLS_H */ citadel-9.01/network/0000755000000000000000000000000012507024051013233 5ustar rootrootcitadel-9.01/network/mail.aliases0000644000000000000000000000012112507024051015512 0ustar rootrootbbs,room_aide root,room_aide Auto,room_aide postmaster,room_aide abuse,room_aide citadel-9.01/notify_about_newmail.js0000644000000000000000000000021212507024051016311 0ustar rootroot{ "message_notification" : { "to" : "^notifyuser", "syncsource" : "^syncsource", "msgid" : "^msgid", "msgnum" : "^msgnum" } } citadel-9.01/housekeeping.c0000644000000000000000000000657112507024051014405 0ustar rootroot/* * This file contains miscellaneous housekeeping tasks. * * Copyright (c) 1987-2011 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include "ctdl_module.h" #include "serv_extensions.h" #include "room_ops.h" #include "internet_addressing.h" #include "journaling.h" void check_sched_shutdown(void) { if ((ScheduledShutdown == 1) && (ContextList == NULL)) { syslog(LOG_NOTICE, "Scheduled shutdown initiating.\n"); server_shutting_down = 1; } } /* * Check (and fix) floor reference counts. This doesn't need to be done * very often, since the counts should remain correct during normal operation. */ void check_ref_counts_backend(struct ctdlroom *qrbuf, void *data) { int *new_refcounts; new_refcounts = (int *) data; ++new_refcounts[(int)qrbuf->QRfloor]; } void check_ref_counts(void) { struct floor flbuf; int a; int new_refcounts[MAXFLOORS]; syslog(LOG_DEBUG, "Checking floor reference counts\n"); for (a=0; a 0) { flbuf.f_flags = flbuf.f_flags | QR_INUSE; } else { flbuf.f_flags = flbuf.f_flags & ~QR_INUSE; } lputfloor(&flbuf, a); syslog(LOG_DEBUG, "Floor %d: %d rooms\n", a, new_refcounts[a]); } } /* * This is the housekeeping loop. Worker threads come through here after * processing client requests but before jumping back into the pool. We * only allow housekeeping to execute once per minute, and we only allow one * instance to run at a time. */ static int housekeeping_in_progress = 0; static time_t last_timer = 0L; void do_housekeeping(void) { int do_housekeeping_now = 0; int do_perminute_housekeeping_now = 0; time_t now; /* * We do it this way instead of wrapping the whole loop in an * S_HOUSEKEEPING critical section because it eliminates the need to * potentially have multiple concurrent mutexes in progress. */ begin_critical_section(S_HOUSEKEEPING); if (housekeeping_in_progress == 0) { do_housekeeping_now = 1; housekeeping_in_progress = 1; } end_critical_section(S_HOUSEKEEPING); if (do_housekeeping_now == 0) { return; } /* * Ok, at this point we've made the decision to run the housekeeping * loop. Everything below this point is real work. */ now = time(NULL); if ( (now - last_timer) > (time_t)60 ) { do_perminute_housekeeping_now = 1; last_timer = time(NULL); } /* First, do the "as often as needed" stuff... */ JournalRunQueue(); PerformSessionHooks(EVT_HOUSE); /* Then, do the "once per minute" stuff... */ if (do_perminute_housekeeping_now) { cdb_check_handles(); /* suggested by Justin Case */ PerformSessionHooks(EVT_TIMER); /* Run any timer hooks */ } /* * All done. */ begin_critical_section(S_HOUSEKEEPING); housekeeping_in_progress = 0; end_critical_section(S_HOUSEKEEPING); } citadel-9.01/citadel-slapd.conf0000644000000000000000000000203112507024051015113 0ustar rootroot# This is a sample OpenLDAP configuration file (usually placed in # /etc/openldap/slapd.conf) for use with the Citadel LDAP connector. # # In this sample, we have a server called "servername.domain.org" and # we've built our directory tree's Base DN using the components of that # domain name. You can change this if you'd like. # # Once you've got this in place, all you have to do is run slapd, configure # Citadel to point its LDAP connector at it, and run the IGAB command to # initialize the directory. # A sample schema is included, but you're probably better off # just going with "schemacheck off" and using whatever schema came with # your system's slapd. # # include /usr/local/citadel/citadel-openldap.schema pidfile /usr/local/citadel/openldap-data/slapd.pid argsfile /usr/local/citadel/openldap-data/slapd.args database ldbm schemacheck off allow bind_v2 suffix "dc=servername,dc=domain,dc=org" rootdn "cn=manager,dc=servername,dc=domain,dc=org" rootpw secret directory /usr/local/citadel/openldap-data index objectClass eq citadel-9.01/docs/0000755000000000000000000000000012507024051012472 5ustar rootrootcitadel-9.01/docs/welcomemail.html0000644000000000000000000000552212507024051015662 0ustar rootroot Welcome to your new Citadel installation

    Welcome to your new Citadel installation!

    Congratulations! You've now completed your Citadel Installation.
    Your Citadel system provides you with the following Protocols to communicate with your Client Software:
    • Webmail via http and https. Any remaining configuration Tasks, are most likely able to be completed in Webcit
    • IMAP Directory Services. Access your Inbox and other Rooms easily via IMAP.
    • POP3 Mail Delivery. If you'd like to poll mails over to your Client, use Pop3.
    • GroupDAV Calendaring and Address book Access. Connect your Calendaring Client to a central Storage, and share calendars with others.
    • ICal Access to your personal Calendar.
    • SMTP / MSA access with TLS support. Receive mails from others or relay your own Client via your Citadel.

    For advice regarding how to configure your favorite Mail client to interact with Citadel, please see the Client Matrix . If you don't find your client there, please sign into Uncensored! and tell us about it in the Citadel Documentation Room! And if you would like to provide screenshots on how you did the configuration for documentation purposes, we'll appreciate them.

    Please see the FAQ for more general questions, or the Citadel Administration Manual for more involved situations. Or, join us at Uncensored!, post your message into the Citadel Support Room and you will get help there. You may also join us on IRC (irc.citadel.org, or freenode network) in #Citadel.

    If Webcit does not yet speak your language, please tell us at Uncensored! in the Citadel Development Room, where you will be welcomed and encouraged to add a translation Working contributions in various forms are always welcome, in fact, so feel free to join us in improving the Citadel system!
    Enjoying Citadel? Please let us know. Just log into Uncensored! and drop us a note!

    Your Citadel Development Team

    Read more about Citadel on Wikipedia citadel-9.01/docs/README-FIRST.txt0000644000000000000000000000034112507024051015053 0ustar rootroot Welcome to the Citadel system! Documentation is no longer included with the source code distribution; instead we encourage you to visit http://www.citadel.org and peruse the knowledge base we have online at the web site. citadel-9.01/docs/welcomemail.txt0000644000000000000000000000501212507024051015527 0ustar rootroot Welcome to your new Citadel installation! Congratulations! You've now completed your Citadel Installation. Your Citadel system provides you with the following Protocols to communicate with your Client Software: * Webmail via http and https. Any remaining configuration Tasks, are most likely able to be completed in Webcit * IMAP Directory Services. Access your Inbox and other Rooms easily via IMAP. * POP3 Mail Delivery. If you'd like to poll mails over to your Client, use POP3. * [1]GroupDAV Calendaring and Address book Access. Connect your Calendaring Client to a central Storage, and share calendars with others. * iCalendar-over-WebDAV ("Webcal") Access to your personal Calendar. * SMTP / MSA access with TLS support. Receive mails from others or relay your own Client via your Citadel. For advice regarding how to configure your favorite Mail client to interact with Citadel, please see the [2]Client Matrix . If you don't find your client there, please sign into [3]Uncensored! and tell us about it in the Citadel Documentation Room! And if you would like to provide screenshots on how you did the configuration for documentation purposes, we'll appreciate them. Please see the [4]FAQ for more general questions, or the [5]Citadel Administration Manual for more involved situations. Or, join us at [6]Uncensored!, post your message into the Citadel Support Room and you will get help there. You may also join us on [7]IRC (irc.citadel.org, or freenode network) in #Citadel. If Webcit does not yet speak your language, please tell us at [8]Uncensored! in the Citadel Development Room, where you will be welcomed and encouraged to add a translation Working contributions in various forms are always welcome, in fact, so feel free to join us in improving the Citadel system! Enjoying Citadel? Please let us know. Just log into [9]Uncensored! and drop us a note! Your Citadel Development Team Read more about [10]Citadel on Wikipedia References Visible links 1. http://www.groupdav.org/ 2. http://www.citadel.org/clientmatrix 3. http://uncensored.citadel.org/ 4. http://citadel.org/index.php?option=com_content&task=section&id=3&Itemid=38 5. http://easyinstall.citadel.org/citadel/docs/citadel.html 6. http://uncensored.citadel.org/ 7. irc://irc.citadel.org/#citadel 8. http://uncensored.citadel.org/ 9. http://uncensored.citadel.org/ 10. http://en.wikipedia.org/wiki/Citadel/UX citadel-9.01/support.h0000644000000000000000000000030412507024051013424 0ustar rootroot#include void strproc (char *string); int getstring (FILE *fp, char *string); void mesg_locate (char *targ, size_t n, const char *searchfor, int numdirs, const char * const *dirs); citadel-9.01/euidindex.c0000644000000000000000000001266412507024051013675 0ustar rootroot/* * Index messages by EUID per room. */ #include "sysdep.h" #include #include #include "citserver.h" #include "room_ops.h" /* * The structure of an euidindex record *key* is: * * |----room_number----|----------EUID-------------| * (sizeof long) (actual length of euid) * * * The structure of an euidindex record *value* is: * * |-----msg_number----|----room_number----|----------EUID-------------| * (sizeof long) (sizeof long) (actual length of euid) * */ /* * Return nonzero if the supplied room is one which should have * an EUID index. */ int DoesThisRoomNeedEuidIndexing(struct ctdlroom *qrbuf) { switch(qrbuf->QRdefaultview) { case VIEW_BBS: return(0); case VIEW_MAILBOX: return(0); case VIEW_ADDRESSBOOK: return(1); case VIEW_DRAFTS: return(0); case VIEW_CALENDAR: return(1); case VIEW_TASKS: return(1); case VIEW_NOTES: return(1); case VIEW_WIKI: return(1); case VIEW_WIKIMD: return(1); case VIEW_BLOG: return(1); } return(0); } /* * Locate a message in a given room with a given euid, and return * its message number. */ long locate_message_by_euid(char *euid, struct ctdlroom *qrbuf) { return CtdlLocateMessageByEuid (euid, qrbuf); } long CtdlLocateMessageByEuid(char *euid, struct ctdlroom *qrbuf) { char *key; int key_len; struct cdbdata *cdb_euid; long msgnum = (-1L); syslog(LOG_DEBUG, "Searching for EUID <%s> in <%s>\n", euid, qrbuf->QRname); key_len = strlen(euid) + sizeof(long) + 1; key = malloc(key_len); memcpy(key, &qrbuf->QRnumber, sizeof(long)); strcpy(&key[sizeof(long)], euid); cdb_euid = cdb_fetch(CDB_EUIDINDEX, key, key_len); free(key); if (cdb_euid == NULL) { msgnum = (-1L); } else { /* The first (sizeof long) of the record is what we're * looking for. Throw away the rest. */ memcpy(&msgnum, cdb_euid->ptr, sizeof(long)); cdb_free(cdb_euid); } syslog(LOG_DEBUG, "returning msgnum = %ld\n", msgnum); return(msgnum); } /* * Store the euid index for a message, which has presumably just been * stored in this room by the caller. */ void index_message_by_euid(char *euid, struct ctdlroom *qrbuf, long msgnum) { char *key; int key_len; char *data; int data_len; syslog(LOG_DEBUG, "Indexing message #%ld <%s> in <%s>\n", msgnum, euid, qrbuf->QRname); key_len = strlen(euid) + sizeof(long) + 1; key = malloc(key_len); memcpy(key, &qrbuf->QRnumber, sizeof(long)); strcpy(&key[sizeof(long)], euid); data_len = sizeof(long) + key_len; data = malloc(data_len); memcpy(data, &msgnum, sizeof(long)); memcpy(&data[sizeof(long)], key, key_len); cdb_store(CDB_EUIDINDEX, key, key_len, data, data_len); free(key); free(data); } /* * Called by rebuild_euid_index_for_room() to index one message. */ void rebuild_euid_index_for_msg(long msgnum, void *userdata) { struct CtdlMessage *msg = NULL; msg = CtdlFetchMessage(msgnum, 0); if (msg == NULL) return; if (!CM_IsEmpty(msg, eExclusiveID)) { index_message_by_euid(msg->cm_fields[eExclusiveID], &CC->room, msgnum); } CM_Free(msg); } void rebuild_euid_index_for_room(struct ctdlroom *qrbuf, void *data) { static struct RoomProcList *rplist = NULL; struct RoomProcList *ptr; struct ctdlroom qr; /* Lazy programming here. Call this function as a CtdlForEachRoom backend * in order to queue up the room names, or call it with a null room * to make it do the processing. */ if (qrbuf != NULL) { ptr = (struct RoomProcList *) malloc(sizeof (struct RoomProcList)); if (ptr == NULL) return; safestrncpy(ptr->name, qrbuf->QRname, sizeof ptr->name); ptr->next = rplist; rplist = ptr; return; } while (rplist != NULL) { if (CtdlGetRoom(&qr, rplist->name) == 0) { if (DoesThisRoomNeedEuidIndexing(&qr)) { syslog(LOG_DEBUG, "Rebuilding EUID index for <%s>\n", rplist->name); CtdlUserGoto(rplist->name, 0, 0, NULL, NULL, NULL, NULL); CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, rebuild_euid_index_for_msg, NULL); } } ptr = rplist; rplist = rplist->next; free(ptr); } } /* * Globally rebuild the EUID indices in every room. */ void rebuild_euid_index(void) { cdb_trunc(CDB_EUIDINDEX); /* delete the old indices */ CtdlForEachRoom(rebuild_euid_index_for_room, NULL); /* enumerate rm names */ rebuild_euid_index_for_room(NULL, NULL); /* and index them */ } /* * Server command to fetch a message number given an euid. */ void cmd_euid(char *cmdbuf) { char euid[256]; long msgnum; struct cdbdata *cdbfr; long *msglist = NULL; int num_msgs = 0; int i; if (CtdlAccessCheck(ac_logged_in_or_guest)) return; extract_token(euid, cmdbuf, 0, '|', sizeof euid); msgnum = CtdlLocateMessageByEuid(euid, &CC->room); if (msgnum <= 0L) { cprintf("%d not found\n", ERROR + MESSAGE_NOT_FOUND); return; } cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long)); if (cdbfr != NULL) { num_msgs = cdbfr->len / sizeof(long); msglist = (long *) cdbfr->ptr; for (i = 0; i < num_msgs; ++i) { if (msglist[i] == msgnum) { cdb_free(cdbfr); cprintf("%d %ld\n", CIT_OK, msgnum); return; } } cdb_free(cdbfr); } cprintf("%d not found\n", ERROR + MESSAGE_NOT_FOUND); } CTDL_MODULE_INIT(euidindex) { if (!threading) { CtdlRegisterProtoHook(cmd_euid, "EUID", "Perform operations on Extended IDs for messages"); } /* return our Subversion id for the Log */ return "euidindex"; } citadel-9.01/missing0000755000000000000000000002123112507024051013140 0ustar rootroot#! /bin/sh # Common stub for a few missing GNU programs while installing. # Copyright 1996, 1997, 1999, 2000 Free Software Foundation, Inc. # Originally by Fran,cois Pinard , 1996. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA # 02111-1307, USA. # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. if test $# -eq 0; then echo 1>&2 "Try \`$0 --help' for more information" exit 1 fi run=: # In the cases where this matters, `missing' is being run in the # srcdir already. if test -f configure.ac; then configure_ac=configure.ac else configure_ac=configure.in fi case "$1" in --run) # Try to run requested program, and just exit if it succeeds. run= shift "$@" && exit 0 ;; esac # If it does not exist, or fails to run (possibly an outdated version), # try to emulate it. case "$1" in -h|--h|--he|--hel|--help) echo "\ $0 [OPTION]... PROGRAM [ARGUMENT]... Handle \`PROGRAM [ARGUMENT]...' for when PROGRAM is missing, or return an error status if there is no known handling for PROGRAM. Options: -h, --help display this help and exit -v, --version output version information and exit --run try to run the given command, and emulate it if it fails Supported PROGRAM values: aclocal touch file \`aclocal.m4' autoconf touch file \`configure' autoheader touch file \`config.h.in' automake touch all \`Makefile.in' files bison create \`y.tab.[ch]', if possible, from existing .[ch] flex create \`lex.yy.c', if possible, from existing .c help2man touch the output file lex create \`lex.yy.c', if possible, from existing .c makeinfo touch the output file tar try tar, gnutar, gtar, then tar without non-portable flags yacc create \`y.tab.[ch]', if possible, from existing .[ch]" ;; -v|--v|--ve|--ver|--vers|--versi|--versio|--version) echo "missing 0.3 - GNU automake" ;; -*) echo 1>&2 "$0: Unknown \`$1' option" echo 1>&2 "Try \`$0 --help' for more information" exit 1 ;; aclocal) echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified \`acinclude.m4' or \`${configure_ac}'. You might want to install the \`Automake' and \`Perl' packages. Grab them from any GNU archive site." touch aclocal.m4 ;; autoconf) echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified \`${configure_ac}'. You might want to install the \`Autoconf' and \`GNU m4' packages. Grab them from any GNU archive site." touch configure ;; autoheader) echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified \`acconfig.h' or \`${configure_ac}'. You might want to install the \`Autoconf' and \`GNU m4' packages. Grab them from any GNU archive site." files=`sed -n 's/^[ ]*A[CM]_CONFIG_HEADER(\([^)]*\)).*/\1/p' ${configure_ac}` test -z "$files" && files="config.h" touch_files= for f in $files; do case "$f" in *:*) touch_files="$touch_files "`echo "$f" | sed -e 's/^[^:]*://' -e 's/:.*//'`;; *) touch_files="$touch_files $f.in";; esac done touch $touch_files ;; automake) echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified \`Makefile.am', \`acinclude.m4' or \`${configure_ac}'. You might want to install the \`Automake' and \`Perl' packages. Grab them from any GNU archive site." find . -type f -name Makefile.am -print | sed 's/\.am$/.in/' | while read f; do touch "$f"; done ;; bison|yacc) echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified a \`.y' file. You may need the \`Bison' package in order for those modifications to take effect. You can get \`Bison' from any GNU archive site." rm -f y.tab.c y.tab.h if [ $# -ne 1 ]; then eval LASTARG="\${$#}" case "$LASTARG" in *.y) SRCFILE=`echo "$LASTARG" | sed 's/y$/c/'` if [ -f "$SRCFILE" ]; then cp "$SRCFILE" y.tab.c fi SRCFILE=`echo "$LASTARG" | sed 's/y$/h/'` if [ -f "$SRCFILE" ]; then cp "$SRCFILE" y.tab.h fi ;; esac fi if [ ! -f y.tab.h ]; then echo >y.tab.h fi if [ ! -f y.tab.c ]; then echo 'main() { return 0; }' >y.tab.c fi ;; lex|flex) echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified a \`.l' file. You may need the \`Flex' package in order for those modifications to take effect. You can get \`Flex' from any GNU archive site." rm -f lex.yy.c if [ $# -ne 1 ]; then eval LASTARG="\${$#}" case "$LASTARG" in *.l) SRCFILE=`echo "$LASTARG" | sed 's/l$/c/'` if [ -f "$SRCFILE" ]; then cp "$SRCFILE" lex.yy.c fi ;; esac fi if [ ! -f lex.yy.c ]; then echo 'main() { return 0; }' >lex.yy.c fi ;; help2man) echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified a dependency of a manual page. You may need the \`Help2man' package in order for those modifications to take effect. You can get \`Help2man' from any GNU archive site." file=`echo "$*" | sed -n 's/.*-o \([^ ]*\).*/\1/p'` if test -z "$file"; then file=`echo "$*" | sed -n 's/.*--output=\([^ ]*\).*/\1/p'` fi if [ -f "$file" ]; then touch $file else test -z "$file" || exec >$file echo ".ab help2man is required to generate this page" exit 1 fi ;; makeinfo) if test -z "$run" && (makeinfo --version) > /dev/null 2>&1; then # We have makeinfo, but it failed. exit 1 fi echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified a \`.texi' or \`.texinfo' file, or any other file indirectly affecting the aspect of the manual. The spurious call might also be the consequence of using a buggy \`make' (AIX, DU, IRIX). You might want to install the \`Texinfo' package or the \`GNU make' package. Grab either from any GNU archive site." file=`echo "$*" | sed -n 's/.*-o \([^ ]*\).*/\1/p'` if test -z "$file"; then file=`echo "$*" | sed 's/.* \([^ ]*\) *$/\1/'` file=`sed -n '/^@setfilename/ { s/.* \([^ ]*\) *$/\1/; p; q; }' $file` fi touch $file ;; tar) shift if test -n "$run"; then echo 1>&2 "ERROR: \`tar' requires --run" exit 1 fi # We have already tried tar in the generic part. # Look for gnutar/gtar before invocation to avoid ugly error # messages. if (gnutar --version > /dev/null 2>&1); then gnutar ${1+"$@"} && exit 0 fi if (gtar --version > /dev/null 2>&1); then gtar ${1+"$@"} && exit 0 fi firstarg="$1" if shift; then case "$firstarg" in *o*) firstarg=`echo "$firstarg" | sed s/o//` tar "$firstarg" ${1+"$@"} && exit 0 ;; esac case "$firstarg" in *h*) firstarg=`echo "$firstarg" | sed s/h//` tar "$firstarg" ${1+"$@"} && exit 0 ;; esac fi echo 1>&2 "\ WARNING: I can't seem to be able to run \`tar' with the given arguments. You may want to install GNU tar or Free paxutils, or check the command line arguments." exit 1 ;; *) echo 1>&2 "\ WARNING: \`$1' is needed, and you do not seem to have it handy on your system. You might have modified some files without having the proper tools for further handling them. Check the \`README' file, it often tells you about the needed prerequirements for installing this package. You may also peek at any GNU archive site, in case some other package would contain this missing \`$1' program." exit 1 ;; esac exit 0 citadel-9.01/configure.ac0000644000000000000000000007257512507024051014050 0ustar rootrootdnl Process this file with autoconf to produce a configure script. AC_PREREQ(2.52) AC_INIT([Citadel], [9.01], [http://www.citadel.org/]) AC_REVISION([$Revision: 5108 $]) AC_CONFIG_SRCDIR([citserver.c]) AC_CONFIG_HEADER(sysdep.h) AC_CONFIG_MACRO_DIR([m4]) AC_PREFIX_DEFAULT(/usr/local/citadel) if test "$prefix" = NONE; then AC_DEFINE_UNQUOTED(CTDLDIR, "$ac_default_prefix", [define this to the Citadel home directory]) ssl_dir="$ac_default_prefix/keys" localedir=$ac_default_prefix else AC_DEFINE_UNQUOTED(CTDLDIR, "$prefix", [define this to the Citadel home directory]) ssl_dir="$prefix/keys" localedir=$prefix fi dnl Make sure we see all GNU and Solaris extensions. AC_GNU_SOURCE dnl Checks for the Datadir AC_ARG_WITH(datadir, [ --with-datadir directory to store the databases under], [ if test "x$withval" != "xno" ; then AC_DEFINE(HAVE_DATA_DIR,[],[use alternate database location?]) AC_DEFINE_UNQUOTED(DATA_DIR, "$withval",[define, if the user suplied a data-directory to use.]) MAKE_DATA_DIR=$withval AC_SUBST(MAKE_DATA_DIR) fi ] ) dnl Checks for the helpDatadir AC_ARG_WITH(helpdir, [ --with-helpdir directory to store the helpfiles under], [ if test "x$withval" != "xno" ; then AC_DEFINE(HAVE_HELP_DIR,[],[use alternate database location?]) AC_DEFINE_UNQUOTED(HELP_DIR, "$withval",[define, if the user suplied a helpfile-directory to use.]) MAKE_HELP_DIR=$withval AC_SUBST(MAKE_HELP_DIR) fi ] ) dnl Checks for the Static Datadir AC_ARG_WITH(staticdatadir, [ --with-staticdatadir directory to store citadels system messages under], [ if test "x$withval" != "xno" ; then AC_DEFINE(HAVE_STATICDATA_DIR, [], [should we activate an alternate static text location?]) AC_DEFINE_UNQUOTED(STATICDATA_DIR, "$withval", [where do we put our static text data?]) MAKE_STATICDATA_DIR=$withval AC_SUBST(MAKE_STATICDATA_DIR) fi ] ) dnl Checks for the SSLdir dnl this is a bit different than the rest, dnl because of the citadel used to have a keys/ subdir. AC_ARG_WITH(ssldir, [ --with-ssldir directory to store the ssl certificates under], [ if test "x$withval" != "xno" ; then ssl_dir="$withval" fi AC_SUBST(MAKE_SSL_DIR) ] ) AC_DEFINE_UNQUOTED(SSL_DIR, "$ssl_dir", [were should we put our keys?]) dnl Checks for the spooldir AC_ARG_WITH(spooldir, [ --with-spooldir directory to keep queues under], [ if test "x$withval" != "xno" ; then AC_DEFINE(HAVE_SPOOL_DIR, [], [enable alternate spool dir?]) AC_DEFINE_UNQUOTED(SPOOL_DIR,"$withval", [where do we place our spool dirs?]) MAKE_SPOOL_DIR=$withval AC_SUBST(MAKE_SPOOL_DIR) fi ] ) dnl Checks for the Configdir AC_ARG_WITH(sysconfdir, [ --with-sysconfdir directory to store the configs under], [ if test "x$withval" != "xno" ; then AC_DEFINE(HAVE_ETC_DIR, [], [should we search our system config in an alternate place?]) AC_DEFINE_UNQUOTED(ETC_DIR, "$withval", [where to search our config files]) MAKE_ETC_DIR=$withval AC_SUBST(MAKE_ETC_DIR) fi ] ) dnl Checks for the Configdir AC_ARG_WITH(autosysconfdir, [ --with-autosysconfdir directory to store the automaticaly maintained configs under], [ if test "x$withval" != "xno" ; then AC_DEFINE(HAVE_AUTO_ETC_DIR, [], [should we search our automatic config in an alternate place?]) AC_DEFINE_UNQUOTED(AUTO_ETC_DIR, "$withval", [where to search our automatic config files]) MAKE_AUTO_ETC_DIR=$withval AC_SUBST(MAKE_AUTO_ETC_DIR) fi ] ) dnl Checks for where to put our utilities AC_ARG_WITH(utility-bindir, [ --with-utility-bindir directory where to find helper binaries], [ if test "x$withval" != "xno" ; then AC_DEFINE(HAVE_UTILBIN_DIR,[],[should we put our helper binaries to another location?]) AC_DEFINE_UNQUOTED(UTILBIN_DIR, "$withval", [were to put our helper programs]) MAKE_UTILBIN_DIR=$withval AC_SUBST(MAKE_UTILBIN_DIR) fi ] ) dnl Checks for the run-dir for the sockets AC_ARG_WITH(rundir, [ --with-rundir directory to place runtime files (UDS) to?], [ if test "x$withval" != "xno" ; then AC_DEFINE(HAVE_RUN_DIR, [], [should we put our non volatile files elsewhere?]) AC_DEFINE_UNQUOTED(RUN_DIR, "$withval", [define, where the config should go in unix style]) MAKE_RUN_DIR=$withval AC_SUBST(MAKE_RUN_DIR) fi ] ) dnl Checks for the Pseudo Random Generator sockets TODO: this keeps being default. AC_DEFINE_UNQUOTED(EGD_POOL, "/var/run/egd-pool", [place to keep our pseudo random generator file]) AC_ARG_WITH(egdpool, [ --with-egdpool the socket from Pseudo Random Generator, defaults to /var/run/egd-pool], [ if test "x$withval" != "xno" ; then AC_DEFINE_UNQUOTED(EGD_POOL, "$withval", [the socket from Pseudo Random Generator]) fi ] ) AC_ARG_WITH(docdir, [ --with-docdir where to install the documentation. default: /usr/local/citadel/], [ if test "x$withval" != "xno" ; then MAKE_DOC_DIR=$withval AC_SUBST(MAKE_DOC_DIR) fi ] ) dnl where to put the locale files AC_ARG_WITH(localedir, [ --with-localedir directory to put the locale files to], [ if test "x$withval" != "xno" ; then localedir=$withval fi ] ) AC_DEFINE_UNQUOTED(LOCALEDIR, "$localedir",[where to find our pot files]) LOCALEDIR=$localedir AC_SUBST(LOCALEDIR) dnl Checks for the zlib compression library. saved_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $SERVER_LIBS" AC_CHECK_HEADER(zlib.h, [AC_CHECK_LIB(z, zlibVersion, [ LIBS="-lz $LIBS $SERVER_LIBS" ], [ AC_MSG_ERROR(zlib was not found or is not usable. Please install zlib.) ] , )], [ AC_MSG_ERROR(zlib.h was not found or is not usable. Please install zlib.) ] ) CFLAGS="$saved_CFLAGS" dnl Here is the check for a libc integrated iconv AC_ARG_ENABLE(iconv, [ --disable-iconv do not use iconv charset conversion], ok_iconv=no, ok_iconv=yes) AC_MSG_CHECKING(Checking to see if your system supports iconv) AC_TRY_RUN([ #include main() { iconv_t ic = (iconv_t)(-1) ; ic = iconv_open("UTF-8", "us-ascii"); iconv_close(ic); exit(0); } ], [ ok_iconv=yes AC_MSG_RESULT([yes]) ], [ ok_iconv=no AC_MSG_RESULT([no]) ] ) dnl Check for iconv in external libiconv if test "$ok_iconv" = no; then AC_MSG_CHECKING(Checking for an external libiconv) OLD_LDFLAGS="$LDFLAGS" LDFLAGS="$LDFLAGS -liconv" AC_TRY_RUN([ #include main() { iconv_t ic = (iconv_t)(-1) ; ic = iconv_open("UTF-8", "us-ascii"); iconv_close(ic); } ], [ ok_iconv=yes AC_MSG_RESULT([yes]) ], [ ok_iconv=no LDFLAGS="$OLD_LDFLAGS" AC_MSG_RESULT([no]) ] ) fi if test "$ok_iconv" != "no"; then AC_MSG_RESULT(Citadel will be built with character set conversion.) AC_DEFINE(HAVE_ICONV,[],[whether we have iconv for charset conversion]) else AC_MSG_RESULT(Citadel will be built without character set conversion.) fi AC_CHECK_LIB(intl, libintl_bindtextdomain, [LDFLAGS="$LDFLAGS -lintl"]) AC_ARG_ENABLE(pie, [ --enable-pie build position-independent executables]) AC_ARG_WITH(pam, [ --with-pam use PAM if present (see PAM.txt before you try this)]) AC_ARG_WITH(kthread, [ --with-kthread use kernel threads (on FreeBSD) (not recommended yet)]) AC_ARG_WITH(db, [ --with-db@<:@=DIR@:>@ use Berkeley DB 3.x @<:@DIR=/usr/local/BerkeleyDB.3.@<:@123@:>@@:>@]) AC_ARG_WITH(ssl, [ --with-ssl=PATH Specify path to OpenSSL installation ], [ if test "x$withval" != "xno" ; then tryssldir=$withval fi ] ) AC_ARG_WITH(with_ldap, [ --with-ldap use OpenLDAP client library]) dnl AC_ARG_WITH(with_libdspam, [ --with-libdspam use libdspam mail spam scanning library]) AC_ARG_WITH(with_gc, [ --with-gc use the Boehm-Demers-Weiser garbage collection library]) if test "x$with_db" != xno -a "x$with_db" != xyes -a "$with_db"; then db_dir="$with_db" with_db=yes else test -f /usr/local/lib/libdb.a -o -f /usr/local/lib/libdb.so \ -o -f /usr/local/lib/libdb4.a -o -f /usr/local/lib/libdb4.so \ && db_dir=/usr/local test -d /usr/local/BerkeleyDB.4.1 && db_dir=/usr/local/BerkeleyDB.4.1 test -d /usr/local/BerkeleyDB.4.2 && db_dir=/usr/local/BerkeleyDB.4.2 test -d /usr/local/BerkeleyDB.4.3 && db_dir=/usr/local/BerkeleyDB.4.3 test -d /usr/local/BerkeleyDB.4.4 && db_dir=/usr/local/BerkeleyDB.4.4 test -d /usr/local/BerkeleyDB.4.5 && db_dir=/usr/local/BerkeleyDB.4.5 fi AC_CANONICAL_HOST PTHREAD_DEFS=-D_REENTRANT AC_MSG_CHECKING([how to compile with POSIX threads]) case "$host" in dnl BSDI 3.0 wants relocatable object modules instead of shared libs dnl for dlopen(), and has a wrapper script to link with shared libs. dnl Also has stupid non-reentrant gethostbyaddr() and friends. *-*-bsdi[123]*) test -z "$CC" -a -x /usr/bin/shlicc2 && CC=shlicc2 AC_DEFINE(HAVE_NONREENTRANT_NETDB,[], [define this if the OS has broken non-reentrant gethostby{name,addr}() ]) AC_MSG_RESULT([Old BSDI]) ;; *-*-bsdi*) AC_DEFINE(HAVE_NONREENTRANT_NETDB, [], [define this if the OS has broken non-reentrant gethostby{name,addr}() ]) AC_MSG_RESULT([BSD/OS]) ;; dnl Curses support on Mac OS X is kind of screwed at the moment. dnl TCP buffering isn't ideal under OS X. This define should also be dnl checked in other cases of OS X-Linux differences. *-*-darwin*) AC_DEFINE(HAVE_DARWIN, [], [define if using OS X/Darwin]) AC_MSG_RESULT([Mac OS X]) ;; dnl Digital Unix has an odd way to build for pthreads, and we can't dnl build pthreads programs with gcc due to header problems. alpha*-dec-osf*) test -z "$CC" && CC=cc LIBS="-lpthread -lexc $LIBS" check_pthread=no AC_MSG_RESULT([Tru64 or Digital UNIX]) ;; dnl FreeBSD is similar to Digital UNIX with DEC C, which has a -pthread flag: *-*-freebsd*) if test "$with_kthread" = yes; then LIBS="-kthread $LIBS" else LIBS="-pthread $LIBS" fi check_pthread=no PTHREAD_DEFS=-D_THREAD_SAFE AC_MSG_RESULT([FreeBSD]) ;; *-*-openbsd*) LIBS="-pthread $LIBS" check_pthread=no PTHREAD_DEFS=-pthread AC_MSG_RESULT([OpenBSD]) ;; *-*-linux*) PTHREAD_DEFS="-D_REENTRANT -pthread" AC_MSG_RESULT([Linux]) ;; *-*-solaris*) PTHREAD_DEFS="-D_REENTRANT -D_PTHREADS" AC_MSG_RESULT([Solaris]) ;; *-*-cygwin*) SERVER_LDFLAGS="-Wl,-subsystem,windows" AC_MSG_RESULT([Cygwin]) ;; *) AC_MSG_RESULT([default]) ;; esac dnl DEFS="$DEFS $PTHREAD_DEFS" dnl Checks for programs. AC_PROG_CC dnl Set up system-dependent compiler flags. if test "$GCC" = yes; then if test "$CC" = icc; then CFLAGS="$CFLAGS -w1" else case "$host" in *-*-solaris*|alpha*-dec-osf*) CFLAGS="$CFLAGS -Wall -Wcast-qual -Wcast-align -Wno-char-subscripts $PTHREAD_DEFS" ;; *) CFLAGS="$CFLAGS -Wall -Wcast-qual -Wcast-align -Wstrict-prototypes -Wno-strict-aliasing $PTHREAD_DEFS" ;; esac fi fi if test "x$enable_pie" = xyes; then save_CFLAGS="$CFLAGS" save_LDFLAGS="$LDFLAGS" CFLAGS="$CFLAGS -fpie" LDFLAGS="$LDFLAGS -pie -fpie" AC_CACHE_CHECK([whether compiler accepts -pie -fpie], ac_cv_pie_fpie, [AC_TRY_LINK([], [], ac_cv_pie_fpie=yes, ac_cv_pie_fpie=no)]) if test $ac_cv_pie_fpie = no; then CFLAGS="$save_CFLAGS" LDFLAGS="$save_LDFLAGS" fi fi AC_MSG_CHECKING([how to create dependancy checks]) if test -n "`$CC -V 2>&1 |grep Sun`"; then DEPEND_FLAG=-xM; else DEPEND_FLAG=-M fi AC_SUBST(DEPEND_FLAG) AC_PROG_INSTALL AC_PROG_YACC AC_PATH_PROG(DIFF,diff) AC_PATH_PROG(PATCH,patch) missing_dir=`cd $ac_aux_dir && pwd` AM_MISSING_PROG(AUTOCONF, autoconf, $missing_dir) AM_MISSING_PROG(ACLOCAL, aclocal, $missing_dir) dnl Checks for system services. dnl Check the size of various builtin types; see typesize.h (error) AC_CHECK_SIZEOF(char, 0) AC_CHECK_SIZEOF(short, 0) AC_CHECK_SIZEOF(int, 0) AC_CHECK_SIZEOF(long, 0) AC_CHECK_SIZEOF(size_t, 0) AC_CHECK_SIZEOF(loff_t, 0) dnl AC_CHECK_SIZEOF(long long, 0) dnl Checks for libraries. dnl We want to test for the following in libc before checking for their dnl respective libraries, because some systems (like Irix) have both, and the dnl non-libc versions may be broken. AC_CHECK_FUNCS(crypt gethostbyname connect flock getpwnam_r getpwuid_r getloadavg) AC_CHECK_FUNCS(strftime_l uselocale gettext) if test "$ok_nls" != "no"; then AC_CHECK_PROG(ok_xgettext, xgettext, yes, no) ok_nls=$ok_xgettext fi if test "$ok_nls" != "no"; then AC_CHECK_PROG(ok_msgmerge, msgmerge, yes, no) ok_nls=$ok_msgmerge fi if test "$ok_nls" != "no"; then AC_CHECK_PROG(ok_msgfmt, msgfmt, yes, no) ok_nls=$ok_msgfmt fi if test "$ok_nls" != "no"; then AC_MSG_RESULT(citadel will be built with national language support.) AC_DEFINE(ENABLE_NLS, [], [whether we have NLS support]) PROG_SUBDIRS="$PROG_SUBDIRS po/citadel-setup" else AC_MSG_RESULT(citadel will be built without national language support.) fi dnl disable backtrace if we don't want it. AC_ARG_WITH(backtrace, [ --with-backtrace enable backtrace dumps in the syslog], [ if test "x$withval" != "xno" ; then CFLAGS="$CFLAGS -rdynamic " LDFLAGS="$LDFLAGS -rdynamic " SERVER_LDFLAGS="$SERVER_LDFLAGS -rdynamic " AC_CHECK_FUNCS(backtrace) fi ] ) dnl disable backtrace if we don't want it. AC_ARG_WITH(gprof, [ --with-gprof enable profiling], [ if test "x$withval" != "xno" ; then CFLAGS="$CFLAGS -pg " LDFLAGS="$LDFLAGS -pg " SERVER_LDFLAGS="$SERVER_LDFLAGS -pg " fi ] ) if test "$ac_cv_func_gethostbyname" = no; then AC_CHECK_LIB(nsl, gethostbyname) fi if test "$ac_cv_func_connect" = no; then AC_CHECK_LIB(socket, connect) fi dnl Check for Solaris realtime support AC_CHECK_LIB(rt, sched_yield) dnl Determine the system's authentication capabilities. dnl We currently support PAM, standard getpwnam(), and getspnam() dnl (Linux shadow passwords) save_LIBS=$LIBS AC_CHECK_LIB(pam, pam_start, [chkpwd_LIBS="-lpam $chkpwd_LIBS" LIBS="-lpam $LIBS"]) AC_CHECK_FUNCS(pam_start) test "$enable_chkpwd" != no && LIBS=$save_LIBS if test "$ac_cv_func_pam_start" = no -o "$with_pam" != yes; then save_LIBS=$LIBS AC_SEARCH_LIBS(getspnam, shadow, [ if test "$ac_cv_search_getspnam" = -lshadow; then chkpwd_LIBS="-lshadow $chkpwd_LIBS" fi]) test "$enable_chkpwd" != no && LIBS=$save_LIBS if test "$ac_cv_func_crypt" = no; then AC_CHECK_LIB(crypt, crypt, [ chkpwd_LIBS="-lcrypt $chkpwd_LIBS" test "$enable_chkpwd" = no && \ LIBS="-lcrypt $LIBS"]) fi fi AC_CHECK_FUNCS(vw_printw wcolor_set resizeterm wresize) dnl Check for libpthread(s) if we're not using Digital UNIX or FreeBSD. (On dnl which the -pthread flag takes care of this.) if test "$check_pthread" != no; then AC_CHECK_LIB(pthread, pthread_create) AC_CHECK_LIB(pthreads, pthread_create) fi test -d /usr/kerberos/include && CPPFLAGS="$CPPFLAGS -I/usr/kerberos/include" dnl Checks for the libical iCalendar library. AC_CHECK_HEADER(libical/ical.h, [AC_CHECK_LIB(ical, icaltimezone_set_tzid_prefix, [ SERVER_LIBS="-lical $SERVER_LIBS" ], [ AC_MSG_ERROR(libical was not found and is required. More info: http://www.citadel.org/doku.php/installation:start) ] , )], [ AC_MSG_ERROR(libical/ical.h was not found and is required. More info: http://www.citadel.org/doku.php/installation:start) ] ) dnl Checks for the libsieve mailbox sorting library. AC_CHECK_HEADER(sieve2.h, [AC_CHECK_LIB(sieve, sieve2_license, [ SERVER_LIBS="-lsieve $SERVER_LIBS" ], [ AC_MSG_ERROR(libsieve was not found and is required. More info: http://www.citadel.org/doku.php/installation:start) ] , )], [ AC_MSG_ERROR(sieve2.h was not found and is required. More info: http://www.citadel.org/doku.php/installation:start) ] ) saved_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $SERVER_LIBS" dnl Check for libcitadel AC_CHECK_HEADER(libcitadel.h, [AC_CHECK_LIB(citadel, libcitadel_version_string, [ LIBS="-lcitadel $LIBS $SERVER_LIBS" chkpwd_LIBS="-lcitadel $chkpwd_LIBS" ], [ AC_MSG_ERROR(libcitadel was not found or is not usable. Please install libcitadel.) ] , )], [ AC_MSG_ERROR(libcitadel.h was not found or is not usable. Please install libcitadel.) ] ) CFLAGS="$saved_CFLAGS" AC_CHECK_LIB(cares, ares_parse_mx_reply, [ C_ARES_LIBS=-lcares AC_DEFINE(HAVE_C_ARES, 1, [Define to use c-ares library]) have_good_c_ares=yes ],, $SOCKET_LIBS $NSL_LIBS ) saved_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $SERVER_LIBS" dnl Check for c-ares AC_CHECK_HEADER(ares.h, [AC_CHECK_LIB(cares, ares_parse_mx_reply, [ LIBS="-lcares $LIBS $SERVER_LIBS" ], [ AC_MSG_ERROR(libc-ares was not found or is not usable. Please install libc-ares.) ] )], [ AC_MSG_ERROR(ares.h was not found or is not usable. Please install libc-ares.) ] ) CFLAGS="$saved_CFLAGS" saved_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $SERVER_LIBS" dnl Check for libev AC_CHECK_HEADER(ev.h, [AC_TRY_COMPILE([#include #include ], [ ev_cleanup abort_by_shutdown; struct ev_loop *event_base; ev_cleanup_start(event_base, &abort_by_shutdown); ], [ LIBS="-lev -lm $LIBS $SERVER_LIBS" ], [ AC_MSG_ERROR(libev was not found or is not usable. Please install libev.) ]) ], [ AC_MSG_ERROR(ev.h was not found or is not usable. Please install libev.) ] ) CFLAGS="$saved_CFLAGS" # The big search for OpenSSL if test "$with_ssl" != "no"; then saved_LIBS="$LIBS" saved_LDFLAGS="$LDFLAGS" saved_CFLAGS="$CFLAGS" if test "x$prefix" != "xNONE"; then tryssldir="$tryssldir $prefix" fi AC_CACHE_CHECK([for OpenSSL], ac_cv_openssldir, [ for ssldir in $tryssldir "" /usr /usr/local/openssl /usr/lib/openssl /usr/local/ssl /usr/lib/ssl /usr/local /usr/pkg /opt /opt/openssl ; do CFLAGS="$saved_CFLAGS" LDFLAGS="$saved_LDFLAGS" LIBS="$saved_LIBS -lssl -lcrypto" # Skip directories if they don't exist if test ! -z "$ssldir" -a ! -d "$ssldir" ; then continue; fi if test ! -z "$ssldir" -a "x$ssldir" != "x/usr"; then # Try to use $ssldir/lib if it exists, otherwise # $ssldir if test -d "$ssldir/lib" ; then LDFLAGS="-L$ssldir/lib $saved_LDFLAGS" if test ! -z "$need_dash_r" ; then LDFLAGS="-R$ssldir/lib $LDFLAGS" fi else LDFLAGS="-L$ssldir $saved_LDFLAGS" if test ! -z "$need_dash_r" ; then LDFLAGS="-R$ssldir $LDFLAGS" fi fi # Try to use $ssldir/include if it exists, otherwise # $ssldir if test -d "$ssldir/include" ; then CFLAGS="-I$ssldir/include $saved_CFLAGS" else CFLAGS="-I$ssldir $saved_CFLAGS" fi fi # Basic test to check for compatible version and correct linking # *does not* test for RSA - that comes later. AC_TRY_RUN( [ #include #include int main(void) { char a[2048]; memset(a, 0, sizeof(a)); RAND_add(a, sizeof(a), sizeof(a)); return(RAND_status() <= 0); } ], [ found_crypto=1 break; ], [] ) if test ! -z "$found_crypto" ; then break; fi done if test -z "$ssldir" ; then ssldir="(system)" fi if test ! -z "$found_crypto" ; then ac_cv_openssldir=$ssldir else ac_cv_openssldir="no" fi ]) LIBS="$saved_LIBS" LDFLAGS="$saved_LDFLAGS" CFLAGS="$saved_CFLAGS" if test "x$ac_cv_openssldir" != "xno" ; then AC_DEFINE(HAVE_OPENSSL, [], [Define if you have OpenSSL.]) LIBS="-lssl -lcrypto $LIBS" dnl Need to recover ssldir - test above runs in subshell ssldir=$ac_cv_openssldir if test ! -z "$ssldir" -a "x$ssldir" != "x/usr" -a "x$ssldir" != "x(system)"; then # Try to use $ssldir/lib if it exists, otherwise # $ssldir if test -d "$ssldir/lib" ; then LDFLAGS="-L$ssldir/lib $saved_LDFLAGS" if test ! -z "$need_dash_r" ; then LDFLAGS="-R$ssldir/lib $LDFLAGS" fi else LDFLAGS="-L$ssldir $saved_LDFLAGS" if test ! -z "$need_dash_r" ; then LDFLAGS="-R$ssldir $LDFLAGS" fi fi # Try to use $ssldir/include if it exists, otherwise # $ssldir if test -d "$ssldir/include" ; then CFLAGS="-I$ssldir/include $saved_CFLAGS" else CFLAGS="-I$ssldir $saved_CFLAGS" fi fi fi fi if test "x$with_db" != xno; then test "$db_dir" && LDFLAGS="$LDFLAGS -L$db_dir/lib" dblib="" if test -d "$db_dir/include/db4"; then CPPFLAGS="$CPPFLAGS -I$db_dir/include/db4" dblib="db4" elif test "$db_dir"; then CPPFLAGS="$CPPFLAGS -I$db_dir/include" elif test -d /usr/include/db4; then CPPFLAGS="$CPPFLAGS -I/usr/include/db4" dblib="db4" fi AC_CHECK_DB([db db-4.1 db-4 db4], [ DATABASE=database.c ], AC_MSG_ERROR([[Can not locate a suitable Berkeley DB library. Use --with-db=PATH to specify the path]])) fi dnl Checks for the OpenLDAP client library. if test "x$with_ldap" != xno ; then AC_CHECK_HEADERS(ldap.h, [AC_CHECK_LIB(ldap, ldap_initialize, [ok_ldap=yes],, )]) fi if test "x$ok_ldap" = xyes ; then SERVER_LIBS="-lldap $SERVER_LIBS" AC_DEFINE(HAVE_LDAP, [], [define this if you have OpenLDAP client available]) fi dnl Checks for the libdspam mail spam scanning library. dnl if test "x$with_libdspam" != xno ; then dnl AC_CHECK_HEADERS(dspam/libdspam.h, dnl [AC_CHECK_LIB(dspam, dspam_init, dnl [ok_libdspam=yes],, dnl )]) dnl fi dnl dnl if test "x$ok_libdspam" = xyes ; then dnl SERVER_LIBS="-ldspam $SERVER_LIBS" dnl AC_DEFINE(HAVE_LIBDSPAM, [], [(unfinished) define this if you have the libdspam mail spam scanning library available]) dnl fi dnl Checks for the Expat XML parser. AC_CHECK_HEADER(expat.h, [AC_CHECK_LIB(expat, XML_ParserCreateNS, [ SERVER_LIBS="-lexpat $SERVER_LIBS" ], [ AC_MSG_ERROR(The Expat XML parser was not found and is required. More info: http://www.citadel.org/doku.php/installation:start) ] , )], [ AC_MSG_ERROR(expat.h was not found and is required. More info: http://www.citadel.org/doku.php/installation:start) ] ) dnl Checks for libcurl. AC_CHECK_HEADER(curl/curl.h, [AC_CHECK_LIB(curl, curl_version, [ SERVER_LIBS="-lcurl $SERVER_LIBS" ], [ AC_MSG_ERROR(libcurl was not found and is required. More info: http://www.citadel.org/doku.php/installation:start) ] , )], [ AC_MSG_ERROR(curl/curl.h was not found and is required. More info: http://www.citadel.org/doku.php/installation:start) ] ) dnl Checks for header files. AC_HEADER_DIRENT AC_HEADER_STDC AC_HEADER_SYS_WAIT dnl dnl TODO: for the DB header checks, we should check whether the headers dnl define db_env_create, somehow dnl AC_CHECK_HEADERS(dl.h fcntl.h limits.h malloc.h termios.h sys/ioctl.h sys/select.h sys/stat.h sys/time.h sys/prctl.h syslog.h unistd.h utmp.h utmpx.h paths.h db.h db4/db.h pthread.h netinet/in.h arpa/nameser.h arpa/nameser_compat.h syscall.h sys/syscall.h) AC_CHECK_HEADER(resolv.h,AC_DEFINE(HAVE_RESOLV_H, [], [define this if you have the resolv.h header file.]),, [#ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_NETINET_IN_H #include #endif #ifdef HAVE_ARPA_NAMESER_H #include #endif]) dnl Checks for typedefs, structures, and compiler characteristics. AC_C_CONST AC_TYPE_PID_T AC_TYPE_SIZE_T AC_HEADER_TIME dnl defined in acinclude.m4: CIT_STRUCT_TM AC_CACHE_CHECK([for ut_type in struct utmp], ac_cv_struct_ut_type, [AC_TRY_COMPILE([#include #include ], [struct utmp ut; ut.ut_type;], ac_cv_struct_ut_type=yes, ac_cv_struct_ut_type=no)]) if test $ac_cv_struct_ut_type = yes; then AC_DEFINE(HAVE_UT_TYPE, [], [define this if struct utmp has an ut_type member]) fi AC_CACHE_CHECK( [for call semantics from getpwuid_r], ac_cv_call_getpwuid_r, [AC_TRY_COMPILE([#include #include ], [ struct passwd pw, *pwp; char pwbuf[64]; uid_t uid; getpwuid_r(uid, &pw, pwbuf, sizeof(pwbuf), &pwp); ], ac_cv_call_getpwuid_r=yes, ac_cv_call_getpwuid_r=no) ]) if test $ac_cv_call_getpwuid_r = no; then AC_DEFINE(SOLARIS_GETPWUID,[],[do we need to use solaris call syntax?]) AC_DEFINE(F_UID_T, "%ld", [whats the matching format string for uid_t?]) AC_DEFINE(F_PID_T, "%ld", [whats the matching format string for pid_t?]) AC_DEFINE(F_XPID_T, "%lx", [whats the matching format string for xpid_t?]) else AC_DEFINE(F_UID_T, "%d", [whats the matching format string for uid_t?]) AC_DEFINE(F_PID_T, "%d", [whats the matching format string for pid_t?]) AC_DEFINE(F_XPID_T, "%x", [whats the matching format string for xpid_t?]) fi dnl Our own happy little check for the resolver library. case "`uname -a`" in OpenBSD*) echo "we don't need to check for resolv on openbsd" ;; FreeBSD*) echo "we don't need to check for resolv on freeBSD" ;; *) test -f /usr/local/lib/libresolv.a && LDFLAGS="$LDFLAGS -L/usr/local/lib" AC_CHECK_LIB(resolv, res_query, RESOLV="$RESOLV -lresolv", [dnl Have to include resolv.h as res_query is sometimes defined as a macro AC_MSG_CHECKING([for res_query in -lresolv (with resolv.h if present)]) saved_libs="$LIBS" LIBS="-lresolv $LIBS" AC_TRY_LINK([ #ifdef HAVE_RESOLV_H #include #endif], [res_query(0,0,0,0,0)], [AC_MSG_RESULT(yes) have_res_query=yes], [AC_MSG_RESULT(no) AC_MSG_ERROR(libresolv was not found. Citadel requires the resolver library.) ]) ] ) ;; esac AC_CACHE_CHECK([for ut_host in struct utmp], ac_cv_struct_ut_host, [AC_TRY_COMPILE([#include #include ], [struct utmp ut; ut.ut_host;], ac_cv_struct_ut_host=yes, ac_cv_struct_ut_host=no)]) if test $ac_cv_struct_ut_host = yes; then AC_DEFINE(HAVE_UT_HOST, [], [define this if struct utmp has an ut_host member]) fi dnl Checks for library functions. AC_FUNC_GETPGRP AC_PROG_GCC_TRADITIONAL AC_TYPE_SIGNAL AC_FUNC_VPRINTF AC_CHECK_FUNCS(getspnam getutxline mkdir mkfifo mktime rmdir select socket strerror strcasecmp strncasecmp) dnl Now check for pthreads dnl On some platforms, AC_CHECK_FUNC[S] doesn't work for pthreads programs; dnl we need to include pthread.h AC_CACHE_CHECK([for pthread_cancel], ac_cv_func_pthread_cancel, [AC_TRY_LINK([#include ], [ pthread_t thread; /* The GNU C library defines this for functions which it implements to always fail with ENOSYS. Some functions are actually named something starting with __ and the normal name is an alias. */ #if defined (__stub_pthread_cancel) || defined (__stub___pthread_cancel) choke me #else pthread_cancel(thread); #endif], ac_cv_func_pthread_cancel=yes, ac_cv_func_pthread_cancel=no)]) if test "$ac_cv_func_pthread_cancel" = yes; then AC_DEFINE(HAVE_PTHREAD_CANCEL, [], [define this if you have the pthread_cancel() function]) fi AC_CACHE_CHECK([for pthread_create], ac_cv_func_pthread_create, [AC_TRY_LINK([#include ], [ /* The GNU C library defines this for functions which it implements to always fail with ENOSYS. Some functions are actually named something starting with __ and the normal name is an alias. */ #if defined (__stub_pthread_cancel) || defined (__stub___pthread_cancel) choke me #else pthread_create(NULL, NULL, NULL, NULL); #endif], ac_cv_func_pthread_create=yes, ac_cv_func_pthread_create=no)]) if test "$ac_cv_func_pthread_create" = yes; then test "$DATABASE" && TARGETS="server utils" fi AC_CACHE_CHECK([the weather], ac_cv_weather, [ sleep 1 echo $ECHO_N "opening your window... $ECHO_C" >&6 sleep 2 month=`date | cut -f 2 -d ' '` case $month in Dec | Jan | Feb) ac_cv_weather="it's cold!" ;; Mar | Apr) ac_cv_weather="it's wet!" ;; Jul | Aug) ac_cv_weather="it's hot!" ;; Oct | Nov) ac_cv_weather="it's cool" ;; May | Jun | Sep | *) ac_cv_weather="it's fine" ;; esac ]) AC_CACHE_CHECK([under the bed], ac_cv_under_the_bed, [ number=`date | cut -c 19` case $number in 7) ac_cv_under_the_bed="lunatics and monsters found" ;; *) ac_cv_under_the_bed="dust bunnies found" ;; esac ]) STRUCT_UCRED dnl Done! Now write the Makefile and sysdep.h AC_SUBST(RESOLV) AC_SUBST(chkpwd_LIBS) AC_SUBST(TARGETS) AC_SUBST(DATABASE) AC_SUBST(SERVER_LDFLAGS) AC_SUBST(SERVER_LIBS) AC_SUBST(SETUP_LIBS) AC_SUBST(DIFF) AC_SUBST(PATCH) AC_CONFIG_FILES([Makefile]) AC_OUTPUT(database_cleanup.sh po/citadel-setup/Makefile) if test -z "$DATABASE"; then AC_MSG_WARN([No database driver was found. Please install Berkeley DB.]) fi abs_srcdir="`cd $srcdir && pwd`" abs_builddir="`pwd`" if test "$abs_srcdir" != "$abs_builddir"; then ln -sf $abs_srcdir/include $abs_builddir ln -sf $abs_srcdir/Make_sources $abs_builddir ln -sf $abs_srcdir/Make_modules $abs_builddir for i in $abs_srcdir/*.h ; do if test "$abs_srcdir/sysdep.h" != "$i"; then ln -sf $i $abs_builddir fi done for d in `/bin/ls $abs_srcdir/modules/`; do (mkdir -p $abs_builddir/modules/$d) done if test -d "$abs_srcdir/user_modules/"; then for d in `/bin/ls $abs_srcdir/user_modules/`; do (mkdir -p $abs_builddir/user_modules/$d) done fi mkdir -p $abs_builddir/utils mkdir -p $abs_builddir/utillib fi if test -n "$srcdir"; then export srcdir=. fi echo ------------------------------------------------------------------------ echo 'LDAP support: ' $ok_ldap echo 'Character set conversion support:' $ok_iconv echo 'Boehm-Demers-Weiser support: ' $ok_gc dnl echo 'DSpam Scanning support: ' $ok_libdspam echo echo 'Note: if you are not using Linux, make sure you are using GNU make' echo '(gmake) to compile Citadel.' echo citadel-9.01/server_main.c0000644000000000000000000002026712507024051014227 0ustar rootroot/* * citserver's main() function lives here. * * Copyright (c) 1987-2015 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include "citserver.h" #include "svn_revision.h" #include "modules_init.h" #include "config.h" #include "control.h" #include "serv_extensions.h" #include "citadel_dirs.h" #include "user_ops.h" #include "ecrash.h" const char *CitadelServiceUDS="citadel-UDS"; const char *CitadelServiceTCP="citadel-TCP"; void go_threading(void); /* * Here's where it all begins. */ int main(int argc, char **argv) { size_t basesize = 64; char facility[32]; int a; /* General-purpose variables */ struct passwd pw, *pwp = NULL; char pwbuf[SIZ]; int drop_root_perms = 1; int relh=0; int home=0; int dbg=0; char relhome[PATH_MAX]=""; char ctdldir[PATH_MAX]=CTDLDIR; int syslog_facility = LOG_DAEMON; const char *eDebuglist[] = {NULL, NULL}; #ifdef HAVE_RUN_DIR struct stat filestats; #endif #ifdef HAVE_BACKTRACE eCrashParameters params; // eCrashSymbolTable symbol_table; #endif /* initialize the master context */ InitializeMasterCC(); InitializeMasterTSD(); /* parse command-line arguments */ while ((a=getopt(argc, argv, "l:dh:x:t:B:Dr")) != EOF) switch(a) { case 'l': safestrncpy(facility, optarg, sizeof(facility)); syslog_facility = SyslogFacility(facility); break; /* run in the background if -d was specified */ case 'd': running_as_daemon = 1; break; case 'h': relh = optarg[0] != '/'; if (!relh) { safestrncpy(ctdl_home_directory, optarg, sizeof ctdl_home_directory); } else { safestrncpy(relhome, optarg, sizeof relhome); } home=1; break; case 'x': eDebuglist [0] = optarg; break; case 't': /* deprecated */ break; case 'B': /* Basesize */ basesize = atoi(optarg); break; case 'D': dbg = 1; break; /* -r tells the server not to drop root permissions. * Don't use this unless you know what you're doing. */ case 'r': drop_root_perms = 0; break; default: /* any other parameter makes it crash and burn */ fprintf(stderr, "citserver: usage: " "citserver " "[-l LogFacility] " "[-d] [-D] [-r] " "[-h HomeDir]\n" ); exit(1); } StartLibCitadel(basesize); openlog("citserver", ( running_as_daemon ? (LOG_PID) : (LOG_PID | LOG_PERROR) ), syslog_facility ); calc_dirs_n_files(relh, home, relhome, ctdldir, dbg); /* daemonize, if we were asked to */ if (running_as_daemon) { start_daemon(0); drop_root_perms = 1; } #if 0 def HAVE_BACKTRACE bzero(¶ms, sizeof(params)); params.filename = file_pid_paniclog; panic_fd=open(file_pid_paniclog, O_APPEND|O_CREAT|O_DIRECT); params.filep = fopen(file_pid_paniclog, "a+"); params.debugLevel = ECRASH_DEBUG_VERBOSE; params.dumpAllThreads = TRUE; params.useBacktraceSymbols = 1; params.signals[0]=SIGSEGV; params.signals[1]=SIGILL; params.signals[2]=SIGBUS; params.signals[3]=SIGABRT; eCrash_Init(¶ms); eCrash_RegisterThread("MasterThread", 0); #endif /* Tell 'em who's in da house */ syslog(LOG_NOTICE, " "); syslog(LOG_NOTICE, " "); syslog(LOG_NOTICE, "*** Citadel server engine v%d.%02d (build %s) ***", (REV_LEVEL/100), (REV_LEVEL%100), svn_revision()); syslog(LOG_NOTICE, "Copyright (C) 1987-2015 by the Citadel development team."); syslog(LOG_NOTICE, "This program is distributed under the terms of the GNU " "General Public License."); syslog(LOG_NOTICE, " "); syslog(LOG_DEBUG, "Called as: %s", argv[0]); syslog(LOG_INFO, "%s", libcitadel_version_string()); /* Load site-specific configuration */ syslog(LOG_INFO, "Loading citadel.config"); get_config(); /* get_control() MUST MUST MUST be called BEFORE the databases are opened!! */ syslog(LOG_INFO, "Acquiring control record"); get_control(); put_config(); #ifdef HAVE_RUN_DIR /* on some dists rundir gets purged on startup. so we need to recreate it. */ if (stat(ctdl_run_dir, &filestats)==-1){ #ifdef HAVE_GETPWUID_R #ifdef SOLARIS_GETPWUID pwp = getpwuid_r(config.c_ctdluid, &pw, pwbuf, sizeof(pwbuf)); #else // SOLARIS_GETPWUID getpwuid_r(config.c_ctdluid, &pw, pwbuf, sizeof(pwbuf), &pwp); #endif // SOLARIS_GETPWUID #else // HAVE_GETPWUID_R pwp = NULL; #endif // HAVE_GETPWUID_R if ((mkdir(ctdl_run_dir, 0755) != 0) && (errno != EEXIST)) syslog(LOG_EMERG, "unable to create run directory [%s]: %s", ctdl_run_dir, strerror(errno)); if (chown(ctdl_run_dir, config.c_ctdluid, (pwp==NULL)?-1:pw.pw_gid) != 0) syslog(LOG_EMERG, "unable to set the access rights for [%s]: %s", ctdl_run_dir, strerror(errno)); } #endif /* Initialize... */ init_sysdep(); /* * Do non system dependent startup functions. */ master_startup(); /* * Check that the control record is correct and place sensible values if it isn't */ check_control(); /* * Run any upgrade entry points */ syslog(LOG_INFO, "Upgrading modules."); upgrade_modules(); /* * Load the user for the masterCC or create them if they don't exist */ if (CtdlGetUser(&masterCC.user, "SYS_Citadel")) { /* User doesn't exist. We can't use create user here as the user number needs to be 0 */ strcpy (masterCC.user.fullname, "SYS_Citadel") ; CtdlPutUser(&masterCC.user); CtdlGetUser(&masterCC.user, "SYS_Citadel"); /* Just to be safe */ } /* * Bind the server to a Unix-domain socket (user client access) */ CtdlRegisterServiceHook(0, file_citadel_socket, citproto_begin_session, do_command_loop, do_async_loop, CitadelServiceUDS); /* * Bind the server to a Unix-domain socket (admin client access) */ CtdlRegisterServiceHook(0, file_citadel_admin_socket, citproto_begin_admin_session, do_command_loop, do_async_loop, CitadelServiceUDS); chmod(file_citadel_admin_socket, S_IRWXU); /* for your eyes only */ /* * Bind the server to our favorite TCP port (usually 504). */ CtdlRegisterServiceHook(config.c_port_number, NULL, citproto_begin_session, do_command_loop, do_async_loop, CitadelServiceTCP); /* * Load any server-side extensions available here. */ syslog(LOG_INFO, "Initializing server extensions"); initialise_modules(0); eDebuglist[1] = getenv("CITADEL_LOGDEBUG"); CtdlSetDebugLogFacilities(eDebuglist, 2); /* * If we need host auth, start our chkpwd daemon. */ if (config.c_auth_mode == AUTHMODE_HOST) { start_chkpwd_daemon(); } /* * check, whether we're fired up another time after a crash. * if, post an aide message, so the admin has a chance to react. */ checkcrash (); /* * Now that we've bound the sockets, change to the Citadel user id and its * corresponding group ids */ if (drop_root_perms) { cdb_chmod_data(); /* make sure we own our data files */ #ifdef HAVE_GETPWUID_R #ifdef SOLARIS_GETPWUID pwp = getpwuid_r(config.c_ctdluid, &pw, pwbuf, sizeof(pwbuf)); #else // SOLARIS_GETPWUID getpwuid_r(config.c_ctdluid, &pw, pwbuf, sizeof(pwbuf), &pwp); #endif // SOLARIS_GETPWUID #else // HAVE_GETPWUID_R pwp = NULL; #endif // HAVE_GETPWUID_R if (pwp == NULL) syslog(LOG_CRIT, "WARNING: getpwuid(%ld): %s" "Group IDs will be incorrect.\n", (long)CTDLUID, strerror(errno)); else { initgroups(pw.pw_name, pw.pw_gid); if (setgid(pw.pw_gid)) syslog(LOG_CRIT, "setgid(%ld): %s", (long)pw.pw_gid, strerror(errno)); } syslog(LOG_INFO, "Changing uid to %ld", (long)CTDLUID); if (setuid(CTDLUID) != 0) { syslog(LOG_CRIT, "setuid() failed: %s", strerror(errno)); } #if defined (HAVE_SYS_PRCTL_H) && defined (PR_SET_DUMPABLE) prctl(PR_SET_DUMPABLE, 1); #endif } /* We want to check for idle sessions once per minute */ CtdlRegisterSessionHook(terminate_idle_sessions, EVT_TIMER, PRIO_CLEANUP + 1); go_threading(); master_cleanup(exit_signal); return(0); } citadel-9.01/control.c0000644000000000000000000005247712507024051013405 0ustar rootroot/* * This module handles states which are global to the entire server. * * Copyright (c) 1987-2014 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include "ctdl_module.h" #include "config.h" #include "citserver.h" #include "user_ops.h" struct CitControl CitControl; extern struct config config; FILE *control_fp = NULL; long control_highest_user = 0; /* * lock_control - acquire a lock on the control record file. * This keeps multiple citservers from running concurrently. */ void lock_control(void) { #if defined(LOCK_EX) && defined(LOCK_NB) if (flock(fileno(control_fp), (LOCK_EX | LOCK_NB))) { syslog(LOG_EMERG, "citserver: unable to lock %s.\n", file_citadel_control); syslog(LOG_EMERG, "Is another citserver already running?\n"); exit(CTDLEXIT_CONTROL); } #endif } /* * callback to get highest room number when rebuilding control file */ void control_find_highest(struct ctdlroom *qrbuf, void *data) { struct ctdlroom room; struct cdbdata *cdbfr; long *msglist; int num_msgs=0; int c; int room_fixed = 0; int message_fixed = 0; if (qrbuf->QRnumber > CitControl.MMnextroom) { CitControl.MMnextroom = qrbuf->QRnumber; room_fixed = 1; } CtdlGetRoom (&room, qrbuf->QRname); /* Load the message list */ cdbfr = cdb_fetch(CDB_MSGLISTS, &room.QRnumber, sizeof(long)); if (cdbfr != NULL) { msglist = (long *) cdbfr->ptr; num_msgs = cdbfr->len / sizeof(long); } else { return; /* No messages at all? No further action. */ } if (num_msgs>0) { for (c=0; c CitControl.MMhighest) { CitControl.MMhighest = msglist[c]; message_fixed = 1; } } } cdb_free(cdbfr); if (room_fixed) syslog(LOG_INFO, "Control record checking....Fixed room counter\n"); if (message_fixed) syslog(LOG_INFO, "Control record checking....Fixed message count\n"); return; } /* * Callback to get highest user number. */ void control_find_user (struct ctdluser *EachUser, void *out_data) { int user_fixed = 0; if (EachUser->usernum > CitControl.MMnextuser) { CitControl.MMnextuser = EachUser->usernum; user_fixed = 1; } if(user_fixed) syslog(LOG_INFO, "Control record checking....Fixed user count\n"); } /* * get_control - read the control record into memory. */ void get_control(void) { static int already_have_control = 0; int rv = 0; /* * If we already have the control record in memory, there's no point * in reading it from disk again. */ if (already_have_control) return; /* Zero it out. If the control record on disk is missing or short, * the system functions with all control record fields initialized * to zero. */ memset(&CitControl, 0, sizeof(struct CitControl)); if (control_fp == NULL) { control_fp = fopen(file_citadel_control, "rb+"); if (control_fp != NULL) { lock_control(); rv = fchown(fileno(control_fp), config.c_ctdluid, -1); if (rv == -1) syslog(LOG_EMERG, "Failed to adjust ownership of: %s [%s]\n", file_citadel_control, strerror(errno)); rv = fchmod(fileno(control_fp), S_IRUSR|S_IWUSR); if (rv == -1) syslog(LOG_EMERG, "Failed to adjust accessrights of: %s [%s]\n", file_citadel_control, strerror(errno)); } } if (control_fp == NULL) { control_fp = fopen(file_citadel_control, "wb+"); if (control_fp != NULL) { lock_control(); memset(&CitControl, 0, sizeof(struct CitControl)); rv = fchown(fileno(control_fp), config.c_ctdluid, -1); if (rv == -1) syslog(LOG_EMERG, "Failed to adjust ownership of: %s [%s]\n", file_citadel_control, strerror(errno)); rv = fchmod(fileno(control_fp), S_IRUSR|S_IWUSR); if (rv == -1) syslog(LOG_EMERG, "Failed to adjust accessrights of: %s [%s]\n", file_citadel_control, strerror(errno)); rv = fwrite(&CitControl, sizeof(struct CitControl), 1, control_fp); if (rv == -1) syslog(LOG_EMERG, "Failed to write: %s [%s]\n", file_citadel_control, strerror(errno)); rewind(control_fp); } } if (control_fp == NULL) { syslog(LOG_ALERT, "ERROR opening %s: %s\n", file_citadel_control, strerror(errno)); return; } rewind(control_fp); rv = fread(&CitControl, sizeof(struct CitControl), 1, control_fp); if (rv == -1) syslog(LOG_EMERG, "Failed to read Controlfile: %s [%s]\n", file_citadel_control, strerror(errno)); already_have_control = 1; rv = chown(file_citadel_control, config.c_ctdluid, (-1)); if (rv == -1) syslog(LOG_EMERG, "Failed to adjust ownership of: %s [%s]\n", file_citadel_control, strerror(errno)); } /* * put_control - write the control record to disk. */ void put_control(void) { int rv = 0; if (control_fp != NULL) { rewind(control_fp); rv = fwrite(&CitControl, sizeof(struct CitControl), 1, control_fp); if (rv == -1) syslog(LOG_EMERG, "Failed to write: %s [%s]\n", file_citadel_control, strerror(errno)); fflush(control_fp); } } /* * check_control - check the control record has sensible values for message, user and room numbers */ void check_control(void) { syslog(LOG_INFO, "Checking/re-building control record\n"); get_control(); // Find highest room number and message number. CtdlForEachRoom(control_find_highest, NULL); ForEachUser(control_find_user, NULL); put_control(); } /* * release_control - close our fd on exit */ void release_control(void) { if (control_fp != NULL) { fclose(control_fp); } control_fp = NULL; } /* * get_new_message_number() - Obtain a new, unique ID to be used for a message. */ long get_new_message_number(void) { long retval = 0L; begin_critical_section(S_CONTROL); get_control(); retval = ++CitControl.MMhighest; put_control(); end_critical_section(S_CONTROL); return(retval); } /* * CtdlGetCurrentMessageNumber() - Obtain the current highest message number in the system * This provides a quick way to initialise a variable that might be used to indicate * messages that should not be processed. EG. a new Sieve script will use this * to record determine that messages older than this should not be processed. */ long CtdlGetCurrentMessageNumber(void) { long retval = 0L; begin_critical_section(S_CONTROL); get_control(); retval = CitControl.MMhighest; end_critical_section(S_CONTROL); return(retval); } /* * get_new_user_number() - Obtain a new, unique ID to be used for a user. */ long get_new_user_number(void) { long retval = 0L; begin_critical_section(S_CONTROL); get_control(); retval = ++CitControl.MMnextuser; put_control(); end_critical_section(S_CONTROL); return(retval); } /* * get_new_room_number() - Obtain a new, unique ID to be used for a room. */ long get_new_room_number(void) { long retval = 0L; begin_critical_section(S_CONTROL); get_control(); retval = ++CitControl.MMnextroom; put_control(); end_critical_section(S_CONTROL); return(retval); } /* * Get or set global configuration options * * IF YOU ADD OR CHANGE FIELDS HERE, YOU *MUST* DOCUMENT YOUR CHANGES AT: * http://www.citadel.org/doku.php?id=documentation:applicationprotocol * */ void cmd_conf(char *argbuf) { char cmd[16]; char buf[256]; int a; char *confptr; char confname[128]; if (CtdlAccessCheck(ac_aide)) return; extract_token(cmd, argbuf, 0, '|', sizeof cmd); if (!strcasecmp(cmd, "GET")) { cprintf("%d Configuration...\n", LISTING_FOLLOWS); cprintf("%s\n", config.c_nodename); cprintf("%s\n", config.c_fqdn); cprintf("%s\n", config.c_humannode); cprintf("%s\n", config.c_phonenum); cprintf("%d\n", config.c_creataide); cprintf("%d\n", config.c_sleeping); cprintf("%d\n", config.c_initax); cprintf("%d\n", config.c_regiscall); cprintf("%d\n", config.c_twitdetect); cprintf("%s\n", config.c_twitroom); cprintf("%s\n", config.c_moreprompt); cprintf("%d\n", config.c_restrict); cprintf("%s\n", config.c_site_location); cprintf("%s\n", config.c_sysadm); cprintf("%d\n", config.c_maxsessions); cprintf("xxx\n"); /* placeholder -- field no longer in use */ cprintf("%d\n", config.c_userpurge); cprintf("%d\n", config.c_roompurge); cprintf("%s\n", config.c_logpages); cprintf("%d\n", config.c_createax); cprintf("%ld\n", config.c_maxmsglen); cprintf("%d\n", config.c_min_workers); cprintf("%d\n", config.c_max_workers); cprintf("%d\n", config.c_pop3_port); cprintf("%d\n", config.c_smtp_port); cprintf("%d\n", config.c_rfc822_strict_from); cprintf("%d\n", config.c_aide_zap); cprintf("%d\n", config.c_imap_port); cprintf("%ld\n", config.c_net_freq); cprintf("%d\n", config.c_disable_newu); cprintf("1\n"); /* niu */ cprintf("%d\n", config.c_purge_hour); #ifdef HAVE_LDAP cprintf("%s\n", config.c_ldap_host); cprintf("%d\n", config.c_ldap_port); cprintf("%s\n", config.c_ldap_base_dn); cprintf("%s\n", config.c_ldap_bind_dn); cprintf("%s\n", config.c_ldap_bind_pw); #else cprintf("\n"); cprintf("0\n"); cprintf("\n"); cprintf("\n"); cprintf("\n"); #endif cprintf("%s\n", config.c_ip_addr); cprintf("%d\n", config.c_msa_port); cprintf("%d\n", config.c_imaps_port); cprintf("%d\n", config.c_pop3s_port); cprintf("%d\n", config.c_smtps_port); cprintf("%d\n", config.c_enable_fulltext); cprintf("%d\n", config.c_auto_cull); cprintf("1\n"); cprintf("%d\n", config.c_allow_spoofing); cprintf("%d\n", config.c_journal_email); cprintf("%d\n", config.c_journal_pubmsgs); cprintf("%s\n", config.c_journal_dest); cprintf("%s\n", config.c_default_cal_zone); cprintf("%d\n", config.c_pftcpdict_port); cprintf("%d\n", config.c_managesieve_port); cprintf("%d\n", config.c_auth_mode); cprintf("%s\n", config.c_funambol_host); cprintf("%d\n", config.c_funambol_port); cprintf("%s\n", config.c_funambol_source); cprintf("%s\n", config.c_funambol_auth); cprintf("%d\n", config.c_rbl_at_greeting); cprintf("%s\n", config.c_master_user); cprintf("%s\n", config.c_master_pass); cprintf("%s\n", config.c_pager_program); cprintf("%d\n", config.c_imap_keep_from); cprintf("%d\n", config.c_xmpp_c2s_port); cprintf("%d\n", config.c_xmpp_s2s_port); cprintf("%ld\n", config.c_pop3_fetch); cprintf("%ld\n", config.c_pop3_fastest); cprintf("%d\n", config.c_spam_flag_only); cprintf("%d\n", config.c_guest_logins); cprintf("%d\n", config.c_port_number); cprintf("%d\n", config.c_ctdluid); cprintf("%d\n", config.c_nntp_port); cprintf("%d\n", config.c_nntps_port); cprintf("000\n"); } else if (!strcasecmp(cmd, "SET")) { unbuffer_output(); cprintf("%d Send configuration...\n", SEND_LISTING); a = 0; while (client_getln(buf, sizeof buf) >= 0 && strcmp(buf, "000")) { switch (a) { case 0: configlen.c_nodename = safestrncpy(config.c_nodename, buf, sizeof config.c_nodename); break; case 1: configlen.c_fqdn = safestrncpy(config.c_fqdn, buf, sizeof config.c_fqdn); break; case 2: configlen.c_humannode = safestrncpy(config.c_humannode, buf, sizeof config.c_humannode); break; case 3: configlen.c_phonenum = safestrncpy(config.c_phonenum, buf, sizeof config.c_phonenum); break; case 4: config.c_creataide = atoi(buf); break; case 5: config.c_sleeping = atoi(buf); break; case 6: config.c_initax = atoi(buf); if (config.c_initax < 1) config.c_initax = 1; if (config.c_initax > 6) config.c_initax = 6; break; case 7: config.c_regiscall = atoi(buf); if (config.c_regiscall != 0) config.c_regiscall = 1; break; case 8: config.c_twitdetect = atoi(buf); if (config.c_twitdetect != 0) config.c_twitdetect = 1; break; case 9: configlen.c_twitroom = safestrncpy(config.c_twitroom, buf, sizeof config.c_twitroom); break; case 10: configlen.c_moreprompt = safestrncpy(config.c_moreprompt, buf, sizeof config.c_moreprompt); break; case 11: config.c_restrict = atoi(buf); if (config.c_restrict != 0) config.c_restrict = 1; break; case 12: configlen.c_site_location = safestrncpy( config.c_site_location, buf, sizeof config.c_site_location); break; case 13: configlen.c_sysadm = safestrncpy(config.c_sysadm, buf, sizeof config.c_sysadm); break; case 14: config.c_maxsessions = atoi(buf); if (config.c_maxsessions < 0) config.c_maxsessions = 0; break; case 15: /* placeholder -- field no longer in use */ break; case 16: config.c_userpurge = atoi(buf); break; case 17: config.c_roompurge = atoi(buf); break; case 18: configlen.c_logpages = safestrncpy(config.c_logpages, buf, sizeof config.c_logpages); break; case 19: config.c_createax = atoi(buf); if (config.c_createax < 1) config.c_createax = 1; if (config.c_createax > 6) config.c_createax = 6; break; case 20: if (atoi(buf) >= 8192) config.c_maxmsglen = atoi(buf); break; case 21: if (atoi(buf) >= 2) config.c_min_workers = atoi(buf); case 22: if (atoi(buf) >= config.c_min_workers) config.c_max_workers = atoi(buf); case 23: config.c_pop3_port = atoi(buf); break; case 24: config.c_smtp_port = atoi(buf); break; case 25: config.c_rfc822_strict_from = atoi(buf); break; case 26: config.c_aide_zap = atoi(buf); if (config.c_aide_zap != 0) config.c_aide_zap = 1; break; case 27: config.c_imap_port = atoi(buf); break; case 28: config.c_net_freq = atol(buf); break; case 29: config.c_disable_newu = atoi(buf); if (config.c_disable_newu != 0) config.c_disable_newu = 1; break; case 30: /* niu */ break; case 31: if ((config.c_purge_hour >= 0) && (config.c_purge_hour <= 23)) { config.c_purge_hour = atoi(buf); } break; #ifdef HAVE_LDAP case 32: configlen.c_ldap_host = safestrncpy(config.c_ldap_host, buf, sizeof config.c_ldap_host); break; case 33: config.c_ldap_port = atoi(buf); break; case 34: configlen.c_ldap_base_dn = safestrncpy(config.c_ldap_base_dn, buf, sizeof config.c_ldap_base_dn); break; case 35: configlen.c_ldap_bind_dn = safestrncpy(config.c_ldap_bind_dn, buf, sizeof config.c_ldap_bind_dn); break; case 36: configlen.c_ldap_bind_pw = safestrncpy(config.c_ldap_bind_pw, buf, sizeof config.c_ldap_bind_pw); break; #endif case 37: configlen.c_ip_addr = safestrncpy(config.c_ip_addr, buf, sizeof config.c_ip_addr); case 38: config.c_msa_port = atoi(buf); break; case 39: config.c_imaps_port = atoi(buf); break; case 40: config.c_pop3s_port = atoi(buf); break; case 41: config.c_smtps_port = atoi(buf); break; case 42: config.c_enable_fulltext = atoi(buf); break; case 43: config.c_auto_cull = atoi(buf); break; case 44: /* niu */ break; case 45: config.c_allow_spoofing = atoi(buf); break; case 46: config.c_journal_email = atoi(buf); break; case 47: config.c_journal_pubmsgs = atoi(buf); break; case 48: configlen.c_journal_dest = safestrncpy(config.c_journal_dest, buf, sizeof config.c_journal_dest); case 49: configlen.c_default_cal_zone = safestrncpy( config.c_default_cal_zone, buf, sizeof config.c_default_cal_zone); break; case 50: config.c_pftcpdict_port = atoi(buf); break; case 51: config.c_managesieve_port = atoi(buf); break; case 52: config.c_auth_mode = atoi(buf); case 53: configlen.c_funambol_host = safestrncpy( config.c_funambol_host, buf, sizeof config.c_funambol_host); break; case 54: config.c_funambol_port = atoi(buf); break; case 55: configlen.c_funambol_source = safestrncpy( config.c_funambol_source, buf, sizeof config.c_funambol_source); break; case 56: configlen.c_funambol_auth = safestrncpy( config.c_funambol_auth, buf, sizeof config.c_funambol_auth); break; case 57: config.c_rbl_at_greeting = atoi(buf); break; case 58: configlen.c_master_user = safestrncpy( config.c_master_user, buf, sizeof config.c_master_user); break; case 59: configlen.c_master_pass = safestrncpy( config.c_master_pass, buf, sizeof config.c_master_pass); break; case 60: configlen.c_pager_program = safestrncpy( config.c_pager_program, buf, sizeof config.c_pager_program); break; case 61: config.c_imap_keep_from = atoi(buf); break; case 62: config.c_xmpp_c2s_port = atoi(buf); break; case 63: config.c_xmpp_s2s_port = atoi(buf); break; case 64: config.c_pop3_fetch = atol(buf); break; case 65: config.c_pop3_fastest = atol(buf); break; case 66: config.c_spam_flag_only = atoi(buf); break; case 67: config.c_guest_logins = atoi(buf); break; case 68: config.c_port_number = atoi(buf); break; case 69: config.c_ctdluid = atoi(buf); break; case 70: config.c_nntp_port = atoi(buf); break; case 71: config.c_nntps_port = atoi(buf); break; } ++a; } put_config(); snprintf(buf, sizeof buf, "The global system configuration has been edited by %s.\n", (CC->logged_in ? CC->curr_user : "an administrator") ); CtdlAideMessage(buf,"Citadel Configuration Manager Message"); if (!IsEmptyStr(config.c_logpages)) CtdlCreateRoom(config.c_logpages, 3, "", 0, 1, 1, VIEW_BBS); /* If full text indexing has been disabled, invalidate the * index so it doesn't try to use it later. */ if (config.c_enable_fulltext == 0) { CitControl.fulltext_wordbreaker = 0; put_control(); } } else if (!strcasecmp(cmd, "GETSYS")) { extract_token(confname, argbuf, 1, '|', sizeof confname); confptr = CtdlGetSysConfig(confname); if (confptr != NULL) { long len; len = strlen(confptr); cprintf("%d %s\n", LISTING_FOLLOWS, confname); client_write(confptr, len); if ((len > 0) && (confptr[len - 1] != 10)) client_write("\n", 1); cprintf("000\n"); free(confptr); } else { cprintf("%d No such configuration.\n", ERROR + ILLEGAL_VALUE); } } else if (!strcasecmp(cmd, "PUTSYS")) { extract_token(confname, argbuf, 1, '|', sizeof confname); unbuffer_output(); cprintf("%d %s\n", SEND_LISTING, confname); confptr = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0); CtdlPutSysConfig(confname, confptr); free(confptr); } else { cprintf("%d Illegal option(s) specified.\n", ERROR + ILLEGAL_VALUE); } } typedef struct __ConfType { ConstStr Name; long Type; }ConfType; ConfType CfgNames[] = { { {HKEY("localhost") }, 0}, { {HKEY("directory") }, 0}, { {HKEY("smarthost") }, 2}, { {HKEY("fallbackhost") }, 2}, { {HKEY("rbl") }, 3}, { {HKEY("spamassassin") }, 3}, { {HKEY("masqdomain") }, 1}, { {HKEY("clamav") }, 3}, { {HKEY("notify") }, 3}, { {NULL, 0}, 0} }; HashList *CfgNameHash = NULL; void cmd_gvdn(char *argbuf) { const ConfType *pCfg; char *confptr; long min = atol(argbuf); const char *Pos = NULL; const char *PPos = NULL; const char *HKey; long HKLen; StrBuf *Line; StrBuf *Config; StrBuf *Cfg; StrBuf *CfgToken; HashList *List; HashPos *It; void *vptr; List = NewHash(1, NULL); Cfg = NewStrBufPlain(config.c_fqdn, -1); Put(List, SKEY(Cfg), Cfg, HFreeStrBuf); Cfg = NULL; confptr = CtdlGetSysConfig(INTERNETCFG); Config = NewStrBufPlain(confptr, -1); free(confptr); Line = NewStrBufPlain(NULL, StrLength(Config)); CfgToken = NewStrBufPlain(NULL, StrLength(Config)); while (StrBufSipLine(Line, Config, &Pos)) { if (Cfg == NULL) Cfg = NewStrBufPlain(NULL, StrLength(Line)); PPos = NULL; StrBufExtract_NextToken(Cfg, Line, &PPos, '|'); StrBufExtract_NextToken(CfgToken, Line, &PPos, '|'); if (GetHash(CfgNameHash, SKEY(CfgToken), &vptr) && (vptr != NULL)) { pCfg = (ConfType *) vptr; if (pCfg->Type <= min) { Put(List, SKEY(Cfg), Cfg, HFreeStrBuf); Cfg = NULL; } } } cprintf("%d Valid Domains\n", LISTING_FOLLOWS); It = GetNewHashPos(List, 1); while (GetNextHashPos(List, It, &HKLen, &HKey, &vptr)) { cputbuf(vptr); cprintf("\n"); } cprintf("000\n"); DeleteHashPos(&It); DeleteHash(&List); FreeStrBuf(&Cfg); FreeStrBuf(&Line); FreeStrBuf(&CfgToken); FreeStrBuf(&Config); } /*****************************************************************************/ /* MODULE INITIALIZATION STUFF */ /*****************************************************************************/ void control_cleanup(void) { DeleteHash(&CfgNameHash); } CTDL_MODULE_INIT(control) { if (!threading) { int i; CfgNameHash = NewHash(1, NULL); for (i = 0; CfgNames[i].Name.Key != NULL; i++) Put(CfgNameHash, CKEY(CfgNames[i].Name), &CfgNames[i], reference_free_handler); CtdlRegisterProtoHook(cmd_gvdn, "GVDN", "get valid domain names"); CtdlRegisterProtoHook(cmd_conf, "CONF", "get/set system configuration"); CtdlRegisterCleanupHook(control_cleanup); } /* return our id for the Log */ return "control"; } citadel-9.01/config.guess0000755000000000000000000012626012507024051014071 0ustar rootroot#! /bin/sh # Attempt to guess a canonical system name. # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, # 2000, 2001, 2002, 2003, 2004, 2005, 2006 Free Software Foundation, # Inc. timestamp='2006-07-02' # This file is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA # 02110-1301, USA. # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. # Originally written by Per Bothner . # Please send patches to . Submit a context # diff and a properly formatted ChangeLog entry. # # This script attempts to guess a canonical system name similar to # config.sub. If it succeeds, it prints the system name on stdout, and # exits with 0. Otherwise, it exits with 1. # # The plan is that this can be called by configure scripts if you # don't specify an explicit build system type. me=`echo "$0" | sed -e 's,.*/,,'` usage="\ Usage: $0 [OPTION] Output the configuration name of the system \`$me' is run on. Operation modes: -h, --help print this help, then exit -t, --time-stamp print date of last modification, then exit -v, --version print version number, then exit Report bugs and patches to ." version="\ GNU config.guess ($timestamp) Originally written by Per Bothner. Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." help=" Try \`$me --help' for more information." # Parse command line while test $# -gt 0 ; do case $1 in --time-stamp | --time* | -t ) echo "$timestamp" ; exit ;; --version | -v ) echo "$version" ; exit ;; --help | --h* | -h ) echo "$usage"; exit ;; -- ) # Stop option processing shift; break ;; - ) # Use stdin as input. break ;; -* ) echo "$me: invalid option $1$help" >&2 exit 1 ;; * ) break ;; esac done if test $# != 0; then echo "$me: too many arguments$help" >&2 exit 1 fi trap 'exit 1' 1 2 15 # CC_FOR_BUILD -- compiler used by this script. Note that the use of a # compiler to aid in system detection is discouraged as it requires # temporary files to be created and, as you can see below, it is a # headache to deal with in a portable fashion. # Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still # use `HOST_CC' if defined, but it is deprecated. # Portable tmp directory creation inspired by the Autoconf team. set_cc_for_build=' trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ; trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ; : ${TMPDIR=/tmp} ; { tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } || { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } || { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ; dummy=$tmp/dummy ; tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ; case $CC_FOR_BUILD,$HOST_CC,$CC in ,,) echo "int x;" > $dummy.c ; for c in cc gcc c89 c99 ; do if ($c -c -o $dummy.o $dummy.c) >/dev/null 2>&1 ; then CC_FOR_BUILD="$c"; break ; fi ; done ; if test x"$CC_FOR_BUILD" = x ; then CC_FOR_BUILD=no_compiler_found ; fi ;; ,,*) CC_FOR_BUILD=$CC ;; ,*,*) CC_FOR_BUILD=$HOST_CC ;; esac ; set_cc_for_build= ;' # This is needed to find uname on a Pyramid OSx when run in the BSD universe. # (ghazi@noc.rutgers.edu 1994-08-24) if (test -f /.attbin/uname) >/dev/null 2>&1 ; then PATH=$PATH:/.attbin ; export PATH fi UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown # Note: order is significant - the case branches are not exclusive. case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in *:NetBSD:*:*) # NetBSD (nbsd) targets should (where applicable) match one or # more of the tupples: *-*-netbsdelf*, *-*-netbsdaout*, # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently # switched to ELF, *-*-netbsd* would select the old # object file format. This provides both forward # compatibility and a consistent mechanism for selecting the # object file format. # # Note: NetBSD doesn't particularly care about the vendor # portion of the name. We always set it to "unknown". sysctl="sysctl -n hw.machine_arch" UNAME_MACHINE_ARCH=`(/sbin/$sysctl 2>/dev/null || \ /usr/sbin/$sysctl 2>/dev/null || echo unknown)` case "${UNAME_MACHINE_ARCH}" in armeb) machine=armeb-unknown ;; arm*) machine=arm-unknown ;; sh3el) machine=shl-unknown ;; sh3eb) machine=sh-unknown ;; *) machine=${UNAME_MACHINE_ARCH}-unknown ;; esac # The Operating System including object format, if it has switched # to ELF recently, or will in the future. case "${UNAME_MACHINE_ARCH}" in arm*|i386|m68k|ns32k|sh3*|sparc|vax) eval $set_cc_for_build if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ | grep __ELF__ >/dev/null then # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). # Return netbsd for either. FIX? os=netbsd else os=netbsdelf fi ;; *) os=netbsd ;; esac # The OS release # Debian GNU/NetBSD machines have a different userland, and # thus, need a distinct triplet. However, they do not need # kernel version information, so it can be replaced with a # suitable tag, in the style of linux-gnu. case "${UNAME_VERSION}" in Debian*) release='-gnu' ;; *) release=`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` ;; esac # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: # contains redundant information, the shorter form: # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. echo "${machine}-${os}${release}" exit ;; *:OpenBSD:*:*) UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'` echo ${UNAME_MACHINE_ARCH}-unknown-openbsd${UNAME_RELEASE} exit ;; *:ekkoBSD:*:*) echo ${UNAME_MACHINE}-unknown-ekkobsd${UNAME_RELEASE} exit ;; *:SolidBSD:*:*) echo ${UNAME_MACHINE}-unknown-solidbsd${UNAME_RELEASE} exit ;; macppc:MirBSD:*:*) echo powerpc-unknown-mirbsd${UNAME_RELEASE} exit ;; *:MirBSD:*:*) echo ${UNAME_MACHINE}-unknown-mirbsd${UNAME_RELEASE} exit ;; alpha:OSF1:*:*) case $UNAME_RELEASE in *4.0) UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` ;; *5.*) UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'` ;; esac # According to Compaq, /usr/sbin/psrinfo has been available on # OSF/1 and Tru64 systems produced since 1995. I hope that # covers most systems running today. This code pipes the CPU # types through head -n 1, so we only detect the type of CPU 0. ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1` case "$ALPHA_CPU_TYPE" in "EV4 (21064)") UNAME_MACHINE="alpha" ;; "EV4.5 (21064)") UNAME_MACHINE="alpha" ;; "LCA4 (21066/21068)") UNAME_MACHINE="alpha" ;; "EV5 (21164)") UNAME_MACHINE="alphaev5" ;; "EV5.6 (21164A)") UNAME_MACHINE="alphaev56" ;; "EV5.6 (21164PC)") UNAME_MACHINE="alphapca56" ;; "EV5.7 (21164PC)") UNAME_MACHINE="alphapca57" ;; "EV6 (21264)") UNAME_MACHINE="alphaev6" ;; "EV6.7 (21264A)") UNAME_MACHINE="alphaev67" ;; "EV6.8CB (21264C)") UNAME_MACHINE="alphaev68" ;; "EV6.8AL (21264B)") UNAME_MACHINE="alphaev68" ;; "EV6.8CX (21264D)") UNAME_MACHINE="alphaev68" ;; "EV6.9A (21264/EV69A)") UNAME_MACHINE="alphaev69" ;; "EV7 (21364)") UNAME_MACHINE="alphaev7" ;; "EV7.9 (21364A)") UNAME_MACHINE="alphaev79" ;; esac # A Pn.n version is a patched version. # A Vn.n version is a released version. # A Tn.n version is a released field test version. # A Xn.n version is an unreleased experimental baselevel. # 1.2 uses "1.2" for uname -r. echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[PVTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` exit ;; Alpha\ *:Windows_NT*:*) # How do we know it's Interix rather than the generic POSIX subsystem? # Should we change UNAME_MACHINE based on the output of uname instead # of the specific Alpha model? echo alpha-pc-interix exit ;; 21064:Windows_NT:50:3) echo alpha-dec-winnt3.5 exit ;; Amiga*:UNIX_System_V:4.0:*) echo m68k-unknown-sysv4 exit ;; *:[Aa]miga[Oo][Ss]:*:*) echo ${UNAME_MACHINE}-unknown-amigaos exit ;; *:[Mm]orph[Oo][Ss]:*:*) echo ${UNAME_MACHINE}-unknown-morphos exit ;; *:OS/390:*:*) echo i370-ibm-openedition exit ;; *:z/VM:*:*) echo s390-ibm-zvmoe exit ;; *:OS400:*:*) echo powerpc-ibm-os400 exit ;; arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) echo arm-acorn-riscix${UNAME_RELEASE} exit ;; arm:riscos:*:*|arm:RISCOS:*:*) echo arm-unknown-riscos exit ;; SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) echo hppa1.1-hitachi-hiuxmpp exit ;; Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. if test "`(/bin/universe) 2>/dev/null`" = att ; then echo pyramid-pyramid-sysv3 else echo pyramid-pyramid-bsd fi exit ;; NILE*:*:*:dcosx) echo pyramid-pyramid-svr4 exit ;; DRS?6000:unix:4.0:6*) echo sparc-icl-nx6 exit ;; DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*) case `/usr/bin/uname -p` in sparc) echo sparc-icl-nx7; exit ;; esac ;; sun4H:SunOS:5.*:*) echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit ;; sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit ;; i86pc:SunOS:5.*:*) echo i386-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit ;; sun4*:SunOS:6*:*) # According to config.sub, this is the proper way to canonicalize # SunOS6. Hard to guess exactly what SunOS6 will be like, but # it's likely to be more like Solaris than SunOS4. echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit ;; sun4*:SunOS:*:*) case "`/usr/bin/arch -k`" in Series*|S4*) UNAME_RELEASE=`uname -v` ;; esac # Japanese Language versions have a version number like `4.1.3-JL'. echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'` exit ;; sun3*:SunOS:*:*) echo m68k-sun-sunos${UNAME_RELEASE} exit ;; sun*:*:4.2BSD:*) UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3 case "`/bin/arch`" in sun3) echo m68k-sun-sunos${UNAME_RELEASE} ;; sun4) echo sparc-sun-sunos${UNAME_RELEASE} ;; esac exit ;; aushp:SunOS:*:*) echo sparc-auspex-sunos${UNAME_RELEASE} exit ;; # The situation for MiNT is a little confusing. The machine name # can be virtually everything (everything which is not # "atarist" or "atariste" at least should have a processor # > m68000). The system name ranges from "MiNT" over "FreeMiNT" # to the lowercase version "mint" (or "freemint"). Finally # the system name "TOS" denotes a system which is actually not # MiNT. But MiNT is downward compatible to TOS, so this should # be no problem. atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) echo m68k-atari-mint${UNAME_RELEASE} exit ;; atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) echo m68k-atari-mint${UNAME_RELEASE} exit ;; *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) echo m68k-atari-mint${UNAME_RELEASE} exit ;; milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) echo m68k-milan-mint${UNAME_RELEASE} exit ;; hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) echo m68k-hades-mint${UNAME_RELEASE} exit ;; *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) echo m68k-unknown-mint${UNAME_RELEASE} exit ;; m68k:machten:*:*) echo m68k-apple-machten${UNAME_RELEASE} exit ;; powerpc:machten:*:*) echo powerpc-apple-machten${UNAME_RELEASE} exit ;; RISC*:Mach:*:*) echo mips-dec-mach_bsd4.3 exit ;; RISC*:ULTRIX:*:*) echo mips-dec-ultrix${UNAME_RELEASE} exit ;; VAX*:ULTRIX*:*:*) echo vax-dec-ultrix${UNAME_RELEASE} exit ;; 2020:CLIX:*:* | 2430:CLIX:*:*) echo clipper-intergraph-clix${UNAME_RELEASE} exit ;; mips:*:*:UMIPS | mips:*:*:RISCos) eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #ifdef __cplusplus #include /* for printf() prototype */ int main (int argc, char *argv[]) { #else int main (argc, argv) int argc; char *argv[]; { #endif #if defined (host_mips) && defined (MIPSEB) #if defined (SYSTYPE_SYSV) printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0); #endif #if defined (SYSTYPE_SVR4) printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0); #endif #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0); #endif #endif exit (-1); } EOF $CC_FOR_BUILD -o $dummy $dummy.c && dummyarg=`echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` && SYSTEM_NAME=`$dummy $dummyarg` && { echo "$SYSTEM_NAME"; exit; } echo mips-mips-riscos${UNAME_RELEASE} exit ;; Motorola:PowerMAX_OS:*:*) echo powerpc-motorola-powermax exit ;; Motorola:*:4.3:PL8-*) echo powerpc-harris-powermax exit ;; Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) echo powerpc-harris-powermax exit ;; Night_Hawk:Power_UNIX:*:*) echo powerpc-harris-powerunix exit ;; m88k:CX/UX:7*:*) echo m88k-harris-cxux7 exit ;; m88k:*:4*:R4*) echo m88k-motorola-sysv4 exit ;; m88k:*:3*:R3*) echo m88k-motorola-sysv3 exit ;; AViiON:dgux:*:*) # DG/UX returns AViiON for all architectures UNAME_PROCESSOR=`/usr/bin/uname -p` if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ] then if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \ [ ${TARGET_BINARY_INTERFACE}x = x ] then echo m88k-dg-dgux${UNAME_RELEASE} else echo m88k-dg-dguxbcs${UNAME_RELEASE} fi else echo i586-dg-dgux${UNAME_RELEASE} fi exit ;; M88*:DolphinOS:*:*) # DolphinOS (SVR3) echo m88k-dolphin-sysv3 exit ;; M88*:*:R3*:*) # Delta 88k system running SVR3 echo m88k-motorola-sysv3 exit ;; XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) echo m88k-tektronix-sysv3 exit ;; Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) echo m68k-tektronix-bsd exit ;; *:IRIX*:*:*) echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'` exit ;; ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id exit ;; # Note that: echo "'`uname -s`'" gives 'AIX ' i*86:AIX:*:*) echo i386-ibm-aix exit ;; ia64:AIX:*:*) if [ -x /usr/bin/oslevel ] ; then IBM_REV=`/usr/bin/oslevel` else IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} fi echo ${UNAME_MACHINE}-ibm-aix${IBM_REV} exit ;; *:AIX:2:3) if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #include main() { if (!__power_pc()) exit(1); puts("powerpc-ibm-aix3.2.5"); exit(0); } EOF if $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy` then echo "$SYSTEM_NAME" else echo rs6000-ibm-aix3.2.5 fi elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then echo rs6000-ibm-aix3.2.4 else echo rs6000-ibm-aix3.2 fi exit ;; *:AIX:*:[45]) IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then IBM_ARCH=rs6000 else IBM_ARCH=powerpc fi if [ -x /usr/bin/oslevel ] ; then IBM_REV=`/usr/bin/oslevel` else IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} fi echo ${IBM_ARCH}-ibm-aix${IBM_REV} exit ;; *:AIX:*:*) echo rs6000-ibm-aix exit ;; ibmrt:4.4BSD:*|romp-ibm:BSD:*) echo romp-ibm-bsd4.4 exit ;; ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and echo romp-ibm-bsd${UNAME_RELEASE} # 4.3 with uname added to exit ;; # report: romp-ibm BSD 4.3 *:BOSX:*:*) echo rs6000-bull-bosx exit ;; DPX/2?00:B.O.S.:*:*) echo m68k-bull-sysv3 exit ;; 9000/[34]??:4.3bsd:1.*:*) echo m68k-hp-bsd exit ;; hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) echo m68k-hp-bsd4.4 exit ;; 9000/[34678]??:HP-UX:*:*) HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` case "${UNAME_MACHINE}" in 9000/31? ) HP_ARCH=m68000 ;; 9000/[34]?? ) HP_ARCH=m68k ;; 9000/[678][0-9][0-9]) if [ -x /usr/bin/getconf ]; then sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` case "${sc_cpu_version}" in 523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0 528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1 532) # CPU_PA_RISC2_0 case "${sc_kernel_bits}" in 32) HP_ARCH="hppa2.0n" ;; 64) HP_ARCH="hppa2.0w" ;; '') HP_ARCH="hppa2.0" ;; # HP-UX 10.20 esac ;; esac fi if [ "${HP_ARCH}" = "" ]; then eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #define _HPUX_SOURCE #include #include int main () { #if defined(_SC_KERNEL_BITS) long bits = sysconf(_SC_KERNEL_BITS); #endif long cpu = sysconf (_SC_CPU_VERSION); switch (cpu) { case CPU_PA_RISC1_0: puts ("hppa1.0"); break; case CPU_PA_RISC1_1: puts ("hppa1.1"); break; case CPU_PA_RISC2_0: #if defined(_SC_KERNEL_BITS) switch (bits) { case 64: puts ("hppa2.0w"); break; case 32: puts ("hppa2.0n"); break; default: puts ("hppa2.0"); break; } break; #else /* !defined(_SC_KERNEL_BITS) */ puts ("hppa2.0"); break; #endif default: puts ("hppa1.0"); break; } exit (0); } EOF (CCOPTS= $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy` test -z "$HP_ARCH" && HP_ARCH=hppa fi ;; esac if [ ${HP_ARCH} = "hppa2.0w" ] then eval $set_cc_for_build # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating # 32-bit code. hppa64-hp-hpux* has the same kernel and a compiler # generating 64-bit code. GNU and HP use different nomenclature: # # $ CC_FOR_BUILD=cc ./config.guess # => hppa2.0w-hp-hpux11.23 # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess # => hppa64-hp-hpux11.23 if echo __LP64__ | (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | grep __LP64__ >/dev/null then HP_ARCH="hppa2.0w" else HP_ARCH="hppa64" fi fi echo ${HP_ARCH}-hp-hpux${HPUX_REV} exit ;; ia64:HP-UX:*:*) HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` echo ia64-hp-hpux${HPUX_REV} exit ;; 3050*:HI-UX:*:*) eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #include int main () { long cpu = sysconf (_SC_CPU_VERSION); /* The order matters, because CPU_IS_HP_MC68K erroneously returns true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct results, however. */ if (CPU_IS_PA_RISC (cpu)) { switch (cpu) { case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; default: puts ("hppa-hitachi-hiuxwe2"); break; } } else if (CPU_IS_HP_MC68K (cpu)) puts ("m68k-hitachi-hiuxwe2"); else puts ("unknown-hitachi-hiuxwe2"); exit (0); } EOF $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy` && { echo "$SYSTEM_NAME"; exit; } echo unknown-hitachi-hiuxwe2 exit ;; 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* ) echo hppa1.1-hp-bsd exit ;; 9000/8??:4.3bsd:*:*) echo hppa1.0-hp-bsd exit ;; *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) echo hppa1.0-hp-mpeix exit ;; hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* ) echo hppa1.1-hp-osf exit ;; hp8??:OSF1:*:*) echo hppa1.0-hp-osf exit ;; i*86:OSF1:*:*) if [ -x /usr/sbin/sysversion ] ; then echo ${UNAME_MACHINE}-unknown-osf1mk else echo ${UNAME_MACHINE}-unknown-osf1 fi exit ;; parisc*:Lites*:*:*) echo hppa1.1-hp-lites exit ;; C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) echo c1-convex-bsd exit ;; C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) if getsysinfo -f scalar_acc then echo c32-convex-bsd else echo c2-convex-bsd fi exit ;; C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) echo c34-convex-bsd exit ;; C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) echo c38-convex-bsd exit ;; C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) echo c4-convex-bsd exit ;; CRAY*Y-MP:*:*:*) echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit ;; CRAY*[A-Z]90:*:*:*) echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \ | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ -e 's/\.[^.]*$/.X/' exit ;; CRAY*TS:*:*:*) echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit ;; CRAY*T3E:*:*:*) echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit ;; CRAY*SV1:*:*:*) echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit ;; *:UNICOS/mp:*:*) echo craynv-cray-unicosmp${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit ;; F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'` echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" exit ;; 5000:UNIX_System_V:4.*:*) FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` FUJITSU_REL=`echo ${UNAME_RELEASE} | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/ /_/'` echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" exit ;; i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE} exit ;; sparc*:BSD/OS:*:*) echo sparc-unknown-bsdi${UNAME_RELEASE} exit ;; *:BSD/OS:*:*) echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE} exit ;; *:FreeBSD:*:*) case ${UNAME_MACHINE} in pc98) echo i386-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;; amd64) echo x86_64-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;; *) echo ${UNAME_MACHINE}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;; esac exit ;; i*:CYGWIN*:*) echo ${UNAME_MACHINE}-pc-cygwin exit ;; i*:MINGW*:*) echo ${UNAME_MACHINE}-pc-mingw32 exit ;; i*:windows32*:*) # uname -m includes "-pc" on this system. echo ${UNAME_MACHINE}-mingw32 exit ;; i*:PW*:*) echo ${UNAME_MACHINE}-pc-pw32 exit ;; x86:Interix*:[3456]*) echo i586-pc-interix${UNAME_RELEASE} exit ;; EM64T:Interix*:[3456]*) echo x86_64-unknown-interix${UNAME_RELEASE} exit ;; [345]86:Windows_95:* | [345]86:Windows_98:* | [345]86:Windows_NT:*) echo i${UNAME_MACHINE}-pc-mks exit ;; i*:Windows_NT*:* | Pentium*:Windows_NT*:*) # How do we know it's Interix rather than the generic POSIX subsystem? # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we # UNAME_MACHINE based on the output of uname instead of i386? echo i586-pc-interix exit ;; i*:UWIN*:*) echo ${UNAME_MACHINE}-pc-uwin exit ;; amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*) echo x86_64-unknown-cygwin exit ;; p*:CYGWIN*:*) echo powerpcle-unknown-cygwin exit ;; prep*:SunOS:5.*:*) echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit ;; *:GNU:*:*) # the GNU system echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-gnu`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'` exit ;; *:GNU/*:*:*) # other systems with GNU libc and userland echo ${UNAME_MACHINE}-unknown-`echo ${UNAME_SYSTEM} | sed 's,^[^/]*/,,' | tr '[A-Z]' '[a-z]'``echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`-gnu exit ;; i*86:Minix:*:*) echo ${UNAME_MACHINE}-pc-minix exit ;; arm*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit ;; avr32*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit ;; cris:Linux:*:*) echo cris-axis-linux-gnu exit ;; crisv32:Linux:*:*) echo crisv32-axis-linux-gnu exit ;; frv:Linux:*:*) echo frv-unknown-linux-gnu exit ;; ia64:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit ;; m32r*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit ;; m68*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit ;; mips:Linux:*:*) eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #undef CPU #undef mips #undef mipsel #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) CPU=mipsel #else #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) CPU=mips #else CPU= #endif #endif EOF eval "`$CC_FOR_BUILD -E $dummy.c 2>/dev/null | sed -n ' /^CPU/{ s: ::g p }'`" test x"${CPU}" != x && { echo "${CPU}-unknown-linux-gnu"; exit; } ;; mips64:Linux:*:*) eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #undef CPU #undef mips64 #undef mips64el #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) CPU=mips64el #else #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) CPU=mips64 #else CPU= #endif #endif EOF eval "`$CC_FOR_BUILD -E $dummy.c 2>/dev/null | sed -n ' /^CPU/{ s: ::g p }'`" test x"${CPU}" != x && { echo "${CPU}-unknown-linux-gnu"; exit; } ;; or32:Linux:*:*) echo or32-unknown-linux-gnu exit ;; ppc:Linux:*:*) echo powerpc-unknown-linux-gnu exit ;; ppc64:Linux:*:*) echo powerpc64-unknown-linux-gnu exit ;; alpha:Linux:*:*) case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in EV5) UNAME_MACHINE=alphaev5 ;; EV56) UNAME_MACHINE=alphaev56 ;; PCA56) UNAME_MACHINE=alphapca56 ;; PCA57) UNAME_MACHINE=alphapca56 ;; EV6) UNAME_MACHINE=alphaev6 ;; EV67) UNAME_MACHINE=alphaev67 ;; EV68*) UNAME_MACHINE=alphaev68 ;; esac objdump --private-headers /bin/sh | grep ld.so.1 >/dev/null if test "$?" = 0 ; then LIBC="libc1" ; else LIBC="" ; fi echo ${UNAME_MACHINE}-unknown-linux-gnu${LIBC} exit ;; parisc:Linux:*:* | hppa:Linux:*:*) # Look for CPU level case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in PA7*) echo hppa1.1-unknown-linux-gnu ;; PA8*) echo hppa2.0-unknown-linux-gnu ;; *) echo hppa-unknown-linux-gnu ;; esac exit ;; parisc64:Linux:*:* | hppa64:Linux:*:*) echo hppa64-unknown-linux-gnu exit ;; s390:Linux:*:* | s390x:Linux:*:*) echo ${UNAME_MACHINE}-ibm-linux exit ;; sh64*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit ;; sh*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit ;; sparc:Linux:*:* | sparc64:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit ;; vax:Linux:*:*) echo ${UNAME_MACHINE}-dec-linux-gnu exit ;; x86_64:Linux:*:*) echo x86_64-unknown-linux-gnu exit ;; i*86:Linux:*:*) # The BFD linker knows what the default object file format is, so # first see if it will tell us. cd to the root directory to prevent # problems with other programs or directories called `ld' in the path. # Set LC_ALL=C to ensure ld outputs messages in English. ld_supported_targets=`cd /; LC_ALL=C ld --help 2>&1 \ | sed -ne '/supported targets:/!d s/[ ][ ]*/ /g s/.*supported targets: *// s/ .*// p'` case "$ld_supported_targets" in elf32-i386) TENTATIVE="${UNAME_MACHINE}-pc-linux-gnu" ;; a.out-i386-linux) echo "${UNAME_MACHINE}-pc-linux-gnuaout" exit ;; coff-i386) echo "${UNAME_MACHINE}-pc-linux-gnucoff" exit ;; "") # Either a pre-BFD a.out linker (linux-gnuoldld) or # one that does not give us useful --help. echo "${UNAME_MACHINE}-pc-linux-gnuoldld" exit ;; esac # Determine whether the default compiler is a.out or elf eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #include #ifdef __ELF__ # ifdef __GLIBC__ # if __GLIBC__ >= 2 LIBC=gnu # else LIBC=gnulibc1 # endif # else LIBC=gnulibc1 # endif #else #if defined(__INTEL_COMPILER) || defined(__PGI) || defined(__SUNPRO_C) || defined(__SUNPRO_CC) LIBC=gnu #else LIBC=gnuaout #endif #endif #ifdef __dietlibc__ LIBC=dietlibc #endif EOF eval "`$CC_FOR_BUILD -E $dummy.c 2>/dev/null | sed -n ' /^LIBC/{ s: ::g p }'`" test x"${LIBC}" != x && { echo "${UNAME_MACHINE}-pc-linux-${LIBC}" exit } test x"${TENTATIVE}" != x && { echo "${TENTATIVE}"; exit; } ;; i*86:DYNIX/ptx:4*:*) # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. # earlier versions are messed up and put the nodename in both # sysname and nodename. echo i386-sequent-sysv4 exit ;; i*86:UNIX_SV:4.2MP:2.*) # Unixware is an offshoot of SVR4, but it has its own version # number series starting with 2... # I am not positive that other SVR4 systems won't match this, # I just have to hope. -- rms. # Use sysv4.2uw... so that sysv4* matches it. echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION} exit ;; i*86:OS/2:*:*) # If we were able to find `uname', then EMX Unix compatibility # is probably installed. echo ${UNAME_MACHINE}-pc-os2-emx exit ;; i*86:XTS-300:*:STOP) echo ${UNAME_MACHINE}-unknown-stop exit ;; i*86:atheos:*:*) echo ${UNAME_MACHINE}-unknown-atheos exit ;; i*86:syllable:*:*) echo ${UNAME_MACHINE}-pc-syllable exit ;; i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.0*:*) echo i386-unknown-lynxos${UNAME_RELEASE} exit ;; i*86:*DOS:*:*) echo ${UNAME_MACHINE}-pc-msdosdjgpp exit ;; i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*) UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'` if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL} else echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL} fi exit ;; i*86:*:5:[678]*) # UnixWare 7.x, OpenUNIX and OpenServer 6. case `/bin/uname -X | grep "^Machine"` in *486*) UNAME_MACHINE=i486 ;; *Pentium) UNAME_MACHINE=i586 ;; *Pent*|*Celeron) UNAME_MACHINE=i686 ;; esac echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION} exit ;; i*86:*:3.2:*) if test -f /usr/options/cb.name; then UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ && UNAME_MACHINE=i586 (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ && UNAME_MACHINE=i686 (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ && UNAME_MACHINE=i686 echo ${UNAME_MACHINE}-pc-sco$UNAME_REL else echo ${UNAME_MACHINE}-pc-sysv32 fi exit ;; pc:*:*:*) # Left here for compatibility: # uname -m prints for DJGPP always 'pc', but it prints nothing about # the processor, so we play safe by assuming i386. echo i386-pc-msdosdjgpp exit ;; Intel:Mach:3*:*) echo i386-pc-mach3 exit ;; paragon:*:*:*) echo i860-intel-osf1 exit ;; i860:*:4.*:*) # i860-SVR4 if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4 else # Add other i860-SVR4 vendors below as they are discovered. echo i860-unknown-sysv${UNAME_RELEASE} # Unknown i860-SVR4 fi exit ;; mini*:CTIX:SYS*5:*) # "miniframe" echo m68010-convergent-sysv exit ;; mc68k:UNIX:SYSTEM5:3.51m) echo m68k-convergent-sysv exit ;; M680?0:D-NIX:5.3:*) echo m68k-diab-dnix exit ;; M68*:*:R3V[5678]*:*) test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;; 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0) OS_REL='' test -r /etc/.relid \ && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && { echo i486-ncr-sysv4.3${OS_REL}; exit; } /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ && { echo i586-ncr-sysv4.3${OS_REL}; exit; } ;; 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && { echo i486-ncr-sysv4; exit; } ;; m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) echo m68k-unknown-lynxos${UNAME_RELEASE} exit ;; mc68030:UNIX_System_V:4.*:*) echo m68k-atari-sysv4 exit ;; TSUNAMI:LynxOS:2.*:*) echo sparc-unknown-lynxos${UNAME_RELEASE} exit ;; rs6000:LynxOS:2.*:*) echo rs6000-unknown-lynxos${UNAME_RELEASE} exit ;; PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.0*:*) echo powerpc-unknown-lynxos${UNAME_RELEASE} exit ;; SM[BE]S:UNIX_SV:*:*) echo mips-dde-sysv${UNAME_RELEASE} exit ;; RM*:ReliantUNIX-*:*:*) echo mips-sni-sysv4 exit ;; RM*:SINIX-*:*:*) echo mips-sni-sysv4 exit ;; *:SINIX-*:*:*) if uname -p 2>/dev/null >/dev/null ; then UNAME_MACHINE=`(uname -p) 2>/dev/null` echo ${UNAME_MACHINE}-sni-sysv4 else echo ns32k-sni-sysv fi exit ;; PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort # says echo i586-unisys-sysv4 exit ;; *:UNIX_System_V:4*:FTX*) # From Gerald Hewes . # How about differentiating between stratus architectures? -djm echo hppa1.1-stratus-sysv4 exit ;; *:*:*:FTX*) # From seanf@swdc.stratus.com. echo i860-stratus-sysv4 exit ;; i*86:VOS:*:*) # From Paul.Green@stratus.com. echo ${UNAME_MACHINE}-stratus-vos exit ;; *:VOS:*:*) # From Paul.Green@stratus.com. echo hppa1.1-stratus-vos exit ;; mc68*:A/UX:*:*) echo m68k-apple-aux${UNAME_RELEASE} exit ;; news*:NEWS-OS:6*:*) echo mips-sony-newsos6 exit ;; R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) if [ -d /usr/nec ]; then echo mips-nec-sysv${UNAME_RELEASE} else echo mips-unknown-sysv${UNAME_RELEASE} fi exit ;; BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. echo powerpc-be-beos exit ;; BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. echo powerpc-apple-beos exit ;; BePC:BeOS:*:*) # BeOS running on Intel PC compatible. echo i586-pc-beos exit ;; SX-4:SUPER-UX:*:*) echo sx4-nec-superux${UNAME_RELEASE} exit ;; SX-5:SUPER-UX:*:*) echo sx5-nec-superux${UNAME_RELEASE} exit ;; SX-6:SUPER-UX:*:*) echo sx6-nec-superux${UNAME_RELEASE} exit ;; Power*:Rhapsody:*:*) echo powerpc-apple-rhapsody${UNAME_RELEASE} exit ;; *:Rhapsody:*:*) echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE} exit ;; *:Darwin:*:*) UNAME_PROCESSOR=`uname -p` || UNAME_PROCESSOR=unknown case $UNAME_PROCESSOR in unknown) UNAME_PROCESSOR=powerpc ;; esac echo ${UNAME_PROCESSOR}-apple-darwin${UNAME_RELEASE} exit ;; *:procnto*:*:* | *:QNX:[0123456789]*:*) UNAME_PROCESSOR=`uname -p` if test "$UNAME_PROCESSOR" = "x86"; then UNAME_PROCESSOR=i386 UNAME_MACHINE=pc fi echo ${UNAME_PROCESSOR}-${UNAME_MACHINE}-nto-qnx${UNAME_RELEASE} exit ;; *:QNX:*:4*) echo i386-pc-qnx exit ;; NSE-?:NONSTOP_KERNEL:*:*) echo nse-tandem-nsk${UNAME_RELEASE} exit ;; NSR-?:NONSTOP_KERNEL:*:*) echo nsr-tandem-nsk${UNAME_RELEASE} exit ;; *:NonStop-UX:*:*) echo mips-compaq-nonstopux exit ;; BS2000:POSIX*:*:*) echo bs2000-siemens-sysv exit ;; DS/*:UNIX_System_V:*:*) echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE} exit ;; *:Plan9:*:*) # "uname -m" is not consistent, so use $cputype instead. 386 # is converted to i386 for consistency with other x86 # operating systems. if test "$cputype" = "386"; then UNAME_MACHINE=i386 else UNAME_MACHINE="$cputype" fi echo ${UNAME_MACHINE}-unknown-plan9 exit ;; *:TOPS-10:*:*) echo pdp10-unknown-tops10 exit ;; *:TENEX:*:*) echo pdp10-unknown-tenex exit ;; KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) echo pdp10-dec-tops20 exit ;; XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) echo pdp10-xkl-tops20 exit ;; *:TOPS-20:*:*) echo pdp10-unknown-tops20 exit ;; *:ITS:*:*) echo pdp10-unknown-its exit ;; SEI:*:*:SEIUX) echo mips-sei-seiux${UNAME_RELEASE} exit ;; *:DragonFly:*:*) echo ${UNAME_MACHINE}-unknown-dragonfly`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` exit ;; *:*VMS:*:*) UNAME_MACHINE=`(uname -p) 2>/dev/null` case "${UNAME_MACHINE}" in A*) echo alpha-dec-vms ; exit ;; I*) echo ia64-dec-vms ; exit ;; V*) echo vax-dec-vms ; exit ;; esac ;; *:XENIX:*:SysV) echo i386-pc-xenix exit ;; i*86:skyos:*:*) echo ${UNAME_MACHINE}-pc-skyos`echo ${UNAME_RELEASE}` | sed -e 's/ .*$//' exit ;; i*86:rdos:*:*) echo ${UNAME_MACHINE}-pc-rdos exit ;; esac #echo '(No uname command or uname output not recognized.)' 1>&2 #echo "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" 1>&2 eval $set_cc_for_build cat >$dummy.c < # include #endif main () { #if defined (sony) #if defined (MIPSEB) /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed, I don't know.... */ printf ("mips-sony-bsd\n"); exit (0); #else #include printf ("m68k-sony-newsos%s\n", #ifdef NEWSOS4 "4" #else "" #endif ); exit (0); #endif #endif #if defined (__arm) && defined (__acorn) && defined (__unix) printf ("arm-acorn-riscix\n"); exit (0); #endif #if defined (hp300) && !defined (hpux) printf ("m68k-hp-bsd\n"); exit (0); #endif #if defined (NeXT) #if !defined (__ARCHITECTURE__) #define __ARCHITECTURE__ "m68k" #endif int version; version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`; if (version < 4) printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version); else printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version); exit (0); #endif #if defined (MULTIMAX) || defined (n16) #if defined (UMAXV) printf ("ns32k-encore-sysv\n"); exit (0); #else #if defined (CMU) printf ("ns32k-encore-mach\n"); exit (0); #else printf ("ns32k-encore-bsd\n"); exit (0); #endif #endif #endif #if defined (__386BSD__) printf ("i386-pc-bsd\n"); exit (0); #endif #if defined (sequent) #if defined (i386) printf ("i386-sequent-dynix\n"); exit (0); #endif #if defined (ns32000) printf ("ns32k-sequent-dynix\n"); exit (0); #endif #endif #if defined (_SEQUENT_) struct utsname un; uname(&un); if (strncmp(un.version, "V2", 2) == 0) { printf ("i386-sequent-ptx2\n"); exit (0); } if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */ printf ("i386-sequent-ptx1\n"); exit (0); } printf ("i386-sequent-ptx\n"); exit (0); #endif #if defined (vax) # if !defined (ultrix) # include # if defined (BSD) # if BSD == 43 printf ("vax-dec-bsd4.3\n"); exit (0); # else # if BSD == 199006 printf ("vax-dec-bsd4.3reno\n"); exit (0); # else printf ("vax-dec-bsd\n"); exit (0); # endif # endif # else printf ("vax-dec-bsd\n"); exit (0); # endif # else printf ("vax-dec-ultrix\n"); exit (0); # endif #endif #if defined (alliant) && defined (i860) printf ("i860-alliant-bsd\n"); exit (0); #endif exit (1); } EOF $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null && SYSTEM_NAME=`$dummy` && { echo "$SYSTEM_NAME"; exit; } # Apollos put the system type in the environment. test -d /usr/apollo && { echo ${ISP}-apollo-${SYSTYPE}; exit; } # Convex versions that predate uname can use getsysinfo(1) if [ -x /usr/convex/getsysinfo ] then case `getsysinfo -f cpu_type` in c1*) echo c1-convex-bsd exit ;; c2*) if getsysinfo -f scalar_acc then echo c32-convex-bsd else echo c2-convex-bsd fi exit ;; c34*) echo c34-convex-bsd exit ;; c38*) echo c38-convex-bsd exit ;; c4*) echo c4-convex-bsd exit ;; esac fi cat >&2 < in order to provide the needed information to handle your system. config.guess timestamp = $timestamp uname -m = `(uname -m) 2>/dev/null || echo unknown` uname -r = `(uname -r) 2>/dev/null || echo unknown` uname -s = `(uname -s) 2>/dev/null || echo unknown` uname -v = `(uname -v) 2>/dev/null || echo unknown` /usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` /bin/uname -X = `(/bin/uname -X) 2>/dev/null` hostinfo = `(hostinfo) 2>/dev/null` /bin/universe = `(/bin/universe) 2>/dev/null` /usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` /bin/arch = `(/bin/arch) 2>/dev/null` /usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` /usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` UNAME_MACHINE = ${UNAME_MACHINE} UNAME_RELEASE = ${UNAME_RELEASE} UNAME_SYSTEM = ${UNAME_SYSTEM} UNAME_VERSION = ${UNAME_VERSION} EOF exit 1 # Local variables: # eval: (add-hook 'write-file-hooks 'time-stamp) # time-stamp-start: "timestamp='" # time-stamp-format: "%:y-%02m-%02d" # time-stamp-end: "'" # End: citadel-9.01/server.h0000644000000000000000000002303512507024051013224 0ustar rootroot#ifndef SERVER_H #define SERVER_H #ifdef __GNUC__ #define INLINE __inline__ #else #define INLINE #endif #include "citadel.h" #ifdef HAVE_OPENSSL #define OPENSSL_NO_KRB5 /* work around redhat b0rken ssl headers */ #include #endif /* * New format for a message in memory */ struct CtdlMessage { int cm_magic; /* Self-check (NOT SAVED TO DISK) */ char cm_anon_type; /* Anonymous or author-visible */ char cm_format_type; /* Format type */ char *cm_fields[256]; /* Data fields */ long cm_lengths[256]; /* size of datafields */ unsigned int cm_flags; /* How to handle (NOT SAVED TO DISK) */ }; #define CTDLMESSAGE_MAGIC 0x159d #define CM_SKIP_HOOKS 0x01 /* Don't run server-side handlers */ /* Data structure returned by validate_recipients() */ typedef struct __recptypes { int recptypes_magic; int num_local; int num_internet; int num_ignet; int num_room; int num_error; char *errormsg; char *recp_local; char *recp_internet; char *recp_ignet; char *recp_room; char *recp_orgroom; char *display_recp; char *bounce_to; char *envelope_from; char *sending_room; } recptypes; #define RECPTYPES_MAGIC 0xfeeb #define CTDLEXIT_SHUTDOWN 0 /* Normal shutdown; do NOT auto-restart */ /* * Exit codes 101 through 109 are used for conditions in which * we deliberately do NOT want the service to automatically * restart. */ #define CTDLEXIT_CONFIG 101 /* Could not read citadel.config */ #define CTDLEXIT_CONTROL 102 /* Could not acquire lock */ #define CTDLEXIT_HOME 103 /* Citadel home directory not found */ #define CTDLEXIT_OOD 104 /* Out Of Date config - rerun setup */ #define CTDLEXIT_DB 105 /* Unable to initialize database */ #define CTDLEXIT_LIBCITADEL 106 /* Incorrect version of libcitadel */ #define CTDL_EXIT_UNSUP_AUTH 107 /* Unsupported auth mode configured */ /* * Reasons why a session would be terminated (set CC->kill_me to these values) */ enum { KILLME_NOT, KILLME_UNKNOWN, KILLME_CLIENT_LOGGED_OUT, KILLME_IDLE, KILLME_CLIENT_DISCONNECTED, KILLME_AUTHFAILED, KILLME_SERVER_SHUTTING_DOWN, KILLME_MAX_SESSIONS_EXCEEDED, KILLME_ADMIN_TERMINATE, KILLME_SELECT_INTERRUPTED, KILLME_SELECT_FAILED, KILLME_WRITE_FAILED, KILLME_SIMULATION_WORKER, KILLME_NOLOGIN, KILLME_NO_CRYPTO, KILLME_READSTRING_FAILED, KILLME_MALLOC_FAILED, KILLME_QUOTA, KILLME_READ_FAILED, KILLME_ILLEGAL_MANAGESIEVE_COMMAND, KILLME_SPAMMER, KILLME_XML_PARSER }; #define CS_STEALTH 1 /* stealth mode */ #define CS_CHAT 2 /* chat mode */ #define CS_POSTING 4 /* Posting */ /* * This is the control record for the message base... */ struct CitControl { long MMhighest; /* highest message number in file */ unsigned MMflags; /* Global system flags */ long MMnextuser; /* highest user number on system */ long MMnextroom; /* highest room number on system */ int version; /* Server-hosted upgrade level */ int fulltext_wordbreaker; /* ID of wordbreaker in use */ long MMfulltext; /* highest message number indexed */ int MMdbversion; /* Version of Berkeley DB used on previous server run */ }; extern int ScheduledShutdown; extern struct CitControl CitControl; struct ExpressMessage { struct ExpressMessage *next; time_t timestamp; /* When this message was sent */ unsigned flags; /* Special instructions */ char sender[256]; /* Name of sending user */ char sender_email[256]; /* Email or JID of sending user */ char *text; /* Message text (if applicable) */ }; #define EM_BROADCAST 1 /* Broadcast message */ #define EM_GO_AWAY 2 /* Server requests client log off */ #define EM_CHAT 4 /* Server requests client enter chat */ /* * Various things we need to lock and unlock */ enum { S_USERS, S_ROOMS, S_SESSION_TABLE, S_FLOORTAB, S_CHATQUEUE, S_CONTROL, S_NETDB, S_SUPPMSGMAIN, S_CONFIG, S_HOUSEKEEPING, S_NTTLIST, S_DIRECTORY, S_NETCONFIGS, S_PUBLIC_CLIENTS, S_FLOORCACHE, S_ATBF, S_JOURNAL_QUEUE, S_RPLIST, S_SIEVELIST, S_CHKPWD, S_LOG, S_NETSPOOL, S_XMPP_QUEUE, S_SCHEDULE_LIST, S_SINGLE_USER, S_LDAP, S_IM_LOGS, MAX_SEMAPHORES }; /* * Upload types */ #define UPL_FILE 0 #define UPL_NET 1 #define UPL_IMAGE 2 /* * message transfer formats */ enum { MT_CITADEL, /* Citadel proprietary */ MT_RFC822, /* RFC822 */ MT_MIME, /* MIME-formatted message */ MT_DOWNLOAD, /* Download a component */ MT_SPEW_SECTION /* Download a component in a single operation */ }; /* * Message format types in the database */ #define FMT_CITADEL 0 /* Citadel vari-format (proprietary) */ #define FMT_FIXED 1 /* Fixed format (proprietary) */ #define FMT_RFC822 4 /* Standard (headers are in M field) */ /* * Citadel DataBases (define one for each cdb we need to open) */ enum { CDB_MSGMAIN, /* message base */ CDB_USERS, /* user file */ CDB_ROOMS, /* room index */ CDB_FLOORTAB, /* floor index */ CDB_MSGLISTS, /* room message lists */ CDB_VISIT, /* user/room relationships */ CDB_DIRECTORY, /* address book directory */ CDB_USETABLE, /* network use table */ CDB_BIGMSGS, /* larger message bodies */ CDB_FULLTEXT, /* full text search index */ CDB_EUIDINDEX, /* locate msgs by EUID */ CDB_USERSBYNUMBER, /* index of users by number */ CDB_OPENID, /* associates OpenIDs with users */ MAXCDB /* total number of CDB's defined */ }; struct cdbdata { size_t len; char *ptr; }; /* * Event types can't be enum'ed, because they must remain consistent between * builds (to allow for binary modules built somewhere else) */ #define EVT_STOP 0 /* Session is terminating */ #define EVT_START 1 /* Session is starting */ #define EVT_LOGIN 2 /* A user is logging in */ #define EVT_NEWROOM 3 /* Changing rooms */ #define EVT_LOGOUT 4 /* A user is logging out */ #define EVT_SETPASS 5 /* Setting or changing password */ #define EVT_CMD 6 /* Called after each server command */ #define EVT_RWHO 7 /* An RWHO command is being executed */ #define EVT_ASYNC 8 /* Doing asynchronous messages */ #define EVT_STEALTH 9 /* Entering stealth mode */ #define EVT_UNSTEALTH 10 /* Exiting stealth mode */ #define EVT_TIMER 50 /* Timer events are called once per minute and are not tied to any session */ #define EVT_HOUSE 51 /* as needed houskeeping stuff */ #define EVT_SHUTDOWN 52 /* Server is shutting down */ #define EVT_PURGEUSER 100 /* Deleting a user */ #define EVT_NEWUSER 102 /* Creating a user */ #define EVT_BEFOREREAD 200 #define EVT_BEFORESAVE 201 #define EVT_AFTERSAVE 202 #define EVT_SMTPSCAN 203 /* called before submitting a msg from SMTP */ #define EVT_AFTERUSRMBOXSAVE 204 /* called afte a message was saved into a users inbox */ /* Priority levels for paging functions (lower is better) */ enum { XMSG_PRI_LOCAL, /* Other users on -this- server */ XMSG_PRI_REMOTE, /* Other users on a Citadel network (future) */ XMSG_PRI_FOREIGN, /* Contacts on foreign instant message hosts */ MAX_XMSG_PRI }; /* Defines the relationship of a user to a particular room */ typedef struct __visit { long v_roomnum; long v_roomgen; long v_usernum; long v_lastseen; unsigned int v_flags; char v_seen[SIZ]; char v_answered[SIZ]; int v_view; } visit; #define V_FORGET 1 /* User has zapped this room */ #define V_LOCKOUT 2 /* User is locked out of this room */ #define V_ACCESS 4 /* Access is granted to this room */ /* Supplementary data for a message on disk * These are kept separate from the message itself for one of two reasons: * 1. Either their values may change at some point after initial save, or * 2. They are merely caches of data which exist somewhere else, for speed. */ struct MetaData { long meta_msgnum; /* Message number in *local* message base */ int meta_refcount; /* Number of rooms pointing to this msg */ char meta_content_type[64]; /* Cached MIME content-type */ long meta_rfc822_length; /* Cache of RFC822-translated msg length */ char mimetype[64]; /* if we were able to guess the mimetype for the data */ }; /* Calls to AdjRefCount() are queued and deferred, so the user doesn't * have to wait for various disk-intensive operations to complete synchronously. * This is the record format. */ struct arcq { long arcq_msgnum; /* Message number being adjusted */ int arcq_delta; /* Adjustment ( usually 1 or -1 ) */ }; /* * Serialization routines use this struct to return a pointer and a length */ struct ser_ret { size_t len; unsigned char *ser; }; /* * The S_USETABLE database is used in several modules now, so we define its format here. */ struct UseTable { char ut_msgid[SIZ]; time_t ut_timestamp; }; /* Preferred field order */ /* ********** Important fields */ /* *************** Semi-important fields */ /* ** internal only */ /* * Message text (MUST be last) */ ///#define FORDER "IPTAFONHRDBCEWJGKLQSVXZYU12M" typedef enum _MsgField { eAuthor = 'A', eBig_message = 'B', eRemoteRoom = 'C', eDestination = 'D', eExclusiveID = 'E', erFc822Addr = 'F', eHumanNode = 'H', emessageId = 'I', eJournal = 'J', eReplyTo = 'K', eListID = 'L', eMesageText = 'M', eNodeName = 'N', eOriginalRoom = 'O', eMessagePath = 'P', eRecipient = 'R', eSpecialField = 'S', eTimestamp = 'T', eMsgSubject = 'U', eenVelopeTo = 'V', eWeferences = 'W', eCarbonCopY = 'Y', eErrorMsg = '0', eSuppressIdx = '1', eExtnotify = '2', eVltMsgNum = '3' }eMsgField; #endif /* SERVER_H */ citadel-9.01/event_client.h0000644000000000000000000002306312507024051014376 0ustar rootroot/* * * Copyright (c) 1998-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #ifndef __EVENT_CLIENT_H__ #define __EVENT_CLIENT_H__ #define EV_COMPAT3 0 #include "sysconfig.h" #include #include #include #include #include #include #include #ifndef __ASYNCIO__ #define __ASYNCIO__ typedef struct AsyncIO AsyncIO; #endif #ifndef __CIT_CONTEXT__ #define __CIT_CONTEXT__ typedef struct CitContext CitContext; #endif extern pthread_key_t evConKey; typedef enum __eIOState { eDBQ, eQDBNext, eDBAttach, eDBNext, eDBStop, eDBX, eDBTerm, eIOQ, eIOAttach, eIOConnectSock, eIOAbort, eIOTimeout, eIOConnfail, eIOConnfailNow, eIOConnNow, eIOConnWait, eCurlQ, eCurlStart, eCurlShutdown, eCurlNewIO, eCurlGotIO, eCurlGotData, eCurlGotStatus, eCaresStart, eCaresDoneIO, eCaresFinished, eCaresX, eKill, eExit }eIOState; typedef enum _eNextState { eSendDNSQuery, eReadDNSReply, eDBQuery, eConnect, eSendReply, eSendMore, eSendFile, eReadMessage, eReadMore, eReadPayload, eReadFile, eTerminateConnection, eAbort }eNextState; void SetEVState(AsyncIO *IO, eIOState State); typedef eNextState (*IO_CallBack)(AsyncIO *IO); typedef eReadState (*IO_LineReaderCallback)(AsyncIO *IO); typedef void (*ParseDNSAnswerCb)(AsyncIO*, unsigned char*, int); typedef void (*FreeDNSReply)(void *DNSData); typedef struct __ReadAsyncMsg { StrBuf *MsgBuf; size_t maxlen; /* maximum message length */ const char *terminator; /* token signalling EOT */ long tlen; int dodot; int flushing; /* if we read maxlen, read until nothing more arives and ignore this. */ int crlf; /* CRLF newlines instead of LF */ } ReadAsyncMsg; typedef struct _DNSQueryParts { ParseDNSAnswerCb DNS_CB; IO_CallBack PostDNS; const char *QueryTYPE; const char *QStr; int DNSStatus; void *VParsedDNSReply; FreeDNSReply DNSReplyFree; void *Data; } DNSQueryParts; typedef struct _evcurl_request_data { CURL *chnd; struct curl_slist *headers; char errdesc[CURL_ERROR_SIZE]; const char *CurlError; int attached; char *PlainPostData; long PlainPostDataLen; StrBuf *PostData; StrBuf *ReplyData; long httpcode; } evcurl_request_data; /* DNS Related */ typedef struct __evcares_data { ev_tstamp Start; ev_io recv_event, send_event; ev_timer timeout; /* timeout while requesting ips */ short int SourcePort; struct ares_options Options; ares_channel Channel; DNSQueryParts *Query; IO_CallBack Fail; /* the dns lookup didn't work out. */ } evcares_data; struct AsyncIO { long ID; ev_tstamp Now; ev_tstamp StartIO; ev_tstamp StartDB; eNextState NextState; /* connection related */ ParsedURL *ConnectMe; /* read/send related... */ StrBuf *IOBuf; IOBuffer SendBuf, RecvBuf; FDIOBuffer IOB; /* when sending from / reading into files, this is used. */ /* our events... */ ev_cleanup abort_by_shutdown, /* server wants to go down... */ db_abort_by_shutdown; /* server wants to go down... */ ev_timer conn_fail, /* connection establishing timed out */ rw_timeout; /* timeout while sending data */ ev_idle unwind_stack, /* get c-ares out of the stack */ db_unwind_stack, /* wait for next db operation... */ conn_fail_immediate; /* unwind stack, but fail immediately. */ ev_io recv_event, /* receive data from the client */ send_event, /* send more data to the client */ conn_event; /* Connection successfully established */ StrBuf *ErrMsg; /* if we fail to connect, or lookup, error goes here. */ /* Citadel application callbacks... */ IO_CallBack ReadDone, /* Theres new data to read... */ SendDone, /* we may send more data */ Terminate, /* shutting down... */ DBTerminate, /* shutting down... */ Timeout, /* Timeout handler;may also be conn. timeout */ ConnFail, /* What to do when one connection failed? */ ShutdownAbort,/* we're going down. make your piece. */ NextDBOperation, /* Perform Database IO */ ReAttachCB; /* on the hop from one Q to the other, this is the next CB */ /* if we have linereaders, maybe we want to read more lines before * the real application logic is called? */ IO_LineReaderCallback LineReader; evcares_data DNS; evcurl_request_data HttpReq; /* Saving / loading a message async from / to disk */ ReadAsyncMsg *ReadMsg; struct CtdlMessage *AsyncMsg; recptypes *AsyncRcp; /* Context specific data; Hint: put AsyncIO in there */ void *Data; /* application specific data */ CitContext *CitContext; /* Citadel Session context... */ }; typedef struct _IOAddHandler { AsyncIO *IO; IO_CallBack EvAttch; } IOAddHandler; inline static time_t EvGetNow(AsyncIO *IO) { return (time_t) IO->Now;} extern int DebugEventLoop; extern int DebugCAres; #define IOSTR (const char *) pthread_getspecific(evConKey) #define EDBGLOG(LEVEL) if ((LEVEL != LOG_DEBUG) || (DebugEventLoop != 0)) #define CCID ((CitContext*)IO->CitContext)?((CitContext*)IO->CitContext)->cs_pid:-1 #define EVQ_syslog(LEVEL, FORMAT, ...) \ EDBGLOG (LEVEL) syslog(LEVEL, "%sQ " FORMAT, IOSTR, __VA_ARGS__) #define EVQM_syslog(LEVEL, FORMAT) \ EDBGLOG (LEVEL) syslog(LEVEL, "%s " FORMAT, IOSTR) #define EV_syslog(LEVEL, FORMAT, ...) \ EDBGLOG (LEVEL) syslog(LEVEL, "%s[%ld]CC[%d] " FORMAT, IOSTR, IO->ID, CCID, __VA_ARGS__) #define EVM_syslog(LEVEL, FORMAT) \ EDBGLOG (LEVEL) syslog(LEVEL, "%s[%ld]CC[%d] " FORMAT, IOSTR, IO->ID, CCID) #define EVNC_syslog(LEVEL, FORMAT, ...) \ EDBGLOG (LEVEL) syslog(LEVEL, "%s[%ld] " FORMAT, IOSTR, IO->ID, __VA_ARGS__) #define EVNCM_syslog(LEVEL, FORMAT) EDBGLOG (LEVEL) syslog(LEVEL, "%s[%ld]" FORMAT, IOSTR, IO->ID) #define CDBGLOG() if (DebugCAres != 0) #define CEDBGLOG(LEVEL) if ((LEVEL != LOG_DEBUG) || (DebugCAres != 0)) #define EV_DNS_LOG_START(a) \ CDBGLOG () {syslog(LOG_DEBUG, "%s[%ld]CC[%d] + Starting " #a " %s %p FD %d", IOSTR, IO->ID, CCID, __FUNCTION__, &IO->a, IO->a.fd); \ EV_backtrace(IO);} #define EV_DNS_LOG_STOP(a) \ CDBGLOG () { syslog(LOG_DEBUG, "%s[%ld]CC[%d] - Stopping " #a " %s %p FD %d", IOSTR, IO->ID, CCID, __FUNCTION__, &IO->a, IO->a.fd); \ EV_backtrace(IO);} #define EV_DNS_LOG_INIT(a) \ CDBGLOG () { syslog(LOG_DEBUG, "%s[%ld]CC[%d] * Init " #a " %s %p FD %d", IOSTR, IO->ID, CCID, __FUNCTION__, &IO->a, IO->a.fd); \ EV_backtrace(IO);} #define EV_DNS_LOGT_START(a) \ CDBGLOG () { syslog(LOG_DEBUG, "%s[%ld]CC[%d] + Starting " #a " %s %p", IOSTR, IO->ID, CCID, __FUNCTION__, &IO->a); \ EV_backtrace(IO);} #define EV_DNS_LOGT_STOP(a) \ CDBGLOG () { syslog(LOG_DEBUG, "%s[%ld]CC[%d] - Stopping " #a " %s %p", IOSTR, IO->ID, CCID, __FUNCTION__, &IO->a); \ EV_backtrace(IO); } #define EV_DNS_LOGT_INIT(a) \ CDBGLOG () { syslog(LOG_DEBUG, "%s[%ld]CC[%d] * Init " #a " %p", IOSTR, IO->ID, CCID, &IO->a); \ EV_backtrace(IO);} #define EV_DNS_syslog(LEVEL, FORMAT, ...) \ CEDBGLOG (LEVEL) syslog(LEVEL, "%s[%ld]CC[%d] " FORMAT, IOSTR, IO->ID, CCID, __VA_ARGS__) #define EVM_DNS_syslog(LEVEL, FORMAT) \ CEDBGLOG (LEVEL) syslog(LEVEL, "%s[%ld]CC[%d] " FORMAT, IOSTR, IO->ID, CCID) void FreeAsyncIOContents(AsyncIO *IO); eNextState NextDBOperation(AsyncIO *IO, IO_CallBack CB); eNextState EventQueueDBOperation(AsyncIO *IO, IO_CallBack CB, int CloseFDs); void StopDBWatchers(AsyncIO *IO); eNextState QueueEventContext(AsyncIO *IO, IO_CallBack CB); eNextState QueueCurlContext(AsyncIO *IO); eNextState DBQueueEventContext(AsyncIO *IO, IO_CallBack CB); eNextState EvConnectSock(AsyncIO *IO, double conn_timeout, double first_rw_timeout, int ReadFirst); void IO_postdns_callback(struct ev_loop *loop, ev_idle *watcher, int revents); int QueueQuery(ns_type Type, const char *name, AsyncIO *IO, DNSQueryParts *QueryParts, IO_CallBack PostDNS); void QueueGetHostByName(AsyncIO *IO, const char *Hostname, DNSQueryParts *QueryParts, IO_CallBack PostDNS); void QueryCbDone(AsyncIO *IO); void StopClient(AsyncIO *IO); void StopClientWatchers(AsyncIO *IO, int CloseFD); void SetNextTimeout(AsyncIO *IO, double timeout); #include #define OPT(s, v) \ do { \ sta = curl_easy_setopt(chnd, (CURLOPT_##s), (v)); \ if (sta) { \ EVQ_syslog(LOG_ERR, \ "error setting option " #s \ " on curl handle: %s", \ curl_easy_strerror(sta)); \ } } while (0) void InitIOStruct(AsyncIO *IO, void *Data, eNextState NextState, IO_LineReaderCallback LineReader, IO_CallBack DNS_Fail, IO_CallBack SendDone, IO_CallBack ReadDone, IO_CallBack Terminate, IO_CallBack DBTerminate, IO_CallBack ConnFail, IO_CallBack Timeout, IO_CallBack ShutdownAbort); int InitcURLIOStruct(AsyncIO *IO, void *Data, const char* Desc, IO_CallBack SendDone, IO_CallBack Terminate, IO_CallBack DBTerminate, IO_CallBack ShutdownAbort); void KillAsyncIOContext(AsyncIO *IO); void StopCurlWatchers(AsyncIO *IO); eNextState CurlQueueDBOperation(AsyncIO *IO, IO_CallBack CB); eNextState ReAttachIO(AsyncIO *IO, void *pData, int ReadFirst); void EV_backtrace(AsyncIO *IO); ev_tstamp ctdl_ev_now (void); #endif /* __EVENT_CLIENT_H__ */ citadel-9.01/serv_extensions.c0000644000000000000000000010205212507024051015144 0ustar rootroot/* * Citadel Dynamic Loading Module * Written by Brian Costello * * Copyright (c) 1987-2011 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include "sysdep_decls.h" #include "modules/crypto/serv_crypto.h" /* Needed until a universal crypto startup hook is implimented for CtdlStartTLS */ #include "serv_extensions.h" #include "ctdl_module.h" int DebugModules = 0; /* * Structure defentitions for hook tables */ HashList *LogDebugEntryTable = NULL; typedef struct LogFunctionHook LogFunctionHook; struct LogFunctionHook { LogFunctionHook *next; int loglevel; void (*h_function_pointer) (char *); }; LogFunctionHook *LogHookTable = NULL; typedef struct FixedOutputHook FixedOutputHook; struct FixedOutputHook { FixedOutputHook *next; char content_type[64]; void (*h_function_pointer) (char *, int); }; FixedOutputHook *FixedOutputTable = NULL; /* * TDAPVetoHookFunctionHook extensions are used for any type of hook for which * may prevent the autopurger to run for this specific data class. * the function should at least LOG_INFO that it does so. */ typedef struct TDAPVetoHookFunctionHook TDAPVetoHookFunctionHook; struct TDAPVetoHookFunctionHook { TDAPVetoHookFunctionHook *next; int Priority; int (*h_function_pointer) (StrBuf *); int eventtype; }; TDAPVetoHookFunctionHook *TDAPVetoHookTable = NULL; /* * SessionFunctionHook extensions are used for any type of hook for which * the context in which it's being called (which is determined by the event * type) will make it obvious for the hook function to know where to look for * pertinent data. */ typedef struct SessionFunctionHook SessionFunctionHook; struct SessionFunctionHook { SessionFunctionHook *next; int Priority; void (*h_function_pointer) (void); int eventtype; }; SessionFunctionHook *SessionHookTable = NULL; /* * UserFunctionHook extensions are used for any type of hook which implements * an operation on a user or username (potentially) other than the one * operating the current session. */ typedef struct UserFunctionHook UserFunctionHook; struct UserFunctionHook { UserFunctionHook *next; void (*h_function_pointer) (struct ctdluser *usbuf); int eventtype; }; UserFunctionHook *UserHookTable = NULL; /* * MessageFunctionHook extensions are used for hooks which implement handlers * for various types of message operations (save, read, etc.) */ typedef struct MessageFunctionHook MessageFunctionHook; struct MessageFunctionHook { MessageFunctionHook *next; int (*h_function_pointer) (struct CtdlMessage *msg, recptypes *recps); int eventtype; }; MessageFunctionHook *MessageHookTable = NULL; /* * NetprocFunctionHook extensions are used for hooks which implement handlers * for incoming network messages. */ typedef struct NetprocFunctionHook NetprocFunctionHook; struct NetprocFunctionHook { NetprocFunctionHook *next; int (*h_function_pointer) (struct CtdlMessage *msg, char *target_room); }; NetprocFunctionHook *NetprocHookTable = NULL; /* * DeleteFunctionHook extensions are used for hooks which get called when a * message is about to be deleted. */ typedef struct DeleteFunctionHook DeleteFunctionHook; struct DeleteFunctionHook { DeleteFunctionHook *next; void (*h_function_pointer) (char *target_room, long msgnum); }; DeleteFunctionHook *DeleteHookTable = NULL; /* * ExpressMessageFunctionHook extensions are used for hooks which implement * the sending of an instant message through various channels. Any function * registered should return the number of recipients to whom the message was * successfully transmitted. */ typedef struct XmsgFunctionHook XmsgFunctionHook; struct XmsgFunctionHook { XmsgFunctionHook *next; int (*h_function_pointer) (char *, char *, char *, char *); int order; }; XmsgFunctionHook *XmsgHookTable = NULL; /* * RoomFunctionHook extensions are used for hooks which impliment room * processing functions when new messages are added EG. SIEVE. */ typedef struct RoomFunctionHook RoomFunctionHook; struct RoomFunctionHook { RoomFunctionHook *next; int (*fcn_ptr) (struct ctdlroom *); }; RoomFunctionHook *RoomHookTable = NULL; typedef struct SearchFunctionHook SearchFunctionHook; struct SearchFunctionHook { SearchFunctionHook *next; void (*fcn_ptr) (int *, long **, const char *); char *name; }; SearchFunctionHook *SearchFunctionHookTable = NULL; CleanupFunctionHook *CleanupHookTable = NULL; CleanupFunctionHook *EVCleanupHookTable = NULL; ServiceFunctionHook *ServiceHookTable = NULL; typedef struct ProtoFunctionHook ProtoFunctionHook; struct ProtoFunctionHook { void (*handler) (char *cmdbuf); const char *cmd; const char *desc; }; HashList *ProtoHookList = NULL; #define ERR_PORT (1 << 1) static StrBuf *portlist = NULL; static StrBuf *errormessages = NULL; long DetailErrorFlags; ConstStr Empty = {HKEY("")}; char *ErrSubject = "Startup Problems"; ConstStr ErrGeneral[] = { {HKEY("Citadel had trouble on starting up. ")}, {HKEY(" This means, citadel won't be the service provider for a specific service you configured it to.\n\n" "If you don't want citadel to provide these services, turn them off in WebCit via: ")}, {HKEY("To make both ways actualy take place restart the citserver with \"sendcommand down\"\n\n" "The errors returned by the system were:\n")}, {HKEY("You can recheck the above if you follow this faq item:\n" "http://www.citadel.org/doku.php?id=faq:mastering_your_os:net#netstat")} }; ConstStr ErrPortShort = { HKEY("We couldn't bind all ports you configured to be provided by citadel server.\n")}; ConstStr ErrPortWhere = { HKEY("\"Admin->System Preferences->Network\".\n\nThe failed ports and sockets are: ")}; ConstStr ErrPortHint = { HKEY("If you want citadel to provide you with that functionality, " "check the output of \"netstat -lnp\" on linux Servers or \"netstat -na\" on *BSD" " and stop the program that binds these ports.\n You should eventually remove " " their initscripts in /etc/init.d so that you won't get this trouble once more.\n" " After that goto \"Administration -> Shutdown Citadel\" to make Citadel restart & retry to bind this port.\n")}; void LogPrintMessages(long err) { StrBuf *Message; StrBuf *List, *DetailList; ConstStr *Short, *Where, *Hint; Message = NewStrBufPlain(NULL, StrLength(portlist) + StrLength(errormessages)); DetailErrorFlags = DetailErrorFlags & ~err; switch (err) { case ERR_PORT: Short = &ErrPortShort; Where = &ErrPortWhere; Hint = &ErrPortHint; List = portlist; DetailList = errormessages; break; default: Short = &Empty; Where = &Empty; Hint = &Empty; List = NULL; DetailList = NULL; } StrBufAppendBufPlain(Message, CKEY(ErrGeneral[0]), 0); StrBufAppendBufPlain(Message, CKEY(*Short), 0); StrBufAppendBufPlain(Message, CKEY(ErrGeneral[1]), 0); StrBufAppendBufPlain(Message, CKEY(*Where), 0); StrBufAppendBuf(Message, List, 0); StrBufAppendBufPlain(Message, HKEY("\n\n"), 0); StrBufAppendBufPlain(Message, CKEY(*Hint), 0); StrBufAppendBufPlain(Message, HKEY("\n\n"), 0); StrBufAppendBufPlain(Message, CKEY(ErrGeneral[2]), 0); StrBufAppendBuf(Message, DetailList, 0); StrBufAppendBufPlain(Message, HKEY("\n\n"), 0); StrBufAppendBufPlain(Message, CKEY(ErrGeneral[3]), 0); MOD_syslog(LOG_EMERG, "%s", ChrPtr(Message)); MOD_syslog(LOG_EMERG, "%s", ErrSubject); quickie_message("Citadel", NULL, NULL, AIDEROOM, ChrPtr(Message), FMT_FIXED, ErrSubject); FreeStrBuf(&Message); FreeStrBuf(&List); FreeStrBuf(&DetailList); } void AddPortError(char *Port, char *ErrorMessage) { long len; DetailErrorFlags |= ERR_PORT; len = StrLength(errormessages); if (len > 0) StrBufAppendBufPlain(errormessages, HKEY("; "), 0); else errormessages = NewStrBuf(); StrBufAppendBufPlain(errormessages, ErrorMessage, -1, 0); len = StrLength(portlist); if (len > 0) StrBufAppendBufPlain(portlist, HKEY(";"), 0); else portlist = NewStrBuf(); StrBufAppendBufPlain(portlist, Port, -1, 0); } int DLoader_Exec_Cmd(char *cmdbuf) { void *vP; ProtoFunctionHook *p; if (GetHash(ProtoHookList, cmdbuf, 4, &vP) && (vP != NULL)) { p = (ProtoFunctionHook*) vP; p->handler(&cmdbuf[5]); return 1; } return 0; } long FourHash(const char *key, long length) { int i; int ret = 0; const unsigned char *ptr = (const unsigned char*)key; for (i = 0; i < 4; i++, ptr ++) ret = (ret << 8) | ( ((*ptr >= 'a') && (*ptr <= 'z'))? *ptr - 'a' + 'A': *ptr); return ret; } void CtdlRegisterDebugFlagHook(const char *Name, long Len, CtdlDbgFunction F, const int *LogP) { LogDebugEntry *E; if (LogDebugEntryTable == NULL) LogDebugEntryTable = NewHash(1, NULL); E = (LogDebugEntry*) malloc(sizeof(LogDebugEntry)); E->F = F; E->Name = Name; E->Len = Len; E->LogP = LogP; Put(LogDebugEntryTable, Name, Len, E, NULL); } void CtdlSetDebugLogFacilities(const char **Str, long n) { StrBuf *Token = NULL; StrBuf *Buf = NULL; const char *ch; int i; int DoAll = 0; void *vptr; for (i=0; i < n; i++){ if ((Str[i] != NULL) && !IsEmptyStr(Str[i])) { if (strcmp(Str[i], "all") == 0) { DoAll = 1; continue; } Buf = NewStrBufPlain(Str[i], -1); ch = NULL; if (Token == NULL) Token = NewStrBufPlain(NULL, StrLength(Buf)); while ((ch != StrBufNOTNULL) && StrBufExtract_NextToken(Token, Buf, &ch, ',')) { if (GetHash(LogDebugEntryTable, SKEY(Token), &vptr) && (vptr != NULL)) { LogDebugEntry *E = (LogDebugEntry*)vptr; E->F(1); } } } FreeStrBuf(&Buf); } FreeStrBuf(&Token); if (DoAll) { long HKLen; const char *ch; HashPos *Pos; Pos = GetNewHashPos(LogDebugEntryTable, 0); while (GetNextHashPos(LogDebugEntryTable, Pos, &HKLen, &ch, &vptr)) { LogDebugEntry *E = (LogDebugEntry*)vptr; E->F(1); } DeleteHashPos(&Pos); } } void CtdlDestroyDebugTable(void) { DeleteHash(&LogDebugEntryTable); } void CtdlRegisterProtoHook(void (*handler) (char *), char *cmd, char *desc) { ProtoFunctionHook *p; if (ProtoHookList == NULL) ProtoHookList = NewHash (1, FourHash); p = (ProtoFunctionHook *) malloc(sizeof(ProtoFunctionHook)); if (p == NULL) { fprintf(stderr, "can't malloc new ProtoFunctionHook\n"); exit(EXIT_FAILURE); } p->handler = handler; p->cmd = cmd; p->desc = desc; Put(ProtoHookList, cmd, 4, p, NULL); MOD_syslog(LOG_DEBUG, "Registered server command %s (%s)\n", cmd, desc); } void CtdlDestroyProtoHooks(void) { DeleteHash(&ProtoHookList); } void CtdlRegisterCleanupHook(void (*fcn_ptr) (void)) { CleanupFunctionHook *newfcn; newfcn = (CleanupFunctionHook *) malloc(sizeof(CleanupFunctionHook)); newfcn->next = CleanupHookTable; newfcn->h_function_pointer = fcn_ptr; CleanupHookTable = newfcn; MODM_syslog(LOG_DEBUG, "Registered a new cleanup function\n"); } void CtdlUnregisterCleanupHook(void (*fcn_ptr) (void)) { CleanupFunctionHook *cur, *p, *last; last = NULL; cur = CleanupHookTable; while (cur != NULL) { if (fcn_ptr == cur->h_function_pointer) { MODM_syslog(LOG_DEBUG, "Unregistered cleanup function\n"); p = cur->next; free(cur); cur = NULL; if (last != NULL) last->next = p; else CleanupHookTable = p; cur = p; } else { last = cur; cur = cur->next; } } } void CtdlDestroyCleanupHooks(void) { CleanupFunctionHook *cur, *p; cur = CleanupHookTable; while (cur != NULL) { MODM_syslog(LOG_DEBUG, "Destroyed cleanup function\n"); p = cur->next; free(cur); cur = p; } CleanupHookTable = NULL; } void CtdlRegisterEVCleanupHook(void (*fcn_ptr) (void)) { CleanupFunctionHook *newfcn; newfcn = (CleanupFunctionHook *) malloc(sizeof(CleanupFunctionHook)); newfcn->next = EVCleanupHookTable; newfcn->h_function_pointer = fcn_ptr; EVCleanupHookTable = newfcn; MODM_syslog(LOG_DEBUG, "Registered a new cleanup function\n"); } void CtdlUnregisterEVCleanupHook(void (*fcn_ptr) (void)) { CleanupFunctionHook *cur, *p, *last; last = NULL; cur = EVCleanupHookTable; while (cur != NULL) { if (fcn_ptr == cur->h_function_pointer) { MODM_syslog(LOG_DEBUG, "Unregistered cleanup function\n"); p = cur->next; free(cur); cur = NULL; if (last != NULL) last->next = p; else EVCleanupHookTable = p; cur = p; } else { last = cur; cur = cur->next; } } } void CtdlDestroyEVCleanupHooks(void) { CleanupFunctionHook *cur, *p; cur = EVCleanupHookTable; while (cur != NULL) { MODM_syslog(LOG_DEBUG, "Destroyed cleanup function\n"); p = cur->next; cur->h_function_pointer(); free(cur); cur = p; } EVCleanupHookTable = NULL; } void CtdlRegisterTDAPVetoHook(int (*fcn_ptr) (StrBuf*), int EventType, int Priority) { TDAPVetoHookFunctionHook *newfcn; newfcn = (TDAPVetoHookFunctionHook *) malloc(sizeof(TDAPVetoHookFunctionHook)); newfcn->Priority = Priority; newfcn->h_function_pointer = fcn_ptr; newfcn->eventtype = EventType; TDAPVetoHookFunctionHook **pfcn; pfcn = &TDAPVetoHookTable; while ((*pfcn != NULL) && ((*pfcn)->Priority < newfcn->Priority) && ((*pfcn)->next != NULL)) pfcn = &(*pfcn)->next; newfcn->next = *pfcn; *pfcn = newfcn; MOD_syslog(LOG_DEBUG, "Registered a new TDAP Veto function (type %d Priority %d)\n", EventType, Priority); } void CtdlUnregisterTDAPVetoHook(int (*fcn_ptr) (StrBuf*), int EventType) { TDAPVetoHookFunctionHook *cur, *p, *last; last = NULL; cur = TDAPVetoHookTable; while (cur != NULL) { if ((fcn_ptr == cur->h_function_pointer) && (EventType == cur->eventtype)) { MOD_syslog(LOG_DEBUG, "Unregistered TDAP Veto function (type %d)\n", EventType); p = cur->next; free(cur); cur = NULL; if (last != NULL) last->next = p; else TDAPVetoHookTable = p; cur = p; } else { last = cur; cur = cur->next; } } } void CtdlDestroyTDAPVetoHooks(void) { TDAPVetoHookFunctionHook *cur, *p; cur = TDAPVetoHookTable; while (cur != NULL) { MODM_syslog(LOG_DEBUG, "Destroyed TDAP Veto function\n"); p = cur->next; free(cur); cur = p; } TDAPVetoHookTable = NULL; } void CtdlRegisterSessionHook(void (*fcn_ptr) (void), int EventType, int Priority) { SessionFunctionHook *newfcn; newfcn = (SessionFunctionHook *) malloc(sizeof(SessionFunctionHook)); newfcn->Priority = Priority; newfcn->h_function_pointer = fcn_ptr; newfcn->eventtype = EventType; SessionFunctionHook **pfcn; pfcn = &SessionHookTable; while ((*pfcn != NULL) && ((*pfcn)->Priority < newfcn->Priority) && ((*pfcn)->next != NULL)) pfcn = &(*pfcn)->next; newfcn->next = *pfcn; *pfcn = newfcn; MOD_syslog(LOG_DEBUG, "Registered a new session function (type %d Priority %d)\n", EventType, Priority); } void CtdlUnregisterSessionHook(void (*fcn_ptr) (void), int EventType) { SessionFunctionHook *cur, *p, *last; last = NULL; cur = SessionHookTable; while (cur != NULL) { if ((fcn_ptr == cur->h_function_pointer) && (EventType == cur->eventtype)) { MOD_syslog(LOG_DEBUG, "Unregistered session function (type %d)\n", EventType); p = cur->next; free(cur); cur = NULL; if (last != NULL) last->next = p; else SessionHookTable = p; cur = p; } else { last = cur; cur = cur->next; } } } void CtdlDestroySessionHooks(void) { SessionFunctionHook *cur, *p; cur = SessionHookTable; while (cur != NULL) { MODM_syslog(LOG_DEBUG, "Destroyed session function\n"); p = cur->next; free(cur); cur = p; } SessionHookTable = NULL; } void CtdlRegisterUserHook(void (*fcn_ptr) (ctdluser *), int EventType) { UserFunctionHook *newfcn; newfcn = (UserFunctionHook *) malloc(sizeof(UserFunctionHook)); newfcn->next = UserHookTable; newfcn->h_function_pointer = fcn_ptr; newfcn->eventtype = EventType; UserHookTable = newfcn; MOD_syslog(LOG_DEBUG, "Registered a new user function (type %d)\n", EventType); } void CtdlUnregisterUserHook(void (*fcn_ptr) (struct ctdluser *), int EventType) { UserFunctionHook *cur, *p, *last; last = NULL; cur = UserHookTable; while (cur != NULL) { if ((fcn_ptr == cur->h_function_pointer) && (EventType == cur->eventtype)) { MOD_syslog(LOG_DEBUG, "Unregistered user function (type %d)\n", EventType); p = cur->next; free(cur); cur = NULL; if (last != NULL) last->next = p; else UserHookTable = p; cur = p; } else { last = cur; cur = cur->next; } } } void CtdlDestroyUserHooks(void) { UserFunctionHook *cur, *p; cur = UserHookTable; while (cur != NULL) { MODM_syslog(LOG_DEBUG, "Destroyed user function \n"); p = cur->next; free(cur); cur = p; } UserHookTable = NULL; } void CtdlRegisterMessageHook(int (*handler)(struct CtdlMessage *, recptypes *), int EventType) { MessageFunctionHook *newfcn; newfcn = (MessageFunctionHook *) malloc(sizeof(MessageFunctionHook)); newfcn->next = MessageHookTable; newfcn->h_function_pointer = handler; newfcn->eventtype = EventType; MessageHookTable = newfcn; MOD_syslog(LOG_DEBUG, "Registered a new message function (type %d)\n", EventType); } void CtdlUnregisterMessageHook(int (*handler)(struct CtdlMessage *, recptypes *), int EventType) { MessageFunctionHook *cur, *p, *last; last = NULL; cur = MessageHookTable; while (cur != NULL) { if ((handler == cur->h_function_pointer) && (EventType == cur->eventtype)) { MOD_syslog(LOG_DEBUG, "Unregistered message function (type %d)\n", EventType); p = cur->next; free(cur); cur = NULL; if (last != NULL) last->next = p; else MessageHookTable = p; cur = p; } else { last = cur; cur = cur->next; } } } void CtdlDestroyMessageHook(void) { MessageFunctionHook *cur, *p; cur = MessageHookTable; while (cur != NULL) { MOD_syslog(LOG_DEBUG, "Destroyed message function (type %d)\n", cur->eventtype); p = cur->next; free(cur); cur = p; } MessageHookTable = NULL; } void CtdlRegisterRoomHook(int (*fcn_ptr)(struct ctdlroom *)) { RoomFunctionHook *newfcn; newfcn = (RoomFunctionHook *) malloc(sizeof(RoomFunctionHook)); newfcn->next = RoomHookTable; newfcn->fcn_ptr = fcn_ptr; RoomHookTable = newfcn; MODM_syslog(LOG_DEBUG, "Registered a new room function\n"); } void CtdlUnregisterRoomHook(int (*fcn_ptr)(struct ctdlroom *)) { RoomFunctionHook *cur, *p, *last; last = NULL; cur = RoomHookTable; while (cur != NULL) { if (fcn_ptr == cur->fcn_ptr) { MODM_syslog(LOG_DEBUG, "Unregistered room function\n"); p = cur->next; free(cur); cur = NULL; if (last != NULL) last->next = p; else RoomHookTable = p; cur = p; } else { last = cur; cur = cur->next; } } } void CtdlDestroyRoomHooks(void) { RoomFunctionHook *cur, *p; cur = RoomHookTable; while (cur != NULL) { MODM_syslog(LOG_DEBUG, "Destroyed room function\n"); p = cur->next; free(cur); cur = p; } RoomHookTable = NULL; } void CtdlRegisterNetprocHook(int (*handler)(struct CtdlMessage *, char *) ) { NetprocFunctionHook *newfcn; newfcn = (NetprocFunctionHook *) malloc(sizeof(NetprocFunctionHook)); newfcn->next = NetprocHookTable; newfcn->h_function_pointer = handler; NetprocHookTable = newfcn; MODM_syslog(LOG_DEBUG, "Registered a new netproc function\n"); } void CtdlUnregisterNetprocHook(int (*handler)(struct CtdlMessage *, char *) ) { NetprocFunctionHook *cur, *p, *last; cur = NetprocHookTable; last = NULL; while (cur != NULL) { if (handler == cur->h_function_pointer) { MODM_syslog(LOG_DEBUG, "Unregistered netproc function\n"); p = cur->next; free(cur); if (last != NULL) { last->next = p; } else { NetprocHookTable = p; } cur = p; } else { last = cur; cur = cur->next; } } } void CtdlDestroyNetprocHooks(void) { NetprocFunctionHook *cur, *p; cur = NetprocHookTable; while (cur != NULL) { MODM_syslog(LOG_DEBUG, "Destroyed netproc function\n"); p = cur->next; free(cur); cur = p; } NetprocHookTable = NULL; } void CtdlRegisterDeleteHook(void (*handler)(char *, long) ) { DeleteFunctionHook *newfcn; newfcn = (DeleteFunctionHook *) malloc(sizeof(DeleteFunctionHook)); newfcn->next = DeleteHookTable; newfcn->h_function_pointer = handler; DeleteHookTable = newfcn; MODM_syslog(LOG_DEBUG, "Registered a new delete function\n"); } void CtdlUnregisterDeleteHook(void (*handler)(char *, long) ) { DeleteFunctionHook *cur, *p, *last; last = NULL; cur = DeleteHookTable; while (cur != NULL) { if (handler == cur->h_function_pointer ) { MODM_syslog(LOG_DEBUG, "Unregistered delete function\n"); p = cur->next; free(cur); if (last != NULL) last->next = p; else DeleteHookTable = p; cur = p; } else { last = cur; cur = cur->next; } } } void CtdlDestroyDeleteHooks(void) { DeleteFunctionHook *cur, *p; cur = DeleteHookTable; while (cur != NULL) { MODM_syslog(LOG_DEBUG, "Destroyed delete function\n"); p = cur->next; free(cur); cur = p; } DeleteHookTable = NULL; } void CtdlRegisterFixedOutputHook(char *content_type, void (*handler)(char *, int) ) { FixedOutputHook *newfcn; newfcn = (FixedOutputHook *) malloc(sizeof(FixedOutputHook)); newfcn->next = FixedOutputTable; newfcn->h_function_pointer = handler; safestrncpy(newfcn->content_type, content_type, sizeof newfcn->content_type); FixedOutputTable = newfcn; MOD_syslog(LOG_DEBUG, "Registered a new fixed output function for %s\n", newfcn->content_type); } void CtdlUnregisterFixedOutputHook(char *content_type) { FixedOutputHook *cur, *p, *last; last = NULL; cur = FixedOutputTable; while (cur != NULL) { /* This will also remove duplicates if any */ if (!strcasecmp(content_type, cur->content_type)) { MOD_syslog(LOG_DEBUG, "Unregistered fixed output function for %s\n", content_type); p = cur->next; free(cur); if (last != NULL) last->next = p; else FixedOutputTable = p; cur = p; } else { last = cur; cur = cur->next; } } } void CtdlDestroyFixedOutputHooks(void) { FixedOutputHook *cur, *p; cur = FixedOutputTable; while (cur != NULL) { MOD_syslog(LOG_DEBUG, "Destroyed fixed output function for %s\n", cur->content_type); p = cur->next; free(cur); cur = p; } FixedOutputTable = NULL; } /* returns nonzero if we found a hook and used it */ int PerformFixedOutputHooks(char *content_type, char *content, int content_length) { FixedOutputHook *fcn; for (fcn = FixedOutputTable; fcn != NULL; fcn = fcn->next) { if (!strcasecmp(content_type, fcn->content_type)) { (*fcn->h_function_pointer) (content, content_length); return(1); } } return(0); } void CtdlRegisterXmsgHook(int (*fcn_ptr) (char *, char *, char *, char *), int order) { XmsgFunctionHook *newfcn; newfcn = (XmsgFunctionHook *) malloc(sizeof(XmsgFunctionHook)); newfcn->next = XmsgHookTable; newfcn->order = order; newfcn->h_function_pointer = fcn_ptr; XmsgHookTable = newfcn; MOD_syslog(LOG_DEBUG, "Registered a new x-msg function (priority %d)\n", order); } void CtdlUnregisterXmsgHook(int (*fcn_ptr) (char *, char *, char *, char *), int order) { XmsgFunctionHook *cur, *p, *last; last = NULL; cur = XmsgHookTable; while (cur != NULL) { /* This will also remove duplicates if any */ if (fcn_ptr == cur->h_function_pointer && order == cur->order) { MOD_syslog(LOG_DEBUG, "Unregistered x-msg function " "(priority %d)\n", order); p = cur->next; free(cur); if (last != NULL) last->next = p; else XmsgHookTable = p; cur = p; } else { last = cur; cur = cur->next; } } } void CtdlDestroyXmsgHooks(void) { XmsgFunctionHook *cur, *p; cur = XmsgHookTable; while (cur != NULL) { MOD_syslog(LOG_DEBUG, "Destroyed x-msg function " "(priority %d)\n", cur->order); p = cur->next; free(cur); cur = p; } XmsgHookTable = NULL; } void CtdlRegisterServiceHook(int tcp_port, char *sockpath, void (*h_greeting_function) (void), void (*h_command_function) (void), void (*h_async_function) (void), const char *ServiceName) { ServiceFunctionHook *newfcn; char *message; char error[SIZ]; strcpy(error, ""); newfcn = (ServiceFunctionHook *) malloc(sizeof(ServiceFunctionHook)); message = (char*) malloc (SIZ + SIZ); newfcn->next = ServiceHookTable; newfcn->tcp_port = tcp_port; newfcn->sockpath = sockpath; newfcn->h_greeting_function = h_greeting_function; newfcn->h_command_function = h_command_function; newfcn->h_async_function = h_async_function; newfcn->ServiceName = ServiceName; if (sockpath != NULL) { newfcn->msock = ctdl_uds_server(sockpath, config.c_maxsessions, error); snprintf(message, SIZ, "Unix domain socket '%s': ", sockpath); } else if (tcp_port <= 0) { /* port -1 to disable */ MOD_syslog(LOG_INFO, "Service %s has been manually disabled, skipping\n", ServiceName); free (message); free(newfcn); return; } else { newfcn->msock = ctdl_tcp_server(config.c_ip_addr, tcp_port, config.c_maxsessions, error); snprintf(message, SIZ, "TCP port %s:%d: (%s) ", config.c_ip_addr, tcp_port, ServiceName); } if (newfcn->msock > 0) { ServiceHookTable = newfcn; strcat(message, "registered."); MOD_syslog(LOG_INFO, "%s\n", message); } else { AddPortError(message, error); strcat(message, "FAILED."); MOD_syslog(LOG_CRIT, "%s\n", message); free(newfcn); } free(message); } void CtdlUnregisterServiceHook(int tcp_port, char *sockpath, void (*h_greeting_function) (void), void (*h_command_function) (void), void (*h_async_function) (void) ) { ServiceFunctionHook *cur, *p, *last; last = NULL; cur = ServiceHookTable; while (cur != NULL) { /* This will also remove duplicates if any */ if (h_greeting_function == cur->h_greeting_function && h_command_function == cur->h_command_function && h_async_function == cur->h_async_function && tcp_port == cur->tcp_port && !(sockpath && cur->sockpath && strcmp(sockpath, cur->sockpath)) ) { if (cur->msock > 0) close(cur->msock); if (sockpath) { MOD_syslog(LOG_INFO, "Closed UNIX domain socket %s\n", sockpath); unlink(sockpath); } else if (tcp_port) { MOD_syslog(LOG_INFO, "Closed TCP port %d\n", tcp_port); } else { MOD_syslog(LOG_INFO, "Unregistered service \"%s\"\n", cur->ServiceName); } p = cur->next; free(cur); if (last != NULL) last->next = p; else ServiceHookTable = p; cur = p; } else { last = cur; cur = cur->next; } } } void CtdlShutdownServiceHooks(void) { /* sort of a duplicate of close_masters() but called earlier */ ServiceFunctionHook *cur; cur = ServiceHookTable; while (cur != NULL) { if (cur->msock != -1) { close(cur->msock); cur->msock = -1; if (cur->sockpath != NULL){ MOD_syslog(LOG_INFO, "[%s] Closed UNIX domain socket %s\n", cur->ServiceName, cur->sockpath); unlink(cur->sockpath); } else { MOD_syslog(LOG_INFO, "[%s] closing service\n", cur->ServiceName); } } cur = cur->next; } } void CtdlDestroyServiceHook(void) { const char *Text; ServiceFunctionHook *cur, *p; cur = ServiceHookTable; while (cur != NULL) { if (cur->msock != -1) { close(cur->msock); Text = "Closed"; } else { Text = " Not closing again"; } if (cur->sockpath) { MOD_syslog(LOG_INFO, "%s UNIX domain socket %s\n", Text, cur->sockpath); unlink(cur->sockpath); } else if (cur->tcp_port) { MOD_syslog(LOG_INFO, "%s TCP port %d\n", Text, cur->tcp_port); } else { MOD_syslog(LOG_INFO, "Destroyed service \"%s\"\n", cur->ServiceName); } p = cur->next; free(cur); cur = p; } ServiceHookTable = NULL; } void CtdlRegisterSearchFuncHook(void (*fcn_ptr)(int *, long **, const char *), char *name) { SearchFunctionHook *newfcn; if (!name || !fcn_ptr) { return; } newfcn = (SearchFunctionHook *) malloc(sizeof(SearchFunctionHook)); newfcn->next = SearchFunctionHookTable; newfcn->name = name; newfcn->fcn_ptr = fcn_ptr; SearchFunctionHookTable = newfcn; MOD_syslog(LOG_DEBUG, "Registered a new search function (%s)\n", name); } void CtdlUnregisterSearchFuncHook(void (*fcn_ptr)(int *, long **, const char *), char *name) { SearchFunctionHook *cur, *p, *last; last = NULL; cur = SearchFunctionHookTable; while (cur != NULL) { if (fcn_ptr && (cur->fcn_ptr == fcn_ptr) && name && !strcmp(name, cur->name)) { MOD_syslog(LOG_DEBUG, "Unregistered search function(%s)\n", name); p = cur->next; free (cur); if (last != NULL) last->next = p; else SearchFunctionHookTable = p; cur = p; } else { last = cur; cur = cur->next; } } } void CtdlDestroySearchHooks(void) { SearchFunctionHook *cur, *p; cur = SearchFunctionHookTable; SearchFunctionHookTable = NULL; while (cur != NULL) { p = cur->next; free(cur); cur = p; } } void CtdlModuleDoSearch(int *num_msgs, long **search_msgs, const char *search_string, const char *func_name) { SearchFunctionHook *fcn = NULL; for (fcn = SearchFunctionHookTable; fcn != NULL; fcn = fcn->next) { if (!func_name || !strcmp(func_name, fcn->name)) { (*fcn->fcn_ptr) (num_msgs, search_msgs, search_string); return; } } *num_msgs = 0; } int CheckTDAPVeto (int DBType, StrBuf *ErrMsg) { int Result = 0; TDAPVetoHookFunctionHook *fcn = NULL; for (fcn = TDAPVetoHookTable; (fcn != NULL) && (Result == 0); fcn = fcn->next) { if (fcn->eventtype == DBType) { Result = (*fcn->h_function_pointer) (ErrMsg); } } return Result; } void PerformSessionHooks(int EventType) { SessionFunctionHook *fcn = NULL; for (fcn = SessionHookTable; fcn != NULL; fcn = fcn->next) { if (fcn->eventtype == EventType) { if (EventType == EVT_TIMER) { pthread_setspecific(MyConKey, NULL); /* for every hook */ } (*fcn->h_function_pointer) (); } } } void PerformUserHooks(ctdluser *usbuf, int EventType) { UserFunctionHook *fcn = NULL; for (fcn = UserHookTable; fcn != NULL; fcn = fcn->next) { if (fcn->eventtype == EventType) { (*fcn->h_function_pointer) (usbuf); } } } int PerformMessageHooks(struct CtdlMessage *msg, recptypes *recps, int EventType) { MessageFunctionHook *fcn = NULL; int total_retval = 0; /* Other code may elect to protect this message from server-side * handlers; if this is the case, don't do anything. MOD_syslog(LOG_DEBUG, "** Event type is %d, flags are %d\n", EventType, msg->cm_flags); */ if (msg->cm_flags & CM_SKIP_HOOKS) { MODM_syslog(LOG_DEBUG, "Skipping hooks\n"); return(0); } /* Otherwise, run all the hooks appropriate to this event type. */ for (fcn = MessageHookTable; fcn != NULL; fcn = fcn->next) { if (fcn->eventtype == EventType) { total_retval = total_retval + (*fcn->h_function_pointer) (msg, recps); } } /* Return the sum of the return codes from the hook functions. If * this is an EVT_BEFORESAVE event, a nonzero return code will cause * the save operation to abort. */ return total_retval; } int PerformRoomHooks(struct ctdlroom *target_room) { RoomFunctionHook *fcn; int total_retval = 0; MOD_syslog(LOG_DEBUG, "Performing room hooks for <%s>\n", target_room->QRname); for (fcn = RoomHookTable; fcn != NULL; fcn = fcn->next) { total_retval = total_retval + (*fcn->fcn_ptr) (target_room); } /* Return the sum of the return codes from the hook functions. */ return total_retval; } int PerformNetprocHooks(struct CtdlMessage *msg, char *target_room) { NetprocFunctionHook *fcn; int total_retval = 0; for (fcn = NetprocHookTable; fcn != NULL; fcn = fcn->next) { total_retval = total_retval + (*fcn->h_function_pointer) (msg, target_room); } /* Return the sum of the return codes from the hook functions. * A nonzero return code will cause the message to *not* be imported. */ return total_retval; } void PerformDeleteHooks(char *room, long msgnum) { DeleteFunctionHook *fcn; for (fcn = DeleteHookTable; fcn != NULL; fcn = fcn->next) { (*fcn->h_function_pointer) (room, msgnum); } } int PerformXmsgHooks(char *sender, char *sender_email, char *recp, char *msg) { XmsgFunctionHook *fcn; int total_sent = 0; int p; for (p=0; pnext) { if (fcn->order == p) { total_sent += (*fcn->h_function_pointer) (sender, sender_email, recp, msg); } } /* Break out of the loop if a higher-priority function * successfully delivered the message. This prevents duplicate * deliveries to local users simultaneously signed onto * remote services. */ if (total_sent) break; } return total_sent; } /* * Dirty hack until we impliment a hook mechanism for this */ void CtdlModuleStartCryptoMsgs(char *ok_response, char *nosup_response, char *error_response) { #ifdef HAVE_OPENSSL CtdlStartTLS (ok_response, nosup_response, error_response); #endif } void DebugModulesEnable(const int n) { DebugModules = n; } CTDL_MODULE_INIT(modules) { if (!threading) { CtdlRegisterDebugFlagHook(HKEY("modules"), DebugModulesEnable, &DebugModules); } return "modules"; } citadel-9.01/guesstimezone.sh0000755000000000000000000002175612507024051015015 0ustar rootroot#!/bin/sh # guesstimezone.sh - an ugly hack of a script to try to guess the time # zone currently in use on the host system, and output its name. # Copyright (c) by Art Cancro # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the Free # Software Foundation; either version 3 of the License, or (at your option) # any later version. # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # more details. # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 675 Mass Ave, Cambridge, MA 02139, USA. convert_timezone() { case "$1" in (right/*|posix/*) convert_timezone "${1#*/}" ;; ("Africa/Asmera") echo "Africa/Asmara" ;; ("America/Argentina/ComodRivadavia"|"America/Catamarca") echo "America/Argentina/Catamarca" ;; ("America/Buenos_Aires") echo "America/Argentina/Buenos_Aires" ;; ("America/Cordoba"|"America/Rosario") echo "America/Argentina/Cordoba" ;; ("America/Jujuy") echo "America/Argentina/Jujuy" ;; ("America/Mendoza") echo "America/Argentina/Mendoza" ;; ("Antarctica/South_Pole") echo "Antarctica/McMurdo" ;; "Asia/Ashkhabad") echo "Asia/Ashgabat" ;; ("Asia/Calcutta") echo "Asia/Kolkata" ;; "Asia/Chungking") echo "Asia/Chongqing" ;; "Asia/Dacca") echo "Asia/Dhaka" ;; ("Asia/Katmandu") echo "Asia/Kathmandu" ;; "Asia/Macao") echo "Asia/Macau" ;; ("Asia/Saigon") echo "Asia/Ho_Chi_Minh" ;; "Asia/Thimbu") echo "Asia/Thimphu" ;; "Asia/Ulan_Bator") echo "Asia/Ulaanbaatar" ;; "Atlantic/Faeroe") echo "Atlantic/Faroe" ;; "Australia/ACT" | "Australia/NSW") echo "Australia/Sydney" ;; "Australia/LHI") echo "Australia/Lord_Howe" ;; "Australia/North") echo "Australia/Darwin" ;; "Australia/Queensland") echo "Australia/Brisbane" ;; "Australia/South") echo "Australia/Adelaide" ;; "Australia/Tasmania") echo "Australia/Hobart" ;; "Australia/Victoria") echo "Australia/Melbourne" ;; "Australia/West") echo "Australia/Perth" ;; "Brazil/Acre") echo "America/Rio_Branco" ;; "Brazil/DeNoronha") echo "America/Noronha" ;; "Brazil/East") echo "America/Sao_Paulo" ;; "Brazil/West") echo "America/Manaus" ;; "Canada/Atlantic") echo "America/Halifax" ;; "Canada/Central") echo "America/Winnipeg" ;; "Canada/East-Saskatchewan") echo "America/Regina" ;; "Canada/Eastern") echo "America/Toronto" ;; "Canada/Mountain") echo "America/Edmonton" ;; "Canada/Newfoundland") echo "America/St_Johns" ;; "Canada/Pacific") echo "America/Vancouver" ;; "Canada/Saskatchewan") echo "America/Regina" ;; "Canada/Yukon") echo "America/Whitehorse" ;; "CET") echo "Europe/Paris" ;; "Chile/Continental") echo "America/Santiago" ;; "Chile/EasterIsland") echo "Pacific/Easter" ;; "CST6CDT") echo "SystemV/CST6CDT" ;; "Cuba") echo "America/Havana" ;; "EET") echo "Europe/Helsinki" ;; "Egypt") echo "Africa/Cairo" ;; "Eire") echo "Europe/Dublin" ;; "EST") echo "SystemV/EST5" ;; "EST5EDT") echo "SystemV/EST5EDT" ;; "GB") echo "Europe/London" ;; "GB-Eire") echo "Europe/London" ;; "GMT") echo "Etc/GMT" ;; "GMT0") echo "Etc/GMT0" ;; "GMT-0") echo "Etc/GMT-0" ;; "GMT+0") echo "Etc/GMT+0" ;; "Greenwich") echo "Etc/Greenwich" ;; "Hongkong") echo "Asia/Hong_Kong" ;; "HST") echo "Pacific/Honolulu" ;; "Iceland") echo "Atlantic/Reykjavik" ;; "Iran") echo "Asia/Tehran" ;; "Israel") echo "Asia/Tel_Aviv" ;; "Jamaica") echo "America/Jamaica" ;; "Japan") echo "Asia/Tokyo" ;; "Kwajalein") echo "Pacific/Kwajalein" ;; "Libya") echo "Africa/Tripoli" ;; "MET") echo "Europe/Paris" ;; "Mexico/BajaNorte") echo "America/Tijuana" ;; "Mexico/BajaSur") echo "America/Mazatlan" ;; "Mexico/General") echo "America/Mexico_City" ;; "Mideast/Riyadh87") echo "Asia/Riyadh87" ;; "Mideast/Riyadh88") echo "Asia/Riyadh88" ;; "Mideast/Riyadh89") echo "Asia/Riyadh89" ;; "MST") echo "SystemV/MST7" ;; "MST7MDT") echo "SystemV/MST7MDT" ;; "Navajo") echo "America/Denver" ;; "NZ") echo "Pacific/Auckland" ;; "NZ-CHAT") echo "Pacific/Chatham" ;; "Poland") echo "Europe/Warsaw" ;; "Portugal") echo "Europe/Lisbon" ;; "PRC") echo "Asia/Shanghai" ;; "PST8PDT") echo "SystemV/PST8PDT" ;; "ROC") echo "Asia/Taipei" ;; "ROK") echo "Asia/Seoul" ;; "Singapore") echo "Asia/Singapore" ;; "Turkey") echo "Europe/Istanbul" ;; "UCT") echo "Etc/UCT" ;; "Universal") echo "Etc/UTC" ;; "US/Alaska") echo "America/Anchorage" ;; "US/Aleutian") echo "America/Adak" ;; "US/Arizona") echo "America/Phoenix" ;; "US/Central") echo "America/Chicago" ;; "US/East-Indiana") echo "America/Indianapolis" ;; "US/Eastern") echo "America/New_York" ;; "US/Hawaii") echo "Pacific/Honolulu" ;; "US/Indiana-Starke") echo "America/Indianapolis" ;; "US/Michigan") echo "America/Detroit" ;; "US/Mountain") echo "America/Denver" ;; "US/Pacific") echo "America/Los_Angeles" ;; "US/Samoa") echo "Pacific/Pago_Pago" ;; "UTC") echo "Etc/UTC" ;; "WET") echo "Europe/Lisbon" ;; "W-SU") echo "Europe/Moscow" ;; "Zulu") echo "Etc/UTC" ;; *) echo "$1" ;; esac } md5sum /dev/null 2>/dev/null || exit 1 # this check will find the timezone if /etc/localtime is a link to the right timezone file # or that file has been copied to /etc/localtime and not changed afterwards LOCALTIMESUM=`md5sum /etc/localtime | awk ' { print $1 } ' 2>/dev/null` find /usr/share/zoneinfo -type f -print | while read filename do THISTIMESUM=`md5sum $filename | awk ' { print $1 } '` if [ $LOCALTIMESUM = $THISTIMESUM ] ; then echo $filename | cut -c21- exit 0 fi done 2>/dev/null # seems we haven't found the timezone yet, let's see whether /etc/timezone has some info if [ -e /etc/timezone ]; then TIMEZONE="$(head -n 1 /etc/timezone)" TIMEZONE="${TIMEZONE%% *}" TIMEZONE="${TIMEZONE##/}" TIMEZONE="${TIMEZONE%%/}" TIMEZONE="$(convert_timezone $TIMEZONE)" if [ -f "/usr/share/zoneinfo/$TIMEZONE" ] ; then echo $TIMEZONE exit 0 fi fi exit 2 citadel-9.01/database.h0000644000000000000000000000400312507024051013454 0ustar rootroot/* * Copyright (c) 1987-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #ifndef DATABASE_H #define DATABASE_H void open_databases (void); void close_databases (void); int cdb_store (int cdb, const void *key, int keylen, void *data, int datalen); int cdb_delete (int cdb, void *key, int keylen); struct cdbdata *cdb_fetch (int cdb, const void *key, int keylen); void cdb_free (struct cdbdata *cdb); void cdb_rewind (int cdb); struct cdbdata *cdb_next_item (int cdb); void cdb_close_cursor(int cdb); void cdb_begin_transaction(void); void cdb_end_transaction(void); void cdb_allocate_tsd(void); void cdb_free_tsd(void); void cdb_check_handles(void); void cdb_trunc(int cdb); void *checkpoint_thread(void *arg); void cdb_chmod_data(void); void cdb_checkpoint(void); void check_handles(void *arg); void cdb_cull_logs(void); /* * Database records beginning with this magic number are assumed to * be compressed. In the event that a database record actually begins with * this magic number, we *must* compress it whether we want to or not, * because the fetch function will try to uncompress it anyway. * * (No need to #ifdef this stuff; it compiles ok even if zlib is not present * and doesn't declare anything so it won't bloat the code) */ #define COMPRESS_MAGIC 0xc0ffeeee struct CtdlCompressHeader { int magic; size_t uncompressed_len; size_t compressed_len; }; typedef enum __eCheckType { eCheckExist, eCheckUpdate, eUpdate, eWrite }eCheckType; time_t CheckIfAlreadySeen(const char *Facility, StrBuf *guid, time_t now, time_t antiexpire, eCheckType cType, long ccid, long ioid); #endif /* DATABASE_H */ citadel-9.01/messages/0000755000000000000000000000000012507024051013351 5ustar rootrootcitadel-9.01/messages/unlisted0000644000000000000000000000027512507024051015127 0ustar rootrootThe user list is provided as a service to the users of this system. Please don't choose to be unlisted unless you really find it it necessary. By the way, aides still see unlisted entries. citadel-9.01/messages/mainmenu0000644000000000000000000000116612507024051015111 0ustar rootroot ----------------------------------------------------------------------- Room cmds: nown rooms, oto next room, <.G>oto a specific room, kip this room, bandon this room, ap this room, ngoto (move back) Message cmds: ew msgs, orward read, everse read, ld msgs, ast five msgs, nter a message General cmds: help, erminate, hat, ho is online Misc: toggle eXpert mode, irectory (Type .Help SUMMARY for extended commands, to hide this menu) ----------------------------------------------------------------------- citadel-9.01/messages/register0000644000000000000000000000040312507024051015115 0ustar rootroot Please enter the correct information here. In most cases we do not voice validate, but we must have this information in order to discourage vandalism. If you skip this section, you will NOT get access! WE MEAN IT! citadel-9.01/messages/readopt0000644000000000000000000000036512507024051014736 0ustar rootrootOne of: io irectory onfiguration ile unformatted nfo file ast five messages ew messages ld messages everse ystem info extfile serlist file using modem file using modem file using modem citadel-9.01/messages/dotopt0000644000000000000000000000040712507024051014606 0ustar rootrootOne of: nter nown ead ide options (aides only) oto: (type room name) kip to: (type room name) elp: (type name of help file) apped rooms list erminate <+>, <-> skip to next, previous room <>>, <<> skip to next, previous floor citadel-9.01/messages/aideopt0000644000000000000000000000031712507024051014722 0ustar rootrootOne of: dit room ile elete ile end over net ile ove edit nfo file ill room oom nvite user oom ick out user ser edit alidate new users ho knows room citadel-9.01/messages/newuser0000644000000000000000000000007212507024051014763 0ustar rootroot < this new user policy resides in ^bbsdir/newuser > citadel-9.01/messages/goodbye0000644000000000000000000000026512507024051014727 0ustar rootroot < this logoff banner resides in ^bbsdir/goodbye > Thanks for visiting ^humannode - please come back soon! Also be sure to visit UNCENSORED! BBS at uncensored.citadel.org citadel-9.01/messages/entopt0000644000000000000000000000032112507024051014601 0ustar rootrootOne of: message using scii io onfiguration message with ditor loor reistration essage

    assword oom extfile file using modem file using modem file using modem citadel-9.01/messages/roomaccess0000644000000000000000000000150312507024051015431 0ustar rootrootMost rooms are public. Anyone on the system may get into a public room. Private rooms may take several forms: Hidden: to gain access to this type of room, a user need only know the room's name. No hints are given by the system. Passworded: same as hidden but the user must also know a password to get access. Invitation-only: users may only gain access to this type of room if an Admin gives it to them. Personal: this type of room acts as a user-specific "mailbox." Once a user has access to a private room, it shows up on the known rooms list and acts the same as any other room. Admins may kick users out of any type of private room, and out of excludable rooms. Please also keep in mind that Admins have access to ALL private rooms. Before making it private, think about it. Does it really NEED to be private? citadel-9.01/messages/help0000644000000000000000000000314612507024051014230 0ustar rootroot ^variantname Help Menu ? Help. (Typing a '?' will give you a menu almost anywhere) A Abandon this room where you stopped reading, goto next room. C Chat (multiuser chat, where available) D Prints directory, if there is one in the current room. E Enter a message. F Read all messages in the room, forward. G Goto next room which has UNREAD messages. H Help. Same as '?' I Reads the Information file for this room. K List of Known rooms. L Reads the last five messages in the room. N Reads all new messages in the room. O Reads all old messages, backwards. P Page another user (send an instant message) R Reads all messages in the room, in reverse order. S Skips current room without making its messages old. T Terminate (logout) U Ungoto (returns to the last room you were in) W Displays who is currently logged in. X Toggle eXpert mode (menus and help blurbs on/off) Z Zap (forget) room. (Removes the room from your list) + - Goto next, previous room on current floor. > < Goto next, previous floor. * Enter any locally installed 'doors'. In addition, there are dot commands. You hit the . (dot), then press the first letter of each word of the command. As you hit the letters, the words pop onto your screen. Exceptions: after you hit .Help or .Goto, the remainder of the command is a help file name or room name. *** USE .elp ? or .elp SUMMARY for additional help *** citadel-9.01/messages/changepw0000644000000000000000000000051512507024051015071 0ustar rootroot You should choose a password that no one will ever be able to guess, so no one can abuse your account. In addition, if you even suspect that someone knows your password, change it immediately. Your password will not be echoed to the screen when you type it, and unless you tell someone, no one can find out what your password is. citadel-9.01/messages/entermsg0000644000000000000000000000025012507024051015115 0ustar rootrootEntering message. Word wrap will give you soft linebreaks. Pressing the 'enter' key will give you a hard linebreak and an indent. Press 'enter' twice when finished. citadel-9.01/messages/hello0000644000000000000000000000016112507024051014375 0ustar rootroot Welcome to ^humannode! This logon banner resides in ^bbsdir/hello -- please customize it for your site. citadel-9.01/packageversion0000644000000000000000000000000212507024051014456 0ustar rootroot1 citadel-9.01/housekeeping.h0000644000000000000000000000013212507024051014375 0ustar rootrootvoid check_sched_shutdown(void); void check_ref_counts(void); void do_housekeeping(void); citadel-9.01/journaling.c0000644000000000000000000001711412507024051014062 0ustar rootroot/* * Message journaling functions. */ #include #include #include "ctdl_module.h" #include "citserver.h" #include "user_ops.h" #include "serv_vcard.h" /* Needed for vcard_getuser and extract_inet_email_addrs */ #include "internet_addressing.h" #include "journaling.h" struct jnlq *jnlq = NULL; /* journal queue */ /* * Hand off a copy of a message to be journalized. */ void JournalBackgroundSubmit(struct CtdlMessage *msg, StrBuf *saved_rfc822_version, recptypes *recps) { struct jnlq *jptr = NULL; /* Avoid double journaling! */ if (!CM_IsEmpty(msg, eJournal)) { FreeStrBuf(&saved_rfc822_version); return; } jptr = (struct jnlq *)malloc(sizeof(struct jnlq)); if (jptr == NULL) { FreeStrBuf(&saved_rfc822_version); return; } memset(jptr, 0, sizeof(struct jnlq)); if (recps != NULL) memcpy(&jptr->recps, recps, sizeof(recptypes)); if (!CM_IsEmpty(msg, eAuthor)) jptr->from = strdup(msg->cm_fields[eAuthor]); if (!CM_IsEmpty(msg, eNodeName)) jptr->node = strdup(msg->cm_fields[eNodeName]); if (!CM_IsEmpty(msg, erFc822Addr)) jptr->rfca = strdup(msg->cm_fields[erFc822Addr]); if (!CM_IsEmpty(msg, eMsgSubject)) jptr->subj = strdup(msg->cm_fields[eMsgSubject]); if (!CM_IsEmpty(msg, emessageId)) jptr->msgn = strdup(msg->cm_fields[emessageId]); jptr->rfc822 = SmashStrBuf(&saved_rfc822_version); /* Add to the queue */ begin_critical_section(S_JOURNAL_QUEUE); jptr->next = jnlq; jnlq = jptr; end_critical_section(S_JOURNAL_QUEUE); } /* * Convert a local user name to an internet email address for the journal */ /* * TODODRW: change this into a CtdlModuleDo type function that returns alternative address info * for this local user. Something like CtdlModuleGoGetAddr(char *localuser, int type, char *alt_addr, size_t alt_addr_len) * where type can be ADDR_EMAIL, ADDR_FIDO, ADDR_UUCP, ADDR_WEB, ADDR_POSTAL etc etc. * This then begs the question of what should be returned. Is it wise to return a single char* using a comma as a * delimiter? Or would it be better to return a linked list of some kind? */ void local_to_inetemail(char *inetemail, char *localuser, size_t inetemail_len) { struct ctdluser us; struct vCard *v; strcpy(inetemail, ""); if (CtdlGetUser(&us, localuser) != 0) { return; } v = vcard_get_user(&us); if (v == NULL) { return; } extract_inet_email_addrs(inetemail, inetemail_len, NULL, 0, v, 1); vcard_free(v); } /* * Called by JournalRunQueue() to send an individual message. */ void JournalRunQueueMsg(struct jnlq *jmsg) { struct CtdlMessage *journal_msg = NULL; recptypes *journal_recps = NULL; StrBuf *message_text = NULL; char mime_boundary[256]; long mblen; long rfc822len; char recipient[256]; char inetemail[256]; static int seq = 0; int i; if (jmsg == NULL) return; journal_recps = validate_recipients(config.c_journal_dest, NULL, 0); if (journal_recps != NULL) { if ( (journal_recps->num_local > 0) || (journal_recps->num_internet > 0) || (journal_recps->num_ignet > 0) || (journal_recps->num_room > 0) ) { /* * Construct journal message. * Note that we are transferring ownership of some of the memory here. */ journal_msg = malloc(sizeof(struct CtdlMessage)); memset(journal_msg, 0, sizeof(struct CtdlMessage)); journal_msg->cm_magic = CTDLMESSAGE_MAGIC; journal_msg->cm_anon_type = MES_NORMAL; journal_msg->cm_format_type = FMT_RFC822; CM_SetField(journal_msg, eJournal, HKEY("is journal")); CM_SetField(journal_msg, eAuthor, jmsg->from, strlen(jmsg->from)); CM_SetField(journal_msg, eNodeName, jmsg->node, strlen(jmsg->node)); CM_SetField(journal_msg, erFc822Addr, jmsg->rfca, strlen(jmsg->rfca)); CM_SetField(journal_msg, eMsgSubject, jmsg->subj, strlen(jmsg->subj)); mblen = snprintf(mime_boundary, sizeof(mime_boundary), "--Citadel-Journal-%08lx-%04x--", time(NULL), ++seq); rfc822len = strlen(jmsg->rfc822); message_text = NewStrBufPlain(NULL, rfc822len + sizeof(recptypes) + 1024); /* * Here is where we begin to compose the journalized message. * NOTE: the superfluous "Content-Identifer: ExJournalReport" header was * requested by a paying customer, and yes, it is intentionally * spelled wrong. Do NOT remove or change it. */ StrBufAppendBufPlain( message_text, HKEY("Content-type: multipart/mixed; boundary=\""), 0); StrBufAppendBufPlain(message_text, mime_boundary, mblen, 0); StrBufAppendBufPlain( message_text, HKEY("\"\r\n" "Content-Identifer: ExJournalReport\r\n" "MIME-Version: 1.0\r\n" "\n" "--"), 0); StrBufAppendBufPlain(message_text, mime_boundary, mblen, 0); StrBufAppendBufPlain( message_text, HKEY("\r\n" "Content-type: text/plain\r\n" "\r\n" "Sender: "), 0); if (CM_IsEmpty(journal_msg, eAuthor)) StrBufAppendBufPlain( message_text, journal_msg->cm_fields[eAuthor], -1, 0); else StrBufAppendBufPlain( message_text, HKEY("(null)"), 0); if (!CM_IsEmpty(journal_msg, erFc822Addr)) { StrBufAppendPrintf(message_text, " <%s>", journal_msg->cm_fields[erFc822Addr]); } else if (!CM_IsEmpty(journal_msg, eNodeName)) { StrBufAppendPrintf(message_text, " @ %s", journal_msg->cm_fields[eNodeName]); } else StrBufAppendBufPlain( message_text, HKEY(" "), 0); StrBufAppendBufPlain( message_text, HKEY("\r\n" "Message-ID: <"), 0); StrBufAppendBufPlain(message_text, jmsg->msgn, -1, 0); StrBufAppendBufPlain( message_text, HKEY(">\r\n" "Recipients:\r\n"), 0); if (jmsg->recps.num_local > 0) { for (i=0; irecps.num_local; ++i) { extract_token(recipient, jmsg->recps.recp_local, i, '|', sizeof recipient); local_to_inetemail(inetemail, recipient, sizeof inetemail); StrBufAppendPrintf(message_text, " %s <%s>\r\n", recipient, inetemail); } } if (jmsg->recps.num_ignet > 0) { for (i=0; irecps.num_ignet; ++i) { extract_token(recipient, jmsg->recps.recp_ignet, i, '|', sizeof recipient); StrBufAppendPrintf(message_text, " %s\r\n", recipient); } } if (jmsg->recps.num_internet > 0) { for (i=0; irecps.num_internet; ++i) { extract_token(recipient, jmsg->recps.recp_internet, i, '|', sizeof recipient); StrBufAppendPrintf(message_text, " %s\r\n", recipient); } } StrBufAppendBufPlain( message_text, HKEY("\r\n" "--"), 0); StrBufAppendBufPlain(message_text, mime_boundary, mblen, 0); StrBufAppendBufPlain( message_text, HKEY("\r\n" "Content-type: message/rfc822\r\n" "\r\n"), 0); StrBufAppendBufPlain(message_text, jmsg->rfc822, rfc822len, 0); StrBufAppendBufPlain( message_text, HKEY("--"), 0); StrBufAppendBufPlain(message_text, mime_boundary, mblen, 0); StrBufAppendBufPlain( message_text, HKEY("--\r\n"), 0); CM_SetAsFieldSB(journal_msg, eMesageText, &message_text); free(jmsg->rfc822); free(jmsg->msgn); jmsg->rfc822 = NULL; jmsg->msgn = NULL; /* Submit journal message */ CtdlSubmitMsg(journal_msg, journal_recps, "", 0); CM_Free(journal_msg); } free_recipients(journal_recps); } /* We are responsible for freeing this memory. */ free(jmsg); } /* * Run the queue. */ void JournalRunQueue(void) { struct jnlq *jptr = NULL; while (jnlq != NULL) { begin_critical_section(S_JOURNAL_QUEUE); if (jnlq != NULL) { jptr = jnlq; jnlq = jnlq->next; } end_critical_section(S_JOURNAL_QUEUE); JournalRunQueueMsg(jptr); } } citadel-9.01/autom4te.cache/0000755000000000000000000000000012507024062014350 5ustar rootrootcitadel-9.01/techdoc/0000755000000000000000000000000012507024051013153 5ustar rootrootcitadel-9.01/techdoc/netconfigs.txt0000644000000000000000000001062012507024051016052 0ustar rootroot Description of the files in the "netconfigs" directory These files contain a set of network configurations for a room. They are stored in the directory $CTDLDIR/netconfigs and are named according to each room's internal ID number. When a room is deleted, its network configuration file is deleted as well. The configuration file contains one or more lines of text, each line containing a configuration option. These lines may specify message pointers, room sharing instructions, mailing list recipients, etc. Fields are separated by the vertical bar character ("|") and there will always be at least one field on each line. INSTRUCTION: lastsent SYNTAX: lastsent|0000000 DESCRIPTION: Defines the *local* message number of the last message in this room which we have performed outbound network processing on. Any batch job which sends out messages should do stuff. INSTRUCTION: listrecp SYNTAX: listrecp|friko@mumjiboolean.com DESCRIPTION: Defines a recipient to whom all messages in this room should be sent. This is used for "list serve" applications. INSTRUCTION: digestrecp SYNTAX: digestrecp|friko@mumjiboolean.com DESCRIPTION: Defines a recipient to whom all messages in this room should be sent. This is used for "list serve" applications. The difference between listrecps and digestrecps is that the latter will have messages embedded inside a message sent by the listserver. The message will appear to be sent by the room's e-mail address instead of the sender's e-mail address. INSTRUCTION: ignet_push_share SYNTAX: ignet_push_share|uncnsrd (or) ignet_push_share|uncnsrd|Foo Bar Baz DESCRIPTION: Specifies that the second argument is the name of a neighboring node on an IGnet (Citadel networking) network, to which this room should be pushed (spooled). Conceptually, this node will have a corresponding record pushing data in the other direction. If the third argument is present, it is the name of the corresponding room on the remote node. This allows a room to be shared even when the room name is different on each node. Such a configuration *must* be configured mutually: each node must know the name of the room on the other. INSTRUCTION: subpending SYNTAX: subpending|friko@mumjiboolean.com|listrecp|A234Z|1234567890|http://foo.com/lists "Subscription pending" for the specified address. This means that someone has requested to subscribe an e-mail address (in this case, friko@mumjiboolean.com) to the list. The third parameter is either "list" or "digest" to specify a normal subscription or a digest subscription. The fourth parameter is an authentication token which is generated by the server and e-mailed to the specified address. When the user comes back and supplies this token, the subscription is complete. The fifth parameter is a simple timestamp, so that we may purge old records which were never confirmed. The sixth field is the URL of the web page used to enter the subscription request, minus any parameters. INSTRUCTION: unsubpending SYNTAX: unsubpending|friko@mumjiboolean.com|A234Z|1234567890|http://foo.com/lists Similar to the 'subpending' command, except this one is for unsubscribe requests. The same rules apply with regard to the token and the web page. INSTRUCTION: participate SYNTAX: participate|list-addr@example.com This allows a room to be a *subscriber* to an external mailing list somewhere. The room's email address should be subscribed to the list, and the external listserv should be told "do not send me my own messages." Any messages which originated on Citadel (as opposed to messages which arrived via email, such as those from the list) will be sent to the list, with the display name of the author kept intact, but the email address changed to the address of the room. This is somewhat finicky, which is why it is not officially supported, and why there are no facilities in the user interface to configure it. If you want to use this option you are on your own. INSTRUCTION: pop3client SYNTAX: pop3client|hostname|username|password|keep|interval Periodically fetch messages from a remote POP3 account and store them in this room. Messages will be deleted from the remote account once successfully stored. if Keep is set, messages won't be erased on the remote host. Interval can specify a bigger value than the system configs value. citadel-9.01/techdoc/test_suite.txt0000644000000000000000000000305712507024051016111 0ustar rootrootSYSTEM TESTING PROPOSAL This document is intended as a discussion of possible automated tests. It does not describe any existing tests. --- First we should create a client that leverages expect (or something similar) to perform automated testing of the client interface. Tests can be written as expect scripts. Each system being tested will need to create an aide level user for the test client to connect as. The test client will create another user to carry out the tests. This allows the aide level user to vary the level of the test user and check the access level code. ---- For a first step each test site should create a test user that can send internet mail. This test user needs some sieve rules to forward mail around (eventually this will be created automatically by the test client). These rules will forward mail to other test users at other sites participating in the test system and to a networked room. Each system participating in the test should share some rooms. The idea is: 1. A test site posts a message to its test user using citmail or some other email prog. 2. The sieve rules forward the message to each of the other test users at the other sites. 3. The sieve rules for the other test users detect that the message was forwarded to them and they file it into a networked room 4. By virtue of the networked room the message returns to the originating system where the administrator can see it. Once I (davew) have written my module to alter the message body we can have it add text to the message to indicate the full path of the message. citadel-9.01/techdoc/delivery-list.txt0000644000000000000000000000775312507024051016524 0ustar rootrootDescription of the custom MIME type "application/x-citadel-delivery-list" This MIME type may be found in the outbound queue room on Citadel systems, which is typically called "__CitadelSMTPspoolout__". The room is flagged as a "system" room, which means that it's completely hidden to everyone, although an Admin can get to it if the full name is specified (but unlike a normal hidden room, it does not appear in the known rooms list on subsequent visits). Messages in this format contain delivery instructions. Therefore, for each message in the queue to be delivered to one or more recipients, there will be *two* messages in the room: one containing the actual message, and the other containing delivery instructions. It is expected that the instructions message may be replaced at any time (using an Exclusive ID field) if delivery to only some of the recipients has been accomplished. Citadel keeps reference counts of each message on disk. Therefore if a message contains a mixture of local and remote recipients, there may be two or more references to the message itself, one of them being the one in the queue. A delivery list contains one or more lines of text, each line containing a single instruction (usually a recipient). Fields are separated by the vertical bar character ("|") and there will always be at least one field on each line. -- Once per Queue-Item -- INSTRUCTION: msgid SYNTAX: msgid|0000000 DESCRIPTION: Defines the actual message for which we are providing delivery instructions. This instruction must precede all the others. When all deliveries have either succeeded or failed, both the instructions and the copy of the message itself in the queue room should be deleted. The second parameter specifies the message ID in the local database. INSTRUCTION: submitted SYNTAX: submitted|999999999 DESCRIPTION: Contains a timestamp designating when this message was first entered into the system. INSTRUCTION: attempted SYNTAX: attempted|999999999 DESCRIPTION: Contains a timestamp designating the date/time of the last delivery attempt. INSTRUCTION: retry SYNTAX: retry|9999999 DESCRIPTION: Citadel does not retry SMTP delivery at a fixed interval. Instead, it starts at a nominal interval (15 minutes by default) and then doubles the interval after each failed attempt. This instruction contains the interval which should currently be followed (and doubled again, if the next delivery fails). INSTRUCTION: bounceto SYNTAX: bounceto|Big Bad Sender[@host] DESCRIPTION: Where to send "bounce" messages (delivery status notifications). The contents of the second field are supplied as the "recipient" field to CtdlSaveMsg(), and therefore may be a local username, a user on another Citadel, or an Internet e-mail address. INSTRUCTION: envelope_from SYNTAX: envelope_from|blurdybloop@example.com DESCRIPTION: Sets a value to be used as the envelope sender during the 'MAIL FROM:' phase of the SMTP transaction. If an envelope sender is not supplied, one is extracted from the message body. INSTRUCTION: source_room SYNTAX: source_room|Citadel Support DESCRIPTION: when sending mailinglist rooms this contains the source room for displaying and messaging purposes. -- Once per Remote-part per Queue-Item -- INSTRUCTION: remote SYNTAX: remote|friko@mumjiboolean.com|0|delivery status message DESCRIPTION: Names a recipient on a remote system to which the message should be delivered. The third parameter may contain any of the following values: 0 = No delivery has yet been attempted 2 = Delivery was successful 3 = transient error state; like connection failure or DNS lookup failure 4 = A transient error was experienced ... try again later 5 = Delivery to this address failed permanently. The error message should be placed in the fourth field so that a bounce message may be generated. citadel-9.01/techdoc/hack.txt0000644000000000000000000005151412507024051014630 0ustar rootroot ------------------------------------------------------ The totally incomplete guide to Citadel internals ------------------------------------------------------ Citadel has evolved quite a bit since its early days, and the data structures have evolved with it. This document provides a rough overview of how the system works internally. For details you're going to have to dig through the code, but this'll get you started. DATABASE TABLES --------------- As you probably already know by now, Citadel uses a group of tables stored with a record manager (usually Berkeley DB). Since we're using a record manager rather than a relational database, all record structures are managed by Citadel. Here are some of the tables we keep on disk: USER RECORDS ------------ This table contains all user records. It's indexed by user name (translated to lower case for indexing purposes). The records in this file look something like this: struct ctdluser { /* User record */ int version; /* Cit vers. which created this rec */ uid_t uid; /* Associate with a unix account? */ char password[32]; /* password (for Citadel-only users)*/ unsigned flags; /* See US_ flags below */ long timescalled; /* Total number of logins */ long posted; /* Number of messages posted (ever) */ CIT_UBYTE axlevel; /* Access level */ long usernum; /* User number (never recycled) */ time_t lastcall; /* Last time the user called */ int USuserpurge; /* Purge time (in days) for user */ char fullname[64]; /* Name for Citadel messages & mail */ }; Most fields here should be fairly self-explanatory. The ones that might deserve some attention are: uid -- if uid is not the same as the uid Citadel is running as, then the account is assumed to belong to the user on the underlying Unix system with that uid. This allows us to require the user's OS password instead of having a separate Citadel password. usernum -- these are assigned sequentially, and NEVER REUSED. This is important because it allows us to use this number in other data structures without having to worry about users being added/removed later on, as you'll see later in this document. ROOM RECORDS ------------ These are room records. There is a room record for every room on the system, public or private or mailbox. It's indexed by room name (also in lower case for easy indexing) and it contains records which look like this: struct ctdlroom { char QRname[ROOMNAMELEN]; /* Name of room */ char QRpasswd[10]; /* Only valid if it's a private rm */ long QRroomaide; /* User number of room aide */ long QRhighest; /* Highest message NUMBER in room */ time_t QRgen; /* Generation number of room */ unsigned QRflags; /* See flag values below */ char QRdirname[15]; /* Directory name, if applicable */ long QRinfo; /* Info file update relative to msgs*/ char QRfloor; /* Which floor this room is on */ time_t QRmtime; /* Date/time of last post */ struct ExpirePolicy QRep; /* Message expiration policy */ long QRnumber; /* Globally unique room number */ char QRorder; /* Sort key for room listing order */ unsigned QRflags2; /* Additional flags */ int QRdefaultview; /* How to display the contents */ }; Again, mostly self-explanatory. Here are the interesting ones: QRnumber is a globally unique room ID, while QRgen is the "generation number" of the room (it's actually a timestamp). The two combined produce a unique value which identifies the room. The reason for two separate fields will be explained below when we discuss the visit table. For now just remember that QRnumber remains the same for the duration of the room's existence, and QRgen is timestamped once during room creation but may be restamped later on when certain circumstances exist. FLOORTAB -------- Floors. This is so simplistic it's not worth going into detail about, except to note that we keep a reference count of the number of rooms on each floor. MSGLISTS -------- Each record in this table consists of a bunch of message numbers which represent the contents of a room. A message can exist in more than one room (for example, a mail message with multiple recipients -- 'single instance store'). This table is never, ever traversed in its entirety. When you do any type of read operation, it fetches the msglist for the room you're in (using the room's ID as the index key) and then you can go ahead and read those messages one by one. Each room is basically just a list of message numbers. Each time we enter a new message in a room, its message number is appended to the end of the list. If an old message is to be expired, we must delete it from the message base. Reading a room is just a matter of looking up the messages one by one and sending them to the client for display, printing, or whatever. VISIT ----- This is the tough one. Put on your thinking cap and grab a fresh cup of coffee before attempting to grok the visit table. This table contains records which establish the relationship between users and rooms. Its index is a hash of the user and room combination in question. When looking for such a relationship, the record in this table can tell the server things like "this user has zapped this room," "this user has access to this private room," etc. It's also where we keep track of which messages the user has marked as "old" and which are "new" (which are not necessarily contiguous; contrast with older Citadel implementations which simply kept a "last read" pointer). Here's what the records look like: struct visit { long v_roomnum; long v_roomgen; long v_usernum; long v_lastseen; unsigned int v_flags; char v_seen[SIZ]; int v_view; }; #define V_FORGET 1 /* User has zapped this room */ #define V_LOCKOUT 2 /* User is locked out of this room */ #define V_ACCESS 4 /* Access is granted to this room */ This table is indexed by a concatenation of the first three fields. Whenever we want to learn the relationship between a user and a room, we feed that data to a function which looks up the corresponding record. The record is designed in such a way that an "all zeroes" record (which is what you get if the record isn't found) represents the default relationship. With this data, we now know which private rooms we're allowed to visit: if the V_ACCESS bit is set, the room is one which the user knows, and it may appear in his/her known rooms list. Conversely, we also know which rooms the user has zapped: if the V_FORGET flag is set, we relegate the room to the zapped list and don't bring it up during new message searches. It's also worth noting that the V_LOCKOUT flag works in a similar way to administratively lock users out of rooms. Implementing the "cause all users to forget room" command, then, becomes very simple: we simply change the generation number of the room by putting a new timestamp in the QRgen field. This causes all relevant visit records to become irrelevant, because they appear to point to a different room. At the same time, we don't lose the messages in the room, because the msglists table is indexed by the room number (QRnumber), which never changes. v_seen contains a string which represents the set of messages in this room which the user has read (marked as 'seen' or 'old'). It follows the same syntax used by IMAP and NNTP. When we search for new messages, we simply return any messages that are in the room that are *not* represented by this set. Naturally, when we do want to mark more messages as seen (or unmark them), we change this string. Citadel BBS client implementations are naive and think linearly in terms of "everything is old up to this point," but IMAP clients want to have more granularity. DIRECTORY --------- This table simply maps Internet e-mail addresses to Citadel network addresses for quick lookup. It is generated from data in the Global Address Book room. USETABLE -------- This table keeps track of message ID's of messages arriving over a network, to prevent duplicates from being posted if someone misconfigures the network and a loop is created. This table goes unused on a non-networked Citadel. THE MESSAGE STORE ----------------- This is where all message text is stored. It's indexed by message number: give it a number, get back a message. Messages are numbered sequentially, and the message numbers are never reused. We also keep a "metadata" record for each message. This record is also stored in the msgmain table, using the index (0 - msgnum). We keep in the metadata record, among other things, a reference count for each message. Since a message may exist in more than one room, it's important to keep this reference count up to date, and to delete the message from disk when the reference count reaches zero. Here's the format for the message itself: Each message begins with an 0xFF 'start of message' byte. The next byte denotes whether this is an anonymous message. The codes available are MES_NORMAL, MES_ANON, or MES_AN2 (defined in citadel.h). The third byte is a "message type" code. The following codes are defined: 0 - "Traditional" Citadel format. Message is to be displayed "formatted." 1 - Plain pre-formatted ASCII text (otherwise known as text/plain) 4 - MIME formatted message. The text of the message which follows is expected to begin with a "Content-type:" header. After these three opening bytes, the remainder of the message consists of a sequence of character strings. Each string begins with a type byte indicating the meaning of the string and is ended with a null. All strings are printable ASCII: in particular, all numbers are in ASCII rather than binary. This is for simplicity, both in implementing the system and in implementing other code to work with the system. For instance, a database driven off Citadel archives can do wildcard matching without worrying about unpacking binary data such as message ID's first. To provide later downward compatability all software should be written to IGNORE fields not currently defined. The type bytes currently defined are: BYTE Mnemonic Enum / Comments A Author eAuthor Name of originator of message. B Big message eBig_message This is a flag which indicates that the message is big, and Citadel is storing the body in a separate record. You will never see this field because the internal API handles it. C RemoteRoom eRemoteRoom when sent via Citadel Networking, this is the room its going to be put on the remote site. D Destination eDestination Contains name of the system this message should be sent to, for mail routing (private mail only). E Exclusive ID eExclusiveID A persistent alphanumeric Message ID used for network replication. When a message arrives that contains an Exclusive ID, any existing messages which contain the same Exclusive ID and are *older* than this message should be deleted. If there exist any messages with the same Exclusive ID that are *newer*, then this message should be dropped. F rFc822 address erFc822Addr For Internet mail, this is the delivery address of the message author. H Human node name eHumanNode Human-readable name of system message originated on. I Message ID emessageId An RFC822-compatible message ID for this message. J Journal eJournal The presence of this field indicates that the message is disqualified from being journaled, perhaps because it is itself a journalized message and we wish to avoid double journaling. K Reply-To eReplyTo the Reply-To header for mailinglist outbound messages L List-ID eListID Mailing list identification, as per RFC 2919 M Message Text eMesageText Normal ASCII, newlines seperated by CR's or LF's, null terminated as always. N Nodename eNodeName Contains node name of system message originated on. O Room eOriginalRoom - Room of origin. P Path eMessagePath Complete path of message, as in the UseNet news standard. A user should be able to send Internet mail to this path. (Note that your system name will not be tacked onto this until you're sending the message to someone else) R Recipient eRecipient - Only present in Mail messages. S Special field eSpecialField Only meaningful for messages being spooled over a network. Usually means that the message isn't really a message, but rather some other network function: -> "S" followed by "FILE" (followed by a null, of course) means that the message text is actually an IGnet/Open file transfer. (OBSOLETE) -> "S" followed by "CANCEL" means that this message should be deleted from the local message base once it has been replicated to all network systems. T date/Time eTimestamp Unix timestamp containing the creation date/time of the message. U sUbject eMsgSubject - Optional. Developers may choose whether they wish to generate or display subject fields. V enVelope-to eenVelopeTo The recipient specified in incoming SMTP messages. W Wefewences eWeferences Previous message ID's for conversation threading. When converting from RFC822 we use References: if present, or In-Reply-To: otherwise. (Who in extnotify spool messages which don't need to know other message ids) Y carbon copY eCarbonCopY Optional, and only in Mail messages. 0 Error eErrorMsg This field is typically never found in a message on disk or in transit. Message scanning modules are expected to fill in this field when rejecting a message with an explanation as to what happened (virus found, message looks like spam, etc.) 1 suppress index eSuppressIdx The presence of this field indicates that the message is disqualified from being added to the full text index. 2 extnotify eExtnotify - Used internally by the serv_extnotify module. 3 msgnum eVltMsgNum Used internally to pass the local message number in the database to after-save hooks. Discarded afterwards. EXAMPLE Let be a 0xFF byte, and <0> be a null (0x00) byte. Then a message which prints as... Apr 12, 1988 23:16 From Test User In Network Test> @lifesys (Life Central) Have a nice day! might be stored as... <40><0>I12345<0>Pneighbor!lifesys!test_user<0>T576918988<0> (continued) -----------|Mesg ID#|--Message Path---------------|--Date------ AThe Test User<0>ONetwork Test<0>Nlifesys<0>HLife Central<0>MHave a nice day!<0> |-----Author-----|-Room name-----|-nodename-|Human Name-|--Message text----- Weird things can happen if fields are missing, especially if you use the networker. But basically, the date, author, room, and nodename may be in any order. But the leading fields and the message text must remain in the same place. The H field looks better when it is placed immediately after the N field. EUID (EXCLUSIVE MESSAGE ID'S) ----------------------------- This is where the groupware magic happens. Any message in any room may have a field called the Exclusive message ID, or EUID. We keep an index in the table CDB_EUIDINDEX which knows the message number of any item that has an EUID. This allows us to do two things: 1. If a subsequent message arrives with the same EUID, it automatically *deletes* the existing one, because the new one is considered a replacement for the existing one. 2. If we know the EUID of the item we're looking for, we can fetch it by EUID and get the most up-to-date version, even if it's been updated several times. This functionality is made more useful by server-side hooks. For example, when we save a vCard to an address book room, or an iCalendar item to a calendar room, our server modules detect this condition, and automatically set the EUID of the message to the UUID of the vCard or iCalendar item. Therefore when you save an updated version of an address book entry or a calendar item, the old one is automatically deleted. NETWORKING (REPLICATION) ------------------------ Citadel nodes network by sharing one or more rooms. Any Citadel node can choose to share messages with any other Citadel node, through the sending of spool files. The sending system takes all messages it hasn't sent yet, and spools them to the recieving system, which posts them in the rooms. The EUID discussion above is extremely relevant, because EUID is carried over the network as well, and the replacement rules are followed over the network as well. Therefore, when a message containing an EUID is saved in a networked room, it replaces any existing message with the same EUID *on every node in the network*. Complexities arise primarily from the possibility of densely connected networks: one does not wish to accumulate multiple copies of a given message, which can easily happen. Nor does one want to see old messages percolating indefinitely through the system. This problem is handled by keeping track of the path a message has taken over the network, like the UseNet news system does. When a system sends out a message, it adds its own name to the bang-path in the

    field of the message. If no path field is present, it generates one. With the path present, all the networker has to do to assure that it doesn't send another system a message it's already received is check the

    ath field for that system's name somewhere in the bang path. If it's present, the system has already seen the message, so we don't send it. We also keep a small database, called the "use table," containing the ID's of all messages we've seen recently. If the same message arrives a second or subsequent time, we will find its ID in the use table, indicating that we already have a copy of that message. It will therefore be discarded. The above discussion should make the function of the fields reasonably clear: o Travelling messages need to carry original message-id, system of origin, date of origin, author, and path with them, to keep reproduction and cycling under control. (Uncoincidentally) the format used to transmit messages for networking purposes is precisely that used on disk, serialized. The current distribution includes serv_network.c, which is basically a database replicator; please see network.txt on its operation and functionality (if any). PORTABILITY ISSUES ------------------ Citadel is 64-bit clean, architecture-independent, and Year 2000 compliant. The software should compile on any POSIX compliant system with a full pthreads implementation and TCP/IP support. In the future we may try to port it to non-POSIX systems as well. On the client side, it's also POSIX compliant. The client even seems to build ok on non-POSIX systems with porting libraries (such as Cygwin). SUPPORTING PRIVATE MAIL ----------------------- Can one have an elegant kludge? This must come pretty close. Private mail is sent and recieved in the Mail> room, which otherwise behaves pretty much as any other room. To make this work, we have a separate Mail> room for each user behind the scenes. The actual room name in the database looks like "0000001234.Mail" (where '1234' is the user number) and it's flagged with the QR_MAILBOX flag. The user number is stripped off by the server before the name is presented to the client. This provides the ability to give each user a separate namespace for mailboxes and personal rooms. This requires a little fiddling to get things just right. For example, make_message() has to be kludged to ask for the name of the recipient of the message whenever a message is entered in Mail>. But basically it works pretty well, keeping the code and user interface simple and regular. PASSWORDS AND NAME VALIDATION ----------------------------- This has changed a couple of times over the course of Citadel's history. At this point it's very simple, again due to the fact that record managers are used for everything. The user file (user) is indexed using the user's name, converted to all lower-case. Searching for a user, then, is easy. We just lowercase the name we're looking for and query the database. If no match is found, it is assumed that the user does not exist. This makes it difficult to forge messages from an existing user. (Fine point: nonprinting characters are converted to printing characters, and leading, trailing, and double blanks are deleted.) citadel-9.01/techdoc/PAM.txt0000644000000000000000000000332212507024051014331 0ustar rootroot The citadel.pam configuration file has been updated for Red Hat 7.1. If you have such a system, it should Just Work; if you don't, you're going to have to tweak it, preferably BEFORE you do a make install. See below. Even if you have Red Hat 7.1, you should look at the file anyway and understand how it affects your system security. The original PAM.txt is included below: Citadel 5.53 and later include support for Pluggable Authentication Modules (PAM.) However, we don't recommend enabling this feature (./configure --with-pam) unless you understand exactly how it will affect your system's security. Specifically, the system administrator must supply a configuration for every authentication service which uses PAM. We have automated this process for Linux by supplying a file which is placed in /etc/pam.d during the installation process, but not on other systems, for 2 reasons: 1) Other systems don't have /etc/pam.d; instead they use one big configuration file, usually /etc/pam.conf. It's not quite as trivial to automatically modify this file in a safe and secure fashion. 2) Other systems have a different set of available authentication modules; they are likely to lack all three of the ones we use in the Linux configuration. We don't have enough information about the features of the authentication modules on other platforms to be able to provide secure configurations. That said, the configuration we've provided should work on at least Red Hat Linux 4.2-5.2, (although we don't recommend building Citadel on Red Hat 4.x due to libc thread-safety issues) and if you understand PAM configuration on your system, feel free to build Citadel with PAM support, as long as you realize that YOU'RE ON YOUR OWN. citadel-9.01/techdoc/citadelapi.txt0000644000000000000000000003043012507024051016013 0ustar rootroot Citadel Server Extension API Documentation --------------------------------------------- This is a VERY INCOMPLETE documentation of the API for extending the Citadel server using dynamically loaded modules. It really isn't an API at all, but rather a list of some of the functions available in the server which are likely to be of use to module writers. The current trend is to move as much stuff as possible out of the server proper and into loadable modules. This makes the code much easier to read and understand. Expect this document to become more complete over time, as both the API and the person documenting it have a chance to mature a bit. :) USER RELATED FUNCTIONS ---------------------- The fundamental user data is stored in "struct ctdluser" which is defined in citadel.h. The following functions are available: int getuser(struct ctdluser *usbuf, char name[]) Given the name of a requested user and a buffer to store the user record in, getuser() will search the userlog for the named user and load its data into the buffer. getuser() returns 0 upon success or a nonzero error code if the requested operation could not be performed. void putuser(struct ctdluser *usbuf, char *name) After reading in a user record with getuser() and perhaps modifying the data in some way, a program may use putuser() to write it back to disk. int lgetuser(struct ctdluser *usbuf, char *name) void lputuser(struct ctdluser *usbuf, char *name) If critical-section operation is required, this pair of calls may be used. They function the same as getuser() and putuser(), except that lgetuser() locks the user file immediately after retrieving the record and lputuser() unlocks it. This will guarantee that no other threads manipulate the same user record at the same time. NOTE: do NOT attempt to combine the locking lgetuser/lputuser calls with any other locking calls in this API. Attempting to obtain concurrent locks on multiple files may result in a deadlock condition which would freeze the entire server. void ForEachUser(void (*CallBack)(struct ctdluser *EachUser)) This allows a user-supplied function to be called once for each user on the system. The user-supplied function will be called with a pointer to a user structure as its only argument. int getuserbynumber(struct ctdluser *usbuf, long int number) getuserbynumber() functions similarly to getuser(), except that it is supplied with a user number rather than a name. Calling this function results in a sequential search of the user file, so use it sparingly if at all. int purge_user(char *pname) This function deletes the named user off the system and erases all related objects: bio, photo, etc. It returns 0 upon success or a nonzero error code if the requested operation could not be performed. HOW TO REGISTER FUNCTION HOOKS ------------------------------ The truly powerful part of the Citadel API is the ability for extensions to register "hooks" -- user-supplied functions will be called while the server is performing various tasks. Here are the API calls to register hooks: void CtdlRegisterProtoHook(void (*handler)(char *), char *cmd, char *desc) void CtdlUnregisterProtoHook(void (*handler)(char *), char *cmd) CtdlRegisterProtoHook() adds a new server command to the system. The handler function should accept a single string parameter, which will be set to a string containing any parameters the client software sent along with the server command. "cmd" should be the four-character mnemonic the server command is known by, and "desc" is a description of the new command. CtdlUnregisterProtoHook() removes a server command from the system. It must be called with the same handler and cmd which were previously registered. void CtdlRegisterCleanupHook(void *fcn_ptr) void CtdlUnregisterCleanupHook(void *fcn_ptr) CtdlRegisterCleanupHook() registers a new function to be called whenever the server is shutting down. Cleanup functions accept no parameters. CtdlUnregsiterCleanupHook() removes a cleanup function from the system. It must be called with the same fcn_ptr which was previously registered. void CtdlRegisterSessionHook(void *fcn_ptr, int EventType) void CtdlUnregisterSessionHook(void *fcn_ptr, int EventType) CtdlRegisterSessionHook() registers a session hook. Session hooks accept no parameters. There are multiple types of session hooks; the server extension registers which one it is interested in by setting the value of EventType. The available session hook types are: #define EVT_STOP 0 /* Session is terminating */ #define EVT_START 1 /* Session is starting */ #define EVT_LOGIN 2 /* A user is logging in */ #define EVT_NEWROOM 3 /* Changing rooms */ #define EVT_LOGOUT 4 /* A user is logging out */ #define EVT_SETPASS 5 /* Setting or changing password */ #define EVT_CMD 6 /* Called after each server command */ #define EVT_RWHO 7 /* An RWHO command is being executed */ #define EVT_ASYNC 8 /* Doing asynchronous message */ #define EVT_TIMER 50 /* Timer events are called once per minute */ #define EVT_HOUSE 51 /* Housekeeping event */ CtdlUnregisterSessionHook() removes a session hook. It must be called with the same fcn_ptr and EventTYpe which were previously registered. void CtdlRegisterUserHook(void *fcn_ptr, int EventType) void CtdlUnregisterUserHook(void *fcn_ptr, int EventType) CtdlRegisterUserHook() registers a user hook. User hooks accept two parameters: a string pointer containing the user name, and a long which *may* contain a user number (only applicable for certain types of hooks). The available user hook types are: #define EVT_PURGEUSER 100 /* Deleting a user */ #define EVT_OUTPUTMSG 101 /* Outputting a message */ CtdlUnregisterUserHook() removes a user hook from the system. It must be called with the same fcn_ptr and EventType which were previously registered. void CtdlRegisterLogHook(void (*fcn_ptr) (char *), int loglevel) void CtdlUnregisterLogHook(void (*fcn_ptr) (char *), int loglevel) CtdlRegisterLogHook() adds a new logging function to the system. The handler function should accept a single string as a parameter. Logging functions can be used to implement additional logging facilities in addition to the Citadel trace file, which is output on stderr, or redirected to a file with the -t command line option. The handler function will be called if the loglevel is greater than or equal to loglevel. Security considerations: Logs may contain plain text passwords and other sensitive information. It is your responsibility to ensure that your logs have appropriate access protection. The Citadel trace file is readable only by the superuser when the -t option is used. CtdlUnregisterLogHook() removes a logging function from the system. It must be called with the same fcn_ptr and loglevel which were previously registered. void CtdlRegisterMessageHook(int (*handler) (struct CtdlMessage *), int EventType) void CtdlUnregisterMessageHook(int (*handler) (struct CtdlMessage *), int EventType) CtdlRegisterMessageHook() registers a function with the message handling subsystem. This function will be called with one parameter, a pointer to a CtdlMessage structure, when the message triggers an event of type EventType. The registered function should return non zero if it has handled the event to prevent other hook functions from also processing the event. CtdlUnregisterMessageHook() removes a function from the list of registered message handlers. To successfully remove a function registered with CtdlRegisterMessageHook() CtdlUnregisterMessageHook() must be called with the same parameters. Possible values for EventType are: EVT_BEFOREREAD Called after a message is loaded from disk but before it is presented for reading. EVT_BEFORESAVE Called before the message is saved to disk. returning non zero for this event will prevent the message being saved to disk in the normal manner. EVT_AFTERSAVE Called after the message is saved to disk but before any IGnet spooling is carried out. EVT_SMTPSCAN Called during the SMTP reception of a message after the message is received and before the response is sent to the sender. This is intended for spam filters and virus checkers. A positive return code will cause the message to be rejected by the SMTP server. void CtdlRegisterRoomHook(int (*fcn_ptr) (char *)) void CtdlUnregisterRoomHook(int (*fcn_ptr) (char *)) Register or remove a function with the room processing system. Registered functions are called in the order they are registered when a message is added to a room. This allows modules such as Sieve to process new messages appearing in a room. void CtdlRegisterXmsgHook(int (*fcn_ptr) (char *, char *, char *), int order) void CtdlUnregisterXmsgHook(int (*fcn_ptr) (char *, char *, char *), int order) Please write documentation for me! void CtdlRegisterServiceHook(int tcp_port, char *sockpath, void (*h_greeting_function) (void), void (*h_command_function) (void)) void CtdlUnregisterServiceHook(int tcp_port, char *sockpath, void (*h_greeting_function) (void), void (*h_command_function) (void)) Please write documentation for me! FUNCTIONS WHICH MANIPULATE USER/ROOM RELATIONSHIPS void CtdlGetRelationship(struct visit *vbuf, struct ctdluser *rel_user, struct ctdlroom *rel_room); void CtdlSetRelationship(struct visit *newvisit, struct ctdluser *rel_user, struct ctdlroom *rel_room); These functions get/set a "struct visit" structure which may contain information about the relationship between a user and a room. Specifically: struct visit { char v_roomname[20]; long v_generation; long v_lastseen; unsigned int v_flags; }; #define V_FORGET 1 /* User has zapped this room */ #define V_LOCKOUT 2 /* User is locked out of this room */ #define V_ACCESS 4 /* Access is granted to this room */ Don't change v_roomname or v_generation; they're used to identify the room being referred to. A room is unique to the system by its combination of room name and generation number. If a new room is created with the same name as a recently deleted room, it will have a new generation number, and therefore stale "visit" records will not be applied (and will eventually be purged). v_lastseen contains the number of the newest message the user has read in this room. Any existing messages higher than this number can be considered as "new messages." v_flags contains information regarding access to the room. int CtdlRoomAccess(struct ctdlroom *roombuf, struct ctdluser *userbuf) This is a convenience function which uses CtdlGetRelationship() to determine whether a user has access to a room. It returns a bucket of bits which may contain: #define UA_INUSE 1 /* Room exists */ #define UA_KNOWN 2 /* Room is in user's Known list */ #define UA_GOTOALLOWED 4 /* User may <.G>oto this room */ #define UA_HASNEWMSGS 8 /* Room contains new messages */ #define UA_ZAPPED 16 /* User has forgotten this room */ ROOM RELATED FUNCTIONS ---------------------- unsigned create_room(char *new_room_name, int new_room_type, char *new_room_pass, int new_room_floor, int really_create) This function is used to create a new room. new_room_name should be set to the desired name for the new room. new_room_type should be set to one of the following values: 0 = public room 1 = guess-name room 2 = passworded room 3 = invitation-only room 4 = personal (mailbox) room 5 = personal (mailbox) room, and new_room_name already contains the namespace prefix (use with caution!) new_room_pass should be set to the desired password for the room (obviously this is only valid for passworded rooms). If the room is really to be created, set really_create to 1. Otherwise, the caller may merely check to see if it's possible to create the room without actually creating it by setting really_create to 0. create_room() returns the flags associated with the new room (as in the data structure item room.QRflags). If the room cannot be created (for example, a room with the name already exists), it returns 0. citadel-9.01/techdoc/smtpclient.txt0000644000000000000000000000574012507024051016104 0ustar rootroot====== SMTP Client ====== ===== source files involved: ===== event_client.h - AsyncIO structure, interfaces to event Queue modules/smtp/smtp_clienthandlers.h - SMTP-Client Context struct & functions. modules/smtp/smtpqueue.h - parsing of Queue mails & respective structures modules/smtp/serv_smtpqueue.c - queue handling executed by housekeeping thread; de/serialization of Queue-control-messages modules/smtp/serv_smtpeventclient.c - business logic modules/smtp/smtp_clienthandlers.c - Protocoll logic msgbase.c - writes spool messages + spoolcontrol files event_client.c - base event functions modules/eventclient/serv_eventclient.c - the event queue modules/c-ares-dns/serv_c-ares-dns.c - nameserver lookup ===== Spool Controll ===== Citadel stores two messages for mail deliveries; the spool control message format is described in delivery-list.txt; its generated in msgbase.c, and de/serialized in serv_smtpqueue.c. The for Spool-control structures are kept in memory as OneQueItem per Spool-Control-File, and MailQEntry per Recipient of the message. For each MailQEntry which is still to be delivered, one SmtpOutMsg (containing the AsyncIO context) is spawned. The Jobs are processed in paralell, each job finished triggers an update of the spool control message, so we don't accidently deliver a message twice to one recipient. If the last recipient has succeeded, or permanently failed, the spool-file and the mail-file are removed from the spool room. ===== Business Logic for one SMTP Connection ===== For each SMTP client one SmtpOutMsg is created as Context; we now live inside the IO-Eventloop. SMTP requires to attempt to connect different servers in different situations; not all errors from remote sides are permanent, or fatal for one delivery attempt. One delivery attempt may involve talking to several SMTP-Servers; maybe the first isn't active or currently not accepting messages. If citadel runs in relay mode, only the relays are connected; no MX lookup is done. - MX nameserver lookup; may give several mailservers; IPv4 and IPv6 addresses may be there. If no MX is found, the server is contacted directly. - Now we have a number of Mailservers we have to iterate over; This is the entry for all subsequent tries for delivery; FailOneAttempt() will decide what to do next. - one server has to be resolved; A or AAAA record in case of IPv4 or IPv6 - we try to connect the server nonblocking; if we fail to connect in a given time range, the next server has to be tried. - we now have to wait for the server greeting; we musn't say anything before the server sent a complete line. A timeout may occur, the next server has to be tried, if. - Now the handlers start to control the logic. A timeout may occur between each of them; Servers may reply with fatal or non fatal or temporary error codes; depending on them - we fail the conversation with this server, and try the next - we fail this delivery attempt, and retry later in the next queue run. - we succeed the delivery of this message. citadel-9.01/techdoc/binaries.txt0000644000000000000000000000553312507024051015516 0ustar rootroot BUILDING THE CITADEL SYSTEM WITH PRIVATE LIBRARIES -------------------------------------------------- This method is known to work on Linux and FreeBSD systems. It is a way of building the Citadel system with its own private copies of Berkeley DB and libical. This avoids conflicts with any other version of these libraries which may already exist on your host system. You can perform your builds in any directory (such as /usr/src or even your home directory). The target directories will be: * /usr/local/citadel (Citadel server, text client, utilities) * /usr/local/webcit (the WebCit front end) * /usr/local/ctdlsupport (libical, libdb, and their headers, etc.) The behavior of Easy Install is based upon this methodology. 1. Unpack the Berkeley DB tarball. chdir to the "build_unix" directory and build the package with these commands: ../dist/configure --prefix=/usr/local/ctdlsupport make make install 2. Unpack the libical tarball and build it with these commands: ./configure --prefix=/usr/local/ctdlsupport make make install 3. Set these environment variables for the rest of the build. (This assumes you're using the 'bash' shell. Otherwise you're on your own.) export CFLAGS='-I/usr/local/ctdlsupport/include' export CPPFLAGS='-I/usr/local/ctdlsupport/include' export LDFLAGS='-L/usr/local/ctdlsupport/lib -Wl,--rpath -Wl,/usr/local/ctdlsupport/lib' -L tells the build system where to find libraries during the build process, while -Wl,--rpath inserts that same library path into the Citadel binaries so they know where to find the libs at runtime. Since we can't depend on the correct version of Berkeley DB already being on the system, this is how we carry our own along. It's better than static linking everything. 4. If LDAP support is required, unpack the OpenLDAP tarball and build with: ./configure --prefix=/usr/local/ctdlsupport --with-db=/usr/local/ctdlsupport make make depend make install 5. Now you're ready to build the Citadel server. Unpack the Citadel tarball and build it with these commands: ./configure --prefix=/usr/local/citadel --with-db=/usr/local/ctdlsupport make make install ** NOTE: if you already have a Citadel server in /usr/local/citadel, change the 'make install' to 'make upgrade'. And I'm sure you remembered to shut down your Citadel service and make a backup before starting, right? 6. Finally, unpack the WebCit tarball and build it with these commands: ./configure --prefix=/usr/local/webcit make make install All of your software is now in place. Run /usr/local/citadel/setup to configure the Citadel server, and /usr/local/webcit/setup to configure WebCit. If you are on a Linux machine, setup will tweak /etc/inittab to automatically start the services. If you are on a FreeBSD machine, you will need to manually configure your startup scripts to start the services. citadel-9.01/techdoc/views.txt0000644000000000000000000000310312507024051015046 0ustar rootrootHow "views" work in Citadel --------------------------- There's no need to be rigid and stupid about how different rooms are presented in a Citadel client. And we don't enforce things either. But there's a need to make things look the way the user wants to see them. For example, we might always choose to see a room full of private mail as a summary (one line per message) rather than always dumping out the entire contents like we do on a typical message room. An address book room might look better as a tabbed view or something, rather than as a bunch of messages with vCards attached to them. This is why we define "views" for a room. It gives the client software a hint as to how to display the contents of a room. This is kept on a per-user basis by storing it in the 'visit' record for a particular room/user combination. It is visit.v_view and is an integer. Naturally, there also needs to be a default, for users who have never visited the room before. This is in the room record as room.QRdefaultview (and is also an integer). In recent versions of Citadel, the view for a room also defines when and how it is indexed. For example, mailboxes and bulletin boards don't need to have an euid index, but address books and calendars do. The values currently defined are: #define VIEW_BBS 0 /* Bulletin board view */ #define VIEW_MAILBOX 1 /* Mailbox summary */ #define VIEW_ADDRESSBOOK 2 /* Address book view */ #define VIEW_CALENDAR 3 /* Calendar view */ #define VIEW_TASKS 4 /* Tasks view */ #define VIEW_NOTES 5 /* Notes view */ #define VIEW_WIKI 6 /* Wiki view */ citadel-9.01/techdoc/developers.txt0000644000000000000000000000371012507024051016065 0ustar rootrootEvery client for the Citadel system is capable of identifying itself with a developer code, client code, and client version. Here are the known values for these fields. Developer codes (and where known, client codes) 0 The Citadel core development team (See copyright.txt for the list of people involved in the project) Here are the client ID's we've used: Client Name Status Description 0 Citadel deployed Citadel text based client 1 lcit dead Curses-based text client 2 WinCit legacy Visual Basic client for Win16 4 WebCit deployed Web-based access to Citadel 5 Shaggy dead Java-powered client 6 Daphne dead wx multiplatform "fat" client 7 Shaggy (new) unknown Java-powered client 8 ctdlphp deployed PHP interface to Citadel 9 z-push in development Citadel backend for Z-Push 1 Brian Ledbetter Client Name Status Description 0 libCxClient deployed Client-side API library 1 Infusion deployed Groupware for Citadel 2 OST Systems, Inc. Matthew Scott Client Name Status Description 0 CivisLink development Python Citadel protocol library 3 Jesse Vincent 4 Brian Costello 5 Robert Abatecola 6 Bastille 7 Walden Leverich 8 Michael Hampton Client Name Status Description 0 libcitadel in development Client-side API library 1 D.O.C. dead Dave's Own Citadel look-alike 8 stress deployed Citadel server stress tester 16 Courier in development GTK+ Unix/Win32 Client 31 Stuart Cianos 69 Anticlimactic Teleservices Don Kimberlin (err head) 71 Robert Barta 177 Kevin Fisher 226 David Given citadel-9.01/techdoc/citadel_thread_io.txt0000644000000000000000000001636412507024051017351 0ustar rootroot====== How Threads & IO for clients work ====== Citserver is a multi thread process. It has four types of threads: - Workers - these are anonymous threads which can do any of the server related functions like Imapserver; smtpserver; citadel protocol and so on. - Housekeeping - once per minute one Worker becomes the Housekeeping thread which does recurrent jobs like database maintenance and so on. - IO - one thread is spawned to become the IO-Thread at startup. Inside it lives the event-queue which is implemented by libev. - DB-IO - one thread is spawned to become the database IO thread. Inside it lives another event-queue, which is also implemented by libev. ===== Worker Threads ===== The workers live blocking on selects to server sockets. Once IO on a server socket becomes available, one thread awakes, and takes the task. It is then assigned a CitContext structure on a Thread global var (accessible via the CC macro), which then becomes his personality and controls what the thread is going to do with the IO, and which server code (Imap/SMTP/Citadel/...) is going to evaluate the IO and reply to it. ===== Housekeeping Thread ===== Once every minute one Worker becomes something special: the Housekeeping Thread. Only one Houskekeeper can exist at once. As long as one Housekeeper is running no other is started. Jobs done by the Housekeeper are registered via the <>-API. The current list: - DB maintenance; flush redo logs etc. - fulltext index (this one is a little problematic here, since it may be long running and block other housekeeping tasks for a while) - Networking-Queue - Citadel Networking Queue aggregator (Blocks NTT) - Citadel Inbound Queue operator (Blocks NTT) - Citadel Networking Queue - SMTP-Client-Queue - RSS-Aggregator scheduling - HTTP-Message Notification Queue (Extnotify) - POP3Client Queue The queues operate over their respective queue; which may be implemented with a private room (smtp-Queue, Extnotify Client), per room configs (RSS-Client; POP3Client), or an aggregate of Rooms/Queue/Roomconfigs (Citadel Networker). ==== Networking Queue ==== The networking queue opperats over an aggregate of per room network configs and messages in rooms that are newer than a remembered whatermark (netconfigs.txt#lastsent). All messages Newer than the watermark are then processed: - for all mailinglist recipients an entry is added to the mailqueue - for all mailinglist digest recipients a digest file is written, or once per day scheduled to - for all Citadel networking nodes a spool file is written for later evaluation. ==== Citadel Networking Queue aggregator ==== The aggregator iterates over the spool directory, and combines all per message spool files to one big file which is to be sent by the Citadel Networking Queue later. It blocks the NTT-List for the remote party while doing so; if the NTT-List is blocked the file is skipped for next operation. Once all files are successfully processed, all files not associated with a networking config are flushed. ==== Citadel Inbound Queue operator ==== The inbound queue operator scans for new inbound spool files, processes them, splits them into messages which are then posted into their respective rooms. ==== other Queues ==== Once these Queue-operators have identified a job to be performed like - talking to another Citadel Node to transfer spool files back and forth - sending mails via SMTP - polling remote POP3-Servers for new mails - fetching RSS-Feeds via HTTP - Notifying external facilities for new messages in inboxes via HTTP They create a Context for that Job (AsyncIO struct) plus their own Private data, plus a CitContext which is run under the privileges of a SYS_* user. HTTP Jobs just schedule the HTTP-Job, and are then called back on their previously registered hook once the HTTP-Request is done. All other have to implement their own IO controled business logic. ===== IO-Thread ===== The IO-Event-queue lives in this thread. Code running in this thread has to obey several rules. - it mustn't do blocking operations (like disk IO or nameserver lookups other than the c-ares facilities) - it mustn't use blocking Sockets - other threads mustn't access memory belonging to an IO job - New IO contexts have to be registered via QueueEventContext() from other threads; Its callback are then called from whithin the event queue to start its logic. - New HTTP-IO contexts have to be registered via QueueCurlContext() - once you've registered a context, you must not access its memory anymore to avoid race conditions. ==== Logic in event_client ==== InitIOStruct() which prepares an AsyncIO struct for later use before using QueueEventContext() to activate it. Here we offer the basics for I/O; Hostname lookus, connecting remote parties, basic read and write operation control flows. This logic communicates with the next layer via callback hooks. Before running hooks or logging CC is set from AsyncIO->CitContext Most clients are stacked that way: --- -- -- === event base === While the event base is responsible for reading and writing, it just does so without the understanding of the protocol it speaks. It at most knows about chunksises to send/read, or when a line is sent / received. === IO grammer logic === The IO grammer logic is implemented for each client. It understands the grammer of the protocol spoken; i.e. the SMTP implementation knows that it has to read status lines as long as there are 3 digits followed by a dash (i.e. in the ehlo reply). Once the grammer logic has decided a communication state is completed, it calls the next layer. === state machine dispatcher === The state machine dispatcher knows which part of the application logic is currently operated. Once its called, it dispatches to the right read/write hook. Read/write hooks may in term call the dispatcher again, if they want to divert to another part of the logic. === state hooks === The state hooks or handlers actualy PARSE the IO-Buffer and evaluate what next action should be taken; There could be an error from the remote side so the action has to be aborted, or usually simply the next state can be reached. === surrounding businesslogic === Depending on the protocol there has to be surrounding business logic, which handles i.e. nameserver lookups, which ips to connect, what to do on a connection timeout, or doing local database IO. ===== DB-IO-Thread ===== Since lookups / writes to the database may take time, this mustn't be done inside of the IO-Eventloop. Therefore this other thread & event queue is around. Usually a context should respect that there may be others around also wanting to do DB IO, so it shouldn't do endless numbers of requests. While its ok to do _some_ requests in a row (like deleting some messages, updating messages, etc.), loops over unknown numbers of queries should be unrolled and be done one query at a time (i.e. looking up messages in the 'already known table') === Transitioning back & forth === If the application logic has found that a transition is to be made from the event IO to the DB IO thread. Do so by calling DBQueueEventContext() (DB -> IO) EventQueueDBOperation() (IO->DB) and pass its return value up the row to enable the controll logic to do the actual transition for you. citadel-9.01/techdoc/build.txt0000644000000000000000000000307412507024051015017 0ustar rootrootSome notes on the build process... Oct 28 01:57 1998 from LoanShark @uncnsrd autodependency generation is implemented by generating a bunch of .d files (with another suffix rule) using $(CC) - M and "-include"-ing them from the main Makefile. the .d files are made to depend on the .c files they correspond to, and the referenced header files, so they're updated as needed. the only version of make that I know for sure works with this is GNU make, but the Makefile should still work on other versions of Unix make, just without the autodependency stuff. Oct 28 20:49 1998 from LoanShark @uncnsrd one thing I forgot to mention about the autodependency generation: for a file to be included in the autodepend process, it must be listed in the SOURCES macro near the top of Makefile.in. also, I've added an 'install' target to the makefile. this will make it possible to build RPM's, and bring the installation process closer to that of other packages, which if you're using the new configure script goes like this: ./configure [--enable-ansi-color] [--disable-autologin] [--prefix=/foo] --prefix specifies the location Citadel will run from, /usr/local/citadel by default. to build a highly optimized version without debugging symbols, you could do something like this (with the bourne shell): CC=egcs CFLAGS='-O6 -fomit-frame-pointer' ./configure after configuring, simply type `make', then su to a user who has appropriate permissions, and `make install'. then run setup as usual. are there any other areas that need to be cleared up? citadel-9.01/techdoc/package-setup.txt0000644000000000000000000000225512507024051016451 0ustar rootroot Package builders: you can set the following environment variables before running setup in quiet mode (setup -q) in order to keep it quiet but still do setup. First, please set CITADEL_INSTALLER to "yes" Then set: CITADEL Directory in which Citadel is installed SLAPD_BINARY Location of slapd (optional) LDAP_CONFIG Location of slapd.conf (optional) SUPPORT Location of libraries, etc. (optional) (i.e. /usr/local/ctdlsupport) CTDL_DIALOG Location of the 'dialog' program (optional) ACT_AS_MTA When other MTA's (Postfix, etc.) are found, setup will offer to disable them in order to keep them from interfering with Citadel. Set this variable to "yes" to always disable the other MTA without asking, or to "no" to never disable the other MTA. CREATE_INITTAB_ENTRY Set to "yes" to automatically create an entry in /etc/inittab to start the Citadel server, if needed. CREATE_XINETD_ENTRY Set to "yes" to automatically configure xinetd to route telnet connections into a Citadel client, if needed. SYSADMIN_NAME Name of system administrator. NO_INIT_SCRIPTS Set to non-null to suppress the creation of init scripts when running on a SysV-init based host. citadel-9.01/techdoc/logging.txt0000644000000000000000000001470712507024051015353 0ustar rootroot=====Logging ===== Note: the logging scheme is not yet persistant all over the citserver. citadel server has several components that emmit different log strings. Its designed so you can filter for specific sessions. Since there are several sources for numbering schemes, several numbers need to be employed to make one job / session completely traceable. The syntax is in general like CC[%d] === CC - Citadel Context === All contexts that are visible to the RWHO command have a citadel context. The ID of such a context is generated from a source that can grow and shrink again. Thus CC may not be uniq to one session. Citadel may have several kind of sessions: - sessions generated by external connections; they have a CC. - sessions spawned by repetive tasks; they at first may have their own id-range, then later on an IO (AsyncIO) and CC (Citadel Context) === Identifieing running sessions === The RWHO command will list you the currently active CCs: # sendcommand RWHO sendcommand: started (pid=14528) connecting to Citadel server at /var/run/citadel/citadel-admin.socket 200 potzblitz Citadel server ADMIN CONNECTION ready. RWHO 100 576|SYS_rssclient||http://xkcd.org/rss.xml||0||.||||0 1965|willi|Mail|PotzBlitz|IMAP session|1346695500| |.||||1 sendcommand: processing ended. 576 is the CC connected to this client. with grep 'CC\[576\]' /var/log/syslog will show you the loglines from all contexts that had the ID 576; the one named above is an RSS aggregator: Jul 3 09:07:01 PotzBlitz citserver[1435]: EVCURL:IO[576]CC[576] error description: The requested URL returned error: 404 Jul 3 09:07:01 PotzBlitz citserver[1435]: EVCURL:IO[576]CC[576] error performing request: HTTP response code said error Jul 3 09:07:01 PotzBlitz citserver[1435]: IO[576]CC[576][29]RSSPneed a 200, got a 404 ! grep 'CC\[1965\]' /var/log/syslog will show you the loglines from all contexts that had the ID 1965; the one named above is an inbound IMAP Connection: Jul 3 09:05:01 PotzBlitz citserver[12826]: IMAPCC[1965] Jul 3 09:05:01 PotzBlitz citserver[12826]: CC[1965] 144 new of 144 total messages Jul 3 09:05:02 PotzBlitz citserver[12826]: CC[1965]<0000000010.Mail> 1 new of 17 total messages === Numbering and prefixes === While external sessions will constantly show the same CC (or maybe add their own name space as above with IMAPCC) Its important to understand that not all numbers are available in all situations with sessions from repetive tasks; thus you may need several filters to gather all logmessages belonging to one job. Generaly once per minute one worker becomes an housekeeping thread; This housekeeper scans the whole system for jobs to start. Once a job is identified, its detection is logged; neither a CC nor IO are already assigned to this job yet; but it already has its uniq number assigned to it which is specific to the type of task. After the preanalysis of this job is done, it may become an AsyncIO job with a Citadel Context. The job now lives in one of the eventqueues, waiting for external file IO or Database IO or performing some calculation of a new state. Some basic IO debug logging may not facilitate the CC or the Job-ID; especialy during HTTP or DNS requests. === Debug logging === By default citserver will only log information about errors or traffic analysis related errors. If you want to track all a job does, you can enable more logging: sendcommand LOGP (LOG Print; show status of logs; the following list may increase over time) : sendcommand: started (pid=19897) connecting to Citadel server at /var/run/citadel/citadel-admin.socket 200 potzblitz Citadel server ADMIN CONNECTION ready. LOGP 100 Log modules enabled: modules|0 <- module loading; unloading etc. rssclient|0 <- RSS aggregator client debugging; on message level RSSAtomParser|0 <- debugging the parsing of ATOM / RSS feeds; once a message is isolated, the above logging comes into place again. curl|0 <- HTTP request logging; i.e. querieing the external RSS resources by http is logged with this. networkqueue|0 <- working through rooms, finding jobs to schedule; reading network spool files, writing them; etc. networktalkingto|0 <- locked list of network interconnections to circumvent race conditions by simultaneous spooling / sending / receiving networkclient|0 <- outbound networking connections; this has an IO context. cares|0 <- asynchroneous nameserver lookup eventloop|0 <- adding jobs; connecting; timeouts; etc. eventloopbacktrace|0 <- add stacktraces in certain places; very noisy. sieve|0 <- sieve filtering of mailboxes messages|0 <- message processing; loading / printing / saving imapsrv|0 <- Imap connections serv_xmpp|0 <- XMPP connections pop3client|0 <- pop3 aggregators smtpeventclient|0 <- outbound SMTP connections sendcommand: processing ended. If the second column is 1, that debug log is enabled. You can enable the logging by 3 ways: - sendcommand 'LOGS cares|1' (live) - via webcit->aide->Enable/Disable logging of the server components; in an interactive manner - via environment variable 'CITADEL_LOGDEBUG' as a coma separated list of facilities to enable; before starting citserver - via commandline parameter '-x' as a coma separated list of facilities to enable; please note that this switch used to set the loglevel previously. eventloop,eventloopbacktrace,cares - IO the AsyncIO ID - IOQ (Queueing of events into eventqueues) modules: - modules - module init / registering specific stuff smtpeventclient - S[QueueID][N] where the Queue-ID is the number of the Message to be sent, and N the SMTP-job-ID - SMTPCQ infrastructure logging when the housekeeper is checking for SMTP-Delivery jobs to spawn imapsrv - IMAPCC: imap client contexts; commands sent to us from Imap-Clients networkqueue - CC - networkclient - CC, IO - the context - NW[nodename][networkerid] the name of the node we're trying to talk to; the networker connection counter uniq per attempt curl - CURL - logging where we don't have a context - CC, IO - the context RSSClient - IO, CC, N, 'RSS'; IO & CC not always available. - 'RSSP' - the rss and atom parser pop3client - POP3 IO, CC, N - N the number of that connection sieve - sieve doesn't have CC or IOs, its just ran one at a time serverwide. xmpp - XMPP - whether CC could & should be logged has to be researched and added. Context - 'Context:' context related logging - server infrastructure, cleanup of contexts etc. messages - MSG - CC - the context that is manipulating this message. citadel-9.01/support.c0000644000000000000000000000452512507024051013430 0ustar rootroot/* * Server-side utility functions */ #include "sysdep.h" #include #include #include #include #include "citadel.h" #include "support.h" /* * strproc() - make a string 'nice' */ void strproc(char *string) { int a, b; if (string == NULL) return; if (IsEmptyStr(string)) return; /* Convert non-printable characters to blanks */ for (a=0; !IsEmptyStr(&string[a]); ++a) { if (string[a]<32) string[a]=32; if (string[a]>126) string[a]=32; } /* a is now the length of our string. */ /* Remove leading and trailing blanks */ while( (string[a-1]<33) && (!IsEmptyStr(string)) ) string[--a]=0; b = 0; while( (string[b]<33) && (!IsEmptyStr(&string[b])) ) b++; if (b > 0) memmove(string,&string[b], a - b + 1); /* Remove double blanks */ for (a=0; !IsEmptyStr(&string[a]); ++a) { if ((string[a]==32)&&(string[a+1]==32)) { strcpy(&string[a],&string[a+1]); a=0; } } /* remove characters which would interfere with the network */ for (a=0; !IsEmptyStr(&string[a]); ++a) { while (string[a]=='!') strcpy(&string[a],&string[a+1]); while (string[a]=='@') strcpy(&string[a],&string[a+1]); while (string[a]=='_') strcpy(&string[a],&string[a+1]); while (string[a]==',') strcpy(&string[a],&string[a+1]); while (string[a]=='%') strcpy(&string[a],&string[a+1]); while (string[a]=='|') strcpy(&string[a],&string[a+1]); } } /* * get a line of text from a file * ignores lines starting with # */ int getstring(FILE *fp, char *string) { int a,c; do { strcpy(string,""); a=0; do { c=getc(fp); if (c<0) { string[a]=0; return(-1); } string[a++]=c; } while(c!=10); string[a-1]=0; } while(string[0]=='#'); return(strlen(string)); } /* * mesg_locate() - locate a message or help file, case insensitive */ void mesg_locate(char *targ, size_t n, const char *searchfor, int numdirs, const char * const *dirs) { int a; char buf[SIZ]; struct stat test; for (a=0; a #include "sysdep.h" #include "server.h" #include "sysdep_decls.h" #include "threads.h" /* * Values for CitContext.state * * A session that is doing nothing is in CON_IDLE state. When activity * is detected on the socket, it goes to CON_READY, indicating that it * needs to have a worker thread bound to it. When a thread binds to * the session, it goes to CON_EXECUTING and does its thing. When the * transaction is finished, the thread sets it back to CON_IDLE and lets * it go. */ typedef enum __CCState { CON_IDLE, /* This context is doing nothing */ CON_GREETING, /* This context needs to output its greeting */ CON_STARTING, /* This context is outputting its greeting */ CON_READY, /* This context needs attention */ CON_EXECUTING, /* This context is bound to a thread */ CON_SYS /* This is a system context and mustn't be purged */ } CCState; #ifndef __ASYNCIO__ #define __ASYNCIO__ typedef struct AsyncIO AsyncIO; /* forward declaration for event_client.h */ #endif #ifndef __CIT_CONTEXT__ #define __CIT_CONTEXT__ typedef struct CitContext CitContext; #endif /* * Here's the big one... the Citadel context structure. * * This structure keeps track of all information relating to a running * session on the server. We keep one of these for each session thread. * */ struct CitContext { CitContext *prev; /* Link to previous session in list */ CitContext *next; /* Link to next session in the list */ int cs_pid; /* session ID */ int dont_term; /* for special activities like artv so we don't get killed */ double created; /* time of birth */ time_t lastcmd; /* time of last command executed */ time_t lastidle; /* For computing idle time */ CCState state; /* thread state (see CON_ values below) */ int kill_me; /* Set to nonzero to flag for termination */ IOBuffer SendBuf, /* Our write Buffer */ RecvBuf, /* Our block buffered read buffer */ SBuf; /* Our block buffered read buffer for clients */ StrBuf *MigrateBuf; /* Our block buffered read buffer */ StrBuf *sMigrateBuf; /* Our block buffered read buffer */ int client_socket; int is_local_socket; /* set to 1 if client is on unix domain sock */ /* Redirect this session's output to a memory buffer? */ StrBuf *redirect_buffer; /* the buffer */ StrBuf *StatusMessage; #ifdef HAVE_OPENSSL SSL *ssl; int redirect_ssl; #endif char curr_user[USERNAME_SIZE]; /* name of current user */ int logged_in; /* logged in */ int internal_pgm; /* authenticated as internal program */ int nologin; /* not allowed to log in */ int curr_view; /* The view type for the current user/room */ int is_master; /* Is this session logged in using the master user? */ char net_node[32] ;/* Is the client another Citadel server? */ time_t previous_login; /* Date/time of previous login */ char lastcmdname[5]; /* name of last command executed */ unsigned cs_flags; /* miscellaneous flags */ int is_async; /* Nonzero if client accepts async msgs */ int async_waiting; /* Nonzero if there are async msgs waiting */ int input_waiting; /* Nonzero if there is client input waiting */ int can_receive_im; /* Session is capable of receiving instant messages */ /* Client information */ int cs_clientdev; /* client developer ID */ int cs_clienttyp; /* client type code */ int cs_clientver; /* client version number */ char cs_clientinfo[256];/* if its a unix domain socket, some info for logging. */ uid_t cs_UDSclientUID; /* the uid of the client when talking via UDS */ char cs_clientname[32]; /* name of client software */ char cs_host[64]; /* host logged in from */ char cs_addr[64]; /* address logged in from */ /* The Internet type of thing */ char cs_inet_email[128]; /* Return address of outbound Internet mail */ char cs_inet_other_emails[1024]; /* User's other valid Internet email addresses */ char cs_inet_fn[128]; /* Friendly-name of outbound Internet mail */ FILE *download_fp; /* Fields relating to file transfer */ size_t download_fp_total; char download_desired_section[128]; FILE *upload_fp; char upl_file[256]; char upl_path[PATH_MAX]; char upl_comment[256]; char upl_filedir[PATH_MAX]; char upl_mimetype[64]; char dl_is_net; char upload_type; struct ctdluser user; /* Database record buffers */ struct ctdlroom room; /* A linked list of all instant messages sent to us. */ struct ExpressMessage *FirstExpressMessage; int disable_exp; /* Set to 1 to disable incoming pages */ int newmail; /* Other sessions increment this */ /* Masqueraded values in the 'who is online' list */ char fake_username[USERNAME_SIZE]; char fake_hostname[64]; char fake_roomname[ROOMNAMELEN]; /* Preferred MIME formats */ char preferred_formats[256]; int msg4_dont_decode; /* Dynamically allocated session data */ void *session_specific_data; /* Used by individual protocol modules */ struct cit_ical *CIT_ICAL; /* calendaring data */ struct ma_info *ma; /* multipart/alternative data */ const char *ServiceName; /* readable purpose of this session */ long tcp_port; void *openid_data; /* Data stored by the OpenID module */ char *ldap_dn; /* DN of user when using AUTHMODE_LDAP */ void (*h_command_function) (void) ; /* service command function */ void (*h_async_function) (void) ; /* do async msgs function */ void (*h_greeting_function) (void) ; /* greeting function for session startup */ long *cached_msglist; /* results of the previous CtdlForEachMessage() */ int cached_num_msgs; char vcard_updated_by_ldap; /* !0 iff ldap changed the vcard, treat as aide update */ AsyncIO *IO; /* if this session has AsyncIO going on... */ }; #define CC MyContext() extern pthread_key_t MyConKey; /* TSD key for MyContext() */ extern int num_sessions; extern CitContext masterCC; extern CitContext *ContextList; CitContext *MyContext (void); void RemoveContext (struct CitContext *); CitContext *CreateNewContext (void); void context_cleanup(void); void kill_session (int session_to_kill); void InitializeMasterCC(void); void dead_session_purge(int force); void set_async_waiting(struct CitContext *ccptr); CitContext *CloneContext(CitContext *CloneMe); /* forcibly close and flush fd's on shutdown */ void terminate_all_sessions(void); /* Deprecated, user CtdlBumpNewMailCounter() instead */ void BumpNewMailCounter(long) __attribute__ ((deprecated)); void terminate_idle_sessions(void); int CtdlTerminateOtherSession (int session_num); /* bits returned by CtdlTerminateOtherSession */ #define TERM_FOUND 0x01 #define TERM_ALLOWED 0x02 #define TERM_KILLED 0x03 #define TERM_NOTALLOWED -1 /* * Bind a thread to a context. (It's inline merely to speed things up.) */ static INLINE void become_session(CitContext *which_con) { /* pid_t tid = syscall(SYS_gettid); */ pthread_setspecific(MyConKey, (void *)which_con ); /* syslog(LOG_DEBUG, "[%d]: Now doing %s\n", (int) tid, ((which_con != NULL) && (which_con->ServiceName != NULL)) ? which_con->ServiceName:""); */ } /* typedef void (*CtdlDbgFunction) (const int); */ extern int DebugSession; #define CONDBGLOG(LEVEL) if ((LEVEL != LOG_DEBUG) || (DebugSession != 0)) #define CON_syslog(LEVEL, FORMAT, ...) \ CONDBGLOG(LEVEL) syslog(LEVEL, \ "%s Context: " FORMAT, IOSTR, __VA_ARGS__) #define CONM_syslog(LEVEL, FORMAT) \ CONDBGLOG(LEVEL) syslog(LEVEL, \ "%s Context: " FORMAT, IOSTR); #endif /* CONTEXT_H */ citadel-9.01/modules_init.h0000644000000000000000000000365312507024062014417 0ustar rootroot/* * /var/www/easyinstall/citadel/citadel/modules_init.h * Auto generated by mk_modules_init.sh DO NOT EDIT THIS FILE */ #ifndef MODULES_INIT_H #define MODULES_INIT_H #include "ctdl_module.h" extern size_t nSizErrmsg; void initialise_modules (int threading); void upgrade_modules(void); CTDL_MODULE_INIT(control); CTDL_MODULE_INIT(modules); CTDL_MODULE_INIT(euidindex); CTDL_MODULE_INIT(msgbase); CTDL_MODULE_INIT(nttlist); CTDL_MODULE_INIT(database); CTDL_MODULE_INIT(autocompletion); CTDL_MODULE_INIT(bio); CTDL_MODULE_INIT(blog); CTDL_MODULE_INIT(c_ares_client); CTDL_MODULE_INIT(calendar); CTDL_MODULE_INIT(checkpoint); CTDL_MODULE_INIT(virus); CTDL_MODULE_INIT(file_ops); CTDL_MODULE_INIT(ctdl_message); CTDL_MODULE_INIT(rooms); CTDL_MODULE_INIT(serv_session); CTDL_MODULE_INIT(syscmd); CTDL_MODULE_INIT(serv_user); CTDL_MODULE_INIT(dspam); CTDL_MODULE_INIT(event_client); CTDL_MODULE_INIT(expire); CTDL_MODULE_INIT(extnotify); CTDL_MODULE_INIT(fulltext); CTDL_MODULE_INIT(imap); CTDL_MODULE_INIT(inetcfg); CTDL_MODULE_INIT(instmsg); CTDL_MODULE_INIT(listsub); CTDL_MODULE_INIT(managesieve); CTDL_MODULE_INIT(migrate); CTDL_MODULE_INIT(mrtg); CTDL_MODULE_INIT(netfilter); CTDL_MODULE_INIT(network_spool); CTDL_MODULE_INIT(network); CTDL_MODULE_INIT(network_client); CTDL_MODULE_INIT(newuser); CTDL_MODULE_INIT(nntp); CTDL_MODULE_INIT(notes); CTDL_MODULE_INIT(openid_rp); CTDL_MODULE_INIT(pop3); CTDL_MODULE_INIT(pop3client); CTDL_MODULE_INIT(roomchat); CTDL_MODULE_INIT(rssparser); CTDL_MODULE_INIT(rssclient); CTDL_MODULE_INIT(rwho); CTDL_MODULE_INIT(sieve); CTDL_MODULE_INIT(smtp); CTDL_MODULE_INIT(smtp_eventclient); CTDL_MODULE_INIT(smtp_queu); CTDL_MODULE_INIT(spam); CTDL_MODULE_INIT(test); CTDL_MODULE_INIT(urldeshortener); CTDL_MODULE_INIT(vcard); CTDL_MODULE_INIT(wiki); CTDL_MODULE_INIT(xmpp); CTDL_MODULE_INIT(netconfig); CTDL_MODULE_UPGRADE(upgrade); #endif /* MODULES_INIT_H */ citadel-9.01/modules_init.c0000644000000000000000000001312512507024062014405 0ustar rootroot/* * /var/www/easyinstall/citadel/citadel/modules_init.c * Auto generated by mk_modules_init.sh DO NOT EDIT THIS FILE */ #include "sysdep.h" #include #include #include #include #include #include #include #include "citadel.h" #include "modules_init.h" #include "sysdep_decls.h" #include "serv_extensions.h" void LogPrintMessages(long err); extern long DetailErrorFlags; void initialise_modules (int threading) { long filter; const char *pMod; if (threading) { MODM_syslog(LOG_DEBUG, "Initializing, CtdlThreads enabled.\n"); } else { MODM_syslog(LOG_INFO, "Initializing. CtdlThreads not yet enabled.\n"); } pMod = CTDL_INIT_CALL(control); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(modules); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(euidindex); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(msgbase); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(nttlist); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(database); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(autocompletion); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(bio); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(blog); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(c_ares_client); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(calendar); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(checkpoint); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(virus); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(file_ops); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(ctdl_message); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(rooms); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(serv_session); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(syscmd); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(serv_user); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(dspam); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(event_client); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(expire); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(extnotify); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(fulltext); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(imap); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(inetcfg); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(instmsg); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(listsub); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(managesieve); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(migrate); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(mrtg); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(netfilter); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(network_spool); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(network); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(network_client); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(newuser); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(nntp); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(notes); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(openid_rp); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(pop3); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(pop3client); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(roomchat); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(rssparser); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(rssclient); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(rwho); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(sieve); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(smtp); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(smtp_eventclient); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(smtp_queu); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(spam); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(test); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(urldeshortener); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(vcard); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(wiki); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(xmpp); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); pMod = CTDL_INIT_CALL(netconfig); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); for (filter = 1; filter != 0; filter = filter << 1) if ((filter & DetailErrorFlags) != 0) LogPrintMessages(filter); } citadel-9.01/bootstrap0000755000000000000000000000156612507024051013515 0ustar rootroot#!/bin/sh # # run me after checking Citadel out of the source code repository. # Remove any vestiges of pre-6.05 build environments rm -f .libs modules *.so *.lo *.la 2>/dev/null echo ... running aclocal ... aclocal -I m4 echo ... running autoconf ... autoconf # If your autoconf version changes, the autom4te.cache stuff will mess you up. # Get rid of it. echo ... removing autoheader cache files ... rm -rf autom4te*.cache echo ... running autoheader ... autoheader echo ... running mk_svn_revision.sh ... ./scripts/mk_svn_revision.sh echo ... running mk_module_init.sh ... ./scripts/mk_module_init.sh echo echo This script has been tested with autoconf 2.53 and echo automake 1.5. Other versions may work, but we recommend echo the latest echo compatible versions of these. echo echo Also note that autoconf and automake should be configured echo with the same prefix. echo citadel-9.01/context.c0000644000000000000000000004562512507024051013406 0ustar rootroot/* * Citadel context management stuff. * Here's where we (hopefully) have all the code that manipulates contexts. * * Copyright (c) 1987-2011 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "ctdl_module.h" #include "serv_extensions.h" #include "ecrash.h" #include "citserver.h" #include "user_ops.h" #include "locate_host.h" #include "context.h" #include "control.h" int DebugSession = 0; pthread_key_t MyConKey; /* TSD key for MyContext() */ CitContext masterCC; CitContext *ContextList = NULL; time_t last_purge = 0; /* Last dead session purge */ int num_sessions = 0; /* Current number of sessions */ int next_pid = 0; /* Flag for single user mode */ static int want_single_user = 0; /* Try to go single user */ int CtdlTrySingleUser(void) { int can_do = 0; begin_critical_section(S_SINGLE_USER); if (want_single_user) can_do = 0; else { can_do = 1; want_single_user = 1; } end_critical_section(S_SINGLE_USER); return can_do; } void CtdlEndSingleUser(void) { begin_critical_section(S_SINGLE_USER); want_single_user = 0; end_critical_section(S_SINGLE_USER); } int CtdlWantSingleUser(void) { return want_single_user; } int CtdlIsSingleUser(void) { if (want_single_user) { /* check for only one context here */ if (num_sessions == 1) return TRUE; } return FALSE; } /* * Locate a context by its session number and terminate it if the user is able. * User can NOT terminate their current session. * User CAN terminate any other session that has them logged in. * Aide CAN terminate any session except the current one. */ int CtdlTerminateOtherSession (int session_num) { struct CitContext *CCC = CC; int ret = 0; CitContext *ccptr; int aide; if (session_num == CCC->cs_pid) return TERM_NOTALLOWED; aide = ( (CCC->user.axlevel >= AxAideU) || (CCC->internal_pgm) ) ; CONM_syslog(LOG_DEBUG, "Locating session to kill\n"); begin_critical_section(S_SESSION_TABLE); for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) { if (session_num == ccptr->cs_pid) { ret |= TERM_FOUND; if ((ccptr->user.usernum == CCC->user.usernum) || aide) { ret |= TERM_ALLOWED; } break; } } if (((ret & TERM_FOUND) != 0) && ((ret & TERM_ALLOWED) != 0)) { if (ccptr->IO != NULL) { AsyncIO *IO = ccptr->IO; end_critical_section(S_SESSION_TABLE); KillAsyncIOContext(IO); } else { if (ccptr->user.usernum == CCC->user.usernum) ccptr->kill_me = KILLME_ADMIN_TERMINATE; else ccptr->kill_me = KILLME_IDLE; end_critical_section(S_SESSION_TABLE); } } else end_critical_section(S_SESSION_TABLE); return ret; } /* * Check to see if the user who we just sent mail to is logged in. If yes, * bump the 'new mail' counter for their session. That enables them to * receive a new mail notification without having to hit the database. */ void BumpNewMailCounter(long which_user) { CtdlBumpNewMailCounter(which_user); } void CtdlBumpNewMailCounter(long which_user) { CitContext *ptr; begin_critical_section(S_SESSION_TABLE); for (ptr = ContextList; ptr != NULL; ptr = ptr->next) { if (ptr->user.usernum == which_user) { ptr->newmail += 1; } } end_critical_section(S_SESSION_TABLE); } /* * Check to see if a user is currently logged in * Take care with what you do as a result of this test. * The user may not have been logged in when this function was called BUT * because of threading the user might be logged in before you test the result. */ int CtdlIsUserLoggedIn (char *user_name) { CitContext *cptr; int ret = 0; begin_critical_section (S_SESSION_TABLE); for (cptr = ContextList; cptr != NULL; cptr = cptr->next) { if (!strcasecmp(cptr->user.fullname, user_name)) { ret = 1; break; } } end_critical_section(S_SESSION_TABLE); return ret; } /* * Check to see if a user is currently logged in. * Basically same as CtdlIsUserLoggedIn() but uses the user number instead. * Take care with what you do as a result of this test. * The user may not have been logged in when this function was called BUT * because of threading the user might be logged in before you test the result. */ int CtdlIsUserLoggedInByNum (long usernum) { CitContext *cptr; int ret = 0; begin_critical_section(S_SESSION_TABLE); for (cptr = ContextList; cptr != NULL; cptr = cptr->next) { if (cptr->user.usernum == usernum) { ret = 1; } } end_critical_section(S_SESSION_TABLE); return ret; } /* * Return a pointer to the CitContext structure bound to the thread which * called this function. If there's no such binding (for example, if it's * called by the housekeeper thread) then a generic 'master' CC is returned. * * This function is used *VERY* frequently and must be kept small. */ CitContext *MyContext(void) { register CitContext *c; return ((c = (CitContext *) pthread_getspecific(MyConKey), c == NULL) ? &masterCC : c); } /* * Terminate idle sessions. This function pounds through the session table * comparing the current time to each session's time-of-last-command. If an * idle session is found it is terminated, then the search restarts at the * beginning because the pointer to our place in the list becomes invalid. */ void terminate_idle_sessions(void) { CitContext *ccptr; time_t now; int killed = 0; int longrunners = 0; now = time(NULL); begin_critical_section(S_SESSION_TABLE); for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) { if ( (ccptr != CC) && (config.c_sleeping > 0) && (now - (ccptr->lastcmd) > config.c_sleeping) ) { if (!ccptr->dont_term) { ccptr->kill_me = KILLME_IDLE; ++killed; } else { ++longrunners; } } } end_critical_section(S_SESSION_TABLE); if (killed > 0) CON_syslog(LOG_INFO, "Scheduled %d idle sessions for termination\n", killed); if (longrunners > 0) CON_syslog(LOG_INFO, "Didn't terminate %d protected idle sessions", longrunners); } /* * During shutdown, close the sockets of any sessions still connected. */ void terminate_all_sessions(void) { CitContext *ccptr; int killed = 0; begin_critical_section(S_SESSION_TABLE); for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) { if (ccptr->client_socket != -1) { if (ccptr->IO != NULL) { CON_syslog(LOG_INFO, "terminate_all_sessions() is murdering %s IO[%ld]CC[%d]", ccptr->curr_user, ccptr->IO->ID, ccptr->cs_pid); } else { CON_syslog(LOG_INFO, "terminate_all_sessions() is murdering %s CC[%d]", ccptr->curr_user, ccptr->cs_pid); } close(ccptr->client_socket); ccptr->client_socket = -1; killed++; } } end_critical_section(S_SESSION_TABLE); if (killed > 0) { CON_syslog(LOG_INFO, "Flushed %d stuck sessions\n", killed); } } /* * Terminate a session. */ void RemoveContext (CitContext *con) { const char *c; if (con == NULL) { CONM_syslog(LOG_ERR, "WARNING: RemoveContext() called with NULL!"); return; } c = con->ServiceName; if (c == NULL) { c = "WTF?"; } CON_syslog(LOG_DEBUG, "RemoveContext(%s) session %d", c, con->cs_pid); /// cit_backtrace(); /* Run any cleanup routines registered by loadable modules. * Note: We have to "become_session()" because the cleanup functions * might make references to "CC" assuming it's the right one. */ become_session(con); CtdlUserLogout(); PerformSessionHooks(EVT_STOP); client_close(); /* If the client is still connected, blow 'em away. */ become_session(NULL); CON_syslog(LOG_NOTICE, "[%3d]SRV[%s] Session ended.", con->cs_pid, c); /* * If the client is still connected, blow 'em away. * if the socket is 0 or -1, its already gone or was never there. */ if (con->client_socket > 0) { CON_syslog(LOG_NOTICE, "Closing socket %d", con->client_socket); close(con->client_socket); } /* If using AUTHMODE_LDAP, free the DN */ if (con->ldap_dn) { free(con->ldap_dn); con->ldap_dn = NULL; } FreeStrBuf(&con->StatusMessage); FreeStrBuf(&con->MigrateBuf); FreeStrBuf(&con->RecvBuf.Buf); if (con->cached_msglist) { free(con->cached_msglist); } CONM_syslog(LOG_DEBUG, "Done with RemoveContext()"); } /* * Initialize a new context and place it in the list. The session number * used to be the PID (which is why it's called cs_pid), but that was when we * had one process per session. Now we just assign them sequentially, starting * at 1 (don't change it to 0 because masterCC uses 0). */ CitContext *CreateNewContext(void) { CitContext *me; me = (CitContext *) malloc(sizeof(CitContext)); if (me == NULL) { CONM_syslog(LOG_ALERT, "citserver: can't allocate memory!!\n"); return NULL; } memset(me, 0, sizeof(CitContext)); /* Give the context a name. Hopefully makes it easier to track */ strcpy (me->user.fullname, "SYS_notauth"); /* The new context will be created already in the CON_EXECUTING state * in order to prevent another thread from grabbing it while it's * being set up. */ me->state = CON_EXECUTING; /* * Generate a unique session number and insert this context into * the list. */ me->MigrateBuf = NewStrBuf(); me->RecvBuf.Buf = NewStrBuf(); me->lastcmd = time(NULL); /* set lastcmd to now to prevent idle timer infanticide TODO: if we have a valid IO, use that to set the timer. */ begin_critical_section(S_SESSION_TABLE); me->cs_pid = ++next_pid; me->prev = NULL; me->next = ContextList; ContextList = me; if (me->next != NULL) { me->next->prev = me; } ++num_sessions; end_critical_section(S_SESSION_TABLE); return (me); } /* * Initialize a new context and place it in the list. The session number * used to be the PID (which is why it's called cs_pid), but that was when we * had one process per session. Now we just assign them sequentially, starting * at 1 (don't change it to 0 because masterCC uses 0). */ CitContext *CloneContext(CitContext *CloneMe) { CitContext *me; me = (CitContext *) malloc(sizeof(CitContext)); if (me == NULL) { CONM_syslog(LOG_ALERT, "citserver: can't allocate memory!!\n"); return NULL; } memcpy(me, CloneMe, sizeof(CitContext)); memset(&me->RecvBuf, 0, sizeof(IOBuffer)); memset(&me->SendBuf, 0, sizeof(IOBuffer)); memset(&me->SBuf, 0, sizeof(IOBuffer)); me->MigrateBuf = NULL; me->sMigrateBuf = NULL; me->redirect_buffer = NULL; #ifdef HAVE_OPENSSL me->ssl = NULL; #endif me->download_fp = NULL; me->upload_fp = NULL; /// TODO: what about the room/user? me->ma = NULL; me->openid_data = NULL; me->ldap_dn = NULL; me->session_specific_data = NULL; me->CIT_ICAL = NULL; me->cached_msglist = NULL; me->download_fp = NULL; me->upload_fp = NULL; me->client_socket = 0; me->MigrateBuf = NewStrBuf(); me->RecvBuf.Buf = NewStrBuf(); begin_critical_section(S_SESSION_TABLE); { me->cs_pid = ++next_pid; me->prev = NULL; me->next = ContextList; me->lastcmd = time(NULL); /* set lastcmd to now to prevent idle timer infanticide */ ContextList = me; if (me->next != NULL) { me->next->prev = me; } ++num_sessions; } end_critical_section(S_SESSION_TABLE); return (me); } /* * Return an array containing a copy of the context list. * This allows worker threads to perform "for each context" operations without * having to lock and traverse the live list. */ CitContext *CtdlGetContextArray(int *count) { int nContexts, i; CitContext *nptr, *cptr; nContexts = num_sessions; nptr = malloc(sizeof(CitContext) * nContexts); if (!nptr) { *count = 0; return NULL; } begin_critical_section(S_SESSION_TABLE); for (cptr = ContextList, i=0; cptr != NULL && i < nContexts; cptr = cptr->next, i++) { memcpy(&nptr[i], cptr, sizeof (CitContext)); } end_critical_section (S_SESSION_TABLE); *count = i; return nptr; } /* * Back-end function for starting a session */ void begin_session(CitContext *con) { /* * Initialize some variables specific to our context. */ con->logged_in = 0; con->internal_pgm = 0; con->download_fp = NULL; con->upload_fp = NULL; con->cached_msglist = NULL; con->cached_num_msgs = 0; con->FirstExpressMessage = NULL; time(&con->lastcmd); time(&con->lastidle); strcpy(con->lastcmdname, " "); strcpy(con->cs_clientname, "(unknown)"); strcpy(con->curr_user, NLI); *con->net_node = '\0'; *con->fake_username = '\0'; *con->fake_hostname = '\0'; *con->fake_roomname = '\0'; *con->cs_clientinfo = '\0'; safestrncpy(con->cs_host, config.c_fqdn, sizeof con->cs_host); safestrncpy(con->cs_addr, "", sizeof con->cs_addr); con->cs_UDSclientUID = -1; con->cs_host[sizeof con->cs_host - 1] = 0; if (!CC->is_local_socket) { locate_host(con->cs_host, sizeof con->cs_host, con->cs_addr, sizeof con->cs_addr, con->client_socket ); } else { con->cs_host[0] = 0; con->cs_addr[0] = 0; #ifdef HAVE_STRUCT_UCRED { /* as http://www.wsinnovations.com/softeng/articles/uds.html told us... */ struct ucred credentials; socklen_t ucred_length = sizeof(struct ucred); /*fill in the user data structure */ if(getsockopt(con->client_socket, SOL_SOCKET, SO_PEERCRED, &credentials, &ucred_length)) { syslog(LOG_NOTICE, "could obtain credentials from unix domain socket"); } else { /* the process ID of the process on the other side of the socket */ /* credentials.pid; */ /* the effective UID of the process on the other side of the socket */ con->cs_UDSclientUID = credentials.uid; /* the effective primary GID of the process on the other side of the socket */ /* credentials.gid; */ /* To get supplemental groups, we will have to look them up in our account database, after a reverse lookup on the UID to get the account name. We can take this opportunity to check to see if this is a legit account. */ snprintf(con->cs_clientinfo, sizeof(con->cs_clientinfo), "PID: "F_PID_T"; UID: "F_UID_T"; GID: "F_XPID_T" ", credentials.pid, credentials.uid, credentials.gid); } } #endif } con->cs_flags = 0; con->upload_type = UPL_FILE; con->dl_is_net = 0; con->nologin = 0; if (((config.c_maxsessions > 0)&&(num_sessions > config.c_maxsessions)) || CtdlWantSingleUser()) { con->nologin = 1; } if (!CC->is_local_socket) { syslog(LOG_NOTICE, "Session (%s) started from %s (%s).\n", con->ServiceName, con->cs_host, con->cs_addr); } else { syslog(LOG_NOTICE, "Session (%s) started via local socket UID:%d.\n", con->ServiceName, con->cs_UDSclientUID); } /* Run any session startup routines registered by loadable modules */ PerformSessionHooks(EVT_START); } /* * This function fills in a context and its user field correctly * Then creates/loads that user */ void CtdlFillSystemContext(CitContext *context, char *name) { char sysname[SIZ]; long len; memset(context, 0, sizeof(CitContext)); context->internal_pgm = 1; context->cs_pid = 0; strcpy (sysname, "SYS_"); strcat (sysname, name); len = cutuserkey(sysname); memcpy(context->curr_user, sysname, len + 1); context->client_socket = (-1); context->state = CON_SYS; context->ServiceName = name; /* internal_create_user has the side effect of loading the user regardless of wether they * already existed or needed to be created */ internal_create_user (sysname, len, &(context->user), -1) ; /* Check to see if the system user needs upgrading */ if (context->user.usernum == 0) { /* old system user with number 0, upgrade it */ context->user.usernum = get_new_user_number(); CON_syslog(LOG_INFO, "Upgrading system user \"%s\" from user number 0 to user number %ld\n", context->user.fullname, context->user.usernum); /* add user to the database */ CtdlPutUser(&(context->user)); cdb_store(CDB_USERSBYNUMBER, &(context->user.usernum), sizeof(long), context->user.fullname, strlen(context->user.fullname)+1); } } /* * Cleanup any contexts that are left lying around */ void context_cleanup(void) { CitContext *ptr = NULL; CitContext *rem = NULL; /* * Clean up the contexts. * There are no threads so no critical_section stuff is needed. */ ptr = ContextList; /* We need to update the ContextList because some modules may want to itterate it * Question is should we NULL it before iterating here or should we just keep updating it * as we remove items? * * Answer is to NULL it first to prevent modules from doing any actions on the list at all */ ContextList=NULL; while (ptr != NULL){ /* Remove the session from the active list */ rem = ptr->next; --num_sessions; CON_syslog(LOG_DEBUG, "context_cleanup(): purging session %d\n", ptr->cs_pid); RemoveContext(ptr); free (ptr); ptr = rem; } } /* * Purge all sessions which have the 'kill_me' flag set. * This function has code to prevent it from running more than once every * few seconds, because running it after every single unbind would waste a lot * of CPU time and keep the context list locked too much. To force it to run * anyway, set "force" to nonzero. */ void dead_session_purge(int force) { CitContext *ptr, *ptr2; /* general-purpose utility pointer */ CitContext *rem = NULL; /* list of sessions to be destroyed */ if (force == 0) { if ( (time(NULL) - last_purge) < 5 ) { return; /* Too soon, go away */ } } time(&last_purge); if (try_critical_section(S_SESSION_TABLE)) return; ptr = ContextList; while (ptr) { ptr2 = ptr; ptr = ptr->next; if ( (ptr2->state == CON_IDLE) && (ptr2->kill_me) ) { /* Remove the session from the active list */ if (ptr2->prev) { ptr2->prev->next = ptr2->next; } else { ContextList = ptr2->next; } if (ptr2->next) { ptr2->next->prev = ptr2->prev; } --num_sessions; /* And put it on our to-be-destroyed list */ ptr2->next = rem; rem = ptr2; } } end_critical_section(S_SESSION_TABLE); /* Now that we no longer have the session list locked, we can take * our time and destroy any sessions on the to-be-killed list, which * is allocated privately on this thread's stack. */ while (rem != NULL) { CON_syslog(LOG_DEBUG, "dead_session_purge(): purging session %d, reason=%d\n", rem->cs_pid, rem->kill_me); RemoveContext(rem); ptr = rem; rem = rem->next; free(ptr); } } /* * masterCC is the context we use when not attached to a session. This * function initializes it. */ void InitializeMasterCC(void) { memset(&masterCC, 0, sizeof(struct CitContext)); masterCC.internal_pgm = 1; masterCC.cs_pid = 0; } /* * Set the "async waiting" flag for a session, if applicable */ void set_async_waiting(struct CitContext *ccptr) { CON_syslog(LOG_DEBUG, "Setting async_waiting flag for session %d\n", ccptr->cs_pid); if (ccptr->is_async) { ccptr->async_waiting++; if (ccptr->state == CON_IDLE) { ccptr->state = CON_READY; } } } void DebugSessionEnable(const int n) { DebugSession = n; } CTDL_MODULE_INIT(session) { if (!threading) { CtdlRegisterDebugFlagHook(HKEY("session"), DebugSessionEnable, &DebugSession); } return "session"; } citadel-9.01/citserver.c0000644000000000000000000002534712507024051013727 0ustar rootroot/* * Main source module for the Citadel server * * Copyright (c) 1987-2014 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include "sysdep.h" #include #if HAVE_BACKTRACE #include #endif #include #include "ctdl_module.h" #include "housekeeping.h" #include "locate_host.h" #include "citserver.h" #include "user_ops.h" #include "control.h" #include "config.h" char *unique_session_numbers; int ScheduledShutdown = 0; time_t server_startup_time; int panic_fd; int openid_level_supported = 0; /* * print the actual stack frame. */ void cit_backtrace(void) { #ifdef HAVE_BACKTRACE void *stack_frames[50]; size_t size, i; char **strings; const char *p = IOSTR; if (p == NULL) p = ""; size = backtrace(stack_frames, sizeof(stack_frames) / sizeof(void*)); strings = backtrace_symbols(stack_frames, size); for (i = 0; i < size; i++) { if (strings != NULL) syslog(LOG_ALERT, "%s %s\n", p, strings[i]); else syslog(LOG_ALERT, "%s %p\n", p, stack_frames[i]); } free(strings); #endif } void cit_oneline_backtrace(void) { #ifdef HAVE_BACKTRACE void *stack_frames[50]; size_t size, i; char **strings; StrBuf *Buf; size = backtrace(stack_frames, sizeof(stack_frames) / sizeof(void*)); strings = backtrace_symbols(stack_frames, size); if (size > 0) { Buf = NewStrBuf(); for (i = 1; i < size; i++) { if (strings != NULL) StrBufAppendPrintf(Buf, "%s : ", strings[i]); else StrBufAppendPrintf(Buf, "%p : ", stack_frames[i]); } free(strings); syslog(LOG_ALERT, "%s %s\n", IOSTR, ChrPtr(Buf)); FreeStrBuf(&Buf); } #endif } /* * print the actual stack frame. */ void cit_panic_backtrace(int SigNum) { #ifdef HAVE_BACKTRACE void *stack_frames[10]; size_t size, i; char **strings; printf("caught signal 11\n"); size = backtrace(stack_frames, sizeof(stack_frames) / sizeof(void*)); strings = backtrace_symbols(stack_frames, size); for (i = 0; i < size; i++) { if (strings != NULL) syslog(LOG_ALERT, "%s\n", strings[i]); else syslog(LOG_ALERT, "%p\n", stack_frames[i]); } free(strings); #endif exit(-1); } /* * Various things that need to be initialized at startup */ void master_startup(void) { struct timeval tv; unsigned int seed; FILE *urandom; struct ctdlroom qrbuf; int rv; struct passwd *pw; gid_t gid; syslog(LOG_DEBUG, "master_startup() started\n"); time(&server_startup_time); get_config(); validate_config(); syslog(LOG_INFO, "Checking directory access"); if ((pw = getpwuid(CTDLUID)) == NULL) { gid = getgid(); } else { gid = pw->pw_gid; } if (create_run_directories(CTDLUID, gid) != 0) { syslog(LOG_EMERG, "failed to access & create directories"); exit(1); } syslog(LOG_INFO, "Opening databases"); open_databases(); check_ref_counts(); syslog(LOG_INFO, "Creating base rooms (if necessary)\n"); CtdlCreateRoom(config.c_baseroom, 0, "", 0, 1, 0, VIEW_BBS); CtdlCreateRoom(AIDEROOM, 3, "", 0, 1, 0, VIEW_BBS); CtdlCreateRoom(SYSCONFIGROOM, 3, "", 0, 1, 0, VIEW_BBS); CtdlCreateRoom(config.c_twitroom, 0, "", 0, 1, 0, VIEW_BBS); /* The "Local System Configuration" room doesn't need to be visible */ if (CtdlGetRoomLock(&qrbuf, SYSCONFIGROOM) == 0) { qrbuf.QRflags2 |= QR2_SYSTEM; CtdlPutRoomLock(&qrbuf); } /* Aide needs to be public postable, else we're not RFC conformant. */ if (CtdlGetRoomLock(&qrbuf, AIDEROOM) == 0) { qrbuf.QRflags2 |= QR2_SMTP_PUBLIC; CtdlPutRoomLock(&qrbuf); } syslog(LOG_INFO, "Seeding the pseudo-random number generator...\n"); urandom = fopen("/dev/urandom", "r"); if (urandom != NULL) { rv = fread(&seed, sizeof seed, 1, urandom); if (rv == -1) syslog(LOG_EMERG, "failed to read random seed: %s\n", strerror(errno)); fclose(urandom); } else { gettimeofday(&tv, NULL); seed = tv.tv_usec; } srand(seed); srandom(seed); put_config(); syslog(LOG_DEBUG, "master_startup() finished\n"); } /* * Cleanup routine to be called when the server is shutting down. */ void master_cleanup(int exitcode) { struct CleanupFunctionHook *fcn; static int already_cleaning_up = 0; if (already_cleaning_up) while(1) usleep(1000000); already_cleaning_up = 1; /* Run any cleanup routines registered by loadable modules */ for (fcn = CleanupHookTable; fcn != NULL; fcn = fcn->next) { (*fcn->h_function_pointer)(); } /* Close the AdjRefCount queue file */ AdjRefCount(-1, 0); /* Do system-dependent stuff */ sysdep_master_cleanup(); /* Close databases */ syslog(LOG_INFO, "Closing databases\n"); close_databases(); /* If the operator requested a halt but not an exit, halt here. */ if (shutdown_and_halt) { syslog(LOG_NOTICE, "citserver: Halting server without exiting.\n"); fflush(stdout); fflush(stderr); while(1) { sleep(32767); } } release_control(); /* Now go away. */ syslog(LOG_NOTICE, "citserver: Exiting with status %d\n", exitcode); fflush(stdout); fflush(stderr); if (restart_server != 0) exit(1); if ((running_as_daemon != 0) && ((exitcode == 0) )) exitcode = CTDLEXIT_SHUTDOWN; exit(exitcode); } /* * returns an asterisk if there are any instant messages waiting, * space otherwise. */ char CtdlCheckExpress(void) { if (CC->FirstExpressMessage == NULL) { return(' '); } else { return('*'); } } /* * Check originating host against the public_clients file. This determines * whether the client is allowed to change the hostname for this session * (for example, to show the location of the user rather than the location * of the client). */ int CtdlIsPublicClient(void) { char buf[1024]; char addrbuf[1024]; FILE *fp; int i; char *public_clientspos; char *public_clientsend; char *paddr = NULL; struct stat statbuf; static time_t pc_timestamp = 0; static char public_clients[SIZ]; static char public_clients_file[SIZ]; #define LOCALHOSTSTR "127.0.0.1" snprintf(public_clients_file, sizeof public_clients_file, "%s/public_clients", ctdl_etc_dir); /* * Check the time stamp on the public_clients file. If it's been * updated since the last time we were here (or if this is the first * time we've been through the loop), read its contents and learn * the IP addresses of the listed hosts. */ if (stat(public_clients_file, &statbuf) != 0) { /* No public_clients file exists, so bail out */ syslog(LOG_WARNING, "Warning: '%s' does not exist\n", public_clients_file); return(0); } if (statbuf.st_mtime > pc_timestamp) { begin_critical_section(S_PUBLIC_CLIENTS); syslog(LOG_INFO, "Loading %s\n", public_clients_file); public_clientspos = &public_clients[0]; public_clientsend = public_clientspos + SIZ; safestrncpy(public_clientspos, LOCALHOSTSTR, sizeof public_clients); public_clientspos += sizeof(LOCALHOSTSTR) - 1; if (hostname_to_dotted_quad(addrbuf, config.c_fqdn) == 0) { *(public_clientspos++) = '|'; paddr = &addrbuf[0]; while (!IsEmptyStr (paddr) && (public_clientspos < public_clientsend)) *(public_clientspos++) = *(paddr++); } fp = fopen(public_clients_file, "r"); if (fp != NULL) while ((fgets(buf, sizeof buf, fp)!=NULL) && (public_clientspos < public_clientsend)){ char *ptr; ptr = buf; while (!IsEmptyStr(ptr)) { if (*ptr == '#') { *ptr = 0; break; } else ptr++; } ptr--; while (ptr>buf && isspace(*ptr)) { *(ptr--) = 0; } if (hostname_to_dotted_quad(addrbuf, buf) == 0) { *(public_clientspos++) = '|'; paddr = addrbuf; while (!IsEmptyStr(paddr) && (public_clientspos < public_clientsend)){ *(public_clientspos++) = *(paddr++); } } } if (fp != NULL) fclose(fp); pc_timestamp = time(NULL); end_critical_section(S_PUBLIC_CLIENTS); } syslog(LOG_DEBUG, "Checking whether %s is a local or public client\n", CC->cs_addr); for (i=0; ics_addr, addrbuf)) { syslog(LOG_DEBUG, "... yes its local.\n"); return(1); } } /* No hits. This is not a public client. */ syslog(LOG_DEBUG, "... no it isn't.\n"); return(0); } void citproto_begin_session() { if (CC->nologin==1) { cprintf("%d %s: Too many users are already online (maximum is %d)\n", ERROR + MAX_SESSIONS_EXCEEDED, config.c_nodename, config.c_maxsessions ); CC->kill_me = KILLME_MAX_SESSIONS_EXCEEDED; } else { cprintf("%d %s Citadel server ready.\n", CIT_OK, config.c_nodename); CC->can_receive_im = 1; } } void citproto_begin_admin_session() { CC->internal_pgm = 1; cprintf("%d %s Citadel server ADMIN CONNECTION ready.\n", CIT_OK, config.c_nodename); } /* * This loop recognizes all server commands. */ void do_command_loop(void) { char cmdbuf[SIZ]; time(&CC->lastcmd); memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */ if (client_getln(cmdbuf, sizeof cmdbuf) < 1) { syslog(LOG_ERR, "Citadel client disconnected: ending session.\n"); CC->kill_me = KILLME_CLIENT_DISCONNECTED; return; } /* Log the server command, but don't show passwords... */ if ( (strncasecmp(cmdbuf, "PASS", 4)) && (strncasecmp(cmdbuf, "SETP", 4)) ) { syslog(LOG_INFO, "[%d][%s(%ld)] %s", CC->cs_pid, CC->curr_user, CC->user.usernum, cmdbuf ); } else { syslog(LOG_INFO, "[%d][%s(%ld)]

    "), 0); StrBufAppendBufPlain(this_im->conversation, me->user.fullname, -1, 0); StrBufAppendBufPlain(this_im->conversation, HKEY(": "), 0); StrEscAppend(this_im->conversation, NULL, msgtext, 0, 0); StrBufAppendBufPlain(this_im->conversation, HKEY("

    \r\n"), 0); } end_critical_section(S_IM_LOGS); } /* * Delete any remaining instant messages */ void delete_instant_messages(void) { struct ExpressMessage *ptr; begin_critical_section(S_SESSION_TABLE); while (CC->FirstExpressMessage != NULL) { ptr = CC->FirstExpressMessage->next; if (CC->FirstExpressMessage->text != NULL) free(CC->FirstExpressMessage->text); free(CC->FirstExpressMessage); CC->FirstExpressMessage = ptr; } end_critical_section(S_SESSION_TABLE); } /* * Retrieve instant messages */ void cmd_gexp(char *argbuf) { struct ExpressMessage *ptr; if (CC->FirstExpressMessage == NULL) { cprintf("%d No instant messages waiting.\n", ERROR + MESSAGE_NOT_FOUND); return; } begin_critical_section(S_SESSION_TABLE); ptr = CC->FirstExpressMessage; CC->FirstExpressMessage = CC->FirstExpressMessage->next; end_critical_section(S_SESSION_TABLE); cprintf("%d %d|%ld|%d|%s|%s|%s\n", LISTING_FOLLOWS, ((ptr->next != NULL) ? 1 : 0), /* more msgs? */ (long)ptr->timestamp, /* time sent */ ptr->flags, /* flags */ ptr->sender, /* sender of msg */ config.c_nodename, /* static for now (and possibly deprecated) */ ptr->sender_email /* email or jid of sender */ ); if (ptr->text != NULL) { memfmout(ptr->text, "\n"); free(ptr->text); } cprintf("000\n"); free(ptr); } /* * Asynchronously deliver instant messages */ void cmd_gexp_async(void) { /* Only do this if the session can handle asynchronous protocol */ if (CC->is_async == 0) return; /* And don't do it if there's nothing to send. */ if (CC->FirstExpressMessage == NULL) return; cprintf("%d instant msg\n", ASYNC_MSG + ASYNC_GEXP); } /* * Back end support function for send_instant_message() and company */ void add_xmsg_to_context(struct CitContext *ccptr, struct ExpressMessage *newmsg) { struct ExpressMessage *findend; if (ccptr->FirstExpressMessage == NULL) { ccptr->FirstExpressMessage = newmsg; } else { findend = ccptr->FirstExpressMessage; while (findend->next != NULL) { findend = findend->next; } findend->next = newmsg; } /* If the target context is a session which can handle asynchronous * messages, go ahead and set the flag for that. */ set_async_waiting(ccptr); } /* * This is the back end to the instant message sending function. * Returns the number of users to which the message was sent. * Sending a zero-length message tests for recipients without sending messages. */ int send_instant_message(char *lun, char *lem, char *x_user, char *x_msg) { int message_sent = 0; /* number of successful sends */ struct CitContext *ccptr; struct ExpressMessage *newmsg = NULL; char *un; int do_send = 0; /* 1 = send message; 0 = only check for valid recipient */ static int serial_number = 0; /* this keeps messages from getting logged twice */ if (strlen(x_msg) > 0) { do_send = 1; } /* find the target user's context and append the message */ begin_critical_section(S_SESSION_TABLE); ++serial_number; for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) { if (ccptr->fake_username[0]) { un = ccptr->fake_username; } else { un = ccptr->user.fullname; } if ( ((!strcasecmp(un, x_user)) || (!strcasecmp(x_user, "broadcast"))) && (ccptr->can_receive_im) && ((ccptr->disable_exp == 0) || (CC->user.axlevel >= AxAideU)) ) { if (do_send) { newmsg = (struct ExpressMessage *) malloc(sizeof (struct ExpressMessage)); memset(newmsg, 0, sizeof (struct ExpressMessage)); time(&(newmsg->timestamp)); safestrncpy(newmsg->sender, lun, sizeof newmsg->sender); safestrncpy(newmsg->sender_email, lem, sizeof newmsg->sender_email); if (!strcasecmp(x_user, "broadcast")) { newmsg->flags |= EM_BROADCAST; } newmsg->text = strdup(x_msg); add_xmsg_to_context(ccptr, newmsg); /* and log it ... */ if (ccptr != CC) { log_instant_message(CC, ccptr, newmsg->text, serial_number); } } ++message_sent; } } end_critical_section(S_SESSION_TABLE); return (message_sent); } /* * send instant messages */ void cmd_sexp(char *argbuf) { int message_sent = 0; char x_user[USERNAME_SIZE]; char x_msg[1024]; char *lun; char *lem; char *x_big_msgbuf = NULL; if ((!(CC->logged_in)) && (!(CC->internal_pgm))) { cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN); return; } if (CC->fake_username[0]) lun = CC->fake_username; else lun = CC->user.fullname; lem = CC->cs_inet_email; extract_token(x_user, argbuf, 0, '|', sizeof x_user); extract_token(x_msg, argbuf, 1, '|', sizeof x_msg); if (!x_user[0]) { cprintf("%d You were not previously paged.\n", ERROR + NO_SUCH_USER); return; } if ((!strcasecmp(x_user, "broadcast")) && (CC->user.axlevel < AxAideU)) { cprintf("%d Higher access required to send a broadcast.\n", ERROR + HIGHER_ACCESS_REQUIRED); return; } /* This loop handles text-transfer pages */ if (!strcmp(x_msg, "-")) { message_sent = PerformXmsgHooks(lun, lem, x_user, ""); if (message_sent == 0) { if (CtdlGetUser(NULL, x_user)) cprintf("%d '%s' does not exist.\n", ERROR + NO_SUCH_USER, x_user); else cprintf("%d '%s' is not logged in " "or is not accepting pages.\n", ERROR + RESOURCE_NOT_OPEN, x_user); return; } unbuffer_output(); cprintf("%d Transmit message (will deliver to %d users)\n", SEND_LISTING, message_sent); x_big_msgbuf = malloc(SIZ); memset(x_big_msgbuf, 0, SIZ); while (client_getln(x_msg, sizeof x_msg) >= 0 && strcmp(x_msg, "000")) { x_big_msgbuf = realloc(x_big_msgbuf, strlen(x_big_msgbuf) + strlen(x_msg) + 4); if (!IsEmptyStr(x_big_msgbuf)) if (x_big_msgbuf[strlen(x_big_msgbuf)] != '\n') strcat(x_big_msgbuf, "\n"); strcat(x_big_msgbuf, x_msg); } PerformXmsgHooks(lun, lem, x_user, x_big_msgbuf); free(x_big_msgbuf); /* This loop handles inline pages */ } else { message_sent = PerformXmsgHooks(lun, lem, x_user, x_msg); if (message_sent > 0) { if (!IsEmptyStr(x_msg)) cprintf("%d Message sent", CIT_OK); else cprintf("%d Ok to send message", CIT_OK); if (message_sent > 1) cprintf(" to %d users", message_sent); cprintf(".\n"); } else { if (CtdlGetUser(NULL, x_user)) cprintf("%d '%s' does not exist.\n", ERROR + NO_SUCH_USER, x_user); else cprintf("%d '%s' is not logged in " "or is not accepting pages.\n", ERROR + RESOURCE_NOT_OPEN, x_user); } } } /* * Enter or exit paging-disabled mode */ void cmd_dexp(char *argbuf) { int new_state; if (CtdlAccessCheck(ac_logged_in)) return; new_state = extract_int(argbuf, 0); if ((new_state == 0) || (new_state == 1)) { CC->disable_exp = new_state; } cprintf("%d %d\n", CIT_OK, CC->disable_exp); } /* * Request client termination */ void cmd_reqt(char *argbuf) { struct CitContext *ccptr; int sessions = 0; int which_session; struct ExpressMessage *newmsg; if (CtdlAccessCheck(ac_aide)) return; which_session = extract_int(argbuf, 0); begin_critical_section(S_SESSION_TABLE); for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) { if ((ccptr->cs_pid == which_session) || (which_session == 0)) { newmsg = (struct ExpressMessage *) malloc(sizeof (struct ExpressMessage)); memset(newmsg, 0, sizeof (struct ExpressMessage)); time(&(newmsg->timestamp)); safestrncpy(newmsg->sender, CC->user.fullname, sizeof newmsg->sender); newmsg->flags |= EM_GO_AWAY; newmsg->text = strdup("Automatic logoff requested."); add_xmsg_to_context(ccptr, newmsg); ++sessions; } } end_critical_section(S_SESSION_TABLE); cprintf("%d Sent termination request to %d sessions.\n", CIT_OK, sessions); } /* * This is the back end for flush_conversations_to_disk() * At this point we've isolated a single conversation (struct imlog) * and are ready to write it to disk. */ void flush_individual_conversation(struct imlog *im) { struct CtdlMessage *msg; long msgnum = 0; char roomname[ROOMNAMELEN]; StrBuf *MsgBuf, *FullMsgBuf; StrBufAppendBufPlain(im->conversation, HKEY( "\r\n" "\r\n" ), 0 ); MsgBuf = StrBufRFC2047encodeMessage(im->conversation); FlushStrBuf(im->conversation); FullMsgBuf = NewStrBufPlain(NULL, StrLength(im->conversation) + 100); StrBufAppendBufPlain(FullMsgBuf, HKEY( "Content-type: text/html; charset=UTF-8\r\n" "Content-Transfer-Encoding: quoted-printable\r\n" "\r\n" ), 0); StrBufAppendBuf (FullMsgBuf, MsgBuf, 0); FreeStrBuf(&MsgBuf); msg = malloc(sizeof(struct CtdlMessage)); memset(msg, 0, sizeof(struct CtdlMessage)); msg->cm_magic = CTDLMESSAGE_MAGIC; msg->cm_anon_type = MES_NORMAL; msg->cm_format_type = FMT_RFC822; if (!IsEmptyStr(im->usernames[0])) { CM_SetField(msg, eAuthor, im->usernames[0], strlen(im->usernames[0])); } else { CM_SetField(msg, eAuthor, HKEY("Citadel")); } if (!IsEmptyStr(im->usernames[1])) { CM_SetField(msg, eRecipient, im->usernames[1], strlen(im->usernames[1])); } CM_SetField(msg, eOriginalRoom, HKEY(PAGELOGROOM)); CM_SetField(msg, eNodeName, CFG_KEY(c_nodename)); CM_SetAsFieldSB(msg, eMesageText, &FullMsgBuf); /* we own this memory now */ /* Start with usernums[1] because it's guaranteed to be higher than usernums[0], * so if there's only one party, usernums[0] will be zero but usernums[1] won't. * Create the room if necessary. Note that we create as a type 5 room rather * than 4, which indicates that it's a personal room but we've already supplied * the namespace prefix. * * In the unlikely event that usernums[1] is zero, a room with an invalid namespace * prefix will be created. That's ok because the auto-purger will clean it up later. */ snprintf(roomname, sizeof roomname, "%010ld.%s", im->usernums[1], PAGELOGROOM); CtdlCreateRoom(roomname, 5, "", 0, 1, 1, VIEW_BBS); msgnum = CtdlSubmitMsg(msg, NULL, roomname, 0); CM_Free(msg); /* If there is a valid user number in usernums[0], save a copy for them too. */ if (im->usernums[0] > 0) { snprintf(roomname, sizeof roomname, "%010ld.%s", im->usernums[0], PAGELOGROOM); CtdlCreateRoom(roomname, 5, "", 0, 1, 1, VIEW_BBS); CtdlSaveMsgPointerInRoom(roomname, msgnum, 0, NULL); } /* Finally, if we're logging instant messages globally, do that now. */ if (!IsEmptyStr(config.c_logpages)) { CtdlCreateRoom(config.c_logpages, 3, "", 0, 1, 1, VIEW_BBS); CtdlSaveMsgPointerInRoom(config.c_logpages, msgnum, 0, NULL); } } /* * Locate instant message conversations which have gone idle * (or, if the server is shutting down, locate *all* conversations) * and flush them to disk (in the participants' log rooms, etc.) */ void flush_conversations_to_disk(time_t if_older_than) { struct imlog *flush_these = NULL; struct imlog *dont_flush_these = NULL; struct imlog *imptr = NULL; struct CitContext *nptr; int nContexts, i; nptr = CtdlGetContextArray(&nContexts) ; /* Make a copy of the current wholist */ begin_critical_section(S_IM_LOGS); while (imlist) { imptr = imlist; imlist = imlist->next; /* For a two party conversation, if one party has logged out, force flush. */ if (nptr) { int user0_is_still_online = 0; int user1_is_still_online = 0; for (i=0; iusernums[0]) ++user0_is_still_online; if (nptr[i].user.usernum == imptr->usernums[1]) ++user1_is_still_online; } if (imptr->usernums[0] != imptr->usernums[1]) { /* two party conversation */ if ((!user0_is_still_online) || (!user1_is_still_online)) { imptr->lastmsg = 0L; /* force flush */ } } else { /* one party conversation (yes, people do IM themselves) */ if (!user0_is_still_online) { imptr->lastmsg = 0L; /* force flush */ } } } /* Now test this conversation to see if it qualifies for flushing. */ if ((time(NULL) - imptr->lastmsg) > if_older_than) { /* This conversation qualifies. Move it to the list of ones to flush. */ imptr->next = flush_these; flush_these = imptr; } else { /* Move it to the list of ones not to flush. */ imptr->next = dont_flush_these; dont_flush_these = imptr; } } imlist = dont_flush_these; end_critical_section(S_IM_LOGS); free(nptr); /* We are now outside of the critical section, and we are the only thread holding a * pointer to a linked list of conversations to be flushed to disk. */ while (flush_these) { flush_individual_conversation(flush_these); /* This will free the string buffer */ imptr = flush_these; flush_these = flush_these->next; free(imptr); } } void instmsg_timer(void) { flush_conversations_to_disk(300); /* Anything that hasn't peeped in more than 5 minutes */ } void instmsg_shutdown(void) { flush_conversations_to_disk(0); /* Get it ALL onto disk NOW. */ } CTDL_MODULE_INIT(instmsg) { if (!threading) { CtdlRegisterProtoHook(cmd_gexp, "GEXP", "Get instant messages"); CtdlRegisterProtoHook(cmd_sexp, "SEXP", "Send an instant message"); CtdlRegisterProtoHook(cmd_dexp, "DEXP", "Disable instant messages"); CtdlRegisterProtoHook(cmd_reqt, "REQT", "Request client termination"); CtdlRegisterSessionHook(cmd_gexp_async, EVT_ASYNC, PRIO_ASYNC + 1); CtdlRegisterSessionHook(delete_instant_messages, EVT_STOP, PRIO_STOP + 1); CtdlRegisterXmsgHook(send_instant_message, XMSG_PRI_LOCAL); CtdlRegisterSessionHook(instmsg_timer, EVT_TIMER, PRIO_CLEANUP + 400); CtdlRegisterSessionHook(instmsg_shutdown, EVT_SHUTDOWN, PRIO_SHUTDOWN + 10); } /* return our module name for the log */ return "instmsg"; } citadel-9.01/modules/urldeshortener/0000755000000000000000000000000012507024051016257 5ustar rootrootcitadel-9.01/modules/urldeshortener/serv_expand_shorter_urls.c0000644000000000000000000001604212507024051023557 0ustar rootroot /* * * Copyright (c) 1998-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * */ #include "sysdep.h" #include #include #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "control.h" #include "user_ops.h" #include "database.h" #include "msgbase.h" #include "internet_addressing.h" #include "genstamp.h" #include "domain.h" #include "ctdl_module.h" #include "locate_host.h" #include "citadel_dirs.h" #include "event_client.h" HashList *UrlShorteners = NULL; size_t GetLocationString( void *ptr, size_t size, size_t nmemb, void *userdata) { #define LOCATION "location" if (strncasecmp((char*)ptr, LOCATION, sizeof(LOCATION) - 1) == 0) { StrBuf *pURL = (StrBuf*) userdata; char *pch = (char*) ptr; char *pche; pche = pch + (size * nmemb); pch += sizeof(LOCATION); while (isspace(*pch) || (*pch == ':')) pch ++; while (isspace(*pche) || (*pche == '\0')) pche--; FlushStrBuf(pURL); StrBufPlain(pURL, pch, pche - pch + 1); } return size * nmemb; } eNextState ShutdownLookuUrl(AsyncIO *IO) { //TOOD return eAbort; } eNextState TerminateLookupUrl(AsyncIO *IO) { //TOOD return eAbort; } eNextState TerminateLookupUrlDB(AsyncIO *IO) { //TOOD return eAbort; } eNextState LookupUrlResult(AsyncIO *IO) { return eTerminateConnection; /// /TODO } int LookupUrl(StrBuf *ShorterUrlStr) { CURLcode sta; int rc = 0; CURL *chnd; AsyncIO *IO; IO = (AsyncIO*) malloc(sizeof(AsyncIO)); memset(IO, 0, sizeof(AsyncIO)); IO->CitContext = CloneContext(CC); ParseURL(&IO->ConnectMe, ShorterUrlStr, 80); CurlPrepareURL(IO->ConnectMe); if (! InitcURLIOStruct(IO, // Ctx, NULL, "Citadel RSS ShorterURL Expander", LookupUrlResult, TerminateLookupUrl, TerminateLookupUrlDB, ShutdownLookuUrl)) { syslog(LOG_ALERT, "Unable to initialize libcurl.\n"); goto shutdown; } chnd = IO->HttpReq.chnd; OPT(SSL_VERIFYPEER, 0); OPT(SSL_VERIFYHOST, 0); OPT(FOLLOWLOCATION, 10); #ifdef CURLOPT_HTTP_CONTENT_DECODING OPT(HTTP_CONTENT_DECODING, 1); OPT(ENCODING, ""); #endif OPT(HEADERFUNCTION , GetLocationString); OPT(WRITEHEADER, ShorterUrlStr); if (server_shutting_down) goto shutdown ; QueueCurlContext(IO); shutdown: return rc; } void CrawlMessageForShorterUrls(HashList *pUrls, StrBuf *Message) { int nHits = 0; void *pv; int nShorter = 0; const char *pch; const char *pUrl; ConstStr *pCUrl; while (GetHash(UrlShorteners, IKEY(nShorter), &pv)) { nShorter++; pch = ChrPtr(Message); pUrl = strstr(pch, ChrPtr((StrBuf*)pv)); while ((pUrl != NULL) && (nHits < 99)) { pCUrl = malloc(sizeof(ConstStr)); pCUrl->Key = pUrl; pch = pUrl + StrLength((StrBuf*)pv); while (isalnum(*pch)||(*pch == '-')||(*pch == '/')) pch++; pCUrl->len = pch - pCUrl->Key; Put(pUrls, IKEY(nHits), pCUrl, NULL); nHits ++; pUrl = strstr(pch, ChrPtr((StrBuf*)pv)); } } } int SortConstStrByPosition(const void *Item1, const void *Item2) { const ConstStr *p1, *p2; p1 = (const ConstStr*) Item1; p2 = (const ConstStr*) Item2; if (p1->Key == p2->Key) return 0; if (p1->Key > p2->Key) return 1; return -1; } HashList *GetShorterUrls(StrBuf *Message) { HashList *pUrls; /* we just suspect URL shorteners to be inside of feeds from twitter * or other short content messages, so don't crawl through real blogs. */ if (StrLength(Message) > 500) return NULL; pUrls = NewHash(1, Flathash); CrawlMessageForShorterUrls(pUrls, Message); if (GetCount(pUrls) > 0) return pUrls; else return NULL; } void ExpandShortUrls(StrBuf *Message, HashList *pUrls, int Callback) { StrBuf *Shadow; ConstStr *pCUrl; const char *pch; const char *pche; StrBuf *ShorterUrlStr; HashPos *Pos; const char *Key; void *pv; long len; Shadow = NewStrBufPlain(NULL, StrLength(Message)); SortByPayload (pUrls, SortConstStrByPosition); ShorterUrlStr = NewStrBufPlain(NULL, StrLength(Message)); pch = ChrPtr(Message); pche = pch + StrLength(Message); Pos = GetNewHashPos(pUrls, 1); while (GetNextHashPos(pUrls, Pos, &len, &Key, &pv)) { pCUrl = (ConstStr*) pv; if (pch != pCUrl->Key) StrBufAppendBufPlain(Shadow, pch, pCUrl->Key - pch, 0); StrBufPlain(ShorterUrlStr, CKEY(*pCUrl)); if (LookupUrl(ShorterUrlStr)) { StrBufAppendBufPlain(Shadow, HKEY("
    "), 0); StrBufAppendBuf(Shadow, ShorterUrlStr, 0); StrBufAppendBufPlain(Shadow, HKEY("["), 0); StrBufAppendBufPlain(Shadow, pCUrl->Key, pCUrl->len, 0); StrBufAppendBufPlain(Shadow, HKEY("]"), 0); } else { StrBufAppendBufPlain(Shadow, HKEY("Key, pCUrl->len, 0); StrBufAppendBufPlain(Shadow, HKEY("\">"), 0); StrBufAppendBufPlain(Shadow, pCUrl->Key, pCUrl->len, 0); StrBufAppendBufPlain(Shadow, HKEY(""), 0); } pch = pCUrl->Key + pCUrl->len + 1; } if (pch < pche) StrBufAppendBufPlain(Shadow, pch, pche - pch, 0); FlushStrBuf(Message); StrBufAppendBuf(Message, Shadow, 0); FreeStrBuf(&ShorterUrlStr); FreeStrBuf(&Shadow); DeleteHashPos(&Pos); DeleteHash(&pUrls); } void LoadUrlShorteners(void) { int i = 0; int fd; const char *POS = NULL; const char *Err = NULL; StrBuf *Content, *Line; UrlShorteners = NewHash(0, Flathash); fd = open(file_citadel_urlshorteners, 0); if (fd != 0) { Content = NewStrBufPlain(NULL, SIZ); Line = NewStrBuf(); while (POS != StrBufNOTNULL) { StrBufTCP_read_buffered_line_fast (Line, Content, &POS, &fd, 1, 1, &Err); StrBufTrim(Line); if ((*ChrPtr(Line) != '#') && (StrLength(Line) > 0)) { Put(UrlShorteners, IKEY(i), Line, HFreeStrBuf); i++; Line = NewStrBuf(); } else FlushStrBuf(Line); if (POS == NULL) POS = StrBufNOTNULL; } FreeStrBuf(&Line); FreeStrBuf(&Content); } close(fd); } void shorter_url_cleanup(void) { DeleteHash(&UrlShorteners); } CTDL_MODULE_INIT(urldeshortener) { if (threading) { syslog(LOG_INFO, "%s\n", curl_version()); } else { LoadUrlShorteners (); CtdlRegisterCleanupHook(shorter_url_cleanup); } return "UrlShortener"; } citadel-9.01/modules/urldeshortener/serv_expand_shorter_urls.h0000644000000000000000000000013312507024051023556 0ustar rootrootvoid ExpandShortUrls(StrBuf *Message, Callback); HashList GetShorterUrls(StrBuf Message); citadel-9.01/modules/rssclient/0000755000000000000000000000000012507024051015220 5ustar rootrootcitadel-9.01/modules/rssclient/serv_rssclient.c0000644000000000000000000006062212507024051020437 0ustar rootroot/* * Bring external RSS feeds into rooms. * * Copyright (c) 2007-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the * GNU General Public License for more details. */ #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H #include # else #include # endif #endif #include #include #include #include #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "threads.h" #include "ctdl_module.h" #include "msgbase.h" #include "parsedate.h" #include "database.h" #include "citadel_dirs.h" #include "md5.h" #include "context.h" #include "event_client.h" #include "rss_atom_parser.h" #define TMP_MSGDATA 0xFF #define TMP_SHORTER_URL_OFFSET 0xFE #define TMP_SHORTER_URLS 0xFD time_t last_run = 0L; pthread_mutex_t RSSQueueMutex; /* locks the access to the following vars: */ HashList *RSSQueueRooms = NULL; /* rss_room_counter */ HashList *RSSFetchUrls = NULL; /*->rss_aggregator;->RefCount access locked*/ eNextState RSSAggregator_Terminate(AsyncIO *IO); eNextState RSSAggregator_TerminateDB(AsyncIO *IO); eNextState RSSAggregator_ShutdownAbort(AsyncIO *IO); struct CitContext rss_CC; struct rssnetcfg *rnclist = NULL; int RSSClientDebugEnabled = 0; #define N ((rss_aggregator*)IO->Data)->Cfg.QRnumber #define DBGLOG(LEVEL) if ((LEVEL != LOG_DEBUG) || (RSSClientDebugEnabled != 0)) #define EVRSSC_syslog(LEVEL, FORMAT, ...) \ DBGLOG(LEVEL) syslog(LEVEL, \ "%s[%ld]CC[%d][%ld]RSS" FORMAT, \ IOSTR, IO->ID, CCID, N, __VA_ARGS__) #define EVRSSCM_syslog(LEVEL, FORMAT) \ DBGLOG(LEVEL) syslog(LEVEL, \ "%s[%ld]CC[%d][%ld]RSS" FORMAT, \ IOSTR, IO->ID, CCID, N) #define EVRSSQ_syslog(LEVEL, FORMAT, ...) \ DBGLOG(LEVEL) syslog(LEVEL, "RSS" FORMAT, \ __VA_ARGS__) #define EVRSSQM_syslog(LEVEL, FORMAT) \ DBGLOG(LEVEL) syslog(LEVEL, "RSS" FORMAT) #define EVRSSCSM_syslog(LEVEL, FORMAT) \ DBGLOG(LEVEL) syslog(LEVEL, "%s[%ld][%ld]RSS" FORMAT, \ IOSTR, IO->ID, N) typedef enum _RSSState { eRSSCreated, eRSSFetching, eRSSFailure, eRSSParsing, eRSSUT } RSSState; ConstStr RSSStates[] = { {HKEY("Aggregator created")}, {HKEY("Fetching content")}, {HKEY("Failed")}, {HKEY("parsing content")}, {HKEY("checking usetable")} }; static size_t GetLocationString( void *ptr, size_t size, size_t nmemb, void *userdata) { #define LOCATION "location" if (strncasecmp((char*)ptr, LOCATION, sizeof(LOCATION) - 1) == 0) { AsyncIO *IO = (AsyncIO *) userdata; rss_aggregator *RSSAggr = (rss_aggregator *)IO->Data; char *pch = (char*) ptr; char *pche; pche = pch + (size * nmemb); pch += sizeof(LOCATION); while (isspace(*pch) || (*pch == ':')) pch ++; while (isspace(*pche) || (*pche == '\0')) pche--; if (RSSAggr->RedirectUrl == NULL) { RSSAggr->RedirectUrl = NewStrBufPlain(pch, pche - pch + 1); } else { FlushStrBuf(RSSAggr->RedirectUrl); StrBufPlain(RSSAggr->RedirectUrl, pch, pche - pch + 1); } } return size * nmemb; } static void SetRSSState(AsyncIO *IO, RSSState State) { CitContext* CCC = IO->CitContext; if (CCC != NULL) memcpy(CCC->cs_clientname, RSSStates[State].Key, RSSStates[State].len + 1); } void DeleteRoomReference(long QRnumber) { HashPos *At; long HKLen; const char *HK; void *vData = NULL; rss_room_counter *pRoomC; At = GetNewHashPos(RSSQueueRooms, 0); if (GetHashPosFromKey(RSSQueueRooms, LKEY(QRnumber), At)) { GetHashPos(RSSQueueRooms, At, &HKLen, &HK, &vData); if (vData != NULL) { pRoomC = (rss_room_counter *) vData; pRoomC->count --; if (pRoomC->count == 0) DeleteEntryFromHash(RSSQueueRooms, At); } } DeleteHashPos(&At); } void UnlinkRooms(rss_aggregator *RSSAggr) { DeleteRoomReference(RSSAggr->Cfg.QRnumber); if (RSSAggr->OtherQRnumbers != NULL) { long HKLen; const char *HK; HashPos *At; void *vData; At = GetNewHashPos(RSSAggr->OtherQRnumbers, 0); while (! server_shutting_down && GetNextHashPos(RSSAggr->OtherQRnumbers, At, &HKLen, &HK, &vData) && (vData != NULL)) { pRSSConfig *Data = (pRSSConfig*) vData; DeleteRoomReference(Data->QRnumber); } DeleteHashPos(&At); } } void UnlinkRSSAggregator(rss_aggregator *RSSAggr) { HashPos *At; pthread_mutex_lock(&RSSQueueMutex); UnlinkRooms(RSSAggr); At = GetNewHashPos(RSSFetchUrls, 0); if (GetHashPosFromKey(RSSFetchUrls, SKEY(RSSAggr->Url), At)) { DeleteEntryFromHash(RSSFetchUrls, At); } DeleteHashPos(&At); last_run = time(NULL); pthread_mutex_unlock(&RSSQueueMutex); } void DeleteRssCfg(void *vptr) { rss_aggregator *RSSAggr = (rss_aggregator *)vptr; AsyncIO *IO = &RSSAggr->IO; if (IO->CitContext != NULL) EVRSSCM_syslog(LOG_DEBUG, "RSS: destroying\n"); FreeStrBuf(&RSSAggr->Url); FreeStrBuf(&RSSAggr->RedirectUrl); FreeStrBuf(&RSSAggr->rooms); FreeStrBuf(&RSSAggr->CData); FreeStrBuf(&RSSAggr->Key); DeleteHash(&RSSAggr->OtherQRnumbers); DeleteHashPos (&RSSAggr->Pos); DeleteHash (&RSSAggr->Messages); if (RSSAggr->recp.recp_room != NULL) free(RSSAggr->recp.recp_room); if (RSSAggr->Item != NULL) { flush_rss_item(RSSAggr->Item); free(RSSAggr->Item); } FreeAsyncIOContents(&RSSAggr->IO); memset(RSSAggr, 0, sizeof(rss_aggregator)); free(RSSAggr); } eNextState RSSAggregator_Terminate(AsyncIO *IO) { rss_aggregator *RSSAggr = (rss_aggregator *)IO->Data; EVRSSCM_syslog(LOG_DEBUG, "RSS: Terminating.\n"); StopCurlWatchers(IO); UnlinkRSSAggregator(RSSAggr); return eAbort; } eNextState RSSAggregator_TerminateDB(AsyncIO *IO) { rss_aggregator *RSSAggr = (rss_aggregator *)IO->Data; EVRSSCM_syslog(LOG_DEBUG, "RSS: Terminating.\n"); StopDBWatchers(&RSSAggr->IO); UnlinkRSSAggregator(RSSAggr); return eAbort; } eNextState RSSAggregator_ShutdownAbort(AsyncIO *IO) { const char *pUrl; rss_aggregator *RSSAggr = (rss_aggregator *)IO->Data; pUrl = IO->ConnectMe->PlainUrl; if (pUrl == NULL) pUrl = ""; EVRSSC_syslog(LOG_DEBUG, "RSS: Aborting by shutdown: %s.\n", pUrl); StopCurlWatchers(IO); UnlinkRSSAggregator(RSSAggr); return eAbort; } void AppendLink(StrBuf *Message, StrBuf *link, StrBuf *LinkTitle, const char *Title) { if (StrLength(link) > 0) { StrBufAppendBufPlain(Message, HKEY(""), 0); if (StrLength(LinkTitle) > 0) StrBufAppendBuf(Message, LinkTitle, 0); else if ((Title != NULL) && !IsEmptyStr(Title)) StrBufAppendBufPlain(Message, Title, -1, 0); else StrBufAppendBuf(Message, link, 0); StrBufAppendBufPlain(Message, HKEY("
    \n"), 0); } } int rss_format_item(AsyncIO *IO, networker_save_message *SaveMsg) { StrBuf *Message; int msglen = 0; if (StrLength(SaveMsg->description) + StrLength(SaveMsg->link) + StrLength(SaveMsg->linkTitle) + StrLength(SaveMsg->reLink) + StrLength(SaveMsg->reLinkTitle) + StrLength(SaveMsg->title) == 0) { EVRSSCM_syslog(LOG_INFO, "Refusing to save empty message."); return 0; } CM_Flush(&SaveMsg->Msg); if (SaveMsg->author_or_creator != NULL) { char *From; StrBuf *Encoded = NULL; int FromAt; From = html_to_ascii(ChrPtr(SaveMsg->author_or_creator), StrLength(SaveMsg->author_or_creator), 512, 0); StrBufPlain(SaveMsg->author_or_creator, From, -1); StrBufTrim(SaveMsg->author_or_creator); free(From); FromAt = strchr(ChrPtr(SaveMsg->author_or_creator), '@') != NULL; if (!FromAt && StrLength (SaveMsg->author_email) > 0) { StrBufRFC2047encode(&Encoded, SaveMsg->author_or_creator); CM_SetAsFieldSB(&SaveMsg->Msg, eAuthor, &Encoded); CM_SetAsFieldSB(&SaveMsg->Msg, eMessagePath, &SaveMsg->author_email); } else { if (FromAt) { CM_SetAsFieldSB(&SaveMsg->Msg, eAuthor, &SaveMsg->author_or_creator); CM_CopyField(&SaveMsg->Msg, eMessagePath, eAuthor); } else { StrBufRFC2047encode(&Encoded, SaveMsg->author_or_creator); CM_SetAsFieldSB(&SaveMsg->Msg, eAuthor, &Encoded); CM_SetField(&SaveMsg->Msg, eMessagePath, HKEY("rss@localhost")); } } } else { CM_SetField(&SaveMsg->Msg, eAuthor, HKEY("rss")); } CM_SetField(&SaveMsg->Msg, eNodeName, CFG_KEY(c_nodename)); if (SaveMsg->title != NULL) { long len; char *Sbj; StrBuf *Encoded, *QPEncoded; QPEncoded = NULL; StrBufSpaceToBlank(SaveMsg->title); len = StrLength(SaveMsg->title); Sbj = html_to_ascii(ChrPtr(SaveMsg->title), len, 512, 0); len = strlen(Sbj); if ((len > 0) && (Sbj[len - 1] == '\n')) { len --; Sbj[len] = '\0'; } Encoded = NewStrBufPlain(Sbj, len); free(Sbj); StrBufTrim(Encoded); StrBufRFC2047encode(&QPEncoded, Encoded); CM_SetAsFieldSB(&SaveMsg->Msg, eMsgSubject, &QPEncoded); FreeStrBuf(&Encoded); } if (SaveMsg->link == NULL) SaveMsg->link = NewStrBufPlain(HKEY("")); #if 0 /* temporarily disable shorter urls. */ SaveMsg->Msg.cm_fields[TMP_SHORTER_URLS] = GetShorterUrls(SaveMsg->description); #endif msglen += 1024 + StrLength(SaveMsg->link) + StrLength(SaveMsg->description) ; Message = NewStrBufPlain(NULL, msglen); StrBufPlain(Message, HKEY( "Content-type: text/html; charset=\"UTF-8\"\r\n\r\n" "\n")); #if 0 /* disable shorter url for now. */ SaveMsg->Msg.cm_fields[TMP_SHORTER_URL_OFFSET] = StrLength(Message); #endif StrBufAppendBuf(Message, SaveMsg->description, 0); StrBufAppendBufPlain(Message, HKEY("

    \n"), 0); AppendLink(Message, SaveMsg->link, SaveMsg->linkTitle, NULL); AppendLink(Message, SaveMsg->reLink, SaveMsg->reLinkTitle, "Reply to this"); StrBufAppendBufPlain(Message, HKEY("\n"), 0); SaveMsg->Message = Message; return 1; } eNextState RSSSaveMessage(AsyncIO *IO) { long len; const char *Key; rss_aggregator *RSSAggr = (rss_aggregator *) IO->Data; if (rss_format_item(IO, RSSAggr->ThisMsg)) { CM_SetAsFieldSB(&RSSAggr->ThisMsg->Msg, eMesageText, &RSSAggr->ThisMsg->Message); CtdlSubmitMsg(&RSSAggr->ThisMsg->Msg, &RSSAggr->recp, NULL, 0); /* write the uidl to the use table so we don't store this item again */ CheckIfAlreadySeen("RSS Item Insert", RSSAggr->ThisMsg->MsgGUID, EvGetNow(IO), 0, eWrite, CCID, IO->ID); } if (GetNextHashPos(RSSAggr->Messages, RSSAggr->Pos, &len, &Key, (void**) &RSSAggr->ThisMsg)) return NextDBOperation(IO, RSS_FetchNetworkUsetableEntry); else return eAbort; } eNextState RSS_FetchNetworkUsetableEntry(AsyncIO *IO) { static const time_t antiExpire = USETABLE_ANTIEXPIRE_HIRES; #ifndef DEBUG_RSS time_t seenstamp = 0; const char *Key; long len; rss_aggregator *Ctx = (rss_aggregator *) IO->Data; /* Find out if we've already seen this item */ // todo: expiry? SetRSSState(IO, eRSSUT); seenstamp = CheckIfAlreadySeen("RSS Item Seen", Ctx->ThisMsg->MsgGUID, EvGetNow(IO), antiExpire, eCheckUpdate, CCID, IO->ID); if (seenstamp != 0) { /* Item has already been seen */ EVRSSC_syslog(LOG_DEBUG, "%s has already been seen - %ld < %ld", ChrPtr(Ctx->ThisMsg->MsgGUID), seenstamp, antiExpire); SetRSSState(IO, eRSSParsing); if (GetNextHashPos(Ctx->Messages, Ctx->Pos, &len, &Key, (void**) &Ctx->ThisMsg)) return NextDBOperation( IO, RSS_FetchNetworkUsetableEntry); else return eAbort; } else #endif { /* Item has already been seen */ EVRSSC_syslog(LOG_DEBUG, "%s Parsing - %ld >= %ld", ChrPtr(Ctx->ThisMsg->MsgGUID), seenstamp, antiExpire); SetRSSState(IO, eRSSParsing); NextDBOperation(IO, RSSSaveMessage); return eSendMore; } return eSendMore; } void UpdateLastKnownGood(pRSSConfig *pCfg, time_t now) { OneRoomNetCfg* pRNCfg; begin_critical_section(S_NETCONFIGS); pRNCfg = CtdlGetNetCfgForRoom (pCfg->QRnumber); if (pRNCfg != NULL) { RSSCfgLine *RSSCfg = (RSSCfgLine *)pRNCfg->NetConfigs[rssclient]; while (RSSCfg != NULL) { if (RSSCfg == pCfg->pCfg) break; RSSCfg = RSSCfg->next; } if (RSSCfg != NULL) { pRNCfg->changed = 1; RSSCfg->last_known_good = now; } } end_critical_section(S_NETCONFIGS); } eNextState RSSAggregator_AnalyseReply(AsyncIO *IO) { HashPos *it = NULL; long len; const char *Key; pRSSConfig *pCfg; u_char rawdigest[MD5_DIGEST_LEN]; struct MD5Context md5context; StrBuf *guid; rss_aggregator *Ctx = (rss_aggregator *) IO->Data; if ((IO->HttpReq.httpcode >= 300) && (IO->HttpReq.httpcode < 400) && (Ctx->RedirectUrl != NULL)) { StrBuf *ErrMsg; long lens[2]; const char *strs[2]; SetRSSState(IO, eRSSFailure); ErrMsg = NewStrBuf(); if (IO) EVRSSC_syslog(LOG_ALERT, "need a 200, got a %ld !\n", IO->HttpReq.httpcode); strs[0] = ChrPtr(Ctx->Url); lens[0] = StrLength(Ctx->Url); strs[1] = ChrPtr(Ctx->rooms); lens[1] = StrLength(Ctx->rooms); if (IO->HttpReq.CurlError == NULL) IO->HttpReq.CurlError = ""; StrBufPrintf(ErrMsg, "Error while RSS-Aggregation Run of %s\n" " need a 200, got a %ld !\n" " Curl Error message: \n%s / %s\n" " Redirect header points to: %s\n" " Response text was: \n" " \n %s\n", ChrPtr(Ctx->Url), IO->HttpReq.httpcode, IO->HttpReq.errdesc, IO->HttpReq.CurlError, ChrPtr(Ctx->RedirectUrl), ChrPtr(IO->HttpReq.ReplyData) ); CtdlAideFPMessage( ChrPtr(ErrMsg), "RSS Aggregation run failure", 2, strs, (long*) &lens, CCID, IO->ID, EvGetNow(IO)); FreeStrBuf(&ErrMsg); EVRSSC_syslog(LOG_DEBUG, "RSS feed returned an invalid http status code. <%s>\n", ChrPtr(Ctx->Url), IO->HttpReq.httpcode); return eAbort; } else if (IO->HttpReq.httpcode != 200) { StrBuf *ErrMsg; long lens[2]; const char *strs[2]; SetRSSState(IO, eRSSFailure); ErrMsg = NewStrBuf(); if (IO) EVRSSC_syslog(LOG_ALERT, "need a 200, got a %ld !\n", IO->HttpReq.httpcode); strs[0] = ChrPtr(Ctx->Url); lens[0] = StrLength(Ctx->Url); strs[1] = ChrPtr(Ctx->rooms); lens[1] = StrLength(Ctx->rooms); if (IO->HttpReq.CurlError == NULL) IO->HttpReq.CurlError = ""; StrBufPrintf(ErrMsg, "Error while RSS-Aggregation Run of %s\n" " need a 200, got a %ld !\n" " Curl Error message: \n%s / %s\n" " Response text was: \n" " \n %s\n", ChrPtr(Ctx->Url), IO->HttpReq.httpcode, IO->HttpReq.errdesc, IO->HttpReq.CurlError, ChrPtr(IO->HttpReq.ReplyData) ); CtdlAideFPMessage( ChrPtr(ErrMsg), "RSS Aggregation run failure", 2, strs, (long*) &lens, CCID, IO->ID, EvGetNow(IO)); FreeStrBuf(&ErrMsg); EVRSSC_syslog(LOG_DEBUG, "RSS feed returned an invalid http status code. <%s>\n", ChrPtr(Ctx->Url), IO->HttpReq.httpcode); return eAbort; } pCfg = &Ctx->Cfg; while (pCfg != NULL) { UpdateLastKnownGood (pCfg, EvGetNow(IO)); if ((Ctx->roomlist_parts > 1) && (it == NULL)) { it = GetNewHashPos(RSSFetchUrls, 0); } if (it != NULL) { void *vptr; if (GetNextHashPos(Ctx->OtherQRnumbers, it, &len, &Key, &vptr)) pCfg = vptr; else pCfg = NULL; } else pCfg = NULL; } DeleteHashPos (&it); SetRSSState(IO, eRSSUT); MD5Init(&md5context); MD5Update(&md5context, (const unsigned char*)SKEY(IO->HttpReq.ReplyData)); MD5Update(&md5context, (const unsigned char*)SKEY(Ctx->Url)); MD5Final(rawdigest, &md5context); guid = NewStrBufPlain(NULL, MD5_DIGEST_LEN * 2 + 12 /* _rss2ctdl*/); StrBufHexEscAppend(guid, NULL, rawdigest, MD5_DIGEST_LEN); StrBufAppendBufPlain(guid, HKEY("_rssFM"), 0); if (StrLength(guid) > 40) StrBufCutAt(guid, 40, NULL); /* Find out if we've already seen this item */ #ifndef DEBUG_RSS if (CheckIfAlreadySeen("RSS Whole", guid, EvGetNow(IO), EvGetNow(IO) - USETABLE_ANTIEXPIRE, eCheckUpdate, CCID, IO->ID) != 0) { FreeStrBuf(&guid); EVRSSC_syslog(LOG_DEBUG, "RSS feed already seen. <%s>\n", ChrPtr(Ctx->Url)); return eAbort; } FreeStrBuf(&guid); #endif SetRSSState(IO, eRSSParsing); return RSSAggregator_ParseReply(IO); } eNextState RSSAggregator_FinishHttp(AsyncIO *IO) { return CurlQueueDBOperation(IO, RSSAggregator_AnalyseReply); } /* * Begin a feed parse */ int rss_do_fetching(rss_aggregator *RSSAggr) { AsyncIO *IO = &RSSAggr->IO; rss_item *ri; time_t now; CURLcode sta; CURL *chnd; now = time(NULL); if ((RSSAggr->next_poll != 0) && (now < RSSAggr->next_poll)) return 0; ri = (rss_item*) malloc(sizeof(rss_item)); memset(ri, 0, sizeof(rss_item)); RSSAggr->Item = ri; if (! InitcURLIOStruct(&RSSAggr->IO, RSSAggr, "Citadel RSS Client", RSSAggregator_FinishHttp, RSSAggregator_Terminate, RSSAggregator_TerminateDB, RSSAggregator_ShutdownAbort)) { EVRSSCM_syslog(LOG_ALERT, "Unable to initialize libcurl.\n"); return 0; } chnd = IO->HttpReq.chnd; OPT(HEADERDATA, IO); OPT(HEADERFUNCTION, GetLocationString); SetRSSState(IO, eRSSCreated); safestrncpy(((CitContext*)RSSAggr->IO.CitContext)->cs_host, ChrPtr(RSSAggr->Url), sizeof(((CitContext*)RSSAggr->IO.CitContext)->cs_host)); EVRSSC_syslog(LOG_DEBUG, "Fetching RSS feed <%s>\n", ChrPtr(RSSAggr->Url)); ParseURL(&RSSAggr->IO.ConnectMe, RSSAggr->Url, 80); CurlPrepareURL(RSSAggr->IO.ConnectMe); SetRSSState(IO, eRSSFetching); QueueCurlContext(&RSSAggr->IO); return 1; } /* * Scan a room's netconfig to determine whether it is requesting any RSS feeds */ void rssclient_scan_room(struct ctdlroom *qrbuf, void *data, OneRoomNetCfg *OneRNCFG) { const RSSCfgLine *RSSCfg = (RSSCfgLine *)OneRNCFG->NetConfigs[rssclient]; rss_aggregator *RSSAggr = NULL; rss_aggregator *use_this_RSSAggr = NULL; void *vptr; pthread_mutex_lock(&RSSQueueMutex); if (GetHash(RSSQueueRooms, LKEY(qrbuf->QRnumber), &vptr)) { EVRSSQ_syslog(LOG_DEBUG, "rssclient: [%ld] %s already in progress.\n", qrbuf->QRnumber, qrbuf->QRname); pthread_mutex_unlock(&RSSQueueMutex); return; } pthread_mutex_unlock(&RSSQueueMutex); if (server_shutting_down) return; while (RSSCfg != NULL) { pthread_mutex_lock(&RSSQueueMutex); GetHash(RSSFetchUrls, SKEY(RSSCfg->Url), &vptr); use_this_RSSAggr = (rss_aggregator *)vptr; if (use_this_RSSAggr != NULL) { pRSSConfig *pRSSCfg; StrBufAppendBufPlain( use_this_RSSAggr->rooms, qrbuf->QRname, -1, 0); if (use_this_RSSAggr->roomlist_parts==1) { use_this_RSSAggr->OtherQRnumbers = NewHash(1, lFlathash); } pRSSCfg = (pRSSConfig *) malloc(sizeof(pRSSConfig)); pRSSCfg->QRnumber = qrbuf->QRnumber; pRSSCfg->pCfg = RSSCfg; Put(use_this_RSSAggr->OtherQRnumbers, LKEY(qrbuf->QRnumber), pRSSCfg, NULL); use_this_RSSAggr->roomlist_parts++; pthread_mutex_unlock(&RSSQueueMutex); RSSCfg = RSSCfg->next; continue; } pthread_mutex_unlock(&RSSQueueMutex); RSSAggr = (rss_aggregator *) malloc( sizeof(rss_aggregator)); memset (RSSAggr, 0, sizeof(rss_aggregator)); RSSAggr->Cfg.QRnumber = qrbuf->QRnumber; RSSAggr->Cfg.pCfg = RSSCfg; RSSAggr->roomlist_parts = 1; RSSAggr->Url = NewStrBufDup(RSSCfg->Url); RSSAggr->ItemType = RSS_UNSET; RSSAggr->rooms = NewStrBufPlain( qrbuf->QRname, -1); pthread_mutex_lock(&RSSQueueMutex); Put(RSSFetchUrls, SKEY(RSSAggr->Url), RSSAggr, DeleteRssCfg); pthread_mutex_unlock(&RSSQueueMutex); RSSCfg = RSSCfg->next; } } /* * Scan for rooms that have RSS client requests configured */ void rssclient_scan(void) { int RSSRoomCount, RSSCount; rss_aggregator *rptr = NULL; void *vrptr = NULL; HashPos *it; long len; const char *Key; time_t now = time(NULL); /* Run no more than once every 15 minutes. */ if ((now - last_run) < 900) { EVRSSQ_syslog(LOG_DEBUG, "Client: polling interval not yet reached; last run was %ldm%lds ago", ((now - last_run) / 60), ((now - last_run) % 60) ); return; } /* * This is a simple concurrency check to make sure only one rssclient * run is done at a time. */ pthread_mutex_lock(&RSSQueueMutex); RSSCount = GetCount(RSSFetchUrls); RSSRoomCount = GetCount(RSSQueueRooms); pthread_mutex_unlock(&RSSQueueMutex); if ((RSSRoomCount > 0) || (RSSCount > 0)) { EVRSSQ_syslog(LOG_DEBUG, "rssclient: concurrency check failed; %d rooms and %d url's are queued", RSSRoomCount, RSSCount ); return; } become_session(&rss_CC); EVRSSQM_syslog(LOG_DEBUG, "rssclient started"); CtdlForEachNetCfgRoom(rssclient_scan_room, NULL, rssclient); if (GetCount(RSSFetchUrls) > 0) { pthread_mutex_lock(&RSSQueueMutex); EVRSSQ_syslog(LOG_DEBUG, "rssclient starting %d Clients", GetCount(RSSFetchUrls)); it = GetNewHashPos(RSSFetchUrls, 0); while (!server_shutting_down && GetNextHashPos(RSSFetchUrls, it, &len, &Key, &vrptr) && (vrptr != NULL)) { rptr = (rss_aggregator *)vrptr; if (!rss_do_fetching(rptr)) UnlinkRSSAggregator(rptr); } DeleteHashPos(&it); pthread_mutex_unlock(&RSSQueueMutex); } else EVRSSQM_syslog(LOG_DEBUG, "Nothing to do."); EVRSSQM_syslog(LOG_DEBUG, "rssclient ended\n"); return; } void rss_cleanup(void) { /* citthread_mutex_destroy(&RSSQueueMutex); TODO */ DeleteHash(&RSSFetchUrls); DeleteHash(&RSSQueueRooms); } void LogDebugEnableRSSClient(const int n) { RSSClientDebugEnabled = n; } typedef struct __RSSVetoInfo { StrBuf *ErrMsg; time_t Now; int Veto; }RSSVetoInfo; void rssclient_veto_scan_room(struct ctdlroom *qrbuf, void *data, OneRoomNetCfg *OneRNCFG) { RSSVetoInfo *Info = (RSSVetoInfo *) data; const RSSCfgLine *RSSCfg = (RSSCfgLine *)OneRNCFG->NetConfigs[rssclient]; while (RSSCfg != NULL) { if ((RSSCfg->last_known_good != 0) && (RSSCfg->last_known_good + USETABLE_ANTIEXPIRE < Info->Now)) { StrBufAppendPrintf(Info->ErrMsg, "RSS feed not seen for a %d days:: <", (Info->Now - RSSCfg->last_known_good) / (24 * 60 * 60)); StrBufAppendBuf(Info->ErrMsg, RSSCfg->Url, 0); StrBufAppendBufPlain(Info->ErrMsg, HKEY(">\n"), 0); } RSSCfg = RSSCfg->next; } } int RSSCheckUsetableVeto(StrBuf *ErrMsg) { RSSVetoInfo Info; Info.ErrMsg = ErrMsg; Info.Now = time (NULL); Info.Veto = 0; CtdlForEachNetCfgRoom(rssclient_veto_scan_room, &Info, rssclient); return Info.Veto;; } void ParseRSSClientCfgLine(const CfgLineType *ThisOne, StrBuf *Line, const char *LinePos, OneRoomNetCfg *OneRNCFG) { RSSCfgLine *RSSCfg; RSSCfg = (RSSCfgLine *) malloc (sizeof(RSSCfgLine)); RSSCfg->Url = NewStrBufPlain (NULL, StrLength (Line)); StrBufExtract_NextToken(RSSCfg->Url, Line, &LinePos, '|'); RSSCfg->last_known_good = StrBufExtractNext_long(Line, &LinePos, '|'); RSSCfg->next = (RSSCfgLine *)OneRNCFG->NetConfigs[ThisOne->C]; OneRNCFG->NetConfigs[ThisOne->C] = (RoomNetCfgLine*) RSSCfg; } void SerializeRSSClientCfgLine(const CfgLineType *ThisOne, StrBuf *OutputBuffer, OneRoomNetCfg *RNCfg, RoomNetCfgLine *data) { RSSCfgLine *RSSCfg = (RSSCfgLine*) data; StrBufAppendBufPlain(OutputBuffer, CKEY(ThisOne->Str), 0); StrBufAppendBufPlain(OutputBuffer, HKEY("|"), 0); StrBufAppendBufPlain(OutputBuffer, SKEY(RSSCfg->Url), 0); StrBufAppendPrintf(OutputBuffer, "|%ld\n", RSSCfg->last_known_good); } void DeleteRSSClientCfgLine(const CfgLineType *ThisOne, RoomNetCfgLine **data) { RSSCfgLine *RSSCfg = (RSSCfgLine*) *data; FreeStrBuf(&RSSCfg->Url); free(*data); *data = NULL; } CTDL_MODULE_INIT(rssclient) { if (!threading) { CtdlRegisterTDAPVetoHook (RSSCheckUsetableVeto, CDB_USETABLE, 0); CtdlREGISTERRoomCfgType(rssclient, ParseRSSClientCfgLine, 0, 1, SerializeRSSClientCfgLine, DeleteRSSClientCfgLine); pthread_mutex_init(&RSSQueueMutex, NULL); RSSQueueRooms = NewHash(1, lFlathash); RSSFetchUrls = NewHash(1, NULL); syslog(LOG_INFO, "%s\n", curl_version()); CtdlRegisterSessionHook(rssclient_scan, EVT_TIMER, PRIO_AGGR + 300); CtdlRegisterEVCleanupHook(rss_cleanup); CtdlRegisterDebugFlagHook(HKEY("rssclient"), LogDebugEnableRSSClient, &RSSClientDebugEnabled); } else { CtdlFillSystemContext(&rss_CC, "rssclient"); } return "rssclient"; } citadel-9.01/modules/rssclient/rss_atom_parser.c0000644000000000000000000007116612507024051020602 0ustar rootroot/* * Bring external RSS feeds into rooms. * * Copyright (c) 2007-2015 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "threads.h" #include "ctdl_module.h" #include "clientsocket.h" #include "msgbase.h" #include "parsedate.h" #include "database.h" #include "citadel_dirs.h" #include "md5.h" #include "context.h" #include "event_client.h" #include "rss_atom_parser.h" void rss_remember_item(rss_item *ri, rss_aggregator *Cfg); int RSSAtomParserDebugEnabled = 0; #define N ((rss_aggregator*)IO->Data)->Cfg.QRnumber #define DBGLOG(LEVEL) if ((LEVEL != LOG_DEBUG) || (RSSAtomParserDebugEnabled != 0)) #define EVRSSATOM_syslog(LEVEL, FORMAT, ...) \ DBGLOG(LEVEL) syslog(LEVEL, \ "%s[%ld]CC[%d][%ld]RSSP" FORMAT, \ IOSTR, IO->ID, CCID, N, __VA_ARGS__) #define EVRSSATOMM_syslog(LEVEL, FORMAT) \ DBGLOG(LEVEL) syslog(LEVEL, \ "%s[%ld]CC[%d][%ld]RSSP" FORMAT, \ IOSTR, IO->ID, CCID, N) #define EVRSSATOMCS_syslog(LEVEL, FORMAT, ...) \ DBGLOG(LEVEL) syslog(LEVEL, "%s[%ld][%ld]RSSP" FORMAT, \ IOSTR, IO->ID, N, __VA_ARGS__) #define EVRSSATOMSM_syslog(LEVEL, FORMAT) \ DBGLOG(LEVEL) syslog(LEVEL, "%s[%ld][%ld]RSSP" FORMAT, \ IOSTR, IO->ID, N) /* * Convert an RDF/RSS datestamp into a time_t */ time_t rdf_parsedate(const char *p) { struct tm tm; time_t t = 0; if (!p) return 0L; if (strlen(p) < 10) return 0L; memset(&tm, 0, sizeof tm); /* * If the timestamp appears to be in W3C datetime format, try to * parse it. See also: http://www.w3.org/TR/NOTE-datetime * * This code, along with parsedate.c, is a potential candidate for * moving into libcitadel. */ if ( (p[4] == '-') && (p[7] == '-') ) { tm.tm_year = atoi(&p[0]) - 1900; tm.tm_mon = atoi(&p[5]) - 1; tm.tm_mday = atoi(&p[8]); if ( (p[10] == 'T') && (p[13] == ':') ) { tm.tm_hour = atoi(&p[11]); tm.tm_min = atoi(&p[14]); } return mktime(&tm); } /* hmm... try RFC822 date stamp format */ t = parsedate(p); if (t > 0) return(t); /* yeesh. ok, just return the current date and time. */ return(time(NULL)); } void flush_rss_item(rss_item *ri) { /* Initialize the feed item data structure */ FreeStrBuf(&ri->guid); FreeStrBuf(&ri->title); FreeStrBuf(&ri->link); FreeStrBuf(&ri->author_or_creator); FreeStrBuf(&ri->author_email); FreeStrBuf(&ri->author_url); FreeStrBuf(&ri->description); FreeStrBuf(&ri->linkTitle); FreeStrBuf(&ri->reLink); FreeStrBuf(&ri->reLinkTitle); FreeStrBuf(&ri->channel_title); } /****************************************************************************** * XML-Handler * ******************************************************************************/ void RSS_item_rss_start (StrBuf *CData, rss_item *ri, rss_aggregator *RSSAggr, const char** Attr) { AsyncIO *IO = &RSSAggr->IO; EVRSSATOMM_syslog(LOG_DEBUG, "RSS: This is an RSS feed.\n"); RSSAggr->ItemType = RSS_RSS; } void RSS_item_rdf_start(StrBuf *CData, rss_item *ri, rss_aggregator *RSSAggr, const char** Attr) { AsyncIO *IO = &RSSAggr->IO; EVRSSATOMM_syslog(LOG_DEBUG, "RSS: This is an RDF feed.\n"); RSSAggr->ItemType = RSS_RSS; } void ATOM_item_feed_start(StrBuf *CData, rss_item *ri, rss_aggregator *RSSAggr, const char** Attr) { AsyncIO *IO = &RSSAggr->IO; EVRSSATOMM_syslog(LOG_DEBUG, "RSS: This is an ATOM feed.\n"); RSSAggr->ItemType = RSS_ATOM; } void RSS_item_item_start(StrBuf *CData, rss_item *ri, rss_aggregator *RSSAggr, const char** Attr) { ri->item_tag_nesting ++; flush_rss_item(ri); } void ATOM_item_entry_start(StrBuf *CData, rss_item *ri, rss_aggregator *RSSAggr, const char** Attr) { /* Atom feed... */ ri->item_tag_nesting ++; flush_rss_item(ri); } void ATOM_item_link_start (StrBuf *CData, rss_item *ri, rss_aggregator *RSSAggr, const char** Attr) { int i; const char *pHref = NULL; const char *pType = NULL; const char *pRel = NULL; const char *pTitle = NULL; for (i = 0; Attr[i] != NULL; i+=2) { if (!strcmp(Attr[i], "href")) { pHref = Attr[i+1]; } else if (!strcmp(Attr[i], "rel")) { pRel = Attr[i+1]; } else if (!strcmp(Attr[i], "type")) { pType = Attr[i+1]; } else if (!strcmp(Attr[i], "title")) { pTitle = Attr[i+1]; } } if (pHref == NULL) return; /* WHUT? Pointing... where? */ if ((pType != NULL) && !strcasecmp(pType, "application/atom+xml")) return; /* these just point to other rss resources, we're not interested in them. */ if (pRel != NULL) { if (!strcasecmp (pRel, "replies")) { NewStrBufDupAppendFlush(&ri->reLink, NULL, pHref, -1); StrBufTrim(ri->link); NewStrBufDupAppendFlush(&ri->reLinkTitle, NULL, pTitle, -1); } else if (!strcasecmp(pRel, "alternate")) { /* Alternative representation of this Item... */ NewStrBufDupAppendFlush(&ri->link, NULL, pHref, -1); StrBufTrim(ri->link); NewStrBufDupAppendFlush(&ri->linkTitle, NULL, pTitle, -1); } #if 0 /* these are also defined, but dunno what to do with them.. */ else if (!strcasecmp(pRel, "related")) { } else if (!strcasecmp(pRel, "self")) { } else if (!strcasecmp(pRel, "enclosure")) {/*...reference can get big, and is probably the full article*/ } else if (!strcasecmp(pRel, "via")) {/* this article was provided via... */ } #endif } else if (StrLength(ri->link) == 0) { NewStrBufDupAppendFlush(&ri->link, NULL, pHref, -1); StrBufTrim(ri->link); NewStrBufDupAppendFlush(&ri->linkTitle, NULL, pTitle, -1); } } void ATOMRSS_item_title_end(StrBuf *CData, rss_item *ri, rss_aggregator *RSSAggr, const char** Attr) { if ((ri->item_tag_nesting == 0) && (StrLength(CData) > 0)) { NewStrBufDupAppendFlush(&ri->channel_title, CData, NULL, 0); StrBufTrim(ri->channel_title); } } void RSS_item_guid_end(StrBuf *CData, rss_item *ri, rss_aggregator *RSSAggr, const char** Attr) { if (StrLength(CData) > 0) { NewStrBufDupAppendFlush(&ri->guid, CData, NULL, 0); } } void ATOM_item_id_end(StrBuf *CData, rss_item *ri, rss_aggregator *RSSAggr, const char** Attr) { if (StrLength(CData) > 0) { NewStrBufDupAppendFlush(&ri->guid, CData, NULL, 0); } } void RSS_item_link_end (StrBuf *CData, rss_item *ri, rss_aggregator *RSSAggr, const char** Attr) { if (StrLength(CData) > 0) { NewStrBufDupAppendFlush(&ri->link, CData, NULL, 0); StrBufTrim(ri->link); } } void RSS_item_relink_end(StrBuf *CData, rss_item *ri, rss_aggregator *RSSAggr, const char** Attr) { if (StrLength(CData) > 0) { NewStrBufDupAppendFlush(&ri->reLink, CData, NULL, 0); StrBufTrim(ri->reLink); } } void RSSATOM_item_title_end (StrBuf *CData, rss_item *ri, rss_aggregator *RSSAggr, const char** Attr) { if (StrLength(CData) > 0) { NewStrBufDupAppendFlush(&ri->title, CData, NULL, 0); StrBufTrim(ri->title); } } void ATOM_item_content_end (StrBuf *CData, rss_item *ri, rss_aggregator *RSSAggr, const char** Attr) { long olen = StrLength (ri->description); long clen = StrLength (CData); if (clen > 0) { if (olen == 0) { NewStrBufDupAppendFlush(&ri->description, CData, NULL, 0); StrBufTrim(ri->description); } else if (olen < clen) { FlushStrBuf(ri->description); NewStrBufDupAppendFlush(&ri->description, CData, NULL, 0); StrBufTrim(ri->description); } } } void ATOM_item_summary_end (StrBuf *CData, rss_item *ri, rss_aggregator *RSSAggr, const char** Attr) { /* * this can contain an abstract of the article. * but we don't want to verwrite a full document if we already have it. */ if ((StrLength(CData) > 0) && (StrLength(ri->description) == 0)) { NewStrBufDupAppendFlush(&ri->description, CData, NULL, 0); StrBufTrim(ri->description); } } void RSS_item_description_end (StrBuf *CData, rss_item *ri, rss_aggregator *RSSAggr, const char** Attr) { long olen = StrLength (ri->description); long clen = StrLength (CData); if (clen > 0) { if (olen == 0) { NewStrBufDupAppendFlush(&ri->description, CData, NULL, 0); StrBufTrim(ri->description); } else if (olen < clen) { FlushStrBuf(ri->description); NewStrBufDupAppendFlush(&ri->description, CData, NULL, 0); StrBufTrim(ri->description); } } } void ATOM_item_published_end (StrBuf *CData, rss_item *ri, rss_aggregator *RSSAggr, const char** Attr) { if (StrLength(CData) > 0) { StrBufTrim(CData); ri->pubdate = rdf_parsedate(ChrPtr(CData)); } } void ATOM_item_updated_end (StrBuf *CData, rss_item *ri, rss_aggregator *RSSAggr, const char** Attr) { if (StrLength(CData) > 0) { StrBufTrim(CData); ri->pubdate = rdf_parsedate(ChrPtr(CData)); } } void RSS_item_pubdate_end (StrBuf *CData, rss_item *ri, rss_aggregator *RSSAggr, const char** Attr) { if (StrLength(CData) > 0) { StrBufTrim(CData); ri->pubdate = rdf_parsedate(ChrPtr(CData)); } } void RSS_item_date_end (StrBuf *CData, rss_item *ri, rss_aggregator *RSSAggr, const char** Attr) { if (StrLength(CData) > 0) { StrBufTrim(CData); ri->pubdate = rdf_parsedate(ChrPtr(CData)); } } void RSS_item_author_end(StrBuf *CData, rss_item *ri, rss_aggregator *RSSAggr, const char** Attr) { if (StrLength(CData) > 0) { NewStrBufDupAppendFlush(&ri->author_or_creator, CData, NULL, 0); StrBufTrim(ri->author_or_creator); } } void ATOM_item_name_end(StrBuf *CData, rss_item *ri, rss_aggregator *RSSAggr, const char** Attr) { if (StrLength(CData) > 0) { NewStrBufDupAppendFlush(&ri->author_or_creator, CData, NULL, 0); StrBufTrim(ri->author_or_creator); } } void ATOM_item_email_end(StrBuf *CData, rss_item *ri, rss_aggregator *RSSAggr, const char** Attr) { if (StrLength(CData) > 0) { NewStrBufDupAppendFlush(&ri->author_email, CData, NULL, 0); StrBufTrim(ri->author_email); } } void RSS_item_creator_end(StrBuf *CData, rss_item *ri, rss_aggregator *RSSAggr, const char** Attr) { if ((StrLength(CData) > 0) && (StrLength(ri->author_or_creator) == 0)) { NewStrBufDupAppendFlush(&ri->author_or_creator, CData, NULL, 0); StrBufTrim(ri->author_or_creator); } } void ATOM_item_uri_end(StrBuf *CData, rss_item *ri, rss_aggregator *RSSAggr, const char** Attr) { if (StrLength(CData) > 0) { NewStrBufDupAppendFlush(&ri->author_url, CData, NULL, 0); StrBufTrim(ri->author_url); } } void RSS_item_item_end(StrBuf *CData, rss_item *ri, rss_aggregator *RSSAggr, const char** Attr) { --ri->item_tag_nesting; rss_remember_item(ri, RSSAggr); } void ATOM_item_entry_end(StrBuf *CData, rss_item *ri, rss_aggregator *RSSAggr, const char** Attr) { --ri->item_tag_nesting; rss_remember_item(ri, RSSAggr); } void RSS_item_rss_end(StrBuf *CData, rss_item *ri, rss_aggregator *RSSAggr, const char** Attr) { AsyncIO *IO = &RSSAggr->IO; EVRSSATOMM_syslog(LOG_DEBUG, "End of feed detected. Closing parser.\n"); ri->done_parsing = 1; } void RSS_item_rdf_end(StrBuf *CData, rss_item *ri, rss_aggregator *RSSAggr, const char** Attr) { AsyncIO *IO = &RSSAggr->IO; EVRSSATOMM_syslog(LOG_DEBUG, "End of feed detected. Closing parser.\n"); ri->done_parsing = 1; } void RSSATOM_item_ignore(StrBuf *CData, rss_item *ri, rss_aggregator *RSSAggr, const char** Attr) { } /* * This callback stores up the data which appears in between tags. */ void rss_xml_cdata_start(void *data) { rss_aggregator *RSSAggr = (rss_aggregator*) data; FlushStrBuf(RSSAggr->CData); } void rss_xml_cdata_end(void *data) { } void rss_xml_chardata(void *data, const XML_Char *s, int len) { rss_aggregator *RSSAggr = (rss_aggregator*) data; StrBufAppendBufPlain (RSSAggr->CData, s, len, 0); } /****************************************************************************** * RSS parser logic * ******************************************************************************/ extern pthread_mutex_t RSSQueueMutex; HashList *StartHandlers = NULL; HashList *EndHandlers = NULL; HashList *KnownNameSpaces = NULL; void FreeNetworkSaveMessage (void *vMsg) { networker_save_message *Msg = (networker_save_message *) vMsg; CM_FreeContents(&Msg->Msg); FreeStrBuf(&Msg->Message); FreeStrBuf(&Msg->MsgGUID); FreeStrBuf(&Msg->author_email); FreeStrBuf(&Msg->author_or_creator); FreeStrBuf(&Msg->title); FreeStrBuf(&Msg->description); FreeStrBuf(&Msg->link); FreeStrBuf(&Msg->linkTitle); FreeStrBuf(&Msg->reLink); FreeStrBuf(&Msg->reLinkTitle); free(Msg); } /* * Commit a fetched and parsed RSS item to disk */ void rss_remember_item(rss_item *ri, rss_aggregator *RSSAggr) { networker_save_message *SaveMsg; struct MD5Context md5context; u_char rawdigest[MD5_DIGEST_LEN]; StrBuf *guid; AsyncIO *IO = &RSSAggr->IO; int n; SaveMsg = (networker_save_message *) malloc(sizeof(networker_save_message)); memset(SaveMsg, 0, sizeof(networker_save_message)); /* Construct a GUID to use in the S_USETABLE table. * If one is not present in the item itself, make one up. */ if (ri->guid != NULL) { StrBufSpaceToBlank(ri->guid); StrBufTrim(ri->guid); guid = NewStrBufPlain(HKEY("rss/")); StrBufAppendBuf(guid, ri->guid, 0); } else { MD5Init(&md5context); if (ri->title != NULL) { MD5Update(&md5context, (const unsigned char*)SKEY(ri->title)); } if (ri->link != NULL) { MD5Update(&md5context, (const unsigned char*)SKEY(ri->link)); } MD5Final(rawdigest, &md5context); guid = NewStrBufPlain(NULL, MD5_DIGEST_LEN * 2 + 12 /* _rss2ctdl*/); StrBufHexEscAppend(guid, NULL, rawdigest, MD5_DIGEST_LEN); StrBufAppendBufPlain(guid, HKEY("_rss2ctdl"), 0); } /* translate Item into message. */ EVRSSATOMM_syslog(LOG_DEBUG, "RSS: translating item...\n"); if (ri->description == NULL) ri->description = NewStrBufPlain(HKEY("")); StrBufSpaceToBlank(ri->description); SaveMsg->Msg.cm_magic = CTDLMESSAGE_MAGIC; SaveMsg->Msg.cm_anon_type = MES_NORMAL; SaveMsg->Msg.cm_format_type = FMT_RFC822; /* gather the cheaply computed information now... */ if (ri->guid != NULL) { CM_SetField(&SaveMsg->Msg, eExclusiveID, SKEY(ri->guid)); } SaveMsg->MsgGUID = guid; if (ri->pubdate <= 0) { ri->pubdate = time(NULL); } CM_SetFieldLONG(&SaveMsg->Msg, eTimestamp, ri->pubdate); if (ri->channel_title != NULL) { if (StrLength(ri->channel_title) > 0) { CM_SetField(&SaveMsg->Msg, eOriginalRoom, SKEY(ri->channel_title)); } } /* remember the ones for defferred processing to save computing power after we know if we realy need it. */ SaveMsg->author_or_creator = ri->author_or_creator; ri->author_or_creator = NULL; SaveMsg->author_email = ri->author_email; ri->author_email = NULL; SaveMsg->title = ri->title; ri->title = NULL; SaveMsg->link = ri->link; ri->link = NULL; SaveMsg->description = ri->description; ri->description = NULL; SaveMsg->linkTitle = ri->linkTitle; ri->linkTitle = NULL; SaveMsg->reLink = ri->reLink; ri->reLink = NULL; SaveMsg->reLinkTitle = ri->reLinkTitle; ri->reLinkTitle = NULL; n = GetCount(RSSAggr->Messages) + 1; Put(RSSAggr->Messages, IKEY(n), SaveMsg, FreeNetworkSaveMessage); } void rss_xml_start(void *data, const char *supplied_el, const char **attr) { rss_xml_handler *h; rss_aggregator *RSSAggr = (rss_aggregator*) data; AsyncIO *IO = &RSSAggr->IO; rss_item *ri = RSSAggr->Item; void *pv; const char *pel; char *sep = NULL; /* Axe the namespace, we don't care about it */ /* syslog(LOG_DEBUG, "RSS: supplied el %d: %s\n", RSSAggr->RSSAggr->ItemType, supplied_el); */ pel = supplied_el; while (sep = strchr(pel, ':'), sep) { pel = sep + 1; } if (pel != supplied_el) { void *v; if (!GetHash(KnownNameSpaces, supplied_el, pel - supplied_el - 1, &v)) { EVRSSATOM_syslog(LOG_DEBUG, "RSS: START ignoring " "because of wrong namespace [%s]\n", supplied_el); return; } } StrBufPlain(RSSAggr->Key, pel, -1); StrBufLowerCase(RSSAggr->Key); if (GetHash(StartHandlers, SKEY(RSSAggr->Key), &pv)) { h = (rss_xml_handler*) pv; if (((h->Flags & RSS_UNSET) != 0) && (RSSAggr->ItemType == RSS_UNSET)) { h->Handler(RSSAggr->CData, ri, RSSAggr, attr); } else if (((h->Flags & RSS_RSS) != 0) && (RSSAggr->ItemType == RSS_RSS)) { h->Handler(RSSAggr->CData, ri, RSSAggr, attr); } else if (((h->Flags & RSS_ATOM) != 0) && (RSSAggr->ItemType == RSS_ATOM)) { h->Handler(RSSAggr->CData, ri, RSSAggr, attr); } else EVRSSATOM_syslog(LOG_DEBUG, "RSS: START unhandled: [%s] [%s]...\n", pel, supplied_el); } else EVRSSATOM_syslog(LOG_DEBUG, "RSS: START unhandled: [%s] [%s]...\n", pel, supplied_el); } void rss_xml_end(void *data, const char *supplied_el) { rss_xml_handler *h; rss_aggregator *RSSAggr = (rss_aggregator*) data; AsyncIO *IO = &RSSAggr->IO; rss_item *ri = RSSAggr->Item; const char *pel; char *sep = NULL; void *pv; /* Axe the namespace, we don't care about it */ pel = supplied_el; while (sep = strchr(pel, ':'), sep) { pel = sep + 1; } EVRSSATOM_syslog(LOG_DEBUG, "RSS: END %s...\n", supplied_el); if (pel != supplied_el) { void *v; if (!GetHash(KnownNameSpaces, supplied_el, pel - supplied_el - 1, &v)) { EVRSSATOM_syslog(LOG_DEBUG, "RSS: END ignoring because of wrong namespace" "[%s] = [%s]\n", supplied_el, ChrPtr(RSSAggr->CData)); FlushStrBuf(RSSAggr->CData); return; } } StrBufPlain(RSSAggr->Key, pel, -1); StrBufLowerCase(RSSAggr->Key); if (GetHash(EndHandlers, SKEY(RSSAggr->Key), &pv)) { h = (rss_xml_handler*) pv; if (((h->Flags & RSS_UNSET) != 0) && (RSSAggr->ItemType == RSS_UNSET)) { h->Handler(RSSAggr->CData, ri, RSSAggr, NULL); } else if (((h->Flags & RSS_RSS) != 0) && (RSSAggr->ItemType == RSS_RSS)) { h->Handler(RSSAggr->CData, ri, RSSAggr, NULL); } else if (((h->Flags & RSS_ATOM) != 0) && (RSSAggr->ItemType == RSS_ATOM)) { h->Handler(RSSAggr->CData, ri, RSSAggr, NULL); } else EVRSSATOM_syslog(LOG_DEBUG, "RSS: END unhandled: [%s] [%s] = [%s]...\n", pel, supplied_el, ChrPtr(RSSAggr->CData)); } else EVRSSATOM_syslog(LOG_DEBUG, "RSS: END unhandled: [%s] [%s] = [%s]...\n", pel, supplied_el, ChrPtr(RSSAggr->CData)); FlushStrBuf(RSSAggr->CData); } /* * Callback function for passing libcurl's output to expat for parsing * we don't do streamed parsing so expat can handle non-utf8 documents size_t rss_libcurl_callback(void *ptr, size_t size, size_t nmemb, void *stream) { XML_Parse((XML_Parser)stream, ptr, (size * nmemb), 0); return (size*nmemb); } */ eNextState RSSAggregator_ParseReply(AsyncIO *IO) { StrBuf *Buf; rss_aggregator *RSSAggr; rss_item *ri; const char *at; char *ptr; long len; const char *Key; RSSAggr = IO->Data; ri = RSSAggr->Item; RSSAggr->CData = NewStrBufPlain(NULL, SIZ); RSSAggr->Key = NewStrBuf(); at = NULL; StrBufSipLine(RSSAggr->Key, IO->HttpReq.ReplyData, &at); ptr = NULL; #define encoding "encoding=\"" ptr = strstr(ChrPtr(RSSAggr->Key), encoding); if (ptr != NULL) { char *pche; ptr += sizeof (encoding) - 1; pche = strchr(ptr, '"'); if (pche != NULL) StrBufCutAt(RSSAggr->Key, -1, pche); else ptr = "UTF-8"; } else ptr = "UTF-8"; EVRSSATOM_syslog(LOG_DEBUG, "RSS: Now parsing [%s] \n", ChrPtr(RSSAggr->Url)); RSSAggr->xp = XML_ParserCreateNS(ptr, ':'); if (!RSSAggr->xp) { EVRSSATOMM_syslog(LOG_ALERT, "Cannot create XML parser!\n"); return eAbort; } FlushStrBuf(RSSAggr->Key); RSSAggr->Messages = NewHash(1, Flathash); XML_SetElementHandler(RSSAggr->xp, rss_xml_start, rss_xml_end); XML_SetCharacterDataHandler(RSSAggr->xp, rss_xml_chardata); XML_SetUserData(RSSAggr->xp, RSSAggr); XML_SetCdataSectionHandler(RSSAggr->xp, rss_xml_cdata_start, rss_xml_cdata_end); len = StrLength(IO->HttpReq.ReplyData); ptr = SmashStrBuf(&IO->HttpReq.ReplyData); XML_Parse(RSSAggr->xp, ptr, len, 0); free (ptr); if (ri->done_parsing == 0) XML_Parse(RSSAggr->xp, "", 0, 1); EVRSSATOM_syslog(LOG_DEBUG, "RSS: XML Status [%s] \n", XML_ErrorString(XML_GetErrorCode(RSSAggr->xp))); XML_ParserFree(RSSAggr->xp); flush_rss_item(ri); Buf = NewStrBufDup(RSSAggr->rooms); RSSAggr->recp.recp_room = SmashStrBuf(&Buf); RSSAggr->recp.num_room = RSSAggr->roomlist_parts; RSSAggr->recp.recptypes_magic = RECPTYPES_MAGIC; RSSAggr->Pos = GetNewHashPos(RSSAggr->Messages, 1); if (GetNextHashPos(RSSAggr->Messages, RSSAggr->Pos, &len, &Key, (void**) &RSSAggr->ThisMsg)) { return NextDBOperation(IO, RSS_FetchNetworkUsetableEntry); } else { return eAbort; } } /****************************************************************************** * RSS handler registering logic * ******************************************************************************/ void AddRSSStartHandler(rss_handler_func Handler, int Flags, const char *key, long len) { rss_xml_handler *h; h = (rss_xml_handler*) malloc(sizeof (rss_xml_handler)); h->Flags = Flags; h->Handler = Handler; Put(StartHandlers, key, len, h, NULL); } void AddRSSEndHandler(rss_handler_func Handler, int Flags, const char *key, long len) { rss_xml_handler *h; h = (rss_xml_handler*) malloc(sizeof (rss_xml_handler)); h->Flags = Flags; h->Handler = Handler; Put(EndHandlers, key, len, h, NULL); } void rss_parser_cleanup(void) { DeleteHash(&StartHandlers); DeleteHash(&EndHandlers); DeleteHash(&KnownNameSpaces); } void LogDebugEnableRSSATOMParser(const int n) { RSSAtomParserDebugEnabled = n; } CTDL_MODULE_INIT(rssparser) { if (!threading) { StartHandlers = NewHash(1, NULL); EndHandlers = NewHash(1, NULL); AddRSSStartHandler(RSS_item_rss_start, RSS_UNSET, HKEY("rss")); AddRSSStartHandler(RSS_item_rdf_start, RSS_UNSET, HKEY("rdf")); AddRSSStartHandler(ATOM_item_feed_start, RSS_UNSET, HKEY("feed")); AddRSSStartHandler(RSS_item_item_start, RSS_RSS, HKEY("item")); AddRSSStartHandler(ATOM_item_entry_start, RSS_ATOM, HKEY("entry")); AddRSSStartHandler(ATOM_item_link_start, RSS_ATOM, HKEY("link")); AddRSSEndHandler(ATOMRSS_item_title_end, RSS_ATOM|RSS_RSS|RSS_REQUIRE_BUF, HKEY("title")); AddRSSEndHandler(RSS_item_guid_end, RSS_RSS|RSS_REQUIRE_BUF, HKEY("guid")); AddRSSEndHandler(ATOM_item_id_end, RSS_ATOM|RSS_REQUIRE_BUF, HKEY("id")); AddRSSEndHandler(RSS_item_link_end, RSS_RSS|RSS_REQUIRE_BUF, HKEY("link")); #if 0 // hm, rss to the comments of that blog, might be interesting in future, but... AddRSSEndHandler(RSS_item_relink_end, RSS_RSS|RSS_REQUIRE_BUF, HKEY("commentrss")); // comment count... AddRSSEndHandler(RSS_item_relink_end, RSS_RSS|RSS_REQUIRE_BUF, HKEY("comments")); #endif AddRSSEndHandler(RSSATOM_item_title_end, RSS_ATOM|RSS_RSS|RSS_REQUIRE_BUF, HKEY("title")); AddRSSEndHandler(ATOM_item_content_end, RSS_ATOM|RSS_REQUIRE_BUF, HKEY("content")); AddRSSEndHandler(RSS_item_description_end, RSS_RSS|RSS_ATOM|RSS_REQUIRE_BUF, HKEY("encoded")); AddRSSEndHandler(ATOM_item_summary_end, RSS_ATOM|RSS_REQUIRE_BUF, HKEY("summary")); AddRSSEndHandler(RSS_item_description_end, RSS_RSS|RSS_REQUIRE_BUF, HKEY("description")); AddRSSEndHandler(ATOM_item_published_end, RSS_ATOM|RSS_REQUIRE_BUF, HKEY("published")); AddRSSEndHandler(ATOM_item_updated_end, RSS_ATOM|RSS_REQUIRE_BUF, HKEY("updated")); AddRSSEndHandler(RSS_item_pubdate_end, RSS_RSS|RSS_REQUIRE_BUF, HKEY("pubdate")); AddRSSEndHandler(RSS_item_date_end, RSS_RSS|RSS_REQUIRE_BUF, HKEY("date")); AddRSSEndHandler(RSS_item_author_end, RSS_RSS|RSS_REQUIRE_BUF, HKEY("author")); AddRSSEndHandler(RSS_item_creator_end, RSS_RSS|RSS_REQUIRE_BUF, HKEY("creator")); /* */ AddRSSEndHandler(ATOM_item_email_end, RSS_ATOM|RSS_REQUIRE_BUF, HKEY("email")); AddRSSEndHandler(ATOM_item_name_end, RSS_ATOM|RSS_REQUIRE_BUF, HKEY("name")); AddRSSEndHandler(ATOM_item_uri_end, RSS_ATOM|RSS_REQUIRE_BUF, HKEY("uri")); /* */ AddRSSEndHandler(RSS_item_item_end, RSS_RSS, HKEY("item")); AddRSSEndHandler(RSS_item_rss_end, RSS_RSS, HKEY("rss")); AddRSSEndHandler(RSS_item_rdf_end, RSS_RSS, HKEY("rdf")); AddRSSEndHandler(ATOM_item_entry_end, RSS_ATOM, HKEY("entry")); /* at the start of atoms:
  • link to resource
  • ignore them. */ AddRSSStartHandler(RSSATOM_item_ignore, RSS_RSS|RSS_ATOM, HKEY("seq")); AddRSSEndHandler (RSSATOM_item_ignore, RSS_RSS|RSS_ATOM, HKEY("seq")); AddRSSStartHandler(RSSATOM_item_ignore, RSS_RSS|RSS_ATOM, HKEY("li")); AddRSSEndHandler (RSSATOM_item_ignore, RSS_RSS|RSS_ATOM, HKEY("li")); /* links to other feed generators... */ AddRSSStartHandler(RSSATOM_item_ignore, RSS_RSS|RSS_ATOM, HKEY("feedflare")); AddRSSEndHandler (RSSATOM_item_ignore, RSS_RSS|RSS_ATOM, HKEY("feedflare")); AddRSSStartHandler(RSSATOM_item_ignore, RSS_RSS|RSS_ATOM, HKEY("browserfriendly")); AddRSSEndHandler (RSSATOM_item_ignore, RSS_RSS|RSS_ATOM, HKEY("browserfriendly")); KnownNameSpaces = NewHash(1, NULL); Put(KnownNameSpaces, HKEY("http://a9.com/-/spec/opensearch/1.1/"), NULL, reference_free_handler); Put(KnownNameSpaces, HKEY("http://a9.com/-/spec/opensearchrss/1.0/"), NULL, reference_free_handler); Put(KnownNameSpaces, HKEY("http://backend.userland.com/creativeCommonsRssModule"), NULL, reference_free_handler); Put(KnownNameSpaces, HKEY("http://purl.org/atom/ns#"), NULL, reference_free_handler); Put(KnownNameSpaces, HKEY("http://purl.org/dc/elements/1.1/"), NULL, reference_free_handler); Put(KnownNameSpaces, HKEY("http://purl.org/rss/1.0/"), NULL, reference_free_handler); Put(KnownNameSpaces, HKEY("http://purl.org/rss/1.0/modules/content/"), NULL, reference_free_handler); Put(KnownNameSpaces, HKEY("http://purl.org/rss/1.0/modules/slash/"), NULL, reference_free_handler); Put(KnownNameSpaces, HKEY("http://purl.org/rss/1.0/modules/syndication/"), NULL, reference_free_handler); Put(KnownNameSpaces, HKEY("http://purl.org/rss/1.0/"), NULL, reference_free_handler); Put(KnownNameSpaces, HKEY("http://purl.org/syndication/thread/1.0"), NULL, reference_free_handler); Put(KnownNameSpaces, HKEY("http://rssnamespace.org/feedburner/ext/1.0"), NULL, reference_free_handler); Put(KnownNameSpaces, HKEY("http://schemas.google.com/g/2005"), NULL, reference_free_handler); Put(KnownNameSpaces, HKEY("http://webns.net/mvcb/"), NULL, reference_free_handler); Put(KnownNameSpaces, HKEY("http://web.resource.org/cc/"), NULL, reference_free_handler); Put(KnownNameSpaces, HKEY("http://wellformedweb.org/CommentAPI/"), NULL, reference_free_handler); Put(KnownNameSpaces, HKEY("http://www.georss.org/georss"), NULL, reference_free_handler); Put(KnownNameSpaces, HKEY("http://www.w3.org/1999/xhtml"), NULL, reference_free_handler); Put(KnownNameSpaces, HKEY("http://www.w3.org/1999/02/22-rdf-syntax-ns#"), NULL, reference_free_handler); Put(KnownNameSpaces, HKEY("http://www.w3.org/1999/02/22-rdf-syntax-ns#"), NULL, reference_free_handler); Put(KnownNameSpaces, HKEY("http://www.w3.org/2003/01/geo/wgs84_pos#"), NULL, reference_free_handler); Put(KnownNameSpaces, HKEY("http://www.w3.org/2005/Atom"), NULL, reference_free_handler); Put(KnownNameSpaces, HKEY("urn:flickr:"), NULL, reference_free_handler); #if 0 /* we don't like these namespaces because of they shadow our usefull parameters. */ Put(KnownNameSpaces, HKEY("http://search.yahoo.com/mrss/"), NULL, reference_free_handler); #endif CtdlRegisterDebugFlagHook(HKEY("RSSAtomParser"), LogDebugEnableRSSATOMParser, &RSSAtomParserDebugEnabled); CtdlRegisterCleanupHook(rss_parser_cleanup); } return "rssparser"; } citadel-9.01/modules/rssclient/rss_atom_parser.h0000644000000000000000000000467112507024051020604 0ustar rootroot/* * Bring external RSS feeds into rooms. * * Copyright (c) 2007-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * */ #include "internet_addressing.h" #define RSS_UNSET (1<<0) #define RSS_RSS (1<<1) #define RSS_ATOM (1<<2) #define RSS_REQUIRE_BUF (1<<3) typedef struct rss_aggregator rss_aggregator; typedef struct rss_item rss_item; typedef struct rss_room_counter rss_room_counter; typedef void (*rss_handler_func)(StrBuf *CData, rss_item *ri, rss_aggregator *Cfg, const char** Attr); typedef struct __rss_xml_handler { int Flags; rss_handler_func Handler; }rss_xml_handler; struct rss_item { int done_parsing; int item_tag_nesting; time_t pubdate; StrBuf *guid; StrBuf *title; StrBuf *link; StrBuf *linkTitle; StrBuf *reLink; StrBuf *reLinkTitle; StrBuf *description; StrBuf *channel_title; StrBuf *author_or_creator; StrBuf *author_url; StrBuf *author_email; }; void flush_rss_item(rss_item *ri); struct rss_room_counter { int count; long QRnumber; }; typedef struct __networker_save_message { struct CtdlMessage Msg; StrBuf *MsgGUID; StrBuf *Message; StrBuf *author_email; StrBuf *author_or_creator; StrBuf *title; StrBuf *description; StrBuf *link; StrBuf *linkTitle; StrBuf *reLink; StrBuf *reLinkTitle; } networker_save_message; typedef struct RSSCfgLine RSSCfgLine; struct RSSCfgLine { RSSCfgLine *next; StrBuf *Url; time_t last_known_good; }; typedef struct __pRSSConfig { const RSSCfgLine *pCfg; long QRnumber; }pRSSConfig; struct rss_aggregator { AsyncIO IO; XML_Parser xp; int ItemType; int roomlist_parts; time_t last_error_when; time_t next_poll; StrBuf *Url; StrBuf *RedirectUrl; StrBuf *rooms; pRSSConfig Cfg; HashList *OtherQRnumbers; StrBuf *CData; StrBuf *Key; rss_item *Item; recptypes recp; HashPos *Pos; HashList *Messages; networker_save_message *ThisMsg; }; eNextState RSSAggregator_ParseReply(AsyncIO *IO); eNextState RSS_FetchNetworkUsetableEntry(AsyncIO *IO); citadel-9.01/modules/pop3/0000755000000000000000000000000012507024051014073 5ustar rootrootcitadel-9.01/modules/pop3/serv_pop3.c0000644000000000000000000003656712507024051016200 0ustar rootroot/* * POP3 service for the Citadel system * * Copyright (c) 1998-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * * * Current status of standards conformance: * * -> All required POP3 commands described in RFC1939 are implemented. * -> All optional POP3 commands described in RFC1939 are also implemented. * -> The deprecated "LAST" command is included in this implementation, because * there exist mail clients which insist on using it (such as Bynari * TradeMail, and certain versions of Eudora). * -> Capability detection via the method described in RFC2449 is implemented. * */ #include "sysdep.h" #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "user_ops.h" #include "database.h" #include "msgbase.h" #include "internet_addressing.h" #include "serv_pop3.h" #include "md5.h" #include "ctdl_module.h" int POP3DebugEnabled = 0; #define POP3DBGLOG(LEVEL) if ((LEVEL != LOG_DEBUG) || (POP3DebugEnabled != 0)) #define CCCID CCC->cs_pid #define POP3_syslog(LEVEL, FORMAT, ...) \ POP3DBGLOG(LEVEL) syslog(LEVEL, \ "POP3CC[%d] " FORMAT, \ CCCID, __VA_ARGS__) #define POP3M_syslog(LEVEL, FORMAT) \ POP3DBGLOG(LEVEL) syslog(LEVEL, \ "POP3CC[%d] " FORMAT, \ CCCID) /* * This cleanup function blows away the temporary memory and files used by * the POP3 server. */ void pop3_cleanup_function(void) { struct CitContext *CCC = CC; /* Don't do this stuff if this is not a POP3 session! */ if (CCC->h_command_function != pop3_command_loop) return; struct citpop3 *pop3 = ((struct citpop3 *)CCC->session_specific_data); POP3M_syslog(LOG_DEBUG, "Performing POP3 cleanup hook"); if (pop3->msgs != NULL) { free(pop3->msgs); } free(pop3); } /* * Here's where our POP3 session begins its happy day. */ void pop3_greeting(void) { struct CitContext *CCC = CC; strcpy(CCC->cs_clientname, "POP3 session"); CCC->internal_pgm = 1; CCC->session_specific_data = malloc(sizeof(struct citpop3)); memset(POP3, 0, sizeof(struct citpop3)); cprintf("+OK Citadel POP3 server ready.\r\n"); } /* * POP3S is just like POP3, except it goes crypto right away. */ void pop3s_greeting(void) { struct CitContext *CCC = CC; CtdlModuleStartCryptoMsgs(NULL, NULL, NULL); /* kill session if no crypto */ #ifdef HAVE_OPENSSL if (!CCC->redirect_ssl) CCC->kill_me = KILLME_NO_CRYPTO; #else CCC->kill_me = KILLME_NO_CRYPTO; #endif pop3_greeting(); } /* * Specify user name (implements POP3 "USER" command) */ void pop3_user(char *argbuf) { struct CitContext *CCC = CC; char username[SIZ]; if (CCC->logged_in) { cprintf("-ERR You are already logged in.\r\n"); return; } strcpy(username, argbuf); striplt(username); /* POP3_syslog(LOG_DEBUG, "Trying <%s>", username); */ if (CtdlLoginExistingUser(NULL, username) == login_ok) { cprintf("+OK Password required for %s\r\n", username); } else { cprintf("-ERR No such user.\r\n"); } } /* * Back end for pop3_grab_mailbox() */ void pop3_add_message(long msgnum, void *userdata) { struct CitContext *CCC = CC; struct MetaData smi; ++POP3->num_msgs; if (POP3->num_msgs < 2) POP3->msgs = malloc(sizeof(struct pop3msg)); else POP3->msgs = realloc(POP3->msgs, (POP3->num_msgs * sizeof(struct pop3msg)) ) ; POP3->msgs[POP3->num_msgs-1].msgnum = msgnum; POP3->msgs[POP3->num_msgs-1].deleted = 0; /* We need to know the length of this message when it is printed in * RFC822 format. Perhaps we have cached this length in the message's * metadata record. If so, great; if not, measure it and then cache * it for next time. */ GetMetaData(&smi, msgnum); if (smi.meta_rfc822_length <= 0L) { CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ); CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL, SUPPRESS_ENV_TO, NULL, NULL, NULL); smi.meta_rfc822_length = StrLength(CCC->redirect_buffer); FreeStrBuf(&CCC->redirect_buffer); /* TODO: WHEW, all this for just knowing the length???? */ PutMetaData(&smi); } POP3->msgs[POP3->num_msgs-1].rfc822_length = smi.meta_rfc822_length; } /* * Open the inbox and read its contents. * (This should be called only once, by pop3_pass(), and returns the number * of messages in the inbox, or -1 for error) */ int pop3_grab_mailbox(void) { struct CitContext *CCC = CC; visit vbuf; int i; if (CtdlGetRoom(&CCC->room, MAILROOM) != 0) return(-1); /* Load up the messages */ CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, pop3_add_message, NULL); /* Figure out which are old and which are new */ CtdlGetRelationship(&vbuf, &CCC->user, &CCC->room); POP3->lastseen = (-1); if (POP3->num_msgs) for (i=0; inum_msgs; ++i) { if (is_msg_in_sequence_set(vbuf.v_seen, (POP3->msgs[POP3->num_msgs-1].msgnum) )) { POP3->lastseen = i; } } return(POP3->num_msgs); } void pop3_login(void) { struct CitContext *CCC = CC; int msgs; msgs = pop3_grab_mailbox(); if (msgs >= 0) { cprintf("+OK %s is logged in (%d messages)\r\n", CCC->user.fullname, msgs); POP3_syslog(LOG_DEBUG, "POP3 authenticated %s", CCC->user.fullname); } else { cprintf("-ERR Can't open your mailbox\r\n"); } } /* * Authorize with password (implements POP3 "PASS" command) */ void pop3_pass(char *argbuf) { char password[SIZ]; safestrncpy(password, argbuf, sizeof password); striplt(password); /* POP3_syslog(LOG_DEBUG, "Trying <%s>", password); */ if (CtdlTryPassword(password, strlen(password)) == pass_ok) { pop3_login(); } else { cprintf("-ERR That is NOT the password.\r\n"); } } /* * list available msgs */ void pop3_list(char *argbuf) { int i; int which_one; which_one = atoi(argbuf); /* "list one" mode */ if (which_one > 0) { if (which_one > POP3->num_msgs) { cprintf("-ERR no such message, only %d are here\r\n", POP3->num_msgs); return; } else if (POP3->msgs[which_one-1].deleted) { cprintf("-ERR Sorry, you deleted that message.\r\n"); return; } else { cprintf("+OK %d %ld\r\n", which_one, (long)POP3->msgs[which_one-1].rfc822_length ); return; } } /* "list all" (scan listing) mode */ else { cprintf("+OK Here's your mail:\r\n"); if (POP3->num_msgs > 0) for (i=0; inum_msgs; ++i) { if (! POP3->msgs[i].deleted) { cprintf("%d %ld\r\n", i+1, (long)POP3->msgs[i].rfc822_length); } } cprintf(".\r\n"); } } /* * STAT (tally up the total message count and byte count) command */ void pop3_stat(char *argbuf) { int total_msgs = 0; size_t total_octets = 0; int i; if (POP3->num_msgs > 0) for (i=0; inum_msgs; ++i) { if (! POP3->msgs[i].deleted) { ++total_msgs; total_octets += POP3->msgs[i].rfc822_length; } } cprintf("+OK %d %ld\r\n", total_msgs, (long)total_octets); } /* * RETR command (fetch a message) */ void pop3_retr(char *argbuf) { int which_one; which_one = atoi(argbuf); if ( (which_one < 1) || (which_one > POP3->num_msgs) ) { cprintf("-ERR No such message.\r\n"); return; } if (POP3->msgs[which_one - 1].deleted) { cprintf("-ERR Sorry, you deleted that message.\r\n"); return; } cprintf("+OK Message %d:\r\n", which_one); CtdlOutputMsg(POP3->msgs[which_one - 1].msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO), NULL, NULL, NULL); cprintf(".\r\n"); } /* * TOP command (dumb way of fetching a partial message or headers-only) */ void pop3_top(char *argbuf) { struct CitContext *CCC = CC; int which_one; int lines_requested = 0; int lines_dumped = 0; char buf[1024]; StrBuf *msgtext; const char *ptr; int in_body = 0; int done = 0; sscanf(argbuf, "%d %d", &which_one, &lines_requested); if ( (which_one < 1) || (which_one > POP3->num_msgs) ) { cprintf("-ERR No such message.\r\n"); return; } if (POP3->msgs[which_one - 1].deleted) { cprintf("-ERR Sorry, you deleted that message.\r\n"); return; } CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ); CtdlOutputMsg(POP3->msgs[which_one - 1].msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL, SUPPRESS_ENV_TO, NULL, NULL, NULL); msgtext = CCC->redirect_buffer; CCC->redirect_buffer = NULL; cprintf("+OK Message %d:\r\n", which_one); ptr = ChrPtr(msgtext); while (ptr = cmemreadline(ptr, buf, (sizeof buf - 2)), ( (*ptr != 0) && (done == 0))) { strcat(buf, "\r\n"); if (in_body == 1) { if (lines_dumped >= lines_requested) { done = 1; } } if ((in_body == 0) || (done == 0)) { client_write(buf, strlen(buf)); } if (in_body) { ++lines_dumped; } if ((buf[0]==13)||(buf[0]==10)) in_body = 1; } if (buf[strlen(buf)-1] != 10) cprintf("\n"); FreeStrBuf(&msgtext); cprintf(".\r\n"); } /* * DELE (delete message from mailbox) */ void pop3_dele(char *argbuf) { int which_one; which_one = atoi(argbuf); if ( (which_one < 1) || (which_one > POP3->num_msgs) ) { cprintf("-ERR No such message.\r\n"); return; } if (POP3->msgs[which_one - 1].deleted) { cprintf("-ERR You already deleted that message.\r\n"); return; } /* Flag the message as deleted. Will expunge during QUIT command. */ POP3->msgs[which_one - 1].deleted = 1; cprintf("+OK Message %d deleted.\r\n", which_one); } /* Perform "UPDATE state" stuff */ void pop3_update(void) { struct CitContext *CCC = CC; int i; visit vbuf; long *deletemsgs = NULL; int num_deletemsgs = 0; /* Remove messages marked for deletion */ if (POP3->num_msgs > 0) { deletemsgs = malloc(POP3->num_msgs * sizeof(long)); for (i=0; inum_msgs; ++i) { if (POP3->msgs[i].deleted) { deletemsgs[num_deletemsgs++] = POP3->msgs[i].msgnum; } } if (num_deletemsgs > 0) { CtdlDeleteMessages(MAILROOM, deletemsgs, num_deletemsgs, ""); } free(deletemsgs); } /* Set last read pointer */ if (POP3->num_msgs > 0) { CtdlLockGetCurrentUser(); CtdlGetRelationship(&vbuf, &CCC->user, &CCC->room); snprintf(vbuf.v_seen, sizeof vbuf.v_seen, "*:%ld", POP3->msgs[POP3->num_msgs-1].msgnum); CtdlSetRelationship(&vbuf, &CCC->user, &CCC->room); CtdlPutCurrentUserLock(); } } /* * RSET (reset, i.e. undelete any deleted messages) command */ void pop3_rset(char *argbuf) { int i; if (POP3->num_msgs > 0) for (i=0; inum_msgs; ++i) { if (POP3->msgs[i].deleted) { POP3->msgs[i].deleted = 0; } } cprintf("+OK Reset completed.\r\n"); } /* * LAST (Determine which message is the last unread message) */ void pop3_last(char *argbuf) { cprintf("+OK %d\r\n", POP3->lastseen + 1); } /* * CAPA is a command which tells the client which POP3 extensions * are supported. */ void pop3_capa(void) { cprintf("+OK Capability list follows\r\n" "TOP\r\n" "USER\r\n" "UIDL\r\n" "IMPLEMENTATION %s\r\n" ".\r\n" , CITADEL ); } /* * UIDL (Universal IDentifier Listing) is easy. Our 'unique' message * identifiers are simply the Citadel message numbers in the database. */ void pop3_uidl(char *argbuf) { int i; int which_one; which_one = atoi(argbuf); /* "list one" mode */ if (which_one > 0) { if (which_one > POP3->num_msgs) { cprintf("-ERR no such message, only %d are here\r\n", POP3->num_msgs); return; } else if (POP3->msgs[which_one-1].deleted) { cprintf("-ERR Sorry, you deleted that message.\r\n"); return; } else { cprintf("+OK %d %ld\r\n", which_one, POP3->msgs[which_one-1].msgnum ); return; } } /* "list all" (scan listing) mode */ else { cprintf("+OK Here's your mail:\r\n"); if (POP3->num_msgs > 0) for (i=0; inum_msgs; ++i) { if (! POP3->msgs[i].deleted) { cprintf("%d %ld\r\n", i+1, POP3->msgs[i].msgnum); } } cprintf(".\r\n"); } } /* * implements the STLS command (Citadel API version) */ void pop3_stls(void) { char ok_response[SIZ]; char nosup_response[SIZ]; char error_response[SIZ]; sprintf(ok_response, "+OK Begin TLS negotiation now\r\n"); sprintf(nosup_response, "-ERR TLS not supported here\r\n"); sprintf(error_response, "-ERR Internal error\r\n"); CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response); } /* * Main command loop for POP3 sessions. */ void pop3_command_loop(void) { struct CitContext *CCC = CC; char cmdbuf[SIZ]; time(&CCC->lastcmd); memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */ if (client_getln(cmdbuf, sizeof cmdbuf) < 1) { POP3M_syslog(LOG_INFO, "POP3 client disconnected: ending session."); CCC->kill_me = KILLME_CLIENT_DISCONNECTED; return; } if (!strncasecmp(cmdbuf, "PASS", 4)) { POP3M_syslog(LOG_DEBUG, "POP3: PASS..."); } else { POP3_syslog(LOG_DEBUG, "POP3: %s", cmdbuf); } while (strlen(cmdbuf) < 5) strcat(cmdbuf, " "); if (!strncasecmp(cmdbuf, "NOOP", 4)) { cprintf("+OK No operation.\r\n"); } else if (!strncasecmp(cmdbuf, "CAPA", 4)) { pop3_capa(); } else if (!strncasecmp(cmdbuf, "QUIT", 4)) { cprintf("+OK Goodbye...\r\n"); pop3_update(); CCC->kill_me = KILLME_CLIENT_LOGGED_OUT; return; } else if (!strncasecmp(cmdbuf, "USER", 4)) { pop3_user(&cmdbuf[5]); } else if (!strncasecmp(cmdbuf, "PASS", 4)) { pop3_pass(&cmdbuf[5]); } #ifdef HAVE_OPENSSL else if (!strncasecmp(cmdbuf, "STLS", 4)) { pop3_stls(); } #endif else if (!CCC->logged_in) { cprintf("-ERR Not logged in.\r\n"); } else if (CCC->nologin) { cprintf("-ERR System busy, try later.\r\n"); CCC->kill_me = KILLME_NOLOGIN; } else if (!strncasecmp(cmdbuf, "LIST", 4)) { pop3_list(&cmdbuf[5]); } else if (!strncasecmp(cmdbuf, "STAT", 4)) { pop3_stat(&cmdbuf[5]); } else if (!strncasecmp(cmdbuf, "RETR", 4)) { pop3_retr(&cmdbuf[5]); } else if (!strncasecmp(cmdbuf, "DELE", 4)) { pop3_dele(&cmdbuf[5]); } else if (!strncasecmp(cmdbuf, "RSET", 4)) { pop3_rset(&cmdbuf[5]); } else if (!strncasecmp(cmdbuf, "UIDL", 4)) { pop3_uidl(&cmdbuf[5]); } else if (!strncasecmp(cmdbuf, "TOP", 3)) { pop3_top(&cmdbuf[4]); } else if (!strncasecmp(cmdbuf, "LAST", 4)) { pop3_last(&cmdbuf[4]); } else { cprintf("-ERR I'm afraid I can't do that.\r\n"); } } const char *CitadelServicePop3="POP3"; const char *CitadelServicePop3S="POP3S"; void SetPOP3DebugEnabled(const int n) { POP3DebugEnabled = n; } CTDL_MODULE_INIT(pop3) { if(!threading) { CtdlRegisterDebugFlagHook(HKEY("pop3srv"), SetPOP3DebugEnabled, &POP3DebugEnabled); CtdlRegisterServiceHook(config.c_pop3_port, NULL, pop3_greeting, pop3_command_loop, NULL, CitadelServicePop3); #ifdef HAVE_OPENSSL CtdlRegisterServiceHook(config.c_pop3s_port, NULL, pop3s_greeting, pop3_command_loop, NULL, CitadelServicePop3S); #endif CtdlRegisterSessionHook(pop3_cleanup_function, EVT_STOP, PRIO_STOP + 30); } /* return our module name for the log */ return "pop3"; } citadel-9.01/modules/pop3/serv_pop3.h0000644000000000000000000000231712507024051016167 0ustar rootroot/* * Copyright (c) 1998-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * */ struct pop3msg { long msgnum; size_t rfc822_length; int deleted; }; struct citpop3 { /* Information about the current session */ struct pop3msg *msgs; /* Array of message pointers */ int num_msgs; /* Number of messages in array */ int lastseen; /* Offset of last-read message in array */ }; /* Note: the "lastseen" is represented as the * offset in this array (zero-based), so when * displaying it to a POP3 client, it must be * incremented by one. */ #define POP3 ((struct citpop3 *)CC->session_specific_data) void pop3_cleanup_function(void); void pop3_greeting(void); void pop3_user(char *argbuf); void pop3_pass(char *argbuf); void pop3_list(char *argbuf); void pop3_command_loop(void); void pop3_login(void); citadel-9.01/modules/autocompletion/0000755000000000000000000000000012507024051016254 5ustar rootrootcitadel-9.01/modules/autocompletion/serv_autocompletion.c0000644000000000000000000001261712507024051022530 0ustar rootroot/* * Autocompletion of email recipients, etc. * * Copyright (c) 1987-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "ctdl_module.h" #include "serv_autocompletion.h" /* * Convert a structured name into a friendly name. Caller must free the * returned pointer. */ char *n_to_fn(char *value) { char *nnn = NULL; int i; nnn = malloc(strlen(value) + 10); strcpy(nnn, ""); extract_token(&nnn[strlen(nnn)] , value, 3, ';', 999); strcat(nnn, " "); extract_token(&nnn[strlen(nnn)] , value, 1, ';', 999); strcat(nnn, " "); extract_token(&nnn[strlen(nnn)] , value, 2, ';', 999); strcat(nnn, " "); extract_token(&nnn[strlen(nnn)] , value, 0, ';', 999); strcat(nnn, " "); extract_token(&nnn[strlen(nnn)] , value, 4, ';', 999); strcat(nnn, " "); for (i=0; icm_fields[eMesageText]); CM_Free(msg); /* * Try to match from a friendly name (the "fn" field). If there is * a match, return the entry in the form of: * Display Name */ value = vcard_get_prop(v, "fn", 0, 0, 0); if (value != NULL) if (bmstrcasestr(value, search_string)) { value2 = vcard_get_prop(v, "email", 1, 0, 0); if (value2 == NULL) value2 = ""; cprintf("%s <%s>\n", value, value2); vcard_free(v); return; } /* * Try to match from a structured name (the "n" field). If there is * a match, return the entry in the form of: * Display Name */ value = vcard_get_prop(v, "n", 0, 0, 0); if (value != NULL) if (bmstrcasestr(value, search_string)) { value2 = vcard_get_prop(v, "email", 1, 0, 0); if (value2 == NULL) value2 = ""; nnn = n_to_fn(value); cprintf("%s <%s>\n", nnn, value2); free(nnn); vcard_free(v); return; } /* * Try a partial match on all listed email addresses. */ i = 0; while (value = vcard_get_prop(v, "email", 1, i++, 0), value != NULL) { if (bmstrcasestr(value, search_string)) { if (vcard_get_prop(v, "fn", 0, 0, 0)) { cprintf("%s <%s>\n", vcard_get_prop(v, "fn", 0, 0, 0), value); } else if (vcard_get_prop(v, "n", 0, 0, 0)) { nnn = n_to_fn(vcard_get_prop(v, "n", 0, 0, 0)); cprintf("%s <%s>\n", nnn, value); free(nnn); } else { cprintf("%s\n", value); } vcard_free(v); return; } } vcard_free(v); } /* * Attempt to autocomplete an address based on a partial... */ void cmd_auto(char *argbuf) { char hold_rm[ROOMNAMELEN]; char search_string[256]; long *msglist = NULL; int num_msgs = 0; long *fts_msgs = NULL; int fts_num_msgs = 0; struct cdbdata *cdbfr; int r = 0; int i = 0; int j = 0; int search_match = 0; char *rooms_to_try[] = { USERCONTACTSROOM, ADDRESS_BOOK_ROOM }; if (CtdlAccessCheck(ac_logged_in)) return; extract_token(search_string, argbuf, 0, '|', sizeof search_string); if (IsEmptyStr(search_string)) { cprintf("%d You supplied an empty partial.\n", ERROR + ILLEGAL_VALUE); return; } strcpy(hold_rm, CC->room.QRname); /* save current room */ cprintf("%d try these:\n", LISTING_FOLLOWS); /* * Gather up message pointers in rooms containing vCards */ for (r=0; r < (sizeof(rooms_to_try) / sizeof(char *)); ++r) { if (CtdlGetRoom(&CC->room, rooms_to_try[r]) == 0) { cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long)); if (cdbfr != NULL) { msglist = realloc(msglist, (num_msgs * sizeof(long)) + cdbfr->len + 1); memcpy(&msglist[num_msgs], cdbfr->ptr, cdbfr->len); num_msgs += (cdbfr->len / sizeof(long)); cdb_free(cdbfr); } } } /* * Search-reduce the results if we have the full text index available */ if (config.c_enable_fulltext) { CtdlModuleDoSearch(&fts_num_msgs, &fts_msgs, search_string, "fulltext"); if (fts_msgs) { for (i=0; i 0) for (i=0; iroom.QRname, hold_rm)) { CtdlGetRoom(&CC->room, hold_rm); /* return to saved room */ } if (msglist) { free(msglist); } } CTDL_MODULE_INIT(autocompletion) { if (!threading) { CtdlRegisterProtoHook(cmd_auto, "AUTO", "Do recipient autocompletion"); } /* return our module name for the log */ return "autocompletion"; } citadel-9.01/modules/autocompletion/serv_autocompletion.h0000644000000000000000000000076612507024051022537 0ustar rootroot/* * Copyright (c) 1987-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ char *serv_autocompletion_init(void); citadel-9.01/modules/eventclient/0000755000000000000000000000000012507024051015532 5ustar rootrootcitadel-9.01/modules/eventclient/serv_curl.h0000644000000000000000000000000012507024051017675 0ustar rootrootcitadel-9.01/modules/eventclient/serv_eventclient.c0000644000000000000000000005375612507024051021275 0ustar rootroot/* * Copyright (c) 1998-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * */ #include "sysdep.h" #include #include #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include #include #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "ctdl_module.h" #include "event_client.h" #include "serv_curl.h" ev_loop *event_base; int DebugEventLoop = 0; int DebugEventLoopBacktrace = 0; int DebugCurl = 0; pthread_key_t evConKey; long EvIDSource = 1; /***************************************************************************** * libevent / curl integration * *****************************************************************************/ #define DBGLOG(LEVEL) if ((LEVEL != LOG_DEBUG) || (DebugCurl != 0)) #define EVCURL_syslog(LEVEL, FORMAT, ...) \ DBGLOG (LEVEL) syslog(LEVEL, "EVCURL:%s[%ld]CC[%d] " FORMAT, \ IOSTR, IO->ID, CCID, __VA_ARGS__) #define EVCURLM_syslog(LEVEL, FORMAT) \ DBGLOG (LEVEL) syslog(LEVEL, "EVCURL:%s[%ld]CC[%d] " FORMAT, \ IOSTR, IO->ID, CCID) #define CURL_syslog(LEVEL, FORMAT, ...) \ DBGLOG (LEVEL) syslog(LEVEL, "CURL: " FORMAT, __VA_ARGS__) #define CURLM_syslog(LEVEL, FORMAT) \ DBGLOG (LEVEL) syslog(LEVEL, "CURL: " FORMAT) #define MOPT(s, v) \ do { \ sta = curl_multi_setopt(mhnd, (CURLMOPT_##s), (v)); \ if (sta) { \ EVQ_syslog(LOG_ERR, "error setting option " \ #s " on curl multi handle: %s\n", \ curl_easy_strerror(sta)); \ exit (1); \ } \ } while (0) typedef struct _evcurl_global_data { int magic; CURLM *mhnd; ev_timer timeev; int nrun; } evcurl_global_data; ev_async WakeupCurl; evcurl_global_data global; eNextState QueueAnDBOperation(AsyncIO *IO); static void gotstatus(int nnrun) { CURLMsg *msg; int nmsg; global.nrun = nnrun; CURLM_syslog(LOG_DEBUG, "gotstatus(): about to call curl_multi_info_read\n"); while ((msg = curl_multi_info_read(global.mhnd, &nmsg))) { CURL_syslog(LOG_DEBUG, "got curl multi_info message msg=%d\n", msg->msg); if (CURLMSG_DONE == msg->msg) { CURL *chnd; void *chandle = NULL; CURLcode sta; CURLMcode msta; AsyncIO*IO; chandle = NULL;; chnd = msg->easy_handle; sta = curl_easy_getinfo(chnd, CURLINFO_PRIVATE, &chandle); if (sta) { syslog(LOG_ERR, "error asking curl for private" " cookie of curl handle: %s\n", curl_easy_strerror(sta)); continue; } IO = (AsyncIO *)chandle; if (IO->ID == 0) { EVCURL_syslog(LOG_ERR, "Error, invalid IO context %p\n", IO); continue; } SetEVState(IO, eCurlGotStatus); EVCURLM_syslog(LOG_DEBUG, "request complete\n"); IO->Now = ev_now(event_base); ev_io_stop(event_base, &IO->recv_event); ev_io_stop(event_base, &IO->send_event); sta = msg->data.result; if (sta) { EVCURL_syslog(LOG_ERR, "error description: %s\n", IO->HttpReq.errdesc); IO->HttpReq.CurlError = curl_easy_strerror(sta); EVCURL_syslog(LOG_ERR, "error performing request: %s\n", IO->HttpReq.CurlError); if (sta == CURLE_OPERATION_TIMEDOUT) { IO->SendBuf.fd = 0; IO->RecvBuf.fd = 0; } } sta = curl_easy_getinfo(chnd, CURLINFO_RESPONSE_CODE, &IO->HttpReq.httpcode); if (sta) EVCURL_syslog(LOG_ERR, "error asking curl for " "response code from request: %s\n", curl_easy_strerror(sta)); EVCURL_syslog(LOG_DEBUG, "http response code was %ld\n", (long)IO->HttpReq.httpcode); curl_slist_free_all(IO->HttpReq.headers); msta = curl_multi_remove_handle(global.mhnd, chnd); if (msta) EVCURL_syslog(LOG_ERR, "warning problem detaching " "completed handle from curl multi: " "%s\n", curl_multi_strerror(msta)); ev_cleanup_stop(event_base, &IO->abort_by_shutdown); IO->HttpReq.attached = 0; switch(IO->SendDone(IO)) { case eDBQuery: FreeURL(&IO->ConnectMe); QueueAnDBOperation(IO); break; case eSendDNSQuery: case eReadDNSReply: case eConnect: case eSendReply: case eSendMore: case eSendFile: case eReadMessage: case eReadMore: case eReadPayload: case eReadFile: break; case eTerminateConnection: case eAbort: curl_easy_cleanup(IO->HttpReq.chnd); IO->HttpReq.chnd = NULL; FreeStrBuf(&IO->HttpReq.ReplyData); FreeURL(&IO->ConnectMe); RemoveContext(IO->CitContext); IO->Terminate(IO); } } } } static void stepmulti(void *data, curl_socket_t fd, int which) { int running_handles = 0; CURLMcode msta; msta = curl_multi_socket_action(global.mhnd, fd, which, &running_handles); CURLM_syslog(LOG_DEBUG, "stepmulti(): calling gotstatus()\n"); if (msta) CURL_syslog(LOG_ERR, "error in curl processing events" "on multi handle, fd %d: %s\n", (int)fd, curl_multi_strerror(msta)); if (global.nrun != running_handles) gotstatus(running_handles); } static void gottime(struct ev_loop *loop, ev_timer *timeev, int events) { CURLM_syslog(LOG_DEBUG, "EVCURL: waking up curl for timeout\n"); stepmulti(NULL, CURL_SOCKET_TIMEOUT, 0); } static void got_in(struct ev_loop *loop, ev_io *ioev, int events) { CURL_syslog(LOG_DEBUG, "EVCURL: waking up curl for io on fd %d\n", (int)ioev->fd); stepmulti(ioev->data, ioev->fd, CURL_CSELECT_IN); } static void got_out(struct ev_loop *loop, ev_io *ioev, int events) { CURL_syslog(LOG_DEBUG, "waking up curl for io on fd %d\n", (int)ioev->fd); stepmulti(ioev->data, ioev->fd, CURL_CSELECT_OUT); } static size_t gotdata(void *data, size_t size, size_t nmemb, void *cglobal) { AsyncIO *IO = (AsyncIO*) cglobal; SetEVState(IO, eCurlGotData); if (IO->HttpReq.ReplyData == NULL) { IO->HttpReq.ReplyData = NewStrBufPlain(NULL, SIZ); } IO->Now = ev_now(event_base); return CurlFillStrBuf_callback(data, size, nmemb, IO->HttpReq.ReplyData); } static int gotwatchtime(CURLM *multi, long tblock_ms, void *cglobal) { CURL_syslog(LOG_DEBUG, "EVCURL: gotwatchtime called %ld ms\n", tblock_ms); evcurl_global_data *global = cglobal; ev_timer_stop(EV_DEFAULT, &global->timeev); if (tblock_ms < 0 || 14000 < tblock_ms) tblock_ms = 14000; ev_timer_set(&global->timeev, 0.5e-3 + 1.0e-3 * tblock_ms, 14.0); ev_timer_start(EV_DEFAULT_UC, &global->timeev); curl_multi_perform(global, &global->nrun); return 0; } static int gotwatchsock(CURL *easy, curl_socket_t fd, int action, void *cglobal, void *vIO) { evcurl_global_data *global = cglobal; CURLM *mhnd = global->mhnd; void *f; AsyncIO *IO = (AsyncIO*) vIO; CURLcode sta; const char *Action; if (IO == NULL) { sta = curl_easy_getinfo(easy, CURLINFO_PRIVATE, &f); if (sta) { CURL_syslog(LOG_ERR, "EVCURL: error asking curl for private " "cookie of curl handle: %s\n", curl_easy_strerror(sta)); return -1; } IO = (AsyncIO *) f; SetEVState(IO, eCurlNewIO); EVCURL_syslog(LOG_DEBUG, "EVCURL: got socket for URL: %s\n", IO->ConnectMe->PlainUrl); if (IO->SendBuf.fd != 0) { ev_io_stop(event_base, &IO->recv_event); ev_io_stop(event_base, &IO->send_event); } IO->SendBuf.fd = fd; ev_io_init(&IO->recv_event, &got_in, fd, EV_READ); ev_io_init(&IO->send_event, &got_out, fd, EV_WRITE); curl_multi_assign(mhnd, fd, IO); } SetEVState(IO, eCurlGotIO); IO->Now = ev_now(event_base); Action = ""; switch (action) { case CURL_POLL_NONE: Action = "CURL_POLL_NONE"; break; case CURL_POLL_REMOVE: Action = "CURL_POLL_REMOVE"; break; case CURL_POLL_IN: Action = "CURL_POLL_IN"; break; case CURL_POLL_OUT: Action = "CURL_POLL_OUT"; break; case CURL_POLL_INOUT: Action = "CURL_POLL_INOUT"; break; } EVCURL_syslog(LOG_DEBUG, "EVCURL: gotwatchsock called fd=%d action=%s[%d]\n", (int)fd, Action, action); switch (action) { case CURL_POLL_NONE: EVCURLM_syslog(LOG_DEBUG, "called first time " "to register this sockwatcker\n"); break; case CURL_POLL_REMOVE: EVCURLM_syslog(LOG_DEBUG, "called last time to unregister " "this sockwatcher\n"); ev_io_stop(event_base, &IO->recv_event); ev_io_stop(event_base, &IO->send_event); break; case CURL_POLL_IN: ev_io_start(event_base, &IO->recv_event); ev_io_stop(event_base, &IO->send_event); break; case CURL_POLL_OUT: ev_io_start(event_base, &IO->send_event); ev_io_stop(event_base, &IO->recv_event); break; case CURL_POLL_INOUT: ev_io_start(event_base, &IO->send_event); ev_io_start(event_base, &IO->recv_event); break; } return 0; } void curl_init_connectionpool(void) { CURLM *mhnd ; ev_timer_init(&global.timeev, &gottime, 14.0, 14.0); global.timeev.data = (void *)&global; global.nrun = -1; CURLcode sta = curl_global_init(CURL_GLOBAL_ALL); if (sta) { CURL_syslog(LOG_ERR, "error initializing curl library: %s\n", curl_easy_strerror(sta)); exit(1); } mhnd = global.mhnd = curl_multi_init(); if (!mhnd) { CURLM_syslog(LOG_ERR, "error initializing curl multi handle\n"); exit(3); } MOPT(SOCKETFUNCTION, &gotwatchsock); MOPT(SOCKETDATA, (void *)&global); MOPT(TIMERFUNCTION, &gotwatchtime); MOPT(TIMERDATA, (void *)&global); return; } int evcurl_init(AsyncIO *IO) { CURLcode sta; CURL *chnd; EVCURLM_syslog(LOG_DEBUG, "EVCURL: evcurl_init called ms\n"); IO->HttpReq.attached = 0; chnd = IO->HttpReq.chnd = curl_easy_init(); if (!chnd) { EVCURLM_syslog(LOG_ERR, "EVCURL: error initializing curl handle\n"); return 0; } #if DEBUG OPT(VERBOSE, (long)1); #endif OPT(NOPROGRESS, 1L); OPT(NOSIGNAL, 1L); OPT(FAILONERROR, (long)1); OPT(ENCODING, ""); OPT(FOLLOWLOCATION, (long)0); OPT(MAXREDIRS, (long)0); OPT(USERAGENT, CITADEL); OPT(TIMEOUT, (long)1800); OPT(LOW_SPEED_LIMIT, (long)64); OPT(LOW_SPEED_TIME, (long)600); OPT(CONNECTTIMEOUT, (long)600); OPT(PRIVATE, (void *)IO); OPT(FORBID_REUSE, 1); OPT(WRITEFUNCTION, &gotdata); OPT(WRITEDATA, (void *)IO); OPT(ERRORBUFFER, IO->HttpReq.errdesc); if ((!IsEmptyStr(config.c_ip_addr)) && (strcmp(config.c_ip_addr, "*")) && (strcmp(config.c_ip_addr, "::")) && (strcmp(config.c_ip_addr, "0.0.0.0")) ) { OPT(INTERFACE, config.c_ip_addr); } #ifdef CURLOPT_HTTP_CONTENT_DECODING OPT(HTTP_CONTENT_DECODING, 1); OPT(ENCODING, ""); #endif IO->HttpReq.headers = curl_slist_append(IO->HttpReq.headers, "Connection: close"); return 1; } static void IOcurl_abort_shutdown_callback(struct ev_loop *loop, ev_cleanup *watcher, int revents) { CURLMcode msta; AsyncIO *IO = watcher->data; if (IO == NULL) return; SetEVState(IO, eCurlShutdown); IO->Now = ev_now(event_base); EVCURL_syslog(LOG_DEBUG, "EVENT Curl: %s\n", __FUNCTION__); curl_slist_free_all(IO->HttpReq.headers); msta = curl_multi_remove_handle(global.mhnd, IO->HttpReq.chnd); if (msta) { EVCURL_syslog(LOG_ERR, "EVCURL: warning problem detaching completed handle " "from curl multi: %s\n", curl_multi_strerror(msta)); } curl_easy_cleanup(IO->HttpReq.chnd); IO->HttpReq.chnd = NULL; ev_cleanup_stop(event_base, &IO->abort_by_shutdown); ev_io_stop(event_base, &IO->recv_event); ev_io_stop(event_base, &IO->send_event); assert(IO->ShutdownAbort); IO->ShutdownAbort(IO); } eNextState evcurl_handle_start(AsyncIO *IO) { CURLMcode msta; CURLcode sta; CURL *chnd; SetEVState(IO, eCurlStart); chnd = IO->HttpReq.chnd; EVCURL_syslog(LOG_DEBUG, "EVCURL: Loading URL: %s\n", IO->ConnectMe->PlainUrl); OPT(URL, IO->ConnectMe->PlainUrl); if (StrLength(IO->ConnectMe->CurlCreds)) { OPT(HTTPAUTH, (long)CURLAUTH_BASIC); OPT(USERPWD, ChrPtr(IO->ConnectMe->CurlCreds)); } if (StrLength(IO->HttpReq.PostData) > 0) { OPT(POSTFIELDS, ChrPtr(IO->HttpReq.PostData)); OPT(POSTFIELDSIZE, StrLength(IO->HttpReq.PostData)); } else if ((IO->HttpReq.PlainPostDataLen != 0) && (IO->HttpReq.PlainPostData != NULL)) { OPT(POSTFIELDS, IO->HttpReq.PlainPostData); OPT(POSTFIELDSIZE, IO->HttpReq.PlainPostDataLen); } OPT(HTTPHEADER, IO->HttpReq.headers); IO->NextState = eConnect; EVCURLM_syslog(LOG_DEBUG, "EVCURL: attaching to curl multi handle\n"); msta = curl_multi_add_handle(global.mhnd, IO->HttpReq.chnd); if (msta) { EVCURL_syslog(LOG_ERR, "EVCURL: error attaching to curl multi handle: %s\n", curl_multi_strerror(msta)); } IO->HttpReq.attached = 1; ev_async_send (event_base, &WakeupCurl); ev_cleanup_init(&IO->abort_by_shutdown, IOcurl_abort_shutdown_callback); ev_cleanup_start(event_base, &IO->abort_by_shutdown); return eReadMessage; } static void WakeupCurlCallback(EV_P_ ev_async *w, int revents) { CURLM_syslog(LOG_DEBUG, "waking up curl multi handle\n"); curl_multi_perform(&global, CURL_POLL_NONE); } static void evcurl_shutdown (void) { curl_global_cleanup(); curl_multi_cleanup(global.mhnd); CURLM_syslog(LOG_DEBUG, "exiting\n"); } /***************************************************************************** * libevent integration * *****************************************************************************/ /* * client event queue plus its methods. * this currently is the main loop (which may change in some future?) */ int evbase_count = 0; pthread_mutex_t EventQueueMutex; /* locks the access to the following vars: */ pthread_mutex_t EventExitQueueMutex; /* locks the access to the event queue pointer on exit. */ HashList *QueueEvents = NULL; HashList *InboundEventQueue = NULL; HashList *InboundEventQueues[2] = { NULL, NULL }; extern void ShutDownCLient(AsyncIO *IO); ev_async AddJob; ev_async ExitEventLoop; static void QueueEventAddCallback(EV_P_ ev_async *w, int revents) { CitContext *Ctx; long IOID = -1; long count = 0; ev_tstamp Now; HashList *q; void *v; HashPos*It; long len; const char *Key; /* get the control command... */ pthread_mutex_lock(&EventQueueMutex); if (InboundEventQueues[0] == InboundEventQueue) { InboundEventQueue = InboundEventQueues[1]; q = InboundEventQueues[0]; } else { InboundEventQueue = InboundEventQueues[0]; q = InboundEventQueues[1]; } pthread_mutex_unlock(&EventQueueMutex); Now = ev_now (event_base); It = GetNewHashPos(q, 0); while (GetNextHashPos(q, It, &len, &Key, &v)) { IOAddHandler *h = v; count ++; if (h->IO->ID == 0) { h->IO->ID = EvIDSource++; } IOID = h->IO->ID; if (h->IO->StartIO == 0.0) h->IO->StartIO = Now; SetEVState(h->IO, eIOAttach); Ctx = h->IO->CitContext; become_session(Ctx); h->IO->Now = Now; switch (h->EvAttch(h->IO)) { case eReadMore: case eReadMessage: case eReadFile: case eSendReply: case eSendMore: case eReadPayload: case eSendFile: case eDBQuery: case eSendDNSQuery: case eReadDNSReply: case eConnect: break; case eTerminateConnection: case eAbort: ShutDownCLient(h->IO); break; } } DeleteHashPos(&It); DeleteHashContent(&q); EVQ_syslog(LOG_DEBUG, "%s CC[%ld] EVENT Q Add %ld done.", IOSTR, IOID, count); } static void EventExitCallback(EV_P_ ev_async *w, int revents) { ev_break(event_base, EVBREAK_ALL); EVQM_syslog(LOG_DEBUG, "EVENT Q exiting.\n"); } void InitEventQueue(void) { pthread_mutex_init(&EventQueueMutex, NULL); pthread_mutex_init(&EventExitQueueMutex, NULL); QueueEvents = NewHash(1, Flathash); InboundEventQueues[0] = NewHash(1, Flathash); InboundEventQueues[1] = NewHash(1, Flathash); InboundEventQueue = InboundEventQueues[0]; } extern void CtdlDestroyEVCleanupHooks(void); extern int EVQShutDown; const char *IOLog = "IO"; /* * this thread operates the select() etc. via libev. */ void *client_event_thread(void *arg) { struct CitContext libev_client_CC; CtdlFillSystemContext(&libev_client_CC, "LibEv Thread"); pthread_setspecific(evConKey, IOLog); EVQM_syslog(LOG_DEBUG, "client_event_thread() initializing\n"); event_base = ev_default_loop (EVFLAG_AUTO); ev_async_init(&AddJob, QueueEventAddCallback); ev_async_start(event_base, &AddJob); ev_async_init(&ExitEventLoop, EventExitCallback); ev_async_start(event_base, &ExitEventLoop); ev_async_init(&WakeupCurl, WakeupCurlCallback); ev_async_start(event_base, &WakeupCurl); curl_init_connectionpool(); ev_run (event_base, 0); EVQM_syslog(LOG_DEBUG, "client_event_thread() exiting\n"); ///what todo here? CtdlClearSystemContext(); pthread_mutex_lock(&EventExitQueueMutex); ev_loop_destroy (EV_DEFAULT_UC); event_base = NULL; DeleteHash(&QueueEvents); InboundEventQueue = NULL; DeleteHash(&InboundEventQueues[0]); DeleteHash(&InboundEventQueues[1]); /* citthread_mutex_destroy(&EventQueueMutex); TODO */ evcurl_shutdown(); CtdlDestroyEVCleanupHooks(); pthread_mutex_unlock(&EventExitQueueMutex); EVQShutDown = 1; return(NULL); } /*----------------------------------------------------------------------------*/ /* * DB-Queue; does async bdb operations. * has its own set of handlers. */ ev_loop *event_db; int evdb_count = 0; pthread_mutex_t DBEventQueueMutex; /* locks the access to the following vars: */ pthread_mutex_t DBEventExitQueueMutex; /* locks the access to the db-event queue pointer on exit. */ HashList *DBQueueEvents = NULL; HashList *DBInboundEventQueue = NULL; HashList *DBInboundEventQueues[2] = { NULL, NULL }; ev_async DBAddJob; ev_async DBExitEventLoop; extern void ShutDownDBCLient(AsyncIO *IO); static void DBQueueEventAddCallback(EV_P_ ev_async *w, int revents) { CitContext *Ctx; long IOID = -1; long count = 0;; ev_tstamp Now; HashList *q; void *v; HashPos *It; long len; const char *Key; /* get the control command... */ pthread_mutex_lock(&DBEventQueueMutex); if (DBInboundEventQueues[0] == DBInboundEventQueue) { DBInboundEventQueue = DBInboundEventQueues[1]; q = DBInboundEventQueues[0]; } else { DBInboundEventQueue = DBInboundEventQueues[0]; q = DBInboundEventQueues[1]; } pthread_mutex_unlock(&DBEventQueueMutex); Now = ev_now (event_db); It = GetNewHashPos(q, 0); while (GetNextHashPos(q, It, &len, &Key, &v)) { IOAddHandler *h = v; eNextState rc; count ++; if (h->IO->ID == 0) h->IO->ID = EvIDSource++; IOID = h->IO->ID; if (h->IO->StartDB == 0.0) h->IO->StartDB = Now; h->IO->Now = Now; SetEVState(h->IO, eDBAttach); Ctx = h->IO->CitContext; become_session(Ctx); ev_cleanup_start(event_db, &h->IO->db_abort_by_shutdown); rc = h->EvAttch(h->IO); switch (rc) { case eAbort: ShutDownDBCLient(h->IO); default: break; } } DeleteHashPos(&It); DeleteHashContent(&q); EVQ_syslog(LOG_DEBUG, "%s CC[%ld] DBEVENT Q Add %ld done.", IOSTR, IOID, count); } static void DBEventExitCallback(EV_P_ ev_async *w, int revents) { EVQM_syslog(LOG_DEBUG, "DB EVENT Q exiting.\n"); ev_break(event_db, EVBREAK_ALL); } void DBInitEventQueue(void) { pthread_mutex_init(&DBEventQueueMutex, NULL); pthread_mutex_init(&DBEventExitQueueMutex, NULL); DBQueueEvents = NewHash(1, Flathash); DBInboundEventQueues[0] = NewHash(1, Flathash); DBInboundEventQueues[1] = NewHash(1, Flathash); DBInboundEventQueue = DBInboundEventQueues[0]; } const char *DBLog = "BD"; /* * this thread operates writing to the message database via libev. */ void *db_event_thread(void *arg) { ev_loop *tmp; struct CitContext libev_msg_CC; pthread_setspecific(evConKey, DBLog); CtdlFillSystemContext(&libev_msg_CC, "LibEv DB IO Thread"); EVQM_syslog(LOG_DEBUG, "dbevent_thread() initializing\n"); tmp = event_db = ev_loop_new (EVFLAG_AUTO); ev_async_init(&DBAddJob, DBQueueEventAddCallback); ev_async_start(event_db, &DBAddJob); ev_async_init(&DBExitEventLoop, DBEventExitCallback); ev_async_start(event_db, &DBExitEventLoop); ev_run (event_db, 0); pthread_mutex_lock(&DBEventExitQueueMutex); event_db = NULL; EVQM_syslog(LOG_INFO, "dbevent_thread() exiting\n"); DeleteHash(&DBQueueEvents); DBInboundEventQueue = NULL; DeleteHash(&DBInboundEventQueues[0]); DeleteHash(&DBInboundEventQueues[1]); /* citthread_mutex_destroy(&DBEventQueueMutex); TODO */ ev_loop_destroy (tmp); pthread_mutex_unlock(&DBEventExitQueueMutex); return(NULL); } void ShutDownEventQueues(void) { EVQM_syslog(LOG_DEBUG, "EVENT Qs triggering exits.\n"); pthread_mutex_lock(&DBEventQueueMutex); ev_async_send (event_db, &DBExitEventLoop); pthread_mutex_unlock(&DBEventQueueMutex); pthread_mutex_lock(&EventQueueMutex); ev_async_send (EV_DEFAULT_ &ExitEventLoop); pthread_mutex_unlock(&EventQueueMutex); } void DebugEventloopEnable(const int n) { DebugEventLoop = n; } void DebugEventloopBacktraceEnable(const int n) { DebugEventLoopBacktrace = n; } void DebugCurlEnable(const int n) { DebugCurl = n; } const char *WLog = "WX"; CTDL_MODULE_INIT(event_client) { if (!threading) { if (pthread_key_create(&evConKey, NULL) != 0) { syslog(LOG_CRIT, "Can't create TSD key: %s", strerror(errno)); } pthread_setspecific(evConKey, WLog); CtdlRegisterDebugFlagHook(HKEY("eventloop"), DebugEventloopEnable, &DebugEventLoop); CtdlRegisterDebugFlagHook(HKEY("eventloopbacktrace"), DebugEventloopBacktraceEnable, &DebugEventLoopBacktrace); CtdlRegisterDebugFlagHook(HKEY("curl"), DebugCurlEnable, &DebugCurl); InitEventQueue(); DBInitEventQueue(); CtdlThreadCreate(client_event_thread); CtdlThreadCreate(db_event_thread); } return "event"; } citadel-9.01/modules/dspam/0000755000000000000000000000000012507024051014316 5ustar rootrootcitadel-9.01/modules/dspam/serv_dspam.c0000644000000000000000000001441612507024051016633 0ustar rootroot/* * This module glues libDSpam to the Citadel server in order to implement * DSPAM Spamchecking * * Copyright (c) 2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * */ #include "sysdep.h" #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "database.h" #include "msgbase.h" #include "internet_addressing.h" #include "ctdl_module.h" #ifdef HAVE_LIBDSPAM #define CONFIG_DEFAULT file_dpsam_conf #define LOGDIR file_dspam_log //#define HAVE_CONFIG_H #include //#define HAVE_CONFIG_H typedef struct stringlist stringlist; struct stringlist { char *Str; long len; stringlist *Next; }; /* * Citadel protocol to manage sieve scripts. * This is basically a simplified (read: doesn't resemble IMAP) version * of the 'managesieve' protocol. */ void cmd_tspam(char *argbuf) { char buf[SIZ]; long len; long count; stringlist *Messages; stringlist *NextMsg; Messages = NULL; NextMsg = NULL; count = 0; if (CtdlAccessCheck(ac_room_aide)) return; if (atoi(argbuf) == 0) { cprintf("%d Ok.\n", CIT_OK); return; } cprintf("%d Send info...\n", SEND_LISTING); do { len = client_getln(buf, sizeof buf); if (strcmp(buf, "000")) { if (Messages == NULL) { Messages = malloc (sizeof (stringlist)); NextMsg = Messages; } else { Messages->Next = malloc (sizeof (stringlist)); NextMsg = NextMsg->Next; } NextMsg->Next = NULL; NextMsg->Str = malloc (len+1); NextMsg->len = len; memcpy (NextMsg->Str, buf, len + 1);/// maybe split spam /ham per line? count++; } } while (strcmp(buf, "000")); /// is there a way to filter foreachmessage by a list? /* tag mails as spam or Ham */ /* probably do: dspam_init(ctdl_dspam_dir); dspam_process dspam_addattribute; dspam_destroy*/ // extract DSS_ERROR or DSS_CORPUS from the commandline. error->ham; corpus -> spam? /// todo: send answer listing... } void ctdl_dspam_init(void) { /// libdspam_init("bdb");/* redirect_buffer = malloc(SIZ); CC->redirect_len = 0; CC->redirect_alloc = SIZ; CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, 0); msgtext = CC->redirect_buffer; // don't need? msglen = CC->redirect_len; CC->redirect_buffer = NULL; CC->redirect_len = 0; CC->redirect_alloc = 0; /* Call DSPAM's processor with the message text */ if (dspam_process (CTX, msgtext) != 0) { free(msgtext); syslog(LOG_CRIT, "ERROR: dspam_process failed"); return; } if (CTX->signature == NULL) { syslog(LOG_CRIT,"No signature provided\n"); } else { /* Copy to a safe place */ // TODO: len -> cm_fields? msg->cm_fields[eErrorMsg] = malloc (CTX->signature->length * 2); size_t len = CtdlEncodeBase64(msg->cm_fields[eErrorMsg], CTX->signature->data, CTX->signature->length, 0); if (msg->cm_fields[eErrorMsg][len - 1] == '\n') { msg->cm_fields[eErrorMsg][len - 1] = '\0'; } } free(msgtext); SIG.length = CTX->signature->length; /* Print processing results */ syslog(LOG_DEBUG, "Probability: %2.4f Confidence: %2.4f, Result: %s\n", CTX->probability, CTX->confidence, (CTX->result == DSR_ISSPAM) ? "Spam" : "Innocent"); //// todo: put signature into the citadel message //// todo: save message; destroy message. } int serv_dspam_room(struct ctdlroom *room) { DSPAM_CTX *CTX; /* DSPAM Context */ /* scan for spam; do */ /* probably do: dspam_init; dspam_process dspam_addattribute; dspam_destroy*/ //DSS_NONE //#define DSR_ISSPAM 0x01 //#define DSR_ISINNOCENT 0x02 // dspam_init (cc->username, NULL, ctdl_dspam_home, DSM_PROCESS, // DSF_SIGNATURE | DSF_NOISE); /// todo: if roomname = spam / ham -> learn! if ((room->QRflags & QR_PRIVATE) &&/* Are we sending to a private mailbox? */ (strstr(room->QRname, ".Mail")!=NULL)) { char User[64]; // maybe we should better get our realname here? snprintf(User, 64, "%ld", room->QRroomaide); extract_token(User, room->QRname, 0, '.', sizeof(User)); CTX = dspam_init(User, NULL, ctdl_dspam_dir, DSM_PROCESS, DSF_SIGNATURE | DSF_NOISE); } else return 0;//// /// else -> todo: global user for public rooms etc. if (CTX == NULL) { syslog(LOG_CRIT, "ERROR: dspam_init failed!\n"); return ERROR + INTERNAL_ERROR; } /* Use graham and robinson algorithms, graham's p-values */ CTX->algorithms = DSA_GRAHAM | DSA_BURTON | DSP_GRAHAM; /* Use CHAIN tokenizer */ CTX->tokenizer = DSZ_CHAIN; CtdlForEachMessage(MSGS_GT, 1, NULL, NULL, NULL, dspam_do_msg, (void *) &CTX); return 0; } void serv_dspam_shutdown (void) { libdspam_shutdown (); } #endif /* HAVE_LIBDSPAM */ CTDL_MODULE_INIT(dspam) { return "disabled."; if (!threading) { #ifdef HAVE_LIBDSPAM ctdl_dspam_init(); CtdlRegisterCleanupHook(serv_dspam_shutdown); CtdlRegisterProtoHook(cmd_tspam, "SPAM", "Tag Message as Spam/Ham to learn DSPAM"); CtdlRegisterRoomHook(serv_dspam_room); ///CtdlRegisterSessionHook(perform_dspam_processing, EVT_HOUSE); #else /* HAVE_LIBDSPAM */ syslog(LOG_INFO, "This server is missing libdspam Spam filtering will be disabled.\n"); #endif /* HAVE_LIBDSPAM */ } /* return our module name for the log */ return "dspam"; } citadel-9.01/modules/network/0000755000000000000000000000000012507024051014703 5ustar rootrootcitadel-9.01/modules/network/serv_network.h0000644000000000000000000000263512507024051017612 0ustar rootroot/* * Copyright (c) 2000-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ extern int NetQDebugEnabled; #define DBGLOG(LEVEL) if ((LEVEL != LOG_DEBUG) || (NetQDebugEnabled != 0)) #define QN_syslog(LEVEL, FORMAT, ...) \ DBGLOG(LEVEL) syslog(LEVEL, \ "CC[%d]" FORMAT, \ CCC->cs_pid, __VA_ARGS__) #define QNM_syslog(LEVEL, FORMAT) \ DBGLOG(LEVEL) syslog(LEVEL, \ "CC[%d]" FORMAT, \ CCC->cs_pid) void free_netfilter_list(void); void load_network_filter_list(void); void network_queue_room(struct ctdlroom *, void *); ////void destroy_network_queue_room(void); void network_bounce(struct CtdlMessage *msg, char *reason); int network_usetable(struct CtdlMessage *msg); citadel-9.01/modules/network/netmail.h0000644000000000000000000000014412507024051016504 0ustar rootrootvoid network_deliver_digest(SpoolControl *sc); void network_spool_msg(long msgnum, void *userdata); citadel-9.01/modules/network/serv_network.c0000644000000000000000000004121012507024051017575 0ustar rootroot/* * This module handles shared rooms, inter-Citadel mail, and outbound * mailing list processing. * * Copyright (c) 2000-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ** NOTE ** A word on the S_NETCONFIGS semaphore: * This is a fairly high-level type of critical section. It ensures that no * two threads work on the netconfigs files at the same time. Since we do * so many things inside these, here are the rules: * 1. begin_critical_section(S_NETCONFIGS) *before* begin_ any others. * 2. Do *not* perform any I/O with the client during these sections. * */ /* * Duration of time (in seconds) after which pending list subscribe/unsubscribe * requests that have not been confirmed will be deleted. */ #define EXP 259200 /* three days */ #include "sysdep.h" #include #include #include #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #ifdef HAVE_SYSCALL_H # include #else # if HAVE_SYS_SYSCALL_H # include # endif #endif #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "user_ops.h" #include "database.h" #include "msgbase.h" #include "internet_addressing.h" #include "serv_network.h" #include "clientsocket.h" #include "citadel_dirs.h" #include "threads.h" #include "context.h" #include "ctdl_module.h" #include "netspool.h" #include "netmail.h" int NetQDebugEnabled = 0; struct CitContext networker_spool_CC; /* comes from lookup3.c from libcitadel... */ extern uint32_t hashlittle( const void *key, size_t length, uint32_t initval); typedef struct __roomlists { RoomProcList *rplist; }roomlists; /* * When we do network processing, it's accomplished in two passes; one to * gather a list of rooms and one to actually do them. It's ok that rplist * is global; we have a mutex that keeps it safe. */ struct RoomProcList *rplist = NULL; /* * Check the use table. This is a list of messages which have recently * arrived on the system. It is maintained and queried to prevent the same * message from being entered into the database multiple times if it happens * to arrive multiple times by accident. */ int network_usetable(struct CtdlMessage *msg) { StrBuf *msgid; struct CitContext *CCC = CC; time_t now; /* Bail out if we can't generate a message ID */ if ((msg == NULL) || CM_IsEmpty(msg, emessageId)) { return(0); } /* Generate the message ID */ msgid = NewStrBufPlain(CM_KEY(msg, emessageId)); if (haschar(ChrPtr(msgid), '@') == 0) { StrBufAppendBufPlain(msgid, HKEY("@"), 0); if (!CM_IsEmpty(msg, eNodeName)) { StrBufAppendBufPlain(msgid, CM_KEY(msg, eNodeName), 0); } else { FreeStrBuf(&msgid); return(0); } } now = time(NULL); if (CheckIfAlreadySeen("Networker Import", msgid, now, 0, eCheckUpdate, CCC->cs_pid, 0) != 0) { FreeStrBuf(&msgid); return(1); } FreeStrBuf(&msgid); return(0); } /* * Send the *entire* contents of the current room to one specific network node, * ignoring anything we know about which messages have already undergone * network processing. This can be used to bring a new node into sync. */ int network_sync_to(char *target_node, long len) { struct CitContext *CCC = CC; OneRoomNetCfg OneRNCFG; OneRoomNetCfg *pRNCFG; const RoomNetCfgLine *pCfgLine; SpoolControl sc; int num_spooled = 0; /* Grab the configuration line we're looking for */ begin_critical_section(S_NETCONFIGS); pRNCFG = CtdlGetNetCfgForRoom(CCC->room.QRnumber); if ((pRNCFG == NULL) || (pRNCFG->NetConfigs[ignet_push_share] == NULL)) { return -1; } pCfgLine = pRNCFG->NetConfigs[ignet_push_share]; while (pCfgLine != NULL) { if (!strcmp(ChrPtr(pCfgLine->Value[0]), target_node)) break; pCfgLine = pCfgLine->next; } if (pCfgLine == NULL) { return -1; } memset(&sc, 0, sizeof(SpoolControl)); memset(&OneRNCFG, 0, sizeof(OneRoomNetCfg)); sc.RNCfg = &OneRNCFG; sc.RNCfg->NetConfigs[ignet_push_share] = DuplicateOneGenericCfgLine(pCfgLine); sc.Users[ignet_push_share] = NewStrBufPlain(NULL, StrLength(pCfgLine->Value[0]) + StrLength(pCfgLine->Value[1]) + 10); StrBufAppendBuf(sc.Users[ignet_push_share], pCfgLine->Value[0], 0); StrBufAppendBufPlain(sc.Users[ignet_push_share], HKEY(","), 0); StrBufAppendBuf(sc.Users[ignet_push_share], pCfgLine->Value[1], 0); CalcListID(&sc); end_critical_section(S_NETCONFIGS); sc.working_ignetcfg = CtdlLoadIgNetCfg(); sc.the_netmap = CtdlReadNetworkMap(); /* Send ALL messages */ num_spooled = CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, network_spool_msg, &sc); /* Concise cleanup because we know there's only one node in the sc */ DeleteGenericCfgLine(NULL/*TODO*/, &sc.RNCfg->NetConfigs[ignet_push_share]); DeleteHash(&sc.working_ignetcfg); DeleteHash(&sc.the_netmap); free_spoolcontrol_struct_members(&sc); QN_syslog(LOG_NOTICE, "Synchronized %d messages to <%s>\n", num_spooled, target_node); return(num_spooled); } /* * Implements the NSYN command */ void cmd_nsyn(char *argbuf) { int num_spooled; long len; char target_node[256]; if (CtdlAccessCheck(ac_aide)) return; len = extract_token(target_node, argbuf, 0, '|', sizeof target_node); num_spooled = network_sync_to(target_node, len); if (num_spooled >= 0) { cprintf("%d Spooled %d messages.\n", CIT_OK, num_spooled); } else { cprintf("%d No such room/node share exists.\n", ERROR + ROOM_NOT_FOUND); } } RoomProcList *CreateRoomProcListEntry(struct ctdlroom *qrbuf, OneRoomNetCfg *OneRNCFG) { int i; struct RoomProcList *ptr; ptr = (struct RoomProcList *) malloc(sizeof (struct RoomProcList)); if (ptr == NULL) return NULL; ptr->namelen = strlen(qrbuf->QRname); if (ptr->namelen > ROOMNAMELEN) ptr->namelen = ROOMNAMELEN - 1; memcpy (ptr->name, qrbuf->QRname, ptr->namelen); ptr->name[ptr->namelen] = '\0'; ptr->QRNum = qrbuf->QRnumber; for (i = 0; i < ptr->namelen; i++) { ptr->lcname[i] = tolower(ptr->name[i]); } ptr->lcname[ptr->namelen] = '\0'; ptr->key = hashlittle(ptr->lcname, ptr->namelen, 9872345); ptr->lastsent = OneRNCFG->lastsent; ptr->OneRNCfg = OneRNCFG; return ptr; } /* * Batch up and send all outbound traffic from the current room */ void network_queue_interesting_rooms(struct ctdlroom *qrbuf, void *data, OneRoomNetCfg *OneRNCfg) { struct RoomProcList *ptr; roomlists *RP = (roomlists*) data; if (!HaveSpoolConfig(OneRNCfg)) return; ptr = CreateRoomProcListEntry(qrbuf, OneRNCfg); if (ptr != NULL) { ptr->next = RP->rplist; RP->rplist = ptr; } } /* * Batch up and send all outbound traffic from the current room */ int network_room_handler (struct ctdlroom *qrbuf) { struct RoomProcList *ptr; OneRoomNetCfg* RNCfg; if (qrbuf->QRdefaultview == VIEW_QUEUE) return 1; RNCfg = CtdlGetNetCfgForRoom(qrbuf->QRnumber); if (RNCfg == NULL) return 1; if (!HaveSpoolConfig(RNCfg)) return 1; ptr = CreateRoomProcListEntry(qrbuf, RNCfg); if (ptr == NULL) return 1; ptr->OneRNCfg = NULL; begin_critical_section(S_RPLIST); ptr->next = rplist; rplist = ptr; end_critical_section(S_RPLIST); return 1; } void destroy_network_queue_room(RoomProcList *rplist) { struct RoomProcList *cur, *p; cur = rplist; while (cur != NULL) { p = cur->next; free (cur); cur = p; } } void destroy_network_queue_room_locked (void) { begin_critical_section(S_RPLIST); destroy_network_queue_room(rplist); end_critical_section(S_RPLIST); } /* * Bounce a message back to the sender */ void network_bounce(struct CtdlMessage *msg, char *reason) { struct CitContext *CCC = CC; char buf[SIZ]; char bouncesource[SIZ]; char recipient[SIZ]; recptypes *valid = NULL; char force_room[ROOMNAMELEN]; static int serialnum = 0; long len; QNM_syslog(LOG_DEBUG, "entering network_bounce()\n"); if (msg == NULL) return; snprintf(bouncesource, sizeof bouncesource, "%s@%s", BOUNCESOURCE, config.c_nodename); /* * Give it a fresh message ID */ len = snprintf(buf, sizeof(buf), "%ld.%04lx.%04x@%s", (long)time(NULL), (long)getpid(), ++serialnum, config.c_fqdn); CM_SetField(msg, emessageId, buf, len); /* * FIXME ... right now we're just sending a bounce; we really want to * include the text of the bounced message. */ CM_SetField(msg, eMesageText, reason, strlen(reason)); msg->cm_format_type = 0; /* * Turn the message around */ CM_FlushField(msg, eRecipient); CM_FlushField(msg, eDestination); len = snprintf(recipient, sizeof(recipient), "%s@%s", msg->cm_fields[eAuthor], msg->cm_fields[eNodeName]); CM_SetField(msg, eAuthor, HKEY(BOUNCESOURCE)); CM_SetField(msg, eNodeName, CFG_KEY(c_nodename)); CM_SetField(msg, eMsgSubject, HKEY("Delivery Status Notification (Failure)")); Netmap_AddMe(msg, HKEY("unknown_user")); /* Now submit the message */ valid = validate_recipients(recipient, NULL, 0); if (valid != NULL) if (valid->num_error != 0) { free_recipients(valid); valid = NULL; } if ( (valid == NULL) || (!strcasecmp(recipient, bouncesource)) ) { strcpy(force_room, config.c_aideroom); } else { strcpy(force_room, ""); } if ( (valid == NULL) && IsEmptyStr(force_room) ) { strcpy(force_room, config.c_aideroom); } CtdlSubmitMsg(msg, valid, force_room, 0); /* Clean up */ if (valid != NULL) free_recipients(valid); CM_Free(msg); QNM_syslog(LOG_DEBUG, "leaving network_bounce()\n"); } /* * network_do_queue() * * Run through the rooms doing various types of network stuff. */ void network_do_queue(void) { struct CitContext *CCC = CC; static time_t last_run = 0L; int full_processing = 1; HashList *working_ignetcfg; HashList *the_netmap = NULL; int netmap_changed = 0; roomlists RL; SpoolControl *sc = NULL; SpoolControl *pSC; /* * Run the full set of processing tasks no more frequently * than once every n seconds */ if ( (time(NULL) - last_run) < config.c_net_freq ) { full_processing = 0; syslog(LOG_DEBUG, "Network full processing in %ld seconds.\n", config.c_net_freq - (time(NULL)- last_run) ); } become_session(&networker_spool_CC); begin_critical_section(S_RPLIST); RL.rplist = rplist; rplist = NULL; end_critical_section(S_RPLIST); ///TODO hm, check whether we have a config at all here? /* Load the IGnet Configuration into memory */ working_ignetcfg = CtdlLoadIgNetCfg(); /* * Load the network map and filter list into memory. */ if (!server_shutting_down) the_netmap = CtdlReadNetworkMap(); #if 0 /* filterlist isn't supported anymore if (!server_shutting_down) load_network_filter_list(); */ #endif /* * Go ahead and run the queue */ if (full_processing && !server_shutting_down) { QNM_syslog(LOG_DEBUG, "network: loading outbound queue"); CtdlForEachNetCfgRoom(network_queue_interesting_rooms, &RL, maxRoomNetCfg); } if ((RL.rplist != NULL) && (!server_shutting_down)) { RoomProcList *ptr, *cmp; ptr = RL.rplist; QNM_syslog(LOG_DEBUG, "network: running outbound queue"); while (ptr != NULL && !server_shutting_down) { cmp = ptr->next; /* filter duplicates from the list... */ while (cmp != NULL) { if ((cmp->namelen > 0) && (cmp->key == ptr->key) && (cmp->namelen == ptr->namelen) && (strcmp(cmp->lcname, ptr->lcname) == 0)) { cmp->namelen = 0; } cmp = cmp->next; } if (ptr->namelen > 0) { InspectQueuedRoom(&sc, ptr, working_ignetcfg, the_netmap); } ptr = ptr->next; } } pSC = sc; while (pSC != NULL) { network_spoolout_room(pSC); pSC = pSC->next; } pSC = sc; while (pSC != NULL) { sc = pSC->next; free_spoolcontrol_struct(&pSC); pSC = sc; } /* If there is anything in the inbound queue, process it */ if (!server_shutting_down) { network_do_spoolin(working_ignetcfg, the_netmap, &netmap_changed); } /* Free the filter list in memory */ free_netfilter_list(); /* Save the network map back to disk */ if (netmap_changed) { StrBuf *MapStr = CtdlSerializeNetworkMap(the_netmap); char *pMapStr = SmashStrBuf(&MapStr); CtdlPutSysConfig(IGNETMAP, pMapStr); free(pMapStr); } /* combine singe message files into one spool entry per remote node. */ network_consolidate_spoolout(working_ignetcfg, the_netmap); /* shut down. */ DeleteHash(&the_netmap); DeleteHash(&working_ignetcfg); QNM_syslog(LOG_DEBUG, "network: queue run completed"); if (full_processing) { last_run = time(NULL); } destroy_network_queue_room(RL.rplist); SaveChangedConfigs(); } void network_logout_hook(void) { CitContext *CCC = MyContext(); /* * If we were talking to a network node, we're not anymore... */ if (!IsEmptyStr(CCC->net_node)) { CtdlNetworkTalkingTo(CCC->net_node, strlen(CCC->net_node), NTT_REMOVE); CCC->net_node[0] = '\0'; } } void network_cleanup_function(void) { struct CitContext *CCC = CC; if (!IsEmptyStr(CCC->net_node)) { CtdlNetworkTalkingTo(CCC->net_node, strlen(CCC->net_node), NTT_REMOVE); CCC->net_node[0] = '\0'; } } int ignet_aftersave(struct CtdlMessage *msg, recptypes *recps) /* recipients (if mail) */ { /* For IGnet mail, we have to save a new copy into the spooler for * each recipient, with the R and D fields set to the recipient and * destination-node. This has two ugly side effects: all other * recipients end up being unlisted in this recipient's copy of the * message, and it has to deliver multiple messages to the same * node. We'll revisit this again in a year or so when everyone has * a network spool receiver that can handle the new style messages. */ if ((recps != NULL) && (recps->num_ignet > 0)) { char *recipient; int rv = 0; struct ser_ret smr; FILE *network_fp = NULL; char submit_filename[128]; static int seqnum = 1; int i; char *hold_R, *hold_D, *RBuf, *DBuf; long hrlen, hdlen, rblen, dblen, count, rlen; CitContext *CCC = MyContext(); CM_GetAsField(msg, eRecipient, &hold_R, &hrlen);; CM_GetAsField(msg, eDestination, &hold_D, &hdlen);; count = num_tokens(recps->recp_ignet, '|'); rlen = strlen(recps->recp_ignet); recipient = malloc(rlen + 1); RBuf = malloc(rlen + 1); DBuf = malloc(rlen + 1); for (i=0; irecp_ignet, i, '|', rlen + 1); rblen = extract_token(RBuf, recipient, 0, '@', rlen + 1); dblen = extract_token(DBuf, recipient, 1, '@', rlen + 1); CM_SetAsField(msg, eRecipient, &RBuf, rblen);; CM_SetAsField(msg, eDestination, &DBuf, dblen);; CtdlSerializeMessage(&smr, msg); if (smr.len > 0) { snprintf(submit_filename, sizeof submit_filename, "%s/netmail.%04lx.%04x.%04x", ctdl_netin_dir, (long) getpid(), CCC->cs_pid, ++seqnum); network_fp = fopen(submit_filename, "wb+"); if (network_fp != NULL) { rv = fwrite(smr.ser, smr.len, 1, network_fp); if (rv == -1) { MSG_syslog(LOG_EMERG, "CtdlSubmitMsg(): Couldn't write network spool file: %s\n", strerror(errno)); } fclose(network_fp); } free(smr.ser); } CM_GetAsField(msg, eRecipient, &RBuf, &rblen);; CM_GetAsField(msg, eDestination, &DBuf, &dblen);; } free(RBuf); free(DBuf); free(recipient); CM_SetAsField(msg, eRecipient, &hold_R, hrlen); CM_SetAsField(msg, eDestination, &hold_D, hdlen); return 1; } return 0; } /* * Module entry point */ void SetNetQDebugEnabled(const int n) { NetQDebugEnabled = n; } CTDL_MODULE_INIT(network) { if (!threading) { CtdlRegisterMessageHook(ignet_aftersave, EVT_AFTERSAVE); CtdlFillSystemContext(&networker_spool_CC, "CitNetSpool"); CtdlRegisterDebugFlagHook(HKEY("networkqueue"), SetNetQDebugEnabled, &NetQDebugEnabled); CtdlRegisterSessionHook(network_cleanup_function, EVT_STOP, PRIO_STOP + 30); CtdlRegisterSessionHook(network_logout_hook, EVT_LOGOUT, PRIO_LOGOUT + 10); CtdlRegisterProtoHook(cmd_nsyn, "NSYN", "Synchronize room to node"); CtdlRegisterRoomHook(network_room_handler); CtdlRegisterCleanupHook(destroy_network_queue_room_locked); CtdlRegisterSessionHook(network_do_queue, EVT_TIMER, PRIO_QUEUE + 10); } return "network"; } citadel-9.01/modules/network/netspool.h0000644000000000000000000000472612507024051016730 0ustar rootroot/* * This module handles shared rooms, inter-Citadel mail, and outbound * mailing list processing. * * Copyright (c) 2000-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * ** NOTE ** A word on the S_NETCONFIGS semaphore: * This is a fairly high-level type of critical section. It ensures that no * two threads work on the netconfigs files at the same time. Since we do * so many things inside these, here are the rules: * 1. begin_critical_section(S_NETCONFIGS) *before* begin_ any others. * 2. Do *not* perform any I/O with the client during these sections. * */ typedef struct SpoolControl SpoolControl; struct SpoolControl { OneRoomNetCfg *RNCfg; struct ctdlroom room; StrBuf *Users[maxRoomNetCfg]; StrBuf *RoomInfo; StrBuf *ListID; FILE *digestfp; int haveDigest; int num_msgs_spooled; long lastsent; HashList *working_ignetcfg; HashList *the_netmap; SpoolControl *next; }; void network_spoolout_room(SpoolControl *sc); void InspectQueuedRoom(SpoolControl **pSC, RoomProcList *room_to_spool, HashList *working_ignetcfg, HashList *the_netmap); int HaveSpoolConfig(OneRoomNetCfg* RNCfg); void Netmap_AddMe(struct CtdlMessage *msg, const char *defl, long defllen); void network_do_spoolin(HashList *working_ignetcfg, HashList *the_netmap, int *netmap_changed); void network_consolidate_spoolout(HashList *working_ignetcfg, HashList *the_netmap); void free_spoolcontrol_struct(SpoolControl **scc); void free_spoolcontrol_struct_members(SpoolControl *scc); int writenfree_spoolcontrol_file(SpoolControl **scc, char *filename); int read_spoolcontrol_file(SpoolControl **scc, char *filename); void aggregate_recipients(StrBuf **recps, RoomNetCfg Which, OneRoomNetCfg *OneRNCfg, long nSegments); void CalcListID(SpoolControl *sc); citadel-9.01/modules/network/serv_netmail.c0000644000000000000000000003561712507024051017553 0ustar rootroot/* * This module handles shared rooms, inter-Citadel mail, and outbound * mailing list processing. * * Copyright (c) 2000-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ** NOTE ** A word on the S_NETCONFIGS semaphore: * This is a fairly high-level type of critical section. It ensures that no * two threads work on the netconfigs files at the same time. Since we do * so many things inside these, here are the rules: * 1. begin_critical_section(S_NETCONFIGS) *before* begin_ any others. * 2. Do *not* perform any I/O with the client during these sections. * */ /* * Duration of time (in seconds) after which pending list subscribe/unsubscribe * requests that have not been confirmed will be deleted. */ #define EXP 259200 /* three days */ #include "sysdep.h" #include #include #include #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #ifdef HAVE_SYSCALL_H # include #else # if HAVE_SYS_SYSCALL_H # include # endif #endif #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "user_ops.h" #include "database.h" #include "msgbase.h" #include "internet_addressing.h" #include "serv_network.h" #include "clientsocket.h" #include "citadel_dirs.h" #include "threads.h" #include "context.h" #include "ctdl_module.h" #include "netspool.h" #include "netmail.h" void network_deliver_list(struct CtdlMessage *msg, SpoolControl *sc, const char *RoomName); void aggregate_recipients(StrBuf **recps, RoomNetCfg Which, OneRoomNetCfg *OneRNCfg, long nSegments) { int i; size_t recps_len = 0; RoomNetCfgLine *nptr; struct CitContext *CCC = CC; *recps = NULL; /* * Figure out how big a buffer we need to allocate */ for (nptr = OneRNCfg->NetConfigs[Which]; nptr != NULL; nptr = nptr->next) { recps_len = recps_len + StrLength(nptr->Value[0]) + 2; } /* Nothing todo... */ if (recps_len == 0) return; *recps = NewStrBufPlain(NULL, recps_len); if (*recps == NULL) { QN_syslog(LOG_EMERG, "Cannot allocate %ld bytes for recps...\n", (long)recps_len); abort(); } /* Each recipient */ for (nptr = OneRNCfg->NetConfigs[Which]; nptr != NULL; nptr = nptr->next) { if (nptr != OneRNCfg->NetConfigs[Which]) { for (i = 0; i < nSegments; i++) StrBufAppendBufPlain(*recps, HKEY(","), i); } StrBufAppendBuf(*recps, nptr->Value[0], 0); if (Which == ignet_push_share) { StrBufAppendBufPlain(*recps, HKEY(","), 0); StrBufAppendBuf(*recps, nptr->Value[1], 0); } } } static void ListCalculateSubject(struct CtdlMessage *msg) { struct CitContext *CCC = CC; StrBuf *Subject, *FlatSubject; int rlen; char *pCh; if (CM_IsEmpty(msg, eMsgSubject)) { Subject = NewStrBufPlain(HKEY("(no subject)")); } else { Subject = NewStrBufPlain(CM_KEY(msg, eMsgSubject)); } FlatSubject = NewStrBufPlain(NULL, StrLength(Subject)); StrBuf_RFC822_to_Utf8(FlatSubject, Subject, NULL, NULL); rlen = strlen(CCC->room.QRname); pCh = strstr(ChrPtr(FlatSubject), CCC->room.QRname); if ((pCh == NULL) || (*(pCh + rlen) != ']') || (pCh == ChrPtr(FlatSubject)) || (*(pCh - 1) != '[') ) { StrBuf *tmp; StrBufPlain(Subject, HKEY("[")); StrBufAppendBufPlain(Subject, CCC->room.QRname, rlen, 0); StrBufAppendBufPlain(Subject, HKEY("] "), 0); StrBufAppendBuf(Subject, FlatSubject, 0); /* so we can free the right one swap them */ tmp = Subject; Subject = FlatSubject; FlatSubject = tmp; StrBufRFC2047encode(&Subject, FlatSubject); } CM_SetAsFieldSB(msg, eMsgSubject, &Subject); FreeStrBuf(&FlatSubject); } /* * Deliver digest messages */ void network_deliver_digest(SpoolControl *sc) { struct CitContext *CCC = CC; long len; char buf[SIZ]; char *pbuf; struct CtdlMessage *msg = NULL; long msglen; recptypes *valid; char bounce_to[256]; if (sc->Users[digestrecp] == NULL) return; msg = malloc(sizeof(struct CtdlMessage)); memset(msg, 0, sizeof(struct CtdlMessage)); msg->cm_magic = CTDLMESSAGE_MAGIC; msg->cm_format_type = FMT_RFC822; msg->cm_anon_type = MES_NORMAL; CM_SetFieldLONG(msg, eTimestamp, time(NULL)); CM_SetField(msg, eAuthor, CCC->room.QRname, strlen(CCC->room.QRname)); len = snprintf(buf, sizeof buf, "[%s]", CCC->room.QRname); CM_SetField(msg, eMsgSubject, buf, len); CM_SetField(msg, erFc822Addr, SKEY(sc->Users[roommailalias])); CM_SetField(msg, eRecipient, SKEY(sc->Users[roommailalias])); /* Set the 'List-ID' header */ CM_SetField(msg, eListID, SKEY(sc->ListID)); /* * Go fetch the contents of the digest */ fseek(sc->digestfp, 0L, SEEK_END); msglen = ftell(sc->digestfp); pbuf = malloc(msglen + 1); fseek(sc->digestfp, 0L, SEEK_SET); fread(pbuf, (size_t)msglen, 1, sc->digestfp); pbuf[msglen] = '\0'; CM_SetAsField(msg, eMesageText, &pbuf, msglen); /* Now generate the delivery instructions */ /* Where do we want bounces and other noise to be heard? * Surely not the list members! */ snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", config.c_fqdn); /* Now submit the message */ valid = validate_recipients(ChrPtr(sc->Users[digestrecp]), NULL, 0); if (valid != NULL) { valid->bounce_to = strdup(bounce_to); valid->envelope_from = strdup(bounce_to); CtdlSubmitMsg(msg, valid, NULL, 0); } CM_Free(msg); free_recipients(valid); } void network_process_digest(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send) { struct CtdlMessage *msg = NULL; if (sc->Users[digestrecp] == NULL) return; /* If there are digest recipients, we have to build a digest */ if (sc->digestfp == NULL) { sc->digestfp = create_digest_file(&sc->room, 1); if (sc->digestfp == NULL) return; sc->haveDigest = ftell(sc->digestfp) > 0; if (!sc->haveDigest) { fprintf(sc->digestfp, "Content-type: text/plain\n\n"); } sc->haveDigest = 1; } msg = CM_Duplicate(omsg); if (msg != NULL) { sc->haveDigest = 1; fprintf(sc->digestfp, " -----------------------------------" "------------------------------------" "-------\n"); fprintf(sc->digestfp, "From: "); if (!CM_IsEmpty(msg, eAuthor)) { fprintf(sc->digestfp, "%s ", msg->cm_fields[eAuthor]); } if (!CM_IsEmpty(msg, erFc822Addr)) { fprintf(sc->digestfp, "<%s> ", msg->cm_fields[erFc822Addr]); } else if (!CM_IsEmpty(msg, eNodeName)) { fprintf(sc->digestfp, "@%s ", msg->cm_fields[eNodeName]); } fprintf(sc->digestfp, "\n"); if (!CM_IsEmpty(msg, eMsgSubject)) { fprintf(sc->digestfp, "Subject: %s\n", msg->cm_fields[eMsgSubject]); } CC->redirect_buffer = NewStrBufPlain(NULL, SIZ); safestrncpy(CC->preferred_formats, "text/plain", sizeof CC->preferred_formats); CtdlOutputPreLoadedMsg(msg, MT_CITADEL, HEADERS_NONE, 0, 0, 0); StrBufTrim(CC->redirect_buffer); fwrite(HKEY("\n"), 1, sc->digestfp); fwrite(SKEY(CC->redirect_buffer), 1, sc->digestfp); fwrite(HKEY("\n"), 1, sc->digestfp); FreeStrBuf(&CC->redirect_buffer); sc->num_msgs_spooled += 1; CM_Free(msg); } } void network_process_list(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send) { struct CtdlMessage *msg = NULL; /* * Process mailing list recipients */ if (sc->Users[listrecp] == NULL) return; /* create our own copy of the message. * We're going to need to modify it * in order to insert the [list name] in it, etc. */ msg = CM_Duplicate(omsg); CM_SetField(msg, eReplyTo, SKEY(sc->Users[roommailalias])); /* if there is no other recipient, Set the recipient * of the list message to the email address of the * room itself. */ if (CM_IsEmpty(msg, eRecipient)) { CM_SetField(msg, eRecipient, SKEY(sc->Users[roommailalias])); } /* Set the 'List-ID' header */ CM_SetField(msg, eListID, SKEY(sc->ListID)); /* Prepend "[List name]" to the subject */ ListCalculateSubject(msg); /* Handle delivery */ network_deliver_list(msg, sc, CC->room.QRname); CM_Free(msg); } /* * Deliver list messages to everyone on the list ... efficiently */ void network_deliver_list(struct CtdlMessage *msg, SpoolControl *sc, const char *RoomName) { recptypes *valid; char bounce_to[256]; /* Don't do this if there were no recipients! */ if (sc->Users[listrecp] == NULL) return; /* Now generate the delivery instructions */ /* Where do we want bounces and other noise to be heard? * Surely not the list members! */ snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", config.c_fqdn); /* Now submit the message */ valid = validate_recipients(ChrPtr(sc->Users[listrecp]), NULL, 0); if (valid != NULL) { valid->bounce_to = strdup(bounce_to); valid->envelope_from = strdup(bounce_to); valid->sending_room = strdup(RoomName); CtdlSubmitMsg(msg, valid, NULL, 0); free_recipients(valid); } /* Do not call CM_Free(msg) here; the caller will free it. */ } void network_process_participate(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send) { struct CtdlMessage *msg = NULL; int ok_to_participate = 0; StrBuf *Buf = NULL; recptypes *valid; /* * Process client-side list participations for this room */ if (sc->Users[participate] == NULL) return; msg = CM_Duplicate(omsg); /* Only send messages which originated on our own * Citadel network, otherwise we'll end up sending the * remote mailing list's messages back to it, which * is rude... */ ok_to_participate = 0; if (!CM_IsEmpty(msg, eNodeName)) { if (!strcasecmp(msg->cm_fields[eNodeName], config.c_nodename)) { ok_to_participate = 1; } Buf = NewStrBufPlain(CM_KEY(msg, eNodeName)); if (CtdlIsValidNode(NULL, NULL, Buf, sc->working_ignetcfg, sc->the_netmap) == 0) { ok_to_participate = 1; } } if (ok_to_participate) { /* Replace the Internet email address of the * actual author with the email address of the * room itself, so the remote listserv doesn't * reject us. */ CM_SetField(msg, erFc822Addr, SKEY(sc->Users[roommailalias])); valid = validate_recipients(ChrPtr(sc->Users[participate]) , NULL, 0); CM_SetField(msg, eRecipient, SKEY(sc->Users[roommailalias])); CtdlSubmitMsg(msg, valid, "", 0); free_recipients(valid); } FreeStrBuf(&Buf); CM_Free(msg); } void network_process_ignetpush(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send) { StrBuf *Recipient; StrBuf *RemoteRoom; const char *Pos = NULL; struct CtdlMessage *msg = NULL; struct CitContext *CCC = CC; struct ser_ret sermsg; char buf[SIZ]; char filename[PATH_MAX]; FILE *fp; StrBuf *Buf = NULL; int i; int bang = 0; int send = 1; if (sc->Users[ignet_push_share] == NULL) return; /* * Process IGnet push shares */ msg = CM_Duplicate(omsg); /* Prepend our node name to the Path field whenever * sending a message to another IGnet node */ Netmap_AddMe(msg, HKEY("username")); /* * Determine if this message is set to be deleted * after sending out on the network */ if (!CM_IsEmpty(msg, eSpecialField)) { if (!strcasecmp(msg->cm_fields[eSpecialField], "CANCEL")) { *delete_after_send = 1; } } /* Now send it to every node */ Recipient = NewStrBufPlain(NULL, StrLength(sc->Users[ignet_push_share])); RemoteRoom = NewStrBufPlain(NULL, StrLength(sc->Users[ignet_push_share])); while ((Pos != StrBufNOTNULL) && StrBufExtract_NextToken(Recipient, sc->Users[ignet_push_share], &Pos, ',')) { StrBufExtract_NextToken(RemoteRoom, sc->Users[ignet_push_share], &Pos, ','); send = 1; NewStrBufDupAppendFlush(&Buf, Recipient, NULL, 1); /* Check for valid node name */ if (CtdlIsValidNode(NULL, NULL, Buf, sc->working_ignetcfg, sc->the_netmap) != 0) { QN_syslog(LOG_ERR, "Invalid node <%s>\n", ChrPtr(Recipient)); send = 0; } /* Check for split horizon */ QN_syslog(LOG_DEBUG, "Path is %s\n", msg->cm_fields[eMessagePath]); bang = num_tokens(msg->cm_fields[eMessagePath], '!'); if (bang > 1) { for (i=0; i<(bang-1); ++i) { extract_token(buf, msg->cm_fields[eMessagePath], i, '!', sizeof buf); QN_syslog(LOG_DEBUG, "Compare <%s> to <%s>\n", buf, ChrPtr(Recipient)) ; if (!strcasecmp(buf, ChrPtr(Recipient))) { send = 0; break; } } QN_syslog(LOG_INFO, " %sSending to %s\n", (send)?"":"Not ", ChrPtr(Recipient)); } /* Send the message */ if (send == 1) { /* * Force the message to appear in the correct * room on the far end by setting the C field * correctly */ if (StrLength(RemoteRoom) > 0) { CM_SetField(msg, eRemoteRoom, SKEY(RemoteRoom)); } else { CM_SetField(msg, eRemoteRoom, CCC->room.QRname, strlen(CCC->room.QRname)); } /* serialize it for transmission */ CtdlSerializeMessage(&sermsg, msg); if (sermsg.len > 0) { /* write it to a spool file */ snprintf(filename, sizeof(filename), "%s/%s@%lx%x", ctdl_netout_dir, ChrPtr(Recipient), time(NULL), rand() ); QN_syslog(LOG_DEBUG, "Appending to %s\n", filename); fp = fopen(filename, "ab"); if (fp != NULL) { fwrite(sermsg.ser, sermsg.len, 1, fp); fclose(fp); } else { QN_syslog(LOG_ERR, "%s: %s\n", filename, strerror(errno)); } /* free the serialized version */ free(sermsg.ser); } } } FreeStrBuf(&Buf); FreeStrBuf(&Recipient); FreeStrBuf(&RemoteRoom); CM_Free(msg); } /* * Spools out one message from the list. */ void network_spool_msg(long msgnum, void *userdata) { struct CitContext *CCC = CC; struct CtdlMessage *msg = NULL; long delete_after_send = 0; /* Set to 1 to delete after spooling */ SpoolControl *sc; sc = (SpoolControl *)userdata; msg = CtdlFetchMessage(msgnum, 1); if (msg == NULL) { QN_syslog(LOG_ERR, "failed to load Message <%ld> from disk\n", msgnum); return; } network_process_list(sc, msg, &delete_after_send); network_process_digest(sc, msg, &delete_after_send); network_process_participate(sc, msg, &delete_after_send); network_process_ignetpush(sc, msg, &delete_after_send); CM_Free(msg); /* update lastsent */ sc->lastsent = msgnum; /* Delete this message if delete-after-send is set */ if (delete_after_send) { CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, ""); } } citadel-9.01/modules/network/serv_netfilter.c0000644000000000000000000001027212507024051020104 0ustar rootroot/* * A server-side module for Citadel designed to filter idiots off the network. * * Copyright (c) 2002-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * */ #include "sysdep.h" #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "control.h" #include "user_ops.h" #include "database.h" #include "msgbase.h" #include "ctdl_module.h" typedef struct FilterList FilterList; struct FilterList { FilterList *next; char fl_user[SIZ]; char fl_room[SIZ]; char fl_node[SIZ]; }; struct FilterList *filterlist = NULL; /* * Keep track of what messages to reject */ FilterList *load_filter_list(void) { char *serialized_list = NULL; int i; char buf[SIZ]; FilterList *newlist = NULL; FilterList *nptr; serialized_list = CtdlGetSysConfig(FILTERLIST); if (serialized_list == NULL) return(NULL); /* if null, no entries */ /* Use the string tokenizer to grab one line at a time */ for (i=0; ifl_user, buf, 0, '|', sizeof nptr->fl_user); striplt(nptr->fl_user); extract_token(nptr->fl_room, buf, 1, '|', sizeof nptr->fl_room); striplt(nptr->fl_room); extract_token(nptr->fl_node, buf, 2, '|', sizeof nptr->fl_node); striplt(nptr->fl_node); /* Cowardly refuse to add an any/any/any entry that would * end up filtering every single message. */ if (IsEmptyStr(nptr->fl_user) && IsEmptyStr(nptr->fl_room) && IsEmptyStr(nptr->fl_node)) { free(nptr); } else { nptr->next = newlist; newlist = nptr; } } free(serialized_list); return newlist; } void free_filter_list(FilterList *fl) { if (fl == NULL) return; free_filter_list(fl->next); free(fl); } void free_netfilter_list(void) { free_filter_list(filterlist); filterlist = NULL; } void load_network_filter_list(void) { filterlist = load_filter_list(); } /* * This handler detects whether an incoming network message is from some * moron user who the site operator has elected to filter out. If a match * is found, the message is rejected. */ int filter_the_idiots(struct CtdlMessage *msg, char *target_room) { FilterList *fptr; int zap_user = 0; int zap_room = 0; int zap_node = 0; if ( (msg == NULL) || (filterlist == NULL) ) { return(0); } for (fptr = filterlist; fptr != NULL; fptr = fptr->next) { zap_user = 0; zap_room = 0; zap_node = 0; if (!CM_IsEmpty(msg, eAuthor)) { if ( (!strcasecmp(msg->cm_fields[eAuthor], fptr->fl_user)) || (fptr->fl_user[0] == 0) ) { zap_user = 1; } } if (!CM_IsEmpty(msg, eRemoteRoom)) { if ( (!strcasecmp(msg->cm_fields[eRemoteRoom], fptr->fl_room)) || (fptr->fl_room[0] == 0) ) { zap_room = 1; } } if (!CM_IsEmpty(msg, eOriginalRoom)) { if ( (!strcasecmp(msg->cm_fields[eOriginalRoom], fptr->fl_room)) || (fptr->fl_room[0] == 0) ) { zap_room = 1; } } if (!CM_IsEmpty(msg, eNodeName)) { if ( (!strcasecmp(msg->cm_fields[eNodeName], fptr->fl_node)) || (fptr->fl_node[0] == 0) ) { zap_node = 1; } } if (zap_user + zap_room + zap_node == 3) return(1); } return(0); } CTDL_MODULE_INIT(netfilter) { if (!threading) { /* currently unsupported. CtdlRegisterNetprocHook(filter_the_idiots); */ } /* return our module name for the log */ return "netfilter"; } citadel-9.01/modules/network/serv_netspool.c0000644000000000000000000007005012507024051017753 0ustar rootroot/* * This module handles shared rooms, inter-Citadel mail, and outbound * mailing list processing. * * Copyright (c) 2000-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ** NOTE ** A word on the S_NETCONFIGS semaphore: * This is a fairly high-level type of critical section. It ensures that no * two threads work on the netconfigs files at the same time. Since we do * so many things inside these, here are the rules: * 1. begin_critical_section(S_NETCONFIGS) *before* begin_ any others. * 2. Do *not* perform any I/O with the client during these sections. * */ /* * Duration of time (in seconds) after which pending list subscribe/unsubscribe * requests that have not been confirmed will be deleted. */ #define EXP 259200 /* three days */ #include "sysdep.h" #include #include #include #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #ifdef HAVE_SYSCALL_H # include #else # if HAVE_SYS_SYSCALL_H # include # endif #endif #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "user_ops.h" #include "database.h" #include "msgbase.h" #include "internet_addressing.h" #include "serv_network.h" #include "clientsocket.h" #include "citadel_dirs.h" #include "threads.h" #include "context.h" #include "ctdl_module.h" #include "netspool.h" #include "netmail.h" #ifndef DT_UNKNOWN #define DT_UNKNOWN 0 #define DT_DIR 4 #define DT_REG 8 #define DT_LNK 10 #define IFTODT(mode) (((mode) & 0170000) >> 12) #define DTTOIF(dirtype) ((dirtype) << 12) #endif void ParseLastSent(const CfgLineType *ThisOne, StrBuf *Line, const char *LinePos, OneRoomNetCfg *OneRNCFG) { RoomNetCfgLine *nptr; nptr = (RoomNetCfgLine *) malloc(sizeof(RoomNetCfgLine)); memset(nptr, 0, sizeof(RoomNetCfgLine)); OneRNCFG->lastsent = extract_long(LinePos, 0); OneRNCFG->NetConfigs[ThisOne->C] = nptr; } void ParseRoomAlias(const CfgLineType *ThisOne, StrBuf *Line, const char *LinePos, OneRoomNetCfg *rncfg) { if (rncfg->Sender != NULL) return; ParseGeneric(ThisOne, Line, LinePos, rncfg); rncfg->Sender = NewStrBufDup(rncfg->NetConfigs[roommailalias]->Value[0]); } void ParseSubPendingLine(const CfgLineType *ThisOne, StrBuf *Line, const char *LinePos, OneRoomNetCfg *OneRNCFG) { if (time(NULL) - extract_long(LinePos, 3) > EXP) return; /* expired subscription... */ ParseGeneric(ThisOne, Line, LinePos, OneRNCFG); } void ParseUnSubPendingLine(const CfgLineType *ThisOne, StrBuf *Line, const char *LinePos, OneRoomNetCfg *OneRNCFG) { if (time(NULL) - extract_long(LinePos, 2) > EXP) return; /* expired subscription... */ ParseGeneric(ThisOne, Line, LinePos, OneRNCFG); } void SerializeLastSent(const CfgLineType *ThisOne, StrBuf *OutputBuffer, OneRoomNetCfg *RNCfg, RoomNetCfgLine *data) { StrBufAppendBufPlain(OutputBuffer, CKEY(ThisOne->Str), 0); StrBufAppendPrintf(OutputBuffer, "|%ld\n", RNCfg->lastsent); } void DeleteLastSent(const CfgLineType *ThisOne, RoomNetCfgLine **data) { free(*data); *data = NULL; } static const RoomNetCfg SpoolCfgs [4] = { listrecp, digestrecp, participate, ignet_push_share }; static const long SpoolCfgsCopyN [4] = { 1, 1, 1, 2 }; int HaveSpoolConfig(OneRoomNetCfg* RNCfg) { int i; int interested = 0; for (i=0; i < 4; i++) if (RNCfg->NetConfigs[SpoolCfgs[i]] == NULL) interested = 1; return interested; } void Netmap_AddMe(struct CtdlMessage *msg, const char *defl, long defllen) { long node_len; char buf[SIZ]; /* prepend our node to the path */ if (CM_IsEmpty(msg, eMessagePath)) { CM_SetField(msg, eMessagePath, defl, defllen); } node_len = configlen.c_nodename; if (node_len >= SIZ) node_len = SIZ - 1; memcpy(buf, config.c_nodename, node_len); buf[node_len] = '!'; buf[node_len + 1] = '\0'; CM_PrependToField(msg, eMessagePath, buf, node_len + 1); } void InspectQueuedRoom(SpoolControl **pSC, RoomProcList *room_to_spool, HashList *working_ignetcfg, HashList *the_netmap) { struct CitContext *CCC = CC; SpoolControl *sc; int i = 0; sc = (SpoolControl*)malloc(sizeof(SpoolControl)); memset(sc, 0, sizeof(SpoolControl)); sc->RNCfg = room_to_spool->OneRNCfg; sc->lastsent = room_to_spool->lastsent; sc->working_ignetcfg = working_ignetcfg; sc->the_netmap = the_netmap; /* * If the room doesn't exist, don't try to perform its networking tasks. * Normally this should never happen, but once in a while maybe a room gets * queued for networking and then deleted before it can happen. */ if (CtdlGetRoom(&sc->room, room_to_spool->name) != 0) { syslog(LOG_CRIT, "ERROR: cannot load <%s>\n", room_to_spool->name); free(sc); return; } if (sc->room.QRhighest <= sc->lastsent) { QN_syslog(LOG_DEBUG, "nothing to do for <%s>\n", room_to_spool->name); free(sc); return; } begin_critical_section(S_NETCONFIGS); if (sc->RNCfg == NULL) sc->RNCfg = CtdlGetNetCfgForRoom(sc->room.QRnumber); if (!HaveSpoolConfig(sc->RNCfg)) { end_critical_section(S_NETCONFIGS); free(sc); /* nothing to do for this room... */ return; } /* Now lets remember whats needed for the actual work... */ for (i=0; i < 4; i++) { aggregate_recipients(&sc->Users[SpoolCfgs[i]], SpoolCfgs[i], sc->RNCfg, SpoolCfgsCopyN[i]); } if (StrLength(sc->RNCfg->Sender) > 0) sc->Users[roommailalias] = NewStrBufDup(sc->RNCfg->Sender); end_critical_section(S_NETCONFIGS); sc->next = *pSC; *pSC = sc; } void CalcListID(SpoolControl *sc) { StrBuf *RoomName; const char *err; int fd; struct CitContext *CCC = CC; char filename[PATH_MAX]; #define MAX_LISTIDLENGTH 150 assoc_file_name(filename, sizeof filename, &sc->room, ctdl_info_dir); fd = open(filename, 0); if (fd > 0) { struct stat stbuf; if ((fstat(fd, &stbuf) == 0) && (stbuf.st_size > 0)) { sc->RoomInfo = NewStrBufPlain(NULL, stbuf.st_size + 1); StrBufReadBLOB(sc->RoomInfo, &fd, 0, stbuf.st_size, &err); } close(fd); } sc->ListID = NewStrBufPlain(NULL, 1024); if (StrLength(sc->RoomInfo) > 0) { const char *Pos = NULL; StrBufSipLine(sc->ListID, sc->RoomInfo, &Pos); if (StrLength(sc->ListID) > MAX_LISTIDLENGTH) { StrBufCutAt(sc->ListID, MAX_LISTIDLENGTH, NULL); StrBufAppendBufPlain(sc->ListID, HKEY("..."), 0); } StrBufAsciify(sc->ListID, ' '); } else { StrBufAppendBufPlain(sc->ListID, CCC->room.QRname, -1, 0); } StrBufAppendBufPlain(sc->ListID, HKEY("<"), 0); RoomName = NewStrBufPlain (sc->room.QRname, -1); StrBufAsciify(RoomName, '_'); StrBufReplaceChars(RoomName, ' ', '_'); if (StrLength(sc->Users[roommailalias]) > 0) { long Pos; const char *AtPos; Pos = StrLength(sc->ListID); StrBufAppendBuf(sc->ListID, sc->Users[roommailalias], 0); AtPos = strchr(ChrPtr(sc->ListID) + Pos, '@'); if (AtPos != NULL) { StrBufPeek(sc->ListID, AtPos, 0, '.'); } } else { StrBufAppendBufPlain(sc->ListID, HKEY("room_"), 0); StrBufAppendBuf(sc->ListID, RoomName, 0); StrBufAppendBufPlain(sc->ListID, HKEY("."), 0); StrBufAppendBufPlain(sc->ListID, config.c_fqdn, -1, 0); /* * this used to be: * roomname * according to rfc2919.txt it only has to be a uniq identifier * under the domain of the system; * in general MUAs use it to calculate the reply address nowadays. */ } StrBufAppendBufPlain(sc->ListID, HKEY(">"), 0); if (StrLength(sc->Users[roommailalias]) == 0) { sc->Users[roommailalias] = NewStrBuf(); StrBufAppendBufPlain(sc->Users[roommailalias], HKEY("room_"), 0); StrBufAppendBuf(sc->Users[roommailalias], RoomName, 0); StrBufAppendBufPlain(sc->Users[roommailalias], HKEY("@"), 0); StrBufAppendBufPlain(sc->Users[roommailalias], config.c_fqdn, -1, 0); StrBufLowerCase(sc->Users[roommailalias]); } FreeStrBuf(&RoomName); } static time_t last_digest_delivery = 0; /* * Batch up and send all outbound traffic from the current room */ void network_spoolout_room(SpoolControl *sc) { struct CitContext *CCC = CC; char buf[SIZ]; int i; long lastsent; /* * If the room doesn't exist, don't try to perform its networking tasks. * Normally this should never happen, but once in a while maybe a room gets * queued for networking and then deleted before it can happen. */ memcpy (&CCC->room, &sc->room, sizeof(ctdlroom)); syslog(LOG_INFO, "Networking started for <%s>\n", CCC->room.QRname); CalcListID(sc); /* remember where we started... */ lastsent = sc->lastsent; /* Fetch the messages we ought to send & prepare them. */ CtdlForEachMessage(MSGS_GT, sc->lastsent, NULL, NULL, NULL, network_spool_msg, sc); if (StrLength(sc->Users[roommailalias]) > 0) { long len; len = StrLength(sc->Users[roommailalias]); if (len + 1 > sizeof(buf)) len = sizeof(buf) - 1; memcpy(buf, ChrPtr(sc->Users[roommailalias]), len); buf[len] = '\0'; } else { snprintf(buf, sizeof buf, "room_%s@%s", CCC->room.QRname, config.c_fqdn); } for (i=0; buf[i]; ++i) { buf[i] = tolower(buf[i]); if (isspace(buf[i])) buf[i] = '_'; } /* If we wrote a digest, deliver it and then close it */ if (sc->Users[digestrecp] != NULL) { time_t now = time(NULL); time_t secs_today = now % (24 * 60 * 60); long delta = 0; if (last_digest_delivery != 0) { delta = now - last_digest_delivery; delta = (24 * 60 * 60) - delta; } if ((secs_today < 300) && (delta < 300)) { if (sc->digestfp == NULL) { sc->digestfp = create_digest_file(&sc->room, 0); } if (sc->digestfp != NULL) { last_digest_delivery = now; fprintf(sc->digestfp, " -----------------------------------" "------------------------------------" "-------\n" "You are subscribed to the '%s' " "list.\n" "To post to the list: %s\n", CCC->room.QRname, buf ); network_deliver_digest(sc); /* deliver */ remove_digest_file(&sc->room); } } } if (sc->digestfp != NULL) { fclose(sc->digestfp); sc->digestfp = NULL; } /* Now rewrite the config file */ if (sc->lastsent != lastsent) { begin_critical_section(S_NETCONFIGS); sc->RNCfg = CtdlGetNetCfgForRoom(sc->room.QRnumber); sc->RNCfg->lastsent = sc->lastsent; sc->RNCfg->changed = 1; end_critical_section(S_NETCONFIGS); } } /* * Process a buffer containing a single message from a single file * from the inbound queue */ void network_process_buffer(char *buffer, long size, HashList *working_ignetcfg, HashList *the_netmap, int *netmap_changed) { long len; struct CitContext *CCC = CC; StrBuf *Buf = NULL; struct CtdlMessage *msg = NULL; long pos; int field; recptypes *recp = NULL; char target_room[ROOMNAMELEN]; struct ser_ret sermsg; char filename[PATH_MAX]; FILE *fp; const StrBuf *nexthop = NULL; unsigned char firstbyte; unsigned char lastbyte; QN_syslog(LOG_DEBUG, "network_process_buffer() processing %ld bytes\n", size); /* Validate just a little bit. First byte should be FF and * last byte should be 00. */ firstbyte = buffer[0]; lastbyte = buffer[size-1]; if ( (firstbyte != 255) || (lastbyte != 0) ) { QN_syslog(LOG_ERR, "Corrupt message ignored. Length=%ld, firstbyte = %d, lastbyte = %d\n", size, firstbyte, lastbyte); return; } /* Set default target room to trash */ strcpy(target_room, TWITROOM); /* Load the message into memory */ msg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage)); memset(msg, 0, sizeof(struct CtdlMessage)); msg->cm_magic = CTDLMESSAGE_MAGIC; msg->cm_anon_type = buffer[1]; msg->cm_format_type = buffer[2]; for (pos = 3; pos < size; ++pos) { field = buffer[pos]; len = strlen(buffer + pos + 1); CM_SetField(msg, field, buffer + pos + 1, len); pos = pos + len + 1; } /* Check for message routing */ if (!CM_IsEmpty(msg, eDestination)) { if (strcasecmp(msg->cm_fields[eDestination], config.c_nodename)) { /* route the message */ Buf = NewStrBufPlain(CM_KEY(msg,eDestination)); if (CtdlIsValidNode(&nexthop, NULL, Buf, working_ignetcfg, the_netmap) == 0) { Netmap_AddMe(msg, HKEY("unknown_user")); /* serialize the message */ CtdlSerializeMessage(&sermsg, msg); /* now send it */ if (StrLength(nexthop) == 0) { nexthop = Buf; } snprintf(filename, sizeof filename, "%s/%s@%lx%x", ctdl_netout_dir, ChrPtr(nexthop), time(NULL), rand() ); QN_syslog(LOG_DEBUG, "Appending to %s\n", filename); fp = fopen(filename, "ab"); if (fp != NULL) { fwrite(sermsg.ser, sermsg.len, 1, fp); fclose(fp); } else { QN_syslog(LOG_ERR, "%s: %s\n", filename, strerror(errno)); } free(sermsg.ser); CM_Free(msg); FreeStrBuf(&Buf); return; } else { /* invalid destination node name */ FreeStrBuf(&Buf); network_bounce(msg, "A message you sent could not be delivered due to an invalid destination node" " name. Please check the address and try sending the message again.\n"); msg = NULL; return; } } } /* * Check to see if we already have a copy of this message, and * abort its processing if so. (We used to post a warning to Aide> * every time this happened, but the network is now so densely * connected that it's inevitable.) */ if (network_usetable(msg) != 0) { CM_Free(msg); return; } /* Learn network topology from the path */ if (!CM_IsEmpty(msg, eNodeName) && !CM_IsEmpty(msg, eMessagePath)) { NetworkLearnTopology(msg->cm_fields[eNodeName], msg->cm_fields[eMessagePath], the_netmap, netmap_changed); } /* Is the sending node giving us a very persuasive suggestion about * which room this message should be saved in? If so, go with that. */ if (!CM_IsEmpty(msg, eRemoteRoom)) { safestrncpy(target_room, msg->cm_fields[eRemoteRoom], sizeof target_room); } /* Otherwise, does it have a recipient? If so, validate it... */ else if (!CM_IsEmpty(msg, eRecipient)) { recp = validate_recipients(msg->cm_fields[eRecipient], NULL, 0); if (recp != NULL) if (recp->num_error != 0) { network_bounce(msg, "A message you sent could not be delivered due to an invalid address.\n" "Please check the address and try sending the message again.\n"); msg = NULL; free_recipients(recp); QNM_syslog(LOG_DEBUG, "Bouncing message due to invalid recipient address.\n"); return; } strcpy(target_room, ""); /* no target room if mail */ } /* Our last shot at finding a home for this message is to see if * it has the eOriginalRoom (O) field (Originating room) set. */ else if (!CM_IsEmpty(msg, eOriginalRoom)) { safestrncpy(target_room, msg->cm_fields[eOriginalRoom], sizeof target_room); } /* Strip out fields that are only relevant during transit */ CM_FlushField(msg, eDestination); CM_FlushField(msg, eRemoteRoom); /* save the message into a room */ if (PerformNetprocHooks(msg, target_room) == 0) { msg->cm_flags = CM_SKIP_HOOKS; CtdlSubmitMsg(msg, recp, target_room, 0); } CM_Free(msg); free_recipients(recp); } /* * Process a single message from a single file from the inbound queue */ void network_process_message(FILE *fp, long msgstart, long msgend, HashList *working_ignetcfg, HashList *the_netmap, int *netmap_changed) { long hold_pos; long size; char *buffer; hold_pos = ftell(fp); size = msgend - msgstart + 1; buffer = malloc(size); if (buffer != NULL) { fseek(fp, msgstart, SEEK_SET); if (fread(buffer, size, 1, fp) > 0) { network_process_buffer(buffer, size, working_ignetcfg, the_netmap, netmap_changed); } free(buffer); } fseek(fp, hold_pos, SEEK_SET); } /* * Process a single file from the inbound queue */ void network_process_file(char *filename, HashList *working_ignetcfg, HashList *the_netmap, int *netmap_changed) { struct CitContext *CCC = CC; FILE *fp; long msgstart = (-1L); long msgend = (-1L); long msgcur = 0L; int ch; int nMessages = 0; fp = fopen(filename, "rb"); if (fp == NULL) { QN_syslog(LOG_CRIT, "Error opening %s: %s\n", filename, strerror(errno)); return; } fseek(fp, 0L, SEEK_END); QN_syslog(LOG_INFO, "network: processing %ld bytes from %s\n", ftell(fp), filename); rewind(fp); /* Look for messages in the data stream and break them out */ while (ch = getc(fp), ch >= 0) { if (ch == 255) { if (msgstart >= 0L) { msgend = msgcur - 1; network_process_message(fp, msgstart, msgend, working_ignetcfg, the_netmap, netmap_changed); } msgstart = msgcur; } ++msgcur; nMessages ++; } msgend = msgcur - 1; if (msgstart >= 0L) { network_process_message(fp, msgstart, msgend, working_ignetcfg, the_netmap, netmap_changed); nMessages ++; } if (nMessages > 0) QN_syslog(LOG_INFO, "network: processed %d messages in %s\n", nMessages, filename); fclose(fp); unlink(filename); } /* * Process anything in the inbound queue */ void network_do_spoolin(HashList *working_ignetcfg, HashList *the_netmap, int *netmap_changed) { struct CitContext *CCC = CC; DIR *dp; struct dirent *d; struct dirent *filedir_entry; struct stat statbuf; char filename[PATH_MAX]; static time_t last_spoolin_mtime = 0L; int d_type = 0; int d_namelen; /* * Check the spoolin directory's modification time. If it hasn't * been touched, we don't need to scan it. */ if (stat(ctdl_netin_dir, &statbuf)) return; if (statbuf.st_mtime == last_spoolin_mtime) { QNM_syslog(LOG_DEBUG, "network: nothing in inbound queue\n"); return; } last_spoolin_mtime = statbuf.st_mtime; QNM_syslog(LOG_DEBUG, "network: processing inbound queue\n"); /* * Ok, there's something interesting in there, so scan it. */ dp = opendir(ctdl_netin_dir); if (dp == NULL) return; d = (struct dirent *)malloc(offsetof(struct dirent, d_name) + PATH_MAX + 1); if (d == NULL) { closedir(dp); return; } while ((readdir_r(dp, d, &filedir_entry) == 0) && (filedir_entry != NULL)) { #ifdef _DIRENT_HAVE_D_NAMLEN d_namelen = filedir_entry->d_namlen; #else d_namelen = strlen(filedir_entry->d_name); #endif #ifdef _DIRENT_HAVE_D_TYPE d_type = filedir_entry->d_type; #else d_type = DT_UNKNOWN; #endif if ((d_namelen > 1) && filedir_entry->d_name[d_namelen - 1] == '~') continue; /* Ignore backup files... */ if ((d_namelen == 1) && (filedir_entry->d_name[0] == '.')) continue; if ((d_namelen == 2) && (filedir_entry->d_name[0] == '.') && (filedir_entry->d_name[1] == '.')) continue; if (d_type == DT_UNKNOWN) { struct stat s; char path[PATH_MAX]; snprintf(path, PATH_MAX, "%s/%s", ctdl_netin_dir, filedir_entry->d_name); if (lstat(path, &s) == 0) { d_type = IFTODT(s.st_mode); } } switch (d_type) { case DT_DIR: break; case DT_LNK: /* TODO: check whether its a file or a directory */ case DT_REG: snprintf(filename, sizeof filename, "%s/%s", ctdl_netin_dir, d->d_name ); network_process_file(filename, working_ignetcfg, the_netmap, netmap_changed); } } closedir(dp); free(d); } /* * Step 1: consolidate files in the outbound queue into one file per neighbor node * Step 2: delete any files in the outbound queue that were for neighbors who no longer exist. */ void network_consolidate_spoolout(HashList *working_ignetcfg, HashList *the_netmap) { struct CitContext *CCC = CC; IOBuffer IOB; FDIOBuffer FDIO; int d_namelen; DIR *dp; struct dirent *d; struct dirent *filedir_entry; const char *pch; char spooloutfilename[PATH_MAX]; char filename[PATH_MAX]; const StrBuf *nexthop; StrBuf *NextHop; int i; struct stat statbuf; int nFailed = 0; int d_type = 0; /* Step 1: consolidate files in the outbound queue into one file per neighbor node */ d = (struct dirent *)malloc(offsetof(struct dirent, d_name) + PATH_MAX + 1); if (d == NULL) return; dp = opendir(ctdl_netout_dir); if (dp == NULL) { free(d); return; } NextHop = NewStrBuf(); memset(&IOB, 0, sizeof(IOBuffer)); memset(&FDIO, 0, sizeof(FDIOBuffer)); FDIO.IOB = &IOB; while ((readdir_r(dp, d, &filedir_entry) == 0) && (filedir_entry != NULL)) { #ifdef _DIRENT_HAVE_D_NAMLEN d_namelen = filedir_entry->d_namlen; #else d_namelen = strlen(filedir_entry->d_name); #endif #ifdef _DIRENT_HAVE_D_TYPE d_type = filedir_entry->d_type; #else d_type = DT_UNKNOWN; #endif if (d_type == DT_DIR) continue; if ((d_namelen > 1) && filedir_entry->d_name[d_namelen - 1] == '~') continue; /* Ignore backup files... */ if ((d_namelen == 1) && (filedir_entry->d_name[0] == '.')) continue; if ((d_namelen == 2) && (filedir_entry->d_name[0] == '.') && (filedir_entry->d_name[1] == '.')) continue; pch = strchr(filedir_entry->d_name, '@'); if (pch == NULL) continue; snprintf(filename, sizeof filename, "%s/%s", ctdl_netout_dir, filedir_entry->d_name); StrBufPlain(NextHop, filedir_entry->d_name, pch - filedir_entry->d_name); snprintf(spooloutfilename, sizeof spooloutfilename, "%s/%s", ctdl_netout_dir, ChrPtr(NextHop)); QN_syslog(LOG_DEBUG, "Consolidate %s to %s\n", filename, ChrPtr(NextHop)); if (CtdlNetworkTalkingTo(SKEY(NextHop), NTT_CHECK)) { nFailed++; QN_syslog(LOG_DEBUG, "Currently online with %s - skipping for now\n", ChrPtr(NextHop) ); } else { size_t dsize; size_t fsize; int infd, outfd; const char *err = NULL; CtdlNetworkTalkingTo(SKEY(NextHop), NTT_ADD); infd = open(filename, O_RDONLY); if (infd == -1) { nFailed++; QN_syslog(LOG_ERR, "failed to open %s for reading due to %s; skipping.\n", filename, strerror(errno) ); CtdlNetworkTalkingTo(SKEY(NextHop), NTT_REMOVE); continue; } outfd = open(spooloutfilename, O_EXCL|O_CREAT|O_NONBLOCK|O_WRONLY, S_IRUSR|S_IWUSR); if (outfd == -1) { outfd = open(spooloutfilename, O_EXCL|O_NONBLOCK|O_WRONLY, S_IRUSR | S_IWUSR); } if (outfd == -1) { nFailed++; QN_syslog(LOG_ERR, "failed to open %s for reading due to %s; skipping.\n", spooloutfilename, strerror(errno) ); close(infd); CtdlNetworkTalkingTo(SKEY(NextHop), NTT_REMOVE); continue; } dsize = lseek(outfd, 0, SEEK_END); lseek(outfd, -dsize, SEEK_SET); fstat(infd, &statbuf); fsize = statbuf.st_size; /* fsize = lseek(infd, 0, SEEK_END); */ IOB.fd = infd; FDIOBufferInit(&FDIO, &IOB, outfd, fsize + dsize); FDIO.ChunkSendRemain = fsize; FDIO.TotalSentAlready = dsize; err = NULL; errno = 0; do {} while ((FileMoveChunked(&FDIO, &err) > 0) && (err == NULL)); if (err == NULL) { unlink(filename); QN_syslog(LOG_DEBUG, "Spoolfile %s now "SIZE_T_FMT" k\n", spooloutfilename, (dsize + fsize)/1024 ); } else { nFailed++; QN_syslog(LOG_ERR, "failed to append to %s [%s]; rolling back..\n", spooloutfilename, strerror(errno) ); /* whoops partial append?? truncate spooloutfilename again! */ ftruncate(outfd, dsize); } FDIOBufferDelete(&FDIO); close(infd); close(outfd); CtdlNetworkTalkingTo(SKEY(NextHop), NTT_REMOVE); } } closedir(dp); if (nFailed > 0) { FreeStrBuf(&NextHop); QN_syslog(LOG_INFO, "skipping Spoolcleanup because of %d files unprocessed.\n", nFailed ); return; } /* Step 2: delete any files in the outbound queue that were for neighbors who no longer exist */ dp = opendir(ctdl_netout_dir); if (dp == NULL) { FreeStrBuf(&NextHop); free(d); return; } while ((readdir_r(dp, d, &filedir_entry) == 0) && (filedir_entry != NULL)) { #ifdef _DIRENT_HAVE_D_NAMLEN d_namelen = filedir_entry->d_namlen; #else d_namelen = strlen(filedir_entry->d_name); #endif #ifdef _DIRENT_HAVE_D_TYPE d_type = filedir_entry->d_type; #else d_type = DT_UNKNOWN; #endif if (d_type == DT_DIR) continue; if ((d_namelen == 1) && (filedir_entry->d_name[0] == '.')) continue; if ((d_namelen == 2) && (filedir_entry->d_name[0] == '.') && (filedir_entry->d_name[1] == '.')) continue; pch = strchr(filedir_entry->d_name, '@'); if (pch == NULL) /* no @ in name? consolidated file. */ continue; StrBufPlain(NextHop, filedir_entry->d_name, pch - filedir_entry->d_name); snprintf(filename, sizeof filename, "%s/%s", ctdl_netout_dir, filedir_entry->d_name ); i = CtdlIsValidNode(&nexthop, NULL, NextHop, working_ignetcfg, the_netmap); if ( (i != 0) || (StrLength(nexthop) > 0) ) { unlink(filename); } } FreeStrBuf(&NextHop); free(d); closedir(dp); } void free_spoolcontrol_struct(SpoolControl **sc) { free_spoolcontrol_struct_members(*sc); free(*sc); *sc = NULL; } void free_spoolcontrol_struct_members(SpoolControl *sc) { int i; FreeStrBuf(&sc->RoomInfo); FreeStrBuf(&sc->ListID); for (i = 0; i < maxRoomNetCfg; i++) FreeStrBuf(&sc->Users[i]); } /* * It's ok if these directories already exist. Just fail silently. */ void create_spool_dirs(void) { if ((mkdir(ctdl_spool_dir, 0700) != 0) && (errno != EEXIST)) syslog(LOG_EMERG, "unable to create directory [%s]: %s", ctdl_spool_dir, strerror(errno)); if (chown(ctdl_spool_dir, CTDLUID, (-1)) != 0) syslog(LOG_EMERG, "unable to set the access rights for [%s]: %s", ctdl_spool_dir, strerror(errno)); if ((mkdir(ctdl_netin_dir, 0700) != 0) && (errno != EEXIST)) syslog(LOG_EMERG, "unable to create directory [%s]: %s", ctdl_netin_dir, strerror(errno)); if (chown(ctdl_netin_dir, CTDLUID, (-1)) != 0) syslog(LOG_EMERG, "unable to set the access rights for [%s]: %s", ctdl_netin_dir, strerror(errno)); if ((mkdir(ctdl_nettmp_dir, 0700) != 0) && (errno != EEXIST)) syslog(LOG_EMERG, "unable to create directory [%s]: %s", ctdl_nettmp_dir, strerror(errno)); if (chown(ctdl_nettmp_dir, CTDLUID, (-1)) != 0) syslog(LOG_EMERG, "unable to set the access rights for [%s]: %s", ctdl_nettmp_dir, strerror(errno)); if ((mkdir(ctdl_netout_dir, 0700) != 0) && (errno != EEXIST)) syslog(LOG_EMERG, "unable to create directory [%s]: %s", ctdl_netout_dir, strerror(errno)); if (chown(ctdl_netout_dir, CTDLUID, (-1)) != 0) syslog(LOG_EMERG, "unable to set the access rights for [%s]: %s", ctdl_netout_dir, strerror(errno)); } /* * Module entry point */ CTDL_MODULE_INIT(network_spool) { if (!threading) { CtdlREGISTERRoomCfgType(subpending, ParseSubPendingLine, 0, 5, SerializeGeneric, DeleteGenericCfgLine); /// todo: move this to mailinglist manager CtdlREGISTERRoomCfgType(unsubpending, ParseUnSubPendingLine, 0, 4, SerializeGeneric, DeleteGenericCfgLine); /// todo: move this to mailinglist manager CtdlREGISTERRoomCfgType(lastsent, ParseLastSent, 1, 1, SerializeLastSent, DeleteLastSent); CtdlREGISTERRoomCfgType(ignet_push_share, ParseGeneric, 0, 2, SerializeGeneric, DeleteGenericCfgLine); // [remotenode|remoteroomname (optional)]// todo: move this to the ignet client CtdlREGISTERRoomCfgType(listrecp, ParseGeneric, 0, 1, SerializeGeneric, DeleteGenericCfgLine); CtdlREGISTERRoomCfgType(digestrecp, ParseGeneric, 0, 1, SerializeGeneric, DeleteGenericCfgLine); CtdlREGISTERRoomCfgType(participate, ParseGeneric, 0, 1, SerializeGeneric, DeleteGenericCfgLine); CtdlREGISTERRoomCfgType(roommailalias, ParseRoomAlias, 0, 1, SerializeGeneric, DeleteGenericCfgLine); create_spool_dirs(); //////todo CtdlRegisterCleanupHook(destroy_network_queue_room); } return "network_spool"; } citadel-9.01/modules/pop3client/0000755000000000000000000000000012507024051015272 5ustar rootrootcitadel-9.01/modules/pop3client/serv_pop3client.c0000644000000000000000000007220012507024051020556 0ustar rootroot/* * Consolidate mail from remote POP3 accounts. * * Copyright (c) 2007-2011 by the citadel.org team * * This program is open source software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "ctdl_module.h" #include "clientsocket.h" #include "msgbase.h" #include "internet_addressing.h" #include "database.h" #include "citadel_dirs.h" #include "event_client.h" #define POP3C_OK (strncasecmp(ChrPtr(RecvMsg->IO.IOBuf), "+OK", 3) == 0) int Pop3ClientID = 0; int POP3ClientDebugEnabled = 0; #define N ((pop3aggr*)IO->Data)->n #define DBGLOG(LEVEL) if ((LEVEL != LOG_DEBUG) || (POP3ClientDebugEnabled != 0)) #define EVP3C_syslog(LEVEL, FORMAT, ...) \ DBGLOG(LEVEL) syslog(LEVEL, \ "%s[%ld]CC[%d][%ld]POP3: " FORMAT, \ IOSTR, IO->ID, CCID, N, __VA_ARGS__) #define EVP3CM_syslog(LEVEL, FORMAT) \ DBGLOG(LEVEL) syslog(LEVEL, \ "%s[%ld]CC[%d][%ld]POP3: " FORMAT, \ IOSTR, IO->ID, CCID, N) #define EVP3CQ_syslog(LEVEL, FORMAT, ...) \ DBGLOG(LEVEL) syslog(LEVEL, \ "%s P3Q:" FORMAT, \ IOSTR, __VA_ARGS__) #define EVP3CQM_syslog(LEVEL, FORMAT) \ DBGLOG(LEVEL) syslog(LEVEL, \ "%s P3Q" FORMAT, \ IOSTR) #define EVP3CCS_syslog(LEVEL, FORMAT, ...) \ DBGLOG(LEVEL) syslog(LEVEL, "%s[%ld][%ld]POP3: " FORMAT, \ IOSTR, IO->ID, N, __VA_ARGS__) #define EVP3CCSM_syslog(LEVEL, FORMAT) \ DBGLOG(LEVEL) syslog(LEVEL, "%s[%ld][%ld]POP3: " FORMAT, \ IOSTR, IO->ID, N) #define POP3C_DBG_SEND() \ EVP3C_syslog(LOG_DEBUG, \ "%s[%ld]CC[%d][%ld]POP3: > %s\n", \ IOSTR, IO->ID, CCID, N, \ ChrPtr(RecvMsg->IO.SendBuf.Buf)) #define POP3C_DBG_READ() \ EVP3C_syslog(LOG_DEBUG, \ "%s[%ld]CC[%d][%ld]POP3: < %s\n", \ IOSTR, IO->ID, CCID, N, \ ChrPtr(RecvMsg->IO.IOBuf)) struct CitContext pop3_client_CC; pthread_mutex_t POP3QueueMutex; /* locks the access to the following vars: */ HashList *POP3QueueRooms = NULL; HashList *POP3FetchUrls = NULL; typedef struct pop3aggr pop3aggr; typedef eNextState(*Pop3ClientHandler)(pop3aggr* RecvMsg); eNextState POP3_C_Shutdown(AsyncIO *IO); eNextState POP3_C_Timeout(AsyncIO *IO); eNextState POP3_C_ConnFail(AsyncIO *IO); eNextState POP3_C_DNSFail(AsyncIO *IO); eNextState POP3_C_DispatchReadDone(AsyncIO *IO); eNextState POP3_C_DispatchWriteDone(AsyncIO *IO); eNextState POP3_C_Terminate(AsyncIO *IO); eReadState POP3_C_ReadServerStatus(AsyncIO *IO); eNextState POP3_C_ReAttachToFetchMessages(AsyncIO *IO); typedef struct __pop3_room_counter { int count; long QRnumber; }pop3_room_counter; typedef enum ePOP3_C_States { ReadGreeting, GetUserState, GetPassState, GetListCommandState, GetListOneLine, GetOneMessageIDState, ReadMessageBodyFollowing, ReadMessageBody, GetDeleteState, ReadQuitState, POP3C_MaxRead }ePOP3_C_States; typedef struct _FetchItem { long MSGID; long MSGSize; StrBuf *MsgUIDL; StrBuf *MsgUID; int NeedFetch; struct CtdlMessage *Msg; } FetchItem; void HfreeFetchItem(void *vItem) { FetchItem *Item = (FetchItem*) vItem; FreeStrBuf(&Item->MsgUIDL); FreeStrBuf(&Item->MsgUID); free(Item); } typedef enum _POP3State { eCreated, eGreeting, eUser, ePassword, eListing, eUseTable, eGetMsgID, eGetMsg, eStoreMsg, eDelete, eQuit } POP3State; ConstStr POP3States[] = { {HKEY("Aggregator created")}, {HKEY("Reading Greeting")}, {HKEY("Sending User")}, {HKEY("Sending Password")}, {HKEY("Listing")}, {HKEY("Fetching Usetable")}, {HKEY("Get MSG ID")}, {HKEY("Get Message")}, {HKEY("Store Msg")}, {HKEY("Delete Upstream")}, {HKEY("Quit")} }; static void SetPOP3State(AsyncIO *IO, POP3State State) { CitContext* CCC = IO->CitContext; if (CCC != NULL) memcpy(CCC->cs_clientname, POP3States[State].Key, POP3States[State].len + 1); } struct pop3aggr { AsyncIO IO; long n; double IOStart; long count; long RefCount; DNSQueryParts HostLookup; long QRnumber; HashList *OtherQRnumbers; StrBuf *Url; StrBuf *pop3user; StrBuf *pop3pass; StrBuf *Host; StrBuf *RoomName; // TODO: fill me int keep; time_t interval; ePOP3_C_States State; HashList *MsgNumbers; HashPos *Pos; FetchItem *CurrMsg; }; void DeletePOP3Aggregator(void *vptr) { pop3aggr *ptr = vptr; DeleteHashPos(&ptr->Pos); DeleteHash(&ptr->MsgNumbers); // FreeStrBuf(&ptr->rooms); FreeStrBuf(&ptr->pop3user); FreeStrBuf(&ptr->pop3pass); FreeStrBuf(&ptr->Host); FreeStrBuf(&ptr->RoomName); FreeURL(&ptr->IO.ConnectMe); FreeStrBuf(&ptr->Url); FreeStrBuf(&ptr->IO.IOBuf); FreeStrBuf(&ptr->IO.SendBuf.Buf); FreeStrBuf(&ptr->IO.RecvBuf.Buf); DeleteAsyncMsg(&ptr->IO.ReadMsg); if (((struct CitContext*)ptr->IO.CitContext)) { ((struct CitContext*)ptr->IO.CitContext)->state = CON_IDLE; ((struct CitContext*)ptr->IO.CitContext)->kill_me = 1; } FreeAsyncIOContents(&ptr->IO); free(ptr); } eNextState FinalizePOP3AggrRun(AsyncIO *IO) { HashPos *It; pop3aggr *cpptr = (pop3aggr *)IO->Data; EVP3C_syslog(LOG_INFO, "%s@%s: fetched %ld new of %d messages in %fs. bye.", ChrPtr(cpptr->pop3user), ChrPtr(cpptr->Host), cpptr->count, GetCount(cpptr->MsgNumbers), IO->Now - cpptr->IOStart ); It = GetNewHashPos(POP3FetchUrls, 0); pthread_mutex_lock(&POP3QueueMutex); { if (GetHashPosFromKey(POP3FetchUrls, SKEY(cpptr->Url), It)) DeleteEntryFromHash(POP3FetchUrls, It); } pthread_mutex_unlock(&POP3QueueMutex); DeleteHashPos(&It); return eAbort; } eNextState FailAggregationRun(AsyncIO *IO) { return eAbort; } eNextState POP3C_ReadGreeting(pop3aggr *RecvMsg) { AsyncIO *IO = &RecvMsg->IO; SetPOP3State(IO, eGreeting); POP3C_DBG_READ(); /* Read the server greeting */ if (!POP3C_OK) return eTerminateConnection; else return eSendReply; } eNextState POP3C_SendUser(pop3aggr *RecvMsg) { AsyncIO *IO = &RecvMsg->IO; SetPOP3State(IO, eUser); /* Identify ourselves. NOTE: we have to append a CR to each command. * The LF will automatically be appended by sock_puts(). Believe it * or not, leaving out the CR will cause problems if the server happens * to be Exchange, which is so b0rken it actually barfs on * LF-terminated newlines. */ StrBufPrintf(RecvMsg->IO.SendBuf.Buf, "USER %s\r\n", ChrPtr(RecvMsg->pop3user)); POP3C_DBG_SEND(); return eReadMessage; } eNextState POP3C_GetUserState(pop3aggr *RecvMsg) { AsyncIO *IO = &RecvMsg->IO; POP3C_DBG_READ(); if (!POP3C_OK) return eTerminateConnection; else return eSendReply; } eNextState POP3C_SendPassword(pop3aggr *RecvMsg) { AsyncIO *IO = &RecvMsg->IO; SetPOP3State(IO, ePassword); /* Password */ StrBufPrintf(RecvMsg->IO.SendBuf.Buf, "PASS %s\r\n", ChrPtr(RecvMsg->pop3pass)); EVP3CM_syslog(LOG_DEBUG, "\n"); // POP3C_DBG_SEND(); No, we won't write the passvoid to syslog... return eReadMessage; } eNextState POP3C_GetPassState(pop3aggr *RecvMsg) { AsyncIO *IO = &RecvMsg->IO; POP3C_DBG_READ(); if (!POP3C_OK) return eTerminateConnection; else return eSendReply; } eNextState POP3C_SendListCommand(pop3aggr *RecvMsg) { AsyncIO *IO = &RecvMsg->IO; SetPOP3State(IO, eListing); /* Get the list of messages */ StrBufPlain(RecvMsg->IO.SendBuf.Buf, HKEY("LIST\r\n")); POP3C_DBG_SEND(); return eReadMessage; } eNextState POP3C_GetListCommandState(pop3aggr *RecvMsg) { AsyncIO *IO = &RecvMsg->IO; POP3C_DBG_READ(); if (!POP3C_OK) return eTerminateConnection; RecvMsg->MsgNumbers = NewHash(1, NULL); RecvMsg->State++; return eReadMore; } eNextState POP3C_GetListOneLine(pop3aggr *RecvMsg) { AsyncIO *IO = &RecvMsg->IO; #if 0 int rc; #endif const char *pch; FetchItem *OneMsg = NULL; POP3C_DBG_READ(); if ((StrLength(RecvMsg->IO.IOBuf) == 1) && (ChrPtr(RecvMsg->IO.IOBuf)[0] == '.')) { if (GetCount(RecvMsg->MsgNumbers) == 0) { //// RecvMsg->Sate = ReadQuitState; } else { RecvMsg->Pos = GetNewHashPos(RecvMsg->MsgNumbers, 0); } return eSendReply; } /* * work around buggy pop3 servers which send * empty lines in their listings. */ if ((StrLength(RecvMsg->IO.IOBuf) == 0) || !isdigit(ChrPtr(RecvMsg->IO.IOBuf)[0])) { return eReadMore; } OneMsg = (FetchItem*) malloc(sizeof(FetchItem)); memset(OneMsg, 0, sizeof(FetchItem)); OneMsg->MSGID = atol(ChrPtr(RecvMsg->IO.IOBuf)); pch = strchr(ChrPtr(RecvMsg->IO.IOBuf), ' '); if (pch != NULL) { OneMsg->MSGSize = atol(pch + 1); } #if 0 rc = TestValidateHash(RecvMsg->MsgNumbers); if (rc != 0) EVP3CCS_syslog(LOG_DEBUG, "Hash Invalid: %d\n", rc); #endif Put(RecvMsg->MsgNumbers, LKEY(OneMsg->MSGID), OneMsg, HfreeFetchItem); #if 0 rc = TestValidateHash(RecvMsg->MsgNumbers); if (rc != 0) EVP3CCS_syslog(LOG_DEBUG, "Hash Invalid: %d\n", rc); #endif //RecvMsg->State --; /* read next Line */ return eReadMore; } eNextState POP3_FetchNetworkUsetableEntry(AsyncIO *IO) { long HKLen; const char *HKey; void *vData; pop3aggr *RecvMsg = (pop3aggr *) IO->Data; SetPOP3State(IO, eUseTable); if((RecvMsg->Pos != NULL) && GetNextHashPos(RecvMsg->MsgNumbers, RecvMsg->Pos, &HKLen, &HKey, &vData)) { if (server_shutting_down) return eAbort; if (CheckIfAlreadySeen("POP3 Item Seen", RecvMsg->CurrMsg->MsgUID, EvGetNow(IO), EvGetNow(IO) - USETABLE_ANTIEXPIRE, eCheckUpdate, IO->ID, CCID) != 0) { /* Item has already been seen */ RecvMsg->CurrMsg->NeedFetch = 0; } else { EVP3CCSM_syslog(LOG_DEBUG, "NO\n"); RecvMsg->CurrMsg->NeedFetch = 1; } return NextDBOperation(&RecvMsg->IO, POP3_FetchNetworkUsetableEntry); } else { /* ok, now we know them all, * continue with reading the actual messages. */ DeleteHashPos(&RecvMsg->Pos); return DBQueueEventContext(IO, POP3_C_ReAttachToFetchMessages); } } eNextState POP3C_GetOneMessagID(pop3aggr *RecvMsg) { AsyncIO *IO = &RecvMsg->IO; long HKLen; const char *HKey; void *vData; SetPOP3State(IO, eGetMsgID); #if 0 int rc; rc = TestValidateHash(RecvMsg->MsgNumbers); if (rc != 0) EVP3CCS_syslog(LOG_DEBUG, "Hash Invalid: %d\n", rc); #endif if((RecvMsg->Pos != NULL) && GetNextHashPos(RecvMsg->MsgNumbers, RecvMsg->Pos, &HKLen, &HKey, &vData)) { RecvMsg->CurrMsg = (FetchItem*) vData; /* Find out the UIDL of the message, * to determine whether we've already downloaded it */ StrBufPrintf(RecvMsg->IO.SendBuf.Buf, "UIDL %ld\r\n", RecvMsg->CurrMsg->MSGID); POP3C_DBG_SEND(); } else { RecvMsg->State++; DeleteHashPos(&RecvMsg->Pos); /// done receiving uidls.. start looking them up now. RecvMsg->Pos = GetNewHashPos(RecvMsg->MsgNumbers, 0); return EventQueueDBOperation(&RecvMsg->IO, POP3_FetchNetworkUsetableEntry, 0); } return eReadMore; /* TODO */ } eNextState POP3C_GetOneMessageIDState(pop3aggr *RecvMsg) { AsyncIO *IO = &RecvMsg->IO; #if 0 int rc; rc = TestValidateHash(RecvMsg->MsgNumbers); if (rc != 0) EVP3CCS_syslog(LOG_DEBUG, "Hash Invalid: %d\n", rc); #endif POP3C_DBG_READ(); if (!POP3C_OK) return eTerminateConnection; RecvMsg->CurrMsg->MsgUIDL = NewStrBufPlain(NULL, StrLength(RecvMsg->IO.IOBuf)); RecvMsg->CurrMsg->MsgUID = NewStrBufPlain(NULL, StrLength(RecvMsg->IO.IOBuf) * 2); StrBufExtract_token(RecvMsg->CurrMsg->MsgUIDL, RecvMsg->IO.IOBuf, 2, ' '); StrBufPrintf(RecvMsg->CurrMsg->MsgUID, "pop3/%s/%s:%s@%s", ChrPtr(RecvMsg->RoomName), ChrPtr(RecvMsg->CurrMsg->MsgUIDL), RecvMsg->IO.ConnectMe->User, RecvMsg->IO.ConnectMe->Host); RecvMsg->State --; return eSendReply; } eNextState POP3C_SendGetOneMsg(pop3aggr *RecvMsg) { AsyncIO *IO = &RecvMsg->IO; long HKLen; const char *HKey; void *vData; SetPOP3State(IO, eGetMsg); RecvMsg->CurrMsg = NULL; while ((RecvMsg->Pos != NULL) && GetNextHashPos(RecvMsg->MsgNumbers, RecvMsg->Pos, &HKLen, &HKey, &vData) && (RecvMsg->CurrMsg = (FetchItem*) vData, RecvMsg->CurrMsg->NeedFetch == 0)) {} if ((RecvMsg->CurrMsg != NULL ) && (RecvMsg->CurrMsg->NeedFetch == 1)) { /* Message has not been seen. * Tell the server to fetch the message... */ StrBufPrintf(RecvMsg->IO.SendBuf.Buf, "RETR %ld\r\n", RecvMsg->CurrMsg->MSGID); POP3C_DBG_SEND(); return eReadMessage; } else { RecvMsg->State = ReadQuitState; return POP3_C_DispatchWriteDone(&RecvMsg->IO); } } eNextState POP3C_ReadMessageBodyFollowing(pop3aggr *RecvMsg) { AsyncIO *IO = &RecvMsg->IO; POP3C_DBG_READ(); if (!POP3C_OK) return eTerminateConnection; RecvMsg->IO.ReadMsg = NewAsyncMsg(HKEY("."), RecvMsg->CurrMsg->MSGSize, config.c_maxmsglen, NULL, -1, 1); return eReadPayload; } eNextState POP3C_StoreMsgRead(AsyncIO *IO) { pop3aggr *RecvMsg = (pop3aggr *) IO->Data; SetPOP3State(IO, eStoreMsg); EVP3CCS_syslog(LOG_DEBUG, "MARKING: %s as seen: ", ChrPtr(RecvMsg->CurrMsg->MsgUID)); CheckIfAlreadySeen("POP3 Item Seen", RecvMsg->CurrMsg->MsgUID, EvGetNow(IO), EvGetNow(IO) - USETABLE_ANTIEXPIRE, eWrite, IO->ID, CCID); return DBQueueEventContext(&RecvMsg->IO, POP3_C_ReAttachToFetchMessages); } eNextState POP3C_SaveMsg(AsyncIO *IO) { long msgnum; pop3aggr *RecvMsg = (pop3aggr *) IO->Data; /* Do Something With It (tm) */ msgnum = CtdlSubmitMsg(RecvMsg->CurrMsg->Msg, NULL, ChrPtr(RecvMsg->RoomName), 0); if (msgnum > 0L) { /* Message has been committed to the store * write the uidl to the use table * so we don't fetch this message again */ } CM_Free(RecvMsg->CurrMsg->Msg); RecvMsg->count ++; return NextDBOperation(&RecvMsg->IO, POP3C_StoreMsgRead); } eNextState POP3C_ReadMessageBody(pop3aggr *RecvMsg) { AsyncIO *IO = &RecvMsg->IO; EVP3CM_syslog(LOG_DEBUG, "Converting message..."); RecvMsg->CurrMsg->Msg = convert_internet_message_buf(&RecvMsg->IO.ReadMsg->MsgBuf); return EventQueueDBOperation(&RecvMsg->IO, POP3C_SaveMsg, 0); } eNextState POP3C_SendDelete(pop3aggr *RecvMsg) { AsyncIO *IO = &RecvMsg->IO; SetPOP3State(IO, eDelete); if (!RecvMsg->keep) { StrBufPrintf(RecvMsg->IO.SendBuf.Buf, "DELE %ld\r\n", RecvMsg->CurrMsg->MSGID); POP3C_DBG_SEND(); return eReadMessage; } else { RecvMsg->State = ReadMessageBodyFollowing; return POP3_C_DispatchWriteDone(&RecvMsg->IO); } } eNextState POP3C_ReadDeleteState(pop3aggr *RecvMsg) { AsyncIO *IO = &RecvMsg->IO; POP3C_DBG_READ(); RecvMsg->State = GetOneMessageIDState; return eReadMessage; } eNextState POP3C_SendQuit(pop3aggr *RecvMsg) { AsyncIO *IO = &RecvMsg->IO; SetPOP3State(IO, eQuit); /* Log out */ StrBufPlain(RecvMsg->IO.SendBuf.Buf, HKEY("QUIT\r\n3)")); POP3C_DBG_SEND(); return eReadMessage; } eNextState POP3C_ReadQuitState(pop3aggr *RecvMsg) { AsyncIO *IO = &RecvMsg->IO; POP3C_DBG_READ(); return eTerminateConnection; } const long POP3_C_ConnTimeout = 1000; const long DefaultPOP3Port = 110; Pop3ClientHandler POP3C_ReadHandlers[] = { POP3C_ReadGreeting, POP3C_GetUserState, POP3C_GetPassState, POP3C_GetListCommandState, POP3C_GetListOneLine, POP3C_GetOneMessageIDState, POP3C_ReadMessageBodyFollowing, POP3C_ReadMessageBody, POP3C_ReadDeleteState, POP3C_ReadQuitState, }; const long POP3_C_SendTimeouts[POP3C_MaxRead] = { 100, 100, 100, 100, 100, 100, 100, 100 }; const ConstStr POP3C_ReadErrors[POP3C_MaxRead] = { {HKEY("Connection broken during ")}, {HKEY("Connection broken during ")}, {HKEY("Connection broken during ")}, {HKEY("Connection broken during ")}, {HKEY("Connection broken during ")}, {HKEY("Connection broken during ")}, {HKEY("Connection broken during ")}, {HKEY("Connection broken during ")} }; Pop3ClientHandler POP3C_SendHandlers[] = { NULL, /* we don't send a greeting */ POP3C_SendUser, POP3C_SendPassword, POP3C_SendListCommand, NULL, POP3C_GetOneMessagID, POP3C_SendGetOneMsg, NULL, POP3C_SendDelete, POP3C_SendQuit }; const long POP3_C_ReadTimeouts[] = { 100, 100, 100, 100, 100, 100, 100, 100, 100, 100 }; /*****************************************************************************/ /* POP3 CLIENT DISPATCHER */ /*****************************************************************************/ void POP3SetTimeout(eNextState NextTCPState, pop3aggr *pMsg) { AsyncIO *IO = &pMsg->IO; double Timeout = 0.0; EVP3C_syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__); switch (NextTCPState) { case eSendFile: case eSendReply: case eSendMore: Timeout = POP3_C_SendTimeouts[pMsg->State]; /* if (pMsg->State == eDATABody) { / * if we're sending a huge message, we need more time. * / Timeout += StrLength(pMsg->msgtext) / 1024; } */ break; case eReadFile: case eReadMessage: Timeout = POP3_C_ReadTimeouts[pMsg->State]; /* if (pMsg->State == eDATATerminateBody) { / * * some mailservers take a nap before accepting the message * content inspection and such. * / Timeout += StrLength(pMsg->msgtext) / 1024; } */ break; case eReadPayload: Timeout = 100000; /* TODO!!! */ break; case eSendDNSQuery: case eReadDNSReply: case eConnect: case eTerminateConnection: case eDBQuery: case eAbort: case eReadMore://// TODO return; } SetNextTimeout(&pMsg->IO, Timeout); } eNextState POP3_C_DispatchReadDone(AsyncIO *IO) { /* EVP3CCS_syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__); to noisy anyways. */ pop3aggr *pMsg = IO->Data; eNextState rc; rc = POP3C_ReadHandlers[pMsg->State](pMsg); if (rc != eReadMore) pMsg->State++; POP3SetTimeout(rc, pMsg); return rc; } eNextState POP3_C_DispatchWriteDone(AsyncIO *IO) { pop3aggr *pMsg = IO->Data; eNextState rc; /* EVP3CCS_syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__); to noisy anyways. */ rc = POP3C_SendHandlers[pMsg->State](pMsg); POP3SetTimeout(rc, pMsg); return rc; } /*****************************************************************************/ /* POP3 CLIENT ERROR CATCHERS */ /*****************************************************************************/ eNextState POP3_C_Terminate(AsyncIO *IO) { /// pop3aggr *pMsg = (pop3aggr *)IO->Data; EVP3CCS_syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__); FinalizePOP3AggrRun(IO); return eAbort; } eNextState POP3_C_TerminateDB(AsyncIO *IO) { /// pop3aggr *pMsg = (pop3aggr *)IO->Data; EVP3CCS_syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__); FinalizePOP3AggrRun(IO); return eAbort; } eNextState POP3_C_Timeout(AsyncIO *IO) { pop3aggr *pMsg = IO->Data; EVP3CCS_syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__); StrBufPlain(IO->ErrMsg, CKEY(POP3C_ReadErrors[pMsg->State])); return FailAggregationRun(IO); } eNextState POP3_C_ConnFail(AsyncIO *IO) { pop3aggr *pMsg = (pop3aggr *)IO->Data; EVP3CCS_syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__); StrBufPlain(IO->ErrMsg, CKEY(POP3C_ReadErrors[pMsg->State])); return FailAggregationRun(IO); } eNextState POP3_C_DNSFail(AsyncIO *IO) { pop3aggr *pMsg = (pop3aggr *)IO->Data; EVP3CCS_syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__); StrBufPlain(IO->ErrMsg, CKEY(POP3C_ReadErrors[pMsg->State])); return FailAggregationRun(IO); } eNextState POP3_C_Shutdown(AsyncIO *IO) { EVP3CCS_syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__); //// pop3aggr *pMsg = IO->Data; ////pMsg->MyQEntry->Status = 3; ///StrBufPlain(pMsg->MyQEntry->StatusMessage, HKEY("server shutdown during message retrieval.")); FinalizePOP3AggrRun(IO); return eAbort; } /** * @brief lineread Handler; understands when to read more POP3 lines, * and when this is a one-lined reply. */ eReadState POP3_C_ReadServerStatus(AsyncIO *IO) { eReadState Finished = eBufferNotEmpty; switch (IO->NextState) { case eSendDNSQuery: case eReadDNSReply: case eDBQuery: case eConnect: case eTerminateConnection: case eAbort: Finished = eReadFail; break; case eSendFile: case eSendReply: case eSendMore: case eReadMore: case eReadMessage: Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf); break; case eReadFile: case eReadPayload: Finished = CtdlReadMessageBodyAsync(IO); break; } return Finished; } /***************************************************************************** * So we connect our Server IP here. * *****************************************************************************/ eNextState POP3_C_ReAttachToFetchMessages(AsyncIO *IO) { pop3aggr *cpptr = IO->Data; EVP3CCS_syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__); ////??? cpptr->State ++; if (cpptr->Pos == NULL) cpptr->Pos = GetNewHashPos(cpptr->MsgNumbers, 0); POP3_C_DispatchWriteDone(IO); ReAttachIO(IO, cpptr, 0); IO->NextState = eReadMessage; return IO->NextState; } eNextState pop3_connect_ip(AsyncIO *IO) { pop3aggr *cpptr = IO->Data; if (cpptr->IOStart == 0.0) /* whith or without DNS? */ cpptr->IOStart = IO->Now; EVP3CCS_syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__); return EvConnectSock(IO, POP3_C_ConnTimeout, POP3_C_ReadTimeouts[0], 1); } eNextState pop3_get_one_host_ip_done(AsyncIO *IO) { pop3aggr *cpptr = IO->Data; struct hostent *hostent; QueryCbDone(IO); hostent = cpptr->HostLookup.VParsedDNSReply; if ((cpptr->HostLookup.DNSStatus == ARES_SUCCESS) && (hostent != NULL) ) { memset(&cpptr->IO.ConnectMe->Addr, 0, sizeof(struct in6_addr)); if (cpptr->IO.ConnectMe->IPv6) { memcpy(&cpptr->IO.ConnectMe->Addr.sin6_addr.s6_addr, &hostent->h_addr_list[0], sizeof(struct in6_addr)); cpptr->IO.ConnectMe->Addr.sin6_family = hostent->h_addrtype; cpptr->IO.ConnectMe->Addr.sin6_port = htons(DefaultPOP3Port); } else { struct sockaddr_in *addr = (struct sockaddr_in*) &cpptr->IO.ConnectMe->Addr; memcpy(&addr->sin_addr.s_addr, hostent->h_addr_list[0], sizeof(uint32_t)); addr->sin_family = hostent->h_addrtype; addr->sin_port = htons(DefaultPOP3Port); } return pop3_connect_ip(IO); } else return eAbort; } eNextState pop3_get_one_host_ip(AsyncIO *IO) { pop3aggr *cpptr = IO->Data; cpptr->IOStart = IO->Now; EVP3CCS_syslog(LOG_DEBUG, "POP3: %s\n", __FUNCTION__); EVP3CCS_syslog(LOG_DEBUG, "POP3 client[%ld]: looking up %s-Record %s : %d ...\n", cpptr->n, (cpptr->IO.ConnectMe->IPv6)? "aaaa": "a", cpptr->IO.ConnectMe->Host, cpptr->IO.ConnectMe->Port); QueueQuery((cpptr->IO.ConnectMe->IPv6)? ns_t_aaaa : ns_t_a, cpptr->IO.ConnectMe->Host, &cpptr->IO, &cpptr->HostLookup, pop3_get_one_host_ip_done); IO->NextState = eReadDNSReply; return IO->NextState; } int pop3_do_fetching(pop3aggr *cpptr) { AsyncIO *IO = &cpptr->IO; InitIOStruct(IO, cpptr, eReadMessage, POP3_C_ReadServerStatus, POP3_C_DNSFail, POP3_C_DispatchWriteDone, POP3_C_DispatchReadDone, POP3_C_Terminate, POP3_C_TerminateDB, POP3_C_ConnFail, POP3_C_Timeout, POP3_C_Shutdown); safestrncpy(((CitContext *)cpptr->IO.CitContext)->cs_host, ChrPtr(cpptr->Url), sizeof(((CitContext *)cpptr->IO.CitContext)->cs_host)); if (cpptr->IO.ConnectMe->IsIP) { QueueEventContext(&cpptr->IO, pop3_connect_ip); } else { QueueEventContext(&cpptr->IO, pop3_get_one_host_ip); } return 1; } /* * Scan a room's netconfig to determine whether it requires POP3 aggregation */ void pop3client_scan_room(struct ctdlroom *qrbuf, void *data, OneRoomNetCfg *OneRNCFG) { const RoomNetCfgLine *pLine; void *vptr; pthread_mutex_lock(&POP3QueueMutex); if (GetHash(POP3QueueRooms, LKEY(qrbuf->QRnumber), &vptr)) { pthread_mutex_unlock(&POP3QueueMutex); EVP3CQ_syslog(LOG_DEBUG, "pop3client: [%ld] %s already in progress.", qrbuf->QRnumber, qrbuf->QRname); return; } pthread_mutex_unlock(&POP3QueueMutex); if (server_shutting_down) return; pLine = OneRNCFG->NetConfigs[pop3client]; while (pLine != NULL) { pop3aggr *cptr; cptr = (pop3aggr *) malloc(sizeof(pop3aggr)); memset(cptr, 0, sizeof(pop3aggr)); ///TODO do we need this? cptr->roomlist_parts=1; cptr->RoomName = NewStrBufPlain(qrbuf->QRname, -1); cptr->pop3user = NewStrBufDup(pLine->Value[1]); cptr->pop3pass = NewStrBufDup(pLine->Value[2]); cptr->Url = NewStrBuf(); cptr->Host = NewStrBufDup(pLine->Value[0]); cptr->keep = atol(ChrPtr(pLine->Value[3])); cptr->interval = atol(ChrPtr(pLine->Value[4])); StrBufAppendBufPlain(cptr->Url, HKEY("pop3://"), 0); StrBufUrlescUPAppend(cptr->Url, cptr->pop3user, NULL); StrBufAppendBufPlain(cptr->Url, HKEY(":"), 0); StrBufUrlescUPAppend(cptr->Url, cptr->pop3pass, NULL); StrBufAppendBufPlain(cptr->Url, HKEY("@"), 0); StrBufAppendBuf(cptr->Url, cptr->Host, 0); StrBufAppendBufPlain(cptr->Url, HKEY("/"), 0); StrBufUrlescAppend(cptr->Url, cptr->RoomName, NULL); ParseURL(&cptr->IO.ConnectMe, cptr->Url, 110); #if 0 /* todo: we need to reunite the url to be shure. */ pthread_mutex_lock(&POP3ueueMutex); GetHash(POP3FetchUrls, SKEY(ptr->Url), &vptr); use_this_cptr = (pop3aggr *)vptr; if (use_this_rncptr != NULL) { /* mustn't attach to an active session */ if (use_this_cptr->RefCount > 0) { DeletePOP3Cfg(cptr); /// Count->count--; } else { long *QRnumber; StrBufAppendBufPlain( use_this_cptr->rooms, qrbuf->QRname, -1, 0); if (use_this_cptr->roomlist_parts == 1) { use_this_cptr->OtherQRnumbers = NewHash(1, lFlathash); } QRnumber = (long*)malloc(sizeof(long)); *QRnumber = qrbuf->QRnumber; Put(use_this_cptr->OtherQRnumbers, LKEY(qrbuf->QRnumber), QRnumber, NULL); use_this_cptr->roomlist_parts++; } pthread_mutex_unlock(&POP3QueueMutex); continue; } pthread_mutex_unlock(&RSSQueueMutex); #endif cptr->n = Pop3ClientID++; pthread_mutex_lock(&POP3QueueMutex); Put(POP3FetchUrls, SKEY(cptr->Url), cptr, DeletePOP3Aggregator); pthread_mutex_unlock(&POP3QueueMutex); pLine = pLine->next; } } static int doing_pop3client = 0; void pop3client_scan(void) { static time_t last_run = 0L; time_t fastest_scan; HashPos *it; long len; const char *Key; void *vrptr; pop3aggr *cptr; become_session(&pop3_client_CC); if (config.c_pop3_fastest < config.c_pop3_fetch) fastest_scan = config.c_pop3_fastest; else fastest_scan = config.c_pop3_fetch; /* * Run POP3 aggregation no more frequently than once every n seconds */ if ( (time(NULL) - last_run) < fastest_scan ) { return; } /* * This is a simple concurrency check to make sure only one pop3client * run is done at a time. We could do this with a mutex, but since we * don't really require extremely fine granularity here, we'll do it * with a static variable instead. */ if (doing_pop3client) return; doing_pop3client = 1; EVP3CQM_syslog(LOG_DEBUG, "pop3client started"); CtdlForEachNetCfgRoom(pop3client_scan_room, NULL, pop3client); pthread_mutex_lock(&POP3QueueMutex); it = GetNewHashPos(POP3FetchUrls, 0); while (!server_shutting_down && GetNextHashPos(POP3FetchUrls, it, &len, &Key, &vrptr) && (vrptr != NULL)) { cptr = (pop3aggr *)vrptr; if (cptr->RefCount == 0) if (!pop3_do_fetching(cptr)) DeletePOP3Aggregator(cptr);////TODO /* if ((palist->interval && time(NULL) > (last_run + palist->interval)) || (time(NULL) > last_run + config.c_pop3_fetch)) pop3_do_fetching(palist->roomname, palist->pop3host, palist->pop3user, palist->pop3pass, palist->keep); pptr = palist; palist = palist->next; free(pptr); */ } DeleteHashPos(&it); pthread_mutex_unlock(&POP3QueueMutex); EVP3CQM_syslog(LOG_DEBUG, "pop3client ended"); last_run = time(NULL); doing_pop3client = 0; } void pop3_cleanup(void) { /* citthread_mutex_destroy(&POP3QueueMutex); TODO */ while (doing_pop3client != 0) ; DeleteHash(&POP3FetchUrls); DeleteHash(&POP3QueueRooms); } void LogDebugEnablePOP3Client(const int n) { POP3ClientDebugEnabled = n; } CTDL_MODULE_INIT(pop3client) { if (!threading) { CtdlFillSystemContext(&pop3_client_CC, "POP3aggr"); CtdlREGISTERRoomCfgType(pop3client, ParseGeneric, 0, 5, SerializeGeneric, DeleteGenericCfgLine); pthread_mutex_init(&POP3QueueMutex, NULL); POP3QueueRooms = NewHash(1, lFlathash); POP3FetchUrls = NewHash(1, NULL); CtdlRegisterSessionHook(pop3client_scan, EVT_TIMER, PRIO_AGGR + 50); CtdlRegisterEVCleanupHook(pop3_cleanup); CtdlRegisterDebugFlagHook(HKEY("pop3client"), LogDebugEnablePOP3Client, &POP3ClientDebugEnabled); } /* return our module id for the log */ return "pop3client"; } citadel-9.01/modules/vcard/0000755000000000000000000000000012507024051014311 5ustar rootrootcitadel-9.01/modules/vcard/serv_vcard.c0000644000000000000000000012347312507024051016625 0ustar rootroot/* * A server-side module for Citadel which supports address book information * using the standard vCard format. * * Copyright (c) 1999-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ /* * Format of the "Exclusive ID" field of the message containing a user's * vCard. Doesn't matter what it really looks like as long as it's both * unique and consistent (because we use it for replication checking to * delete the old vCard network-wide when the user enters a new one). */ #define VCARD_EXT_FORMAT "Citadel vCard: personal card for %s at %s" /* * Citadel will accept either text/vcard or text/x-vcard as the MIME type * for a vCard. The following definition determines which one it *generates* * when serializing. */ #define VCARD_MIME_TYPE "text/x-vcard" #include "sysdep.h" #include #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "control.h" #include "user_ops.h" #include "database.h" #include "msgbase.h" #include "room_ops.h" #include "internet_addressing.h" #include "serv_vcard.h" #include "citadel_ldap.h" #include "ctdl_module.h" /* * set global flag calling for an aide to validate new users */ void set_mm_valid(void) { begin_critical_section(S_CONTROL); get_control(); CitControl.MMflags = CitControl.MMflags | MM_VALID ; put_control(); end_critical_section(S_CONTROL); } /* * Extract Internet e-mail addresses from a message containing a vCard, and * perform a callback for any found. */ void vcard_extract_internet_addresses(struct CtdlMessage *msg, int (*callback)(char *, char *) ) { struct vCard *v; char *s; char *k; char *addr; char citadel_address[SIZ]; int instance = 0; int found_something = 0; if (CM_IsEmpty(msg, eAuthor)) return; if (CM_IsEmpty(msg, eNodeName)) return; snprintf(citadel_address, sizeof citadel_address, "%s @ %s", msg->cm_fields[eAuthor], msg->cm_fields[eNodeName]); v = vcard_load(msg->cm_fields[eMesageText]); if (v == NULL) return; /* Go through the vCard searching for *all* instances of * the "email;internet" key */ do { s = vcard_get_prop(v, "email", 1, instance, 0); /* get any 'email' field */ k = vcard_get_prop(v, "email", 1, instance++, 1); /* but also learn it with attrs */ if ( (s != NULL) && (k != NULL) && (bmstrcasestr(k, "internet")) ) { addr = strdup(s); striplt(addr); if (!IsEmptyStr(addr)) { if (callback != NULL) { callback(addr, citadel_address); } } free(addr); found_something = 1; } else { found_something = 0; } } while(found_something); vcard_free(v); } ///TODO: gettext! #define _(a) a /* * Callback for vcard_add_to_directory() * (Lotsa ugly nested callbacks. Oh well.) */ int vcard_directory_add_user(char *internet_addr, char *citadel_addr) { struct CitContext *CCC = CC; char buf[SIZ]; /* We have to validate that we're not stepping on someone else's * email address ... but only if we're logged in. Otherwise it's * probably just the networker or something. */ if (CCC->logged_in) { syslog(LOG_DEBUG, "Checking for <%s>...", internet_addr); if (CtdlDirectoryLookup(buf, internet_addr, sizeof buf) == 0) { if (strcasecmp(buf, citadel_addr)) { /* This address belongs to someone else. * Bail out silently without saving. */ syslog(LOG_DEBUG, "DOOP!"); StrBufAppendPrintf(CCC->StatusMessage, "\n%d|", ERROR+ALREADY_EXISTS); StrBufAppendBufPlain(CCC->StatusMessage, internet_addr, -1, 0); StrBufAppendBufPlain(CCC->StatusMessage, HKEY("|"), 0); StrBufAppendBufPlain(CCC->StatusMessage, _("Unable to add this email address again."), -1, 0); StrBufAppendBufPlain(CCC->StatusMessage, HKEY("\n"), 0); return 0; } } } syslog(LOG_INFO, "Adding %s (%s) to directory", citadel_addr, internet_addr); if (CtdlDirectoryAddUser(internet_addr, citadel_addr)) { StrBufAppendPrintf(CCC->StatusMessage, "\n%d|", CIT_OK); StrBufAppendBufPlain(CCC->StatusMessage, internet_addr, -1, 0); StrBufAppendBufPlain(CCC->StatusMessage, HKEY("|"), 0); StrBufAppendBufPlain(CCC->StatusMessage, _("Successfully added email address."), -1, 0); return 1; } else { StrBufAppendPrintf(CCC->StatusMessage, "\n%d|", ERROR+ ILLEGAL_VALUE); StrBufAppendBufPlain(CCC->StatusMessage, internet_addr, -1, 0); StrBufAppendBufPlain(CCC->StatusMessage, HKEY("|"), 0); StrBufAppendBufPlain(CCC->StatusMessage, _("Unable to add this email address. It does not match any local domain."), -1, 0); return 0; } } /* * Back end function for cmd_igab() */ void vcard_add_to_directory(long msgnum, void *data) { struct CtdlMessage *msg; msg = CtdlFetchMessage(msgnum, 1); if (msg != NULL) { vcard_extract_internet_addresses(msg, vcard_directory_add_user); } CM_Free(msg); } /* * Initialize Global Adress Book */ void cmd_igab(char *argbuf) { char hold_rm[ROOMNAMELEN]; if (CtdlAccessCheck(ac_aide)) return; strcpy(hold_rm, CC->room.QRname); /* save current room */ if (CtdlGetRoom(&CC->room, ADDRESS_BOOK_ROOM) != 0) { CtdlGetRoom(&CC->room, hold_rm); cprintf("%d cannot get address book room\n", ERROR + ROOM_NOT_FOUND); return; } /* Empty the existing database first. */ CtdlDirectoryInit(); /* We want *all* vCards in this room */ NewStrBufDupAppendFlush(&CC->StatusMessage, NULL, NULL, 0); CtdlForEachMessage(MSGS_ALL, 0, NULL, "[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$", NULL, vcard_add_to_directory, NULL); CtdlGetRoom(&CC->room, hold_rm); /* return to saved room */ cprintf("%d Directory has been rebuilt.\n", CIT_OK); } /* * See if there is a valid Internet address in a vCard to use for outbound * Internet messages. If there is, stick it in the buffer. */ void extract_inet_email_addrs(char *emailaddrbuf, size_t emailaddrbuf_len, char *secemailaddrbuf, size_t secemailaddrbuf_len, struct vCard *v, int local_addrs_only) { struct CitContext *CCC = CC; /* put this on the stack, just for speed */ char *s, *k, *addr; int instance = 0; int IsDirectoryAddress; int saved_instance = 0; /* Go through the vCard searching for *all* Internet email addresses */ while (s = vcard_get_prop(v, "email", 1, instance, 0), s != NULL) { k = vcard_get_prop(v, "email", 1, instance, 1); if ( (s != NULL) && (k != NULL) && (bmstrcasestr(k, "internet")) ) { addr = strdup(s); striplt(addr); if (!IsEmptyStr(addr)) { IsDirectoryAddress = IsDirectory(addr, 1); if ( IsDirectoryAddress || !local_addrs_only) { ++saved_instance; if ((saved_instance == 1) && (emailaddrbuf != NULL)) { safestrncpy(emailaddrbuf, addr, emailaddrbuf_len); } else if ((saved_instance == 2) && (secemailaddrbuf != NULL)) { safestrncpy(secemailaddrbuf, addr, secemailaddrbuf_len); } else if ((saved_instance > 2) && (secemailaddrbuf != NULL)) { if ( (strlen(addr) + strlen(secemailaddrbuf) + 2) < secemailaddrbuf_len ) { strcat(secemailaddrbuf, "|"); strcat(secemailaddrbuf, addr); } } } if (!IsDirectoryAddress && local_addrs_only) { StrBufAppendPrintf(CCC->StatusMessage, "\n%d|", ERROR+ ILLEGAL_VALUE); StrBufAppendBufPlain(CCC->StatusMessage, addr, -1, 0); StrBufAppendBufPlain(CCC->StatusMessage, HKEY("|"), 0); StrBufAppendBufPlain(CCC->StatusMessage, _("unable to add this emailaddress; its not matching our domain."), -1, 0); } } free(addr); } ++instance; } } /* * See if there is a name / screen name / friendly name in a vCard to use for outbound * Internet messages. If there is, stick it in the buffer. */ void extract_friendly_name(char *namebuf, size_t namebuf_len, struct vCard *v) { char *s; s = vcard_get_prop(v, "fn", 1, 0, 0); if (s == NULL) { s = vcard_get_prop(v, "n", 1, 0, 0); } if (s != NULL) { safestrncpy(namebuf, s, namebuf_len); } } /* * Callback function for vcard_upload_beforesave() hunts for the real vcard in the MIME structure */ void vcard_extract_vcard(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata) { struct vCard **v = (struct vCard **) cbuserdata; if ( (!strcasecmp(cbtype, "text/x-vcard")) || (!strcasecmp(cbtype, "text/vcard")) ) { syslog(LOG_DEBUG, "Part %s contains a vCard! Loading...", partnum); if (*v != NULL) { vcard_free(*v); } *v = vcard_load(content); } } /* * This handler detects whether the user is attempting to save a new * vCard as part of his/her personal configuration, and handles the replace * function accordingly (delete the user's existing vCard in the config room * and in the global address book). */ int vcard_upload_beforesave(struct CtdlMessage *msg, recptypes *recp) { struct CitContext *CCC = CC; char *s; char buf[SIZ]; struct ctdluser usbuf; long what_user; struct vCard *v = NULL; char *ser = NULL; int i = 0; int yes_my_citadel_config = 0; int yes_any_vcard_room = 0; if ((!CCC->logged_in) && (CCC->vcard_updated_by_ldap==0)) return(0); /* Only do this if logged in, or if ldap changed the vcard. */ /* Is this some user's "My Citadel Config" room? */ if (((CCC->room.QRflags & QR_MAILBOX) != 0) && (!strcasecmp(&CCC->room.QRname[11], USERCONFIGROOM)) ) { /* Yes, we want to do this */ yes_my_citadel_config = 1; #ifdef VCARD_SAVES_BY_AIDES_ONLY /* Prevent non-aides from performing registration changes, but ldap is ok. */ if ((CCC->user.axlevel < AxAideU) && (CCC->vcard_updated_by_ldap==0)) { return(1); } #endif } /* Is this a room with an address book in it? */ if (CCC->room.QRdefaultview == VIEW_ADDRESSBOOK) { yes_any_vcard_room = 1; } /* If neither condition exists, don't run this hook. */ if ( (!yes_my_citadel_config) && (!yes_any_vcard_room) ) { return(0); } /* If this isn't a MIME message, don't bother. */ if (msg->cm_format_type != 4) return(0); /* Ok, if we got this far, look into the situation further... */ if (CM_IsEmpty(msg, eMesageText)) return(0); mime_parser(CM_RANGE(msg, eMesageText), *vcard_extract_vcard, NULL, NULL, &v, /* user data ptr - put the vcard here */ 0 ); if (v == NULL) return(0); /* no vCards were found in this message */ /* If users cannot create their own accounts, they cannot re-register either. */ if ( (yes_my_citadel_config) && (config.c_disable_newu) && (CCC->user.axlevel < AxAideU) && (CCC->vcard_updated_by_ldap==0) ) { return(1); } vcard_get_prop(v, "fn", 1, 0, 0); if (yes_my_citadel_config) { /* Bingo! The user is uploading a new vCard, so * delete the old one. First, figure out which user * is being re-registered... */ what_user = atol(CCC->room.QRname); if (what_user == CCC->user.usernum) { /* It's the logged in user. That was easy. */ memcpy(&usbuf, &CCC->user, sizeof(struct ctdluser)); } else if (CtdlGetUserByNumber(&usbuf, what_user) == 0) { /* We fetched a valid user record */ } else { /* somebody set up us the bomb! */ yes_my_citadel_config = 0; } } if (yes_my_citadel_config) { /* Delete the user's old vCard. This would probably * get taken care of by the replication check, but we * want to make sure there is absolutely only one * vCard in the user's config room at all times. * */ CtdlDeleteMessages(CCC->room.QRname, NULL, 0, "[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$"); /* Make the author of the message the name of the user. */ CM_SetField(msg, eAuthor, usbuf.fullname, strlen(usbuf.fullname)); } /* Insert or replace RFC2739-compliant free/busy URL */ if (yes_my_citadel_config) { sprintf(buf, "http://%s/%s.vfb", config.c_fqdn, usbuf.fullname); for (i=0; buf[i]; ++i) { if (buf[i] == ' ') buf[i] = '_'; } vcard_set_prop(v, "FBURL;PREF", buf, 0); } s = vcard_get_prop(v, "UID", 1, 0, 0); if (s == NULL) { /* Note LDAP auth sets UID from the LDAP UUID, use that if it exists. */ /* Enforce local UID policy if applicable */ if (yes_my_citadel_config) { snprintf(buf, sizeof buf, VCARD_EXT_FORMAT, msg->cm_fields[eAuthor], NODENAME); } else { /* If the vCard has no UID, then give it one. */ generate_uuid(buf); } vcard_set_prop(v, "UID", buf, 0); } /* * Set the EUID of the message to the UID of the vCard. */ CM_FlushField(msg, eExclusiveID); s = vcard_get_prop(v, "UID", 1, 0, 0); if (s != NULL) { CM_SetField(msg, eExclusiveID, s, strlen(s)); if (CM_IsEmpty(msg, eMsgSubject)) { CM_CopyField(msg, eMsgSubject, eExclusiveID); } } /* * Set the Subject to the name in the vCard. */ s = vcard_get_prop(v, "FN", 1, 0, 0); if (s == NULL) { s = vcard_get_prop(v, "N", 1, 0, 0); } if (s != NULL) { CM_SetField(msg, eMsgSubject, s, strlen(s)); } /* Re-serialize it back into the msg body */ ser = vcard_serialize(v); if (ser != NULL) { StrBuf *buf; long serlen; serlen = strlen(ser); buf = NewStrBufPlain(NULL, serlen + 1024); StrBufAppendBufPlain(buf, HKEY("Content-type: " VCARD_MIME_TYPE "\r\n\r\n"), 0); StrBufAppendBufPlain(buf, ser, serlen, 0); StrBufAppendBufPlain(buf, HKEY("\r\n"), 0); CM_SetAsFieldSB(msg, eMesageText, &buf); free(ser); } /* Now allow the save to complete. */ vcard_free(v); return(0); } /* * This handler detects whether the user is attempting to save a new * vCard as part of his/her personal configuration, and handles the replace * function accordingly (copy the vCard from the config room to the global * address book). */ int vcard_upload_aftersave(struct CtdlMessage *msg, recptypes *recp) { struct CitContext *CCC = CC; char *ptr; int linelen; long I; struct vCard *v; int is_UserConf=0; int is_MY_UserConf=0; int is_GAB=0; char roomname[ROOMNAMELEN]; if (msg->cm_format_type != 4) return(0); if ((!CCC->logged_in) && (CCC->vcard_updated_by_ldap==0)) return(0); /* Only do this if logged in, or if ldap changed the vcard. */ /* We're interested in user config rooms only. */ if ( (strlen(CCC->room.QRname) >= 12) && (!strcasecmp(&CCC->room.QRname[11], USERCONFIGROOM)) ) { is_UserConf = 1; /* It's someone's config room */ } CtdlMailboxName(roomname, sizeof roomname, &CCC->user, USERCONFIGROOM); if (!strcasecmp(CCC->room.QRname, roomname)) { is_UserConf = 1; is_MY_UserConf = 1; /* It's MY config room */ } if (!strcasecmp(CCC->room.QRname, ADDRESS_BOOK_ROOM)) { is_GAB = 1; /* It's the Global Address Book */ } if (!is_UserConf && !is_GAB) return(0); if (CM_IsEmpty(msg, eMesageText)) return 0; ptr = msg->cm_fields[eMesageText]; CCC->vcard_updated_by_ldap=0; /* As this will write LDAP's previous changes, disallow LDAP change auth until next LDAP change. */ NewStrBufDupAppendFlush(&CCC->StatusMessage, NULL, NULL, 0); StrBufPrintf(CCC->StatusMessage, "%d\n", LISTING_FOLLOWS); while (ptr != NULL) { linelen = strcspn(ptr, "\n"); if (linelen == 0) return(0); /* end of headers */ if ( (!strncasecmp(ptr, "Content-type: text/x-vcard", 26)) || (!strncasecmp(ptr, "Content-type: text/vcard", 24)) ) { /* * Bingo! The user is uploading a new vCard, so * copy it to the Global Address Book room. */ I = atol(msg->cm_fields[eVltMsgNum]); if (I <= 0L) return(0); /* Store our Internet return address in memory */ if (is_MY_UserConf) { v = vcard_load(msg->cm_fields[eMesageText]); extract_inet_email_addrs(CCC->cs_inet_email, sizeof CCC->cs_inet_email, CCC->cs_inet_other_emails, sizeof CCC->cs_inet_other_emails, v, 1); extract_friendly_name(CCC->cs_inet_fn, sizeof CCC->cs_inet_fn, v); vcard_free(v); } if (!is_GAB) { // This is not the GAB /* Put it in the Global Address Book room... */ CtdlSaveMsgPointerInRoom(ADDRESS_BOOK_ROOM, I, 1, msg); } /* ...and also in the directory database. */ vcard_add_to_directory(I, NULL); /* Some sites want an Aide to be notified when a * user registers or re-registers * But if the user was an Aide or was edited by an Aide then we can * Assume they don't need validating. */ if (CCC->user.axlevel >= AxAideU) { CtdlLockGetCurrentUser(); CCC->user.flags |= US_REGIS; CtdlPutCurrentUserLock(); return (0); } set_mm_valid(); /* ...which also means we need to flag the user */ CtdlLockGetCurrentUser(); CCC->user.flags |= (US_REGIS|US_NEEDVALID); CtdlPutCurrentUserLock(); return(0); } ptr = strchr((char *)ptr, '\n'); if (ptr != NULL) ++ptr; } return(0); } /* * back end function used for callbacks */ void vcard_gu_backend(long supplied_msgnum, void *userdata) { long *msgnum; msgnum = (long *) userdata; *msgnum = supplied_msgnum; } /* * If this user has a vcard on disk, read it into memory, otherwise allocate * and return an empty vCard. */ struct vCard *vcard_get_user(struct ctdluser *u) { struct CitContext *CCC = CC; char hold_rm[ROOMNAMELEN]; char config_rm[ROOMNAMELEN]; struct CtdlMessage *msg = NULL; struct vCard *v; long VCmsgnum; strcpy(hold_rm, CCC->room.QRname); /* save current room */ CtdlMailboxName(config_rm, sizeof config_rm, u, USERCONFIGROOM); if (CtdlGetRoom(&CCC->room, config_rm) != 0) { CtdlGetRoom(&CCC->room, hold_rm); return vcard_new(); } /* We want the last (and probably only) vcard in this room */ VCmsgnum = (-1); CtdlForEachMessage(MSGS_LAST, 1, NULL, "[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$", NULL, vcard_gu_backend, (void *)&VCmsgnum ); CtdlGetRoom(&CCC->room, hold_rm); /* return to saved room */ if (VCmsgnum < 0L) return vcard_new(); msg = CtdlFetchMessage(VCmsgnum, 1); if (msg == NULL) return vcard_new(); v = vcard_load(msg->cm_fields[eMesageText]); CM_Free(msg); return v; } /* * Store this user's vCard in the appropriate place */ /* * Write our config to disk */ void vcard_write_user(struct ctdluser *u, struct vCard *v) { char *ser; ser = vcard_serialize(v); if (ser == NULL) { ser = strdup("begin:vcard\r\nend:vcard\r\n"); } if (!ser) return; /* This handy API function does all the work for us. * NOTE: normally we would want to set that last argument to 1, to * force the system to delete the user's old vCard. But it doesn't * have to, because the vcard_upload_beforesave() hook above * is going to notice what we're trying to do, and delete the old vCard. */ CtdlWriteObject(USERCONFIGROOM, /* which room */ VCARD_MIME_TYPE, /* MIME type */ ser, /* data */ strlen(ser)+1, /* length */ u, /* which user */ 0, /* not binary */ 0, /* don't delete others of this type */ 0); /* no flags */ free(ser); } /* * Old style "enter registration info" command. This function simply honors * the REGI protocol command, translates the entered parameters into a vCard, * and enters the vCard into the user's configuration. */ void cmd_regi(char *argbuf) { struct CitContext *CCC = CC; int a,b,c; char buf[SIZ]; struct vCard *my_vcard; char tmpaddr[SIZ]; char tmpcity[SIZ]; char tmpstate[SIZ]; char tmpzip[SIZ]; char tmpaddress[SIZ]; char tmpcountry[SIZ]; unbuffer_output(); if (!(CCC->logged_in)) { cprintf("%d Not logged in.\n",ERROR + NOT_LOGGED_IN); return; } /* If users cannot create their own accounts, they cannot re-register either. */ if ( (config.c_disable_newu) && (CCC->user.axlevel < AxAideU) ) { cprintf("%d Self-service registration is not allowed here.\n", ERROR + HIGHER_ACCESS_REQUIRED); } my_vcard = vcard_get_user(&CCC->user); strcpy(tmpaddr, ""); strcpy(tmpcity, ""); strcpy(tmpstate, ""); strcpy(tmpzip, ""); strcpy(tmpcountry, "USA"); cprintf("%d Send registration...\n", SEND_LISTING); a=0; while (client_getln(buf, sizeof buf), strcmp(buf,"000")) { if (a==0) vcard_set_prop(my_vcard, "n", buf, 0); if (a==1) strcpy(tmpaddr, buf); if (a==2) strcpy(tmpcity, buf); if (a==3) strcpy(tmpstate, buf); if (a==4) { for (c=0; buf[c]; ++c) { if ((buf[c]>='0') && (buf[c]<='9')) { b = strlen(tmpzip); tmpzip[b] = buf[c]; tmpzip[b+1] = 0; } } } if (a==5) vcard_set_prop(my_vcard, "tel", buf, 0); if (a==6) vcard_set_prop(my_vcard, "email;internet", buf, 0); if (a==7) strcpy(tmpcountry, buf); ++a; } snprintf(tmpaddress, sizeof tmpaddress, ";;%s;%s;%s;%s;%s", tmpaddr, tmpcity, tmpstate, tmpzip, tmpcountry); vcard_set_prop(my_vcard, "adr", tmpaddress, 0); vcard_write_user(&CCC->user, my_vcard); vcard_free(my_vcard); } /* * Protocol command to fetch registration info for a user */ void cmd_greg(char *argbuf) { struct CitContext *CCC = CC; struct ctdluser usbuf; struct vCard *v; char *s; char who[USERNAME_SIZE]; char adr[256]; char buf[256]; extract_token(who, argbuf, 0, '|', sizeof who); if (!(CCC->logged_in)) { cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN); return; } if (!strcasecmp(who,"_SELF_")) strcpy(who,CCC->curr_user); if ((CCC->user.axlevel < AxAideU) && (strcasecmp(who,CCC->curr_user))) { cprintf("%d Higher access required.\n", ERROR + HIGHER_ACCESS_REQUIRED); return; } if (CtdlGetUser(&usbuf, who) != 0) { cprintf("%d '%s' not found.\n", ERROR + NO_SUCH_USER, who); return; } v = vcard_get_user(&usbuf); cprintf("%d %s\n", LISTING_FOLLOWS, usbuf.fullname); cprintf("%ld\n", usbuf.usernum); cprintf("%s\n", usbuf.password); s = vcard_get_prop(v, "n", 1, 0, 0); cprintf("%s\n", s ? s : " "); /* name */ s = vcard_get_prop(v, "adr", 1, 0, 0); snprintf(adr, sizeof adr, "%s", s ? s : " ");/* address... */ extract_token(buf, adr, 2, ';', sizeof buf); cprintf("%s\n", buf); /* street */ extract_token(buf, adr, 3, ';', sizeof buf); cprintf("%s\n", buf); /* city */ extract_token(buf, adr, 4, ';', sizeof buf); cprintf("%s\n", buf); /* state */ extract_token(buf, adr, 5, ';', sizeof buf); cprintf("%s\n", buf); /* zip */ s = vcard_get_prop(v, "tel", 1, 0, 0); if (s == NULL) s = vcard_get_prop(v, "tel", 1, 0, 0); if (s != NULL) { cprintf("%s\n", s); } else { cprintf(" \n"); } cprintf("%d\n", usbuf.axlevel); s = vcard_get_prop(v, "email;internet", 0, 0, 0); cprintf("%s\n", s ? s : " "); s = vcard_get_prop(v, "adr", 0, 0, 0); snprintf(adr, sizeof adr, "%s", s ? s : " ");/* address... */ extract_token(buf, adr, 6, ';', sizeof buf); cprintf("%s\n", buf); /* country */ cprintf("000\n"); vcard_free(v); } /* * When a user is being created, create his/her vCard. */ void vcard_newuser(struct ctdluser *usbuf) { char vname[256]; char buf[256]; int i; struct vCard *v; int need_default_vcard; need_default_vcard =1; vcard_fn_to_n(vname, usbuf->fullname, sizeof vname); syslog(LOG_DEBUG, "Converted <%s> to <%s>", usbuf->fullname, vname); /* Create and save the vCard */ v = vcard_new(); if (v == NULL) return; vcard_add_prop(v, "fn", usbuf->fullname); vcard_add_prop(v, "n", vname); vcard_add_prop(v, "adr", "adr:;;_;_;_;00000;__"); #ifdef HAVE_GETPWUID_R /* If using host auth mode, we add an email address based on the login */ if (config.c_auth_mode == AUTHMODE_HOST) { struct passwd pwd; char pwd_buffer[SIZ]; #ifdef SOLARIS_GETPWUID if (getpwuid_r(usbuf->uid, &pwd, pwd_buffer, sizeof pwd_buffer) != NULL) { #else // SOLARIS_GETPWUID struct passwd *result = NULL; syslog(LOG_DEBUG, "Searching for uid %d", usbuf->uid); if (getpwuid_r(usbuf->uid, &pwd, pwd_buffer, sizeof pwd_buffer, &result) == 0) { #endif // HAVE_GETPWUID_R snprintf(buf, sizeof buf, "%s@%s", pwd.pw_name, config.c_fqdn); vcard_add_prop(v, "email;internet", buf); need_default_vcard=0; } } #endif #ifdef HAVE_LDAP /* * Is this an LDAP session? If so, copy various LDAP attributes from the directory entry * into the user's vCard. */ if ((config.c_auth_mode == AUTHMODE_LDAP) || (config.c_auth_mode == AUTHMODE_LDAP_AD)) { //uid_t ldap_uid; int found_user; char ldap_cn[512]; char ldap_dn[512]; found_user = CtdlTryUserLDAP(usbuf->fullname, ldap_dn, sizeof ldap_dn, ldap_cn, sizeof ldap_cn, &usbuf->uid,1); if (found_user == 0) { if (Ctdl_LDAP_to_vCard(ldap_dn, v)) { /* Allow global address book and internet directory update without login long enough to write this. */ CC->vcard_updated_by_ldap++; /* Otherwise we'll only update the user config. */ need_default_vcard=0; syslog(LOG_DEBUG, "LDAP Created Initial Vcard for %s\n",usbuf->fullname); } } } #endif if (need_default_vcard!=0) { /* Everyone gets an email address based on their display name */ snprintf(buf, sizeof buf, "%s@%s", usbuf->fullname, config.c_fqdn); for (i=0; buf[i]; ++i) { if (buf[i] == ' ') buf[i] = '_'; } vcard_add_prop(v, "email;internet", buf); } vcard_write_user(usbuf, v); vcard_free(v); } /* * When a user is being deleted, we have to remove his/her vCard. * This is accomplished by issuing a message with 'CANCEL' in the S (special) * field, and the same Exclusive ID as the existing card. */ void vcard_purge(struct ctdluser *usbuf) { struct CtdlMessage *msg; char buf[SIZ]; long len; msg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage)); if (msg == NULL) return; memset(msg, 0, sizeof(struct CtdlMessage)); msg->cm_magic = CTDLMESSAGE_MAGIC; msg->cm_anon_type = MES_NORMAL; msg->cm_format_type = 0; CM_SetField(msg, eAuthor, usbuf->fullname, strlen(usbuf->fullname)); CM_SetField(msg, eOriginalRoom, HKEY(ADDRESS_BOOK_ROOM)); CM_SetField(msg, eNodeName, CFG_KEY(c_nodename)); CM_SetField(msg, eMesageText, HKEY("Purge this vCard\n")); len = snprintf(buf, sizeof buf, VCARD_EXT_FORMAT, msg->cm_fields[eAuthor], NODENAME); CM_SetField(msg, eExclusiveID, buf, len); CM_SetField(msg, eSpecialField, HKEY("CANCEL")); CtdlSubmitMsg(msg, NULL, ADDRESS_BOOK_ROOM, QP_EADDR); CM_Free(msg); } /* * Grab vCard directory stuff out of incoming network messages */ int vcard_extract_from_network(struct CtdlMessage *msg, char *target_room) { char *ptr; int linelen; if (msg == NULL) return(0); if (strcasecmp(target_room, ADDRESS_BOOK_ROOM)) { return(0); } if (msg->cm_format_type != 4) return(0); if (CM_IsEmpty(msg, eMesageText)) return 0; ptr = msg->cm_fields[eMesageText]; while (ptr != NULL) { linelen = strcspn(ptr, "\n"); if (linelen == 0) return(0); /* end of headers */ if ( (!strncasecmp(ptr, "Content-type: text/x-vcard", 26)) || (!strncasecmp(ptr, "Content-type: text/vcard", 24)) ) { /* It's a vCard. Add it to the directory. */ vcard_extract_internet_addresses(msg, CtdlDirectoryAddUser); return(0); } ptr = strchr((char *)ptr, '\n'); if (ptr != NULL) ++ptr; } return(0); } /* * When a vCard is being removed from the Global Address Book room, remove it * from the directory as well. */ void vcard_delete_remove(char *room, long msgnum) { struct CtdlMessage *msg; char *ptr; int linelen; if (msgnum <= 0L) return; if (room == NULL) return; if (strcasecmp(room, ADDRESS_BOOK_ROOM)) { return; } msg = CtdlFetchMessage(msgnum, 1); if (msg == NULL) return; if (CM_IsEmpty(msg, eMesageText)) goto EOH; ptr = msg->cm_fields[eMesageText]; while (ptr != NULL) { linelen = strcspn(ptr, "\n"); if (linelen == 0) goto EOH; if ( (!strncasecmp(ptr, "Content-type: text/x-vcard", 26)) || (!strncasecmp(ptr, "Content-type: text/vcard", 24)) ) { /* Bingo! A vCard is being deleted. */ vcard_extract_internet_addresses(msg, CtdlDirectoryDelUser); } ptr = strchr((char *)ptr, '\n'); if (ptr != NULL) ++ptr; } EOH: CM_Free(msg); } /* * Get Valid Screen Names */ void cmd_gvsn(char *argbuf) { struct CitContext *CCC = CC; if (CtdlAccessCheck(ac_logged_in)) return; cprintf("%d valid screen names:\n", LISTING_FOLLOWS); cprintf("%s\n", CCC->user.fullname); if ( (!IsEmptyStr(CCC->cs_inet_fn)) && (strcasecmp(CCC->user.fullname, CCC->cs_inet_fn)) ) { cprintf("%s\n", CCC->cs_inet_fn); } cprintf("000\n"); } /* * Get Valid Email Addresses */ void cmd_gvea(char *argbuf) { struct CitContext *CCC = CC; int num_secondary_emails = 0; int i; char buf[256]; if (CtdlAccessCheck(ac_logged_in)) return; cprintf("%d valid email addresses:\n", LISTING_FOLLOWS); if (!IsEmptyStr(CCC->cs_inet_email)) { cprintf("%s\n", CCC->cs_inet_email); } if (!IsEmptyStr(CCC->cs_inet_other_emails)) { num_secondary_emails = num_tokens(CCC->cs_inet_other_emails, '|'); for (i=0; ics_inet_other_emails,i,'|',sizeof CCC->cs_inet_other_emails); cprintf("%s\n", buf); } } cprintf("000\n"); } /* * Callback function for cmd_dvca() that hunts for vCard content types * and outputs any email addresses found within. */ void dvca_mime_callback(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata) { struct vCard *v; char displayname[256] = ""; int displayname_len; char emailaddr[256] = ""; int i; int has_commas = 0; if ( (strcasecmp(cbtype, "text/vcard")) && (strcasecmp(cbtype, "text/x-vcard")) ) { return; } v = vcard_load(content); if (v == NULL) return; extract_friendly_name(displayname, sizeof displayname, v); extract_inet_email_addrs(emailaddr, sizeof emailaddr, NULL, 0, v, 0); displayname_len = strlen(displayname); for (i=0; i\n", (has_commas ? "\"" : ""), displayname, (has_commas ? "\"" : ""), emailaddr ); vcard_free(v); } /* * Back end callback function for cmd_dvca() * * It's basically just passed a list of message numbers, which we're going * to fetch off the disk and then pass along to the MIME parser via another * layer of callback... */ void dvca_callback(long msgnum, void *userdata) { struct CtdlMessage *msg = NULL; msg = CtdlFetchMessage(msgnum, 1); if (msg == NULL) return; mime_parser(CM_RANGE(msg, eMesageText), *dvca_mime_callback, /* callback function */ NULL, NULL, NULL, /* user data */ 0 ); CM_Free(msg); } /* * Dump VCard Addresses */ void cmd_dvca(char *argbuf) { if (CtdlAccessCheck(ac_logged_in)) return; cprintf("%d addresses:\n", LISTING_FOLLOWS); CtdlForEachMessage(MSGS_ALL, 0, NULL, NULL, NULL, dvca_callback, NULL); cprintf("000\n"); } /* * Query Directory */ void cmd_qdir(char *argbuf) { char citadel_addr[256]; char internet_addr[256]; if (CtdlAccessCheck(ac_logged_in)) return; extract_token(internet_addr, argbuf, 0, '|', sizeof internet_addr); if (CtdlDirectoryLookup(citadel_addr, internet_addr, sizeof citadel_addr) != 0) { cprintf("%d %s was not found.\n", ERROR + NO_SUCH_USER, internet_addr); return; } cprintf("%d %s\n", CIT_OK, citadel_addr); } /* * Query Directory, in fact an alias to match postfix tcp auth. */ void check_get(void) { char internet_addr[256]; char cmdbuf[SIZ]; time(&CC->lastcmd); memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */ if (client_getln(cmdbuf, sizeof cmdbuf) < 1) { syslog(LOG_CRIT, "vcard client disconnected: ending session."); CC->kill_me = KILLME_CLIENT_DISCONNECTED; return; } syslog(LOG_INFO, ": %s", cmdbuf); while (strlen(cmdbuf) < 3) strcat(cmdbuf, " "); syslog(LOG_INFO, "[ %s]", cmdbuf); if (strncasecmp(cmdbuf, "GET ", 4)==0) { recptypes *rcpt; char *argbuf = &cmdbuf[4]; extract_token(internet_addr, argbuf, 0, '|', sizeof internet_addr); rcpt = validate_recipients(internet_addr, NULL, CHECK_EXISTANCE); if ((rcpt != NULL)&& ( (*rcpt->recp_local != '\0')|| (*rcpt->recp_room != '\0')|| (*rcpt->recp_ignet != '\0'))) { cprintf("200 OK %s\n", internet_addr); syslog(LOG_INFO, "sending 200 OK for the room %s", rcpt->display_recp); } else { cprintf("500 REJECT noone here by that name.\n"); syslog(LOG_INFO, "sending 500 REJECT no one here by that name: %s", internet_addr); } if (rcpt != NULL) free_recipients(rcpt); } else { cprintf("500 REJECT invalid Query.\n"); syslog(LOG_INFO, "sending 500 REJECT invalid query: %s", internet_addr); } } void check_get_greeting(void) { /* dummy function, we have no greeting in this verry simple protocol. */ } /* * We don't know if the Contacts room exists so we just create it at login */ void vcard_CtdlCreateRoom(void) { struct ctdlroom qr; visit vbuf; /* Create the calendar room if it doesn't already exist */ CtdlCreateRoom(USERCONTACTSROOM, 4, "", 0, 1, 0, VIEW_ADDRESSBOOK); /* Set expiration policy to manual; otherwise objects will be lost! */ if (CtdlGetRoomLock(&qr, USERCONTACTSROOM)) { syslog(LOG_ERR, "Couldn't get the user CONTACTS room!"); return; } qr.QRep.expire_mode = EXPIRE_MANUAL; qr.QRdefaultview = VIEW_ADDRESSBOOK; /* 2 = address book view */ CtdlPutRoomLock(&qr); /* Set the view to a calendar view */ CtdlGetRelationship(&vbuf, &CC->user, &qr); vbuf.v_view = 2; /* 2 = address book view */ CtdlSetRelationship(&vbuf, &CC->user, &qr); return; } /* * When a user logs in... */ void vcard_session_login_hook(void) { struct vCard *v = NULL; struct CitContext *CCC = CC; /* put this on the stack, just for speed */ #ifdef HAVE_LDAP /* * Is this an LDAP session? If so, copy various LDAP attributes from the directory entry * into the user's vCard. */ if ((config.c_auth_mode == AUTHMODE_LDAP) || (config.c_auth_mode == AUTHMODE_LDAP_AD)) { v = vcard_get_user(&CCC->user); if (v) { if (Ctdl_LDAP_to_vCard(CCC->ldap_dn, v)) { CCC->vcard_updated_by_ldap++; /* Make sure changes make it to the global address book and internet directory, not just the user config. */ syslog(LOG_DEBUG, "LDAP Detected vcard change.\n"); vcard_write_user(&CCC->user, v); } } } #endif /* * Extract from the user's vCard, any Internet email addresses and the user's real name. * These are inserted into the session data for various message entry commands to use. */ v = vcard_get_user(&CCC->user); if (v) { extract_inet_email_addrs(CCC->cs_inet_email, sizeof CCC->cs_inet_email, CCC->cs_inet_other_emails, sizeof CCC->cs_inet_other_emails, v, 1 ); extract_friendly_name(CCC->cs_inet_fn, sizeof CCC->cs_inet_fn, v); vcard_free(v); } /* * Create the user's 'Contacts' room (personal address book) if it doesn't already exist. */ vcard_CtdlCreateRoom(); } /* * Turn an arbitrary RFC822 address into a struct vCard for possible * inclusion into an address book. */ struct vCard *vcard_new_from_rfc822_addr(char *addr) { struct vCard *v; char user[256], node[256], name[256], email[256], n[256], uid[256]; int i; v = vcard_new(); if (v == NULL) return(NULL); process_rfc822_addr(addr, user, node, name); vcard_set_prop(v, "fn", name, 0); vcard_fn_to_n(n, name, sizeof n); vcard_set_prop(v, "n", n, 0); snprintf(email, sizeof email, "%s@%s", user, node); vcard_set_prop(v, "email;internet", email, 0); snprintf(uid, sizeof uid, "collected: %s %s@%s", name, user, node); for (i=0; uid[i]; ++i) { if (isspace(uid[i])) uid[i] = '_'; uid[i] = tolower(uid[i]); } vcard_set_prop(v, "UID", uid, 0); return(v); } /* * This is called by store_harvested_addresses() to remove from the * list any addresses we already have in our address book. */ void strip_addresses_already_have(long msgnum, void *userdata) { char *collected_addresses; struct CtdlMessage *msg = NULL; struct vCard *v; char *value = NULL; int i, j; char addr[256], user[256], node[256], name[256]; collected_addresses = (char *)userdata; msg = CtdlFetchMessage(msgnum, 1); if (msg == NULL) return; v = vcard_load(msg->cm_fields[eMesageText]); CM_Free(msg); i = 0; while (value = vcard_get_prop(v, "email", 1, i++, 0), value != NULL) { for (j=0; jroomname, 0, 0, NULL, NULL, NULL, NULL); CtdlForEachMessage(MSGS_ALL, 0, NULL, "[Tt][Ee][Xx][Tt]/.*[Vv][Cc][Aa][Rr][Dd]$", NULL, strip_addresses_already_have, aptr->collected_addresses); if (!IsEmptyStr(aptr->collected_addresses)) for (i=0; icollected_addresses, ','); ++i) { /* Make a vCard out of each address */ extract_token(recipient, aptr->collected_addresses, i, ',', sizeof recipient); striplt(recipient); v = vcard_new_from_rfc822_addr(recipient); if (v != NULL) { const char *s; vmsg = malloc(sizeof(struct CtdlMessage)); memset(vmsg, 0, sizeof(struct CtdlMessage)); vmsg->cm_magic = CTDLMESSAGE_MAGIC; vmsg->cm_anon_type = MES_NORMAL; vmsg->cm_format_type = FMT_RFC822; CM_SetField(vmsg, eAuthor, HKEY("Citadel")); s = vcard_get_prop(v, "UID", 1, 0, 0); CM_SetField(vmsg, eExclusiveID, s, strlen(s)); ser = vcard_serialize(v); if (ser != NULL) { StrBuf *buf; long serlen; serlen = strlen(ser); buf = NewStrBufPlain(NULL, serlen + 1024); StrBufAppendBufPlain(buf, HKEY("Content-type: " VCARD_MIME_TYPE "\r\n\r\n"), 0); StrBufAppendBufPlain(buf, ser, serlen, 0); StrBufAppendBufPlain(buf, HKEY("\r\n"), 0); CM_SetAsFieldSB(vmsg, eMesageText, &buf); free(ser); } vcard_free(v); syslog(LOG_DEBUG, "Adding contact: %s", recipient); CtdlSubmitMsg(vmsg, NULL, aptr->roomname, QP_EADDR); CM_Free(vmsg); } } free(aptr->roomname); free(aptr->collected_addresses); free(aptr); } /* * When a user sends a message, we may harvest one or more email addresses * from the recipient list to be added to the user's address book. But we * want to do this asynchronously so it doesn't keep the user waiting. */ void store_harvested_addresses(void) { struct addresses_to_be_filed *aptr = NULL; if (atbf == NULL) return; begin_critical_section(S_ATBF); while (atbf != NULL) { aptr = atbf; atbf = atbf->next; end_critical_section(S_ATBF); store_this_ha(aptr); begin_critical_section(S_ATBF); } end_critical_section(S_ATBF); } /* * Function to output vCard data as plain text. Nobody uses MSG0 anymore, so * really this is just so we expose the vCard data to the full text indexer. */ void vcard_fixed_output(char *ptr, int len) { char *serialized_vcard; struct vCard *v; char *key, *value; int i = 0; serialized_vcard = malloc(len + 1); safestrncpy(serialized_vcard, ptr, len+1); v = vcard_load(serialized_vcard); free(serialized_vcard); i = 0; while (key = vcard_get_prop(v, "", 0, i, 1), key != NULL) { value = vcard_get_prop(v, "", 0, i++, 0); cprintf("%s\n", value); } vcard_free(v); } const char *CitadelServiceDICT_TCP="DICT_TCP"; CTDL_MODULE_INIT(vcard) { struct ctdlroom qr; char filename[256]; FILE *fp; int rv = 0; if (!threading) { CtdlRegisterSessionHook(vcard_session_login_hook, EVT_LOGIN, PRIO_LOGIN + 70); CtdlRegisterMessageHook(vcard_upload_beforesave, EVT_BEFORESAVE); CtdlRegisterMessageHook(vcard_upload_aftersave, EVT_AFTERSAVE); CtdlRegisterDeleteHook(vcard_delete_remove); CtdlRegisterProtoHook(cmd_regi, "REGI", "Enter registration info"); CtdlRegisterProtoHook(cmd_greg, "GREG", "Get registration info"); CtdlRegisterProtoHook(cmd_igab, "IGAB", "Initialize Global Address Book"); CtdlRegisterProtoHook(cmd_qdir, "QDIR", "Query Directory"); CtdlRegisterProtoHook(cmd_gvsn, "GVSN", "Get Valid Screen Names"); CtdlRegisterProtoHook(cmd_gvea, "GVEA", "Get Valid Email Addresses"); CtdlRegisterProtoHook(cmd_dvca, "DVCA", "Dump VCard Addresses"); CtdlRegisterUserHook(vcard_newuser, EVT_NEWUSER); CtdlRegisterUserHook(vcard_purge, EVT_PURGEUSER); CtdlRegisterNetprocHook(vcard_extract_from_network); CtdlRegisterSessionHook(store_harvested_addresses, EVT_TIMER, PRIO_CLEANUP + 470); CtdlRegisterFixedOutputHook("text/x-vcard", vcard_fixed_output); CtdlRegisterFixedOutputHook("text/vcard", vcard_fixed_output); /* Create the Global ADdress Book room if necessary */ CtdlCreateRoom(ADDRESS_BOOK_ROOM, 3, "", 0, 1, 0, VIEW_ADDRESSBOOK); /* Set expiration policy to manual; otherwise objects will be lost! */ if (!CtdlGetRoomLock(&qr, ADDRESS_BOOK_ROOM)) { qr.QRep.expire_mode = EXPIRE_MANUAL; qr.QRdefaultview = VIEW_ADDRESSBOOK; /* 2 = address book view */ CtdlPutRoomLock(&qr); /* * Also make sure it has a netconfig file, so the networker runs * on this room even if we don't share it with any other nodes. * This allows the CANCEL messages (i.e. "Purge this vCard") to be * purged. */ assoc_file_name(filename, sizeof filename, &qr, ctdl_netcfg_dir); fp = fopen(filename, "a"); if (fp != NULL) fclose(fp); rv = chown(filename, CTDLUID, (-1)); if (rv == -1) syslog(LOG_EMERG, "Failed to adjust ownership of: %s [%s]", filename, strerror(errno)); rv = chmod(filename, 0600); if (rv == -1) syslog(LOG_EMERG, "Failed to adjust ownership of: %s [%s]", filename, strerror(errno)); } /* for postfix tcpdict */ CtdlRegisterServiceHook(config.c_pftcpdict_port, /* Postfix */ NULL, check_get_greeting, check_get, NULL, CitadelServiceDICT_TCP); } /* return our module name for the log */ return "vcard"; } citadel-9.01/modules/extnotify/0000755000000000000000000000000012507024051015243 5ustar rootrootcitadel-9.01/modules/extnotify/extnotify.h0000644000000000000000000000252312507024051017447 0ustar rootroot/* * File: extnotify.h * Author: Mathew McBride / * Copyright (c) 2008-2009 * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * */ #include "../eventclient/serv_curl.h" #define PAGER_CONFIG_MESSAGE "__ Push email settings __" #define FUNAMBOL_CONFIG_TEXT "funambol" #define PAGER_CONFIG_SYSTEM "textmessage" #define PAGER_CONFIG_HTTP "httpmessage" typedef enum _eNotifyType { eNone, eFunambol, eHttpMessages, eTextMessage }eNotifyType; #define FUNAMBOL_WS "/funambol/services/admin" typedef struct _NotifyContext { StrBuf **NotifyHostList; int nNotifyHosts; HashList *NotifyErrors; AsyncIO IO; } NotifyContext; int notify_http_server(char *remoteurl, const char* template, long tlen, char *user, char *msgid, long MsgNum, NotifyContext *Ctx); void ExtNotify_PutErrorMessage(NotifyContext *Ctx, StrBuf *ErrMsg); ///void process_notify(long msgnum, void *usrdata); citadel-9.01/modules/extnotify/funambol65.c0000644000000000000000000001704312507024051017372 0ustar rootroot/* * funambol65.c * Author: Mathew McBride * * This module facilitates notifications to a Funambol server * for push email * * Based on bits of the previous serv_funambol * Contact: / * * Copyright (c) 2008-2010 * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * */ #include #include #include #include #include #include #include #include #include #include "citadel.h" #include "citserver.h" #include "citadel_dirs.h" #include "clientsocket.h" #include "sysdep.h" #include "config.h" #include "sysdep_decls.h" #include "msgbase.h" #include "ctdl_module.h" #include "event_client.h" #include "extnotify.h" eNextState EvaluateResult(AsyncIO *IO); eNextState ExtNotifyTerminate(AsyncIO *IO); eNextState ExtNotifyTerminateDB(AsyncIO *IO); eNextState ExtNotifyShutdownAbort(AsyncIO *IO); /* * \brief Sends a message to the Funambol server notifying * of new mail for a user * Returns 0 if unsuccessful */ int notify_http_server(char *remoteurl, const char* template, long tlen, char *user, char *msgid, long MsgNum, NotifyContext *Ctx) { CURLcode sta; char msgnumstr[128]; char *buf = NULL; char *SOAPMessage = NULL; char *contenttype = NULL; StrBuf *ReplyBuf; StrBuf *Buf; CURL *chnd; AsyncIO *IO; IO = (AsyncIO*) malloc(sizeof(AsyncIO)); memset(IO, 0, sizeof(AsyncIO)); if (! InitcURLIOStruct(IO, NULL, /* we don't have personal data anymore. */ "Citadel ExtNotify", EvaluateResult, ExtNotifyTerminate, ExtNotifyTerminateDB, ExtNotifyShutdownAbort)) { syslog(LOG_ALERT, "Unable to initialize libcurl.\n"); goto abort; } snprintf(msgnumstr, 128, "%ld", MsgNum); if (tlen > 0) { /* Load the template message. Get mallocs done too */ int fd; struct stat statbuf; const char *mimetype; const char *Err = NULL; fd = open(template, O_RDONLY); if ((fd < 0) || (fstat(fd, &statbuf) == -1)) { char buf[SIZ]; snprintf(buf, SIZ, "Cannot load template file %s [%s] " "won't send notification\r\n", file_funambol_msg, strerror(errno)); syslog(LOG_ERR, "%s", buf); // TODO: once an hour! CtdlAideMessage( buf, "External notifier: " "unable to find/stat message template!"); goto abort; } Buf = NewStrBufPlain(NULL, statbuf.st_size + 1); if (StrBufReadBLOB(Buf, &fd, 1, statbuf.st_size, &Err) < 0) { char buf[SIZ]; close(fd); snprintf(buf, SIZ, "Cannot load template file %s [%s] " "won't send notification\r\n", file_funambol_msg, Err); syslog(LOG_ERR, "%s", buf); // TODO: once an hour! CtdlAideMessage( buf, "External notifier: " "unable to load message template!"); goto abort; } close(fd); mimetype = GuessMimeByFilename(template, tlen); SOAPMessage = SmashStrBuf(&Buf); // Do substitutions help_subst(SOAPMessage, "^notifyuser", user); help_subst(SOAPMessage, "^syncsource", config.c_funambol_source); help_subst(SOAPMessage, "^msgid", msgid); help_subst(SOAPMessage, "^msgnum", msgnumstr); /* pass our list of custom made headers */ contenttype=(char*) malloc(40+strlen(mimetype)); sprintf(contenttype, "Content-Type: %s; charset=utf-8", mimetype); IO->HttpReq.headers = curl_slist_append( IO->HttpReq.headers, "SOAPAction: \"\""); IO->HttpReq.headers = curl_slist_append( IO->HttpReq.headers, contenttype); free(contenttype); contenttype = NULL; IO->HttpReq.headers = curl_slist_append( IO->HttpReq.headers, "Accept: application/soap+xml, " "application/mime, multipart/related, text/*"); IO->HttpReq.headers = curl_slist_append( IO->HttpReq.headers, "Pragma: no-cache"); /* Now specify the POST binary data */ IO->HttpReq.PlainPostData = SOAPMessage; IO->HttpReq.PlainPostDataLen = strlen(SOAPMessage); } else { help_subst(remoteurl, "^notifyuser", user); help_subst(remoteurl, "^syncsource", config.c_funambol_source); help_subst(remoteurl, "^msgid", msgid); help_subst(remoteurl, "^msgnum", msgnumstr); IO->HttpReq.headers = curl_slist_append( IO->HttpReq.headers, "Accept: application/soap+xml, " "application/mime, multipart/related, text/*"); IO->HttpReq.headers = curl_slist_append( IO->HttpReq.headers, "Pragma: no-cache"); } Buf = NewStrBufPlain (remoteurl, -1); ParseURL(&IO->ConnectMe, Buf, 80); FreeStrBuf(&Buf); /* TODO: this is uncool... */ CurlPrepareURL(IO->ConnectMe); chnd = IO->HttpReq.chnd; OPT(SSL_VERIFYPEER, 0); OPT(SSL_VERIFYHOST, 0); QueueCurlContext(IO); return 0; abort: if (contenttype) free(contenttype); if (SOAPMessage != NULL) free(SOAPMessage); if (buf != NULL) free(buf); FreeStrBuf (&ReplyBuf); return 1; } eNextState EvaluateResult(AsyncIO *IO) { if (IO->HttpReq.httpcode != 200) { StrBuf *ErrMsg; syslog(LOG_ALERT, "libcurl error %ld: %s\n", IO->HttpReq.httpcode, IO->HttpReq.errdesc); ErrMsg = NewStrBufPlain( HKEY("Error sending your Notification\n")); StrBufAppendPrintf(ErrMsg, "\nlibcurl error %ld: \n\t\t%s\n", IO->HttpReq.httpcode, IO->HttpReq.errdesc); StrBufAppendBufPlain(ErrMsg, HKEY("\nWas Trying to send: \n"), 0); StrBufAppendBufPlain(ErrMsg, IO->ConnectMe->PlainUrl, -1, 0); if (IO->HttpReq.PlainPostDataLen > 0) { StrBufAppendBufPlain( ErrMsg, HKEY("\nThe Post document was: \n"), 0); StrBufAppendBufPlain(ErrMsg, IO->HttpReq.PlainPostData, IO->HttpReq.PlainPostDataLen, 0); StrBufAppendBufPlain(ErrMsg, HKEY("\n\n"), 0); } if (StrLength(IO->HttpReq.ReplyData) > 0) { StrBufAppendBufPlain( ErrMsg, HKEY("\n\nThe Serverreply was: \n\n"), 0); StrBufAppendBuf(ErrMsg, IO->HttpReq.ReplyData, 0); } else StrBufAppendBufPlain( ErrMsg, HKEY("\n\nThere was no Serverreply.\n\n"), 0); ///ExtNotify_PutErrorMessage(Ctx, ErrMsg); CtdlAideMessage(ChrPtr(ErrMsg), "External notifier: " "unable to contact notification host!"); FreeStrBuf(&ErrMsg); } syslog(LOG_DEBUG, "Funambol notified\n"); /* while ((Ctx.NotifyHostList != NULL) && (Ctx.NotifyHostList[i] != NULL)) FreeStrBuf(&Ctx.NotifyHostList[i]); if (Ctx.NotifyErrors != NULL) { long len; const char *Key; HashPos *It; void *vErr; StrBuf *ErrMsg; It = GetNewHashPos(Ctx.NotifyErrors, 0); while (GetNextHashPos(Ctx.NotifyErrors, It, &len, &Key, &vErr) && (vErr != NULL)) { ErrMsg = (StrBuf*) vErr; quickie_message("Citadel", NULL, NULL, AIDEROOM, ChrPtr(ErrMsg), FMT_FIXED, "Failed to notify external service about inbound mail"); } DeleteHashPos(&It); DeleteHash(&Ctx.NotifyErrors); } */ //// curl_slist_free_all (headers); /// curl_easy_cleanup(curl); ///if (contenttype) free(contenttype); ///if (SOAPMessage != NULL) free(SOAPMessage); ///if (buf != NULL) free(buf); ///FreeStrBuf (&ReplyBuf); return eTerminateConnection; } eNextState ExtNotifyTerminateDB(AsyncIO *IO) { free(IO); return eAbort; } eNextState ExtNotifyTerminate(AsyncIO *IO) { free(IO); return eAbort; } eNextState ExtNotifyShutdownAbort(AsyncIO *IO) { free(IO); return eAbort; } citadel-9.01/modules/extnotify/extnotify_main.c0000644000000000000000000003045012507024051020446 0ustar rootroot/* * extnotify_main.c * Mathew McBride * * This module implements an external pager hook for when notifcation * of a new email is wanted. * * Based on bits of serv_funambol * Contact: / * * Copyright (c) 2008-2011 * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "sysdep.h" #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "control.h" #include "user_ops.h" #include "database.h" #include "msgbase.h" #include "internet_addressing.h" #include "domain.h" #include "clientsocket.h" #include "event_client.h" #include "extnotify.h" #include "ctdl_module.h" struct CitContext extnotify_queue_CC; void ExtNotify_PutErrorMessage(NotifyContext *Ctx, StrBuf *ErrMsg) { int nNext; if (Ctx->NotifyErrors == NULL) Ctx->NotifyErrors = NewHash(1, Flathash); nNext = GetCount(Ctx->NotifyErrors) + 1; Put(Ctx->NotifyErrors, (char*)&nNext, sizeof(int), ErrMsg, HFreeStrBuf); } StrBuf* GetNHBuf(int i, int allocit, StrBuf **NotifyHostList) { if ((NotifyHostList[i] == NULL) && (allocit != 0)) NotifyHostList[i] = NewStrBuf(); return NotifyHostList[i]; } int GetNotifyHosts(NotifyContext *Ctx) { char NotifyHostsBuf[SIZ]; StrBuf *Host; StrBuf *File; StrBuf *NotifyBuf; int notify; const char *pchs, *pche; const char *NextHost = NULL; /* See if we have any Notification Hosts configured */ Ctx->nNotifyHosts = get_hosts(NotifyHostsBuf, "notify"); if (Ctx->nNotifyHosts < 1) return 0; Ctx->NotifyHostList = malloc(sizeof(StrBuf*) * 2 * (Ctx->nNotifyHosts + 1)); memset(Ctx->NotifyHostList, 0, sizeof(StrBuf*) * 2 * (Ctx->nNotifyHosts + 1)); NotifyBuf = NewStrBufPlain(NotifyHostsBuf, -1); /* get all configured notifiers's */ for (notify=0; notifynNotifyHosts; notify++) { Host = GetNHBuf(notify * 2, 1, Ctx->NotifyHostList); StrBufExtract_NextToken(Host, NotifyBuf, &NextHost, '|'); pchs = ChrPtr(Host); pche = strchr(pchs, ':'); if (pche == NULL) { syslog(LOG_ERR, "extnotify: filename of notification " "template not found in %s.\n", pchs); continue; } File = GetNHBuf(notify * 2 + 1, 1, Ctx->NotifyHostList); StrBufPlain(File, pchs, pche - pchs); StrBufCutLeft(Host, pche - pchs + 1); } FreeStrBuf(&NotifyBuf); return Ctx->nNotifyHosts; } /*! \brief Get configuration message for pager/funambol system from the * users "My Citadel Config" room */ eNotifyType extNotify_getConfigMessage(char *username, char **PagerNumber, char **FreeMe) { struct ctdlroom qrbuf; // scratch for room struct ctdluser user; // ctdl user instance char configRoomName[ROOMNAMELEN]; struct CtdlMessage *msg = NULL; struct cdbdata *cdbfr; long *msglist = NULL; int num_msgs = 0; int a; char *configMsg; long clen; char *pch; // Get the user CtdlGetUser(&user, username); CtdlMailboxName(configRoomName, sizeof(configRoomName), &user, USERCONFIGROOM); // Fill qrbuf CtdlGetRoom(&qrbuf, configRoomName); /* Do something really, really stoopid here. Raid the room on ourselves, * loop through the messages manually and find it. I don't want * to use a CtdlForEachMessage callback here, as we would be * already in one */ cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long)); if (cdbfr != NULL) { msglist = (long *) cdbfr->ptr; cdbfr->ptr = NULL; /* CtdlForEachMessage() now owns this memory */ num_msgs = cdbfr->len / sizeof(long); cdb_free(cdbfr); } else { syslog(LOG_DEBUG, "extNotify_getConfigMessage: " "No config messages found\n"); return eNone; /* No messages at all? No further action. */ } for (a = 0; a < num_msgs; ++a) { msg = CtdlFetchMessage(msglist[a], 1); if (msg != NULL) { if (!CM_IsEmpty(msg, eMsgSubject) && (strncasecmp(msg->cm_fields[eMsgSubject], PAGER_CONFIG_MESSAGE, strlen(PAGER_CONFIG_MESSAGE)) == 0)) { break; } CM_Free(msg); msg = NULL; } } free(msglist); if (msg == NULL) return eNone; // Do a simple string search to see if 'funambol' is selected as the // type. This string would be at the very top of the message contents. CM_GetAsField(msg, eMesageText, &configMsg, &clen); CM_Free(msg); /* here we would find the pager number... */ pch = strchr(configMsg, '\n'); if (pch != NULL) { *pch = '\0'; pch ++; } /* Check to see if: * 1. The user has configured paging / They have and disabled it * AND 2. There is an external pager program * 3. A Funambol server has been entered * */ if (!strncasecmp(configMsg, "none", 4)) { free(configMsg); return eNone; } if (!strncasecmp(configMsg, HKEY(PAGER_CONFIG_HTTP))) { free(configMsg); return eHttpMessages; } if (!strncasecmp(configMsg, HKEY(FUNAMBOL_CONFIG_TEXT))) { free(configMsg); return eFunambol; } else if (!strncasecmp(configMsg, HKEY(PAGER_CONFIG_SYSTEM))) { // whats the pager number? if (!pch || (*pch == '\0')) { free(configMsg); return eNone; } while (isspace(*pch)) pch ++; *PagerNumber = pch; while (isdigit(*pch) || (*pch == '+')) pch++; *pch = '\0'; *FreeMe = configMsg; return eTextMessage; } free(configMsg); return eNone; } /* * Process messages in the external notification queue */ void process_notify(long NotifyMsgnum, void *usrdata) { NotifyContext *Ctx; long msgnum = 0; long todelete[1]; char *pch; struct CtdlMessage *msg; eNotifyType Type; char remoteurl[SIZ]; char *FreeMe = NULL; char *PagerNo; Ctx = (NotifyContext*) usrdata; msg = CtdlFetchMessage(NotifyMsgnum, 1); if (!CM_IsEmpty(msg, eExtnotify)) { Type = extNotify_getConfigMessage( msg->cm_fields[eExtnotify], &PagerNo, &FreeMe); pch = strstr(msg->cm_fields[eMesageText], "msgid|"); if (pch != NULL) msgnum = atol(pch + sizeof("msgid")); switch (Type) { case eFunambol: snprintf(remoteurl, SIZ, "http://%s@%s:%d/%s", config.c_funambol_auth, config.c_funambol_host, config.c_funambol_port, FUNAMBOL_WS); notify_http_server(remoteurl, file_funambol_msg, strlen(file_funambol_msg),/*GNA*/ msg->cm_fields[eExtnotify], msg->cm_fields[emessageId], msgnum, NULL); break; case eHttpMessages: { int i = 0; StrBuf *URL; char URLBuf[SIZ]; StrBuf *File; StrBuf *FileBuf = NewStrBuf(); for (i = 0; i < Ctx->nNotifyHosts; i++) { URL = GetNHBuf(i*2, 0, Ctx->NotifyHostList); if (URL==NULL) break; File = GetNHBuf(i*2 + 1, 0, Ctx->NotifyHostList); if (File==NULL) break; if (StrLength(File)>0) StrBufPrintf(FileBuf, "%s/%s", ctdl_shared_dir, ChrPtr(File)); else FlushStrBuf(FileBuf); memcpy(URLBuf, ChrPtr(URL), StrLength(URL) + 1); notify_http_server(URLBuf, ChrPtr(FileBuf), StrLength(FileBuf), msg->cm_fields[eExtnotify], msg->cm_fields[emessageId], msgnum, NULL); } FreeStrBuf(&FileBuf); } break; case eTextMessage: { int commandSiz; char *command; commandSiz = sizeof(config.c_pager_program) + strlen(PagerNo) + msg->cm_lengths[eExtnotify] + 5; command = malloc(commandSiz); snprintf(command, commandSiz, "%s %s -u %s", config.c_pager_program, PagerNo, msg->cm_fields[eExtnotify]); system(command); free(command); } break; case eNone: break; } } if (FreeMe != NULL) free(FreeMe); CM_Free(msg); todelete[0] = NotifyMsgnum; CtdlDeleteMessages(FNBL_QUEUE_ROOM, todelete, 1, ""); } /*! * \brief Run through the pager room queue * Checks to see what notification option the user has set */ void do_extnotify_queue(void) { NotifyContext Ctx; static int doing_queue = 0; int i = 0; /* * This is a simple concurrency check to make sure only one queue run * is done at a time. We could do this with a mutex, but since we * don't really require extremely fine granularity here, we'll do it * with a static variable instead. */ if (IsEmptyStr(config.c_pager_program) && IsEmptyStr(config.c_funambol_host)) { syslog(LOG_ERR, "No external notifiers configured on system/user\n"); return; } if (doing_queue) return; doing_queue = 1; become_session(&extnotify_queue_CC); pthread_setspecific(MyConKey, (void *)&extnotify_queue_CC); /* * Go ahead and run the queue */ syslog(LOG_DEBUG, "serv_extnotify: processing notify queue\n"); memset(&Ctx, 0, sizeof(NotifyContext)); if ((GetNotifyHosts(&Ctx) > 0) && (CtdlGetRoom(&CC->room, FNBL_QUEUE_ROOM) != 0)) { syslog(LOG_ERR, "Cannot find room <%s>\n", FNBL_QUEUE_ROOM); if (Ctx.nNotifyHosts > 0) { for (i = 0; i < Ctx.nNotifyHosts * 2; i++) FreeStrBuf(&Ctx.NotifyHostList[i]); free(Ctx.NotifyHostList); } return; } CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, process_notify, &Ctx); syslog(LOG_DEBUG, "serv_extnotify: queue run completed\n"); doing_queue = 0; if (Ctx.nNotifyHosts > 0) { for (i = 0; i < Ctx.nNotifyHosts * 2; i++) FreeStrBuf(&Ctx.NotifyHostList[i]); free(Ctx.NotifyHostList); } } /* Create the notify message queue. We use the exact same room * as the Funambol module. * * Run at server startup, creates FNBL_QUEUE_ROOM if it doesn't exist * and sets as system room. */ void create_extnotify_queue(void) { struct ctdlroom qrbuf; CtdlCreateRoom(FNBL_QUEUE_ROOM, 3, "", 0, 1, 0, VIEW_QUEUE); CtdlFillSystemContext(&extnotify_queue_CC, "Extnotify"); /* * Make sure it's set to be a "system room" so it doesn't show up * in the nown rooms list for Aides. */ if (CtdlGetRoomLock(&qrbuf, FNBL_QUEUE_ROOM) == 0) { qrbuf.QRflags2 |= QR2_SYSTEM; CtdlPutRoomLock(&qrbuf); } } int extnotify_after_mbox_save(struct CtdlMessage *msg, recptypes *recps) { /* If this is private, local mail, make a copy in the * recipient's mailbox and bump the reference count. */ if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) { /* Generate a instruction message for the Funambol notification * server, in the same style as the SMTP queue */ StrBuf *instr; struct CtdlMessage *imsg; instr = NewStrBufPlain(NULL, 1024); StrBufPrintf(instr, "Content-type: "SPOOLMIME"\n" "\n" "msgid|%s\n" "submitted|%ld\n" "bounceto|%s\n", msg->cm_fields[eVltMsgNum], (long)time(NULL), //todo: time() is expensive! recps->bounce_to ); imsg = malloc(sizeof(struct CtdlMessage)); memset(imsg, 0, sizeof(struct CtdlMessage)); imsg->cm_magic = CTDLMESSAGE_MAGIC; imsg->cm_anon_type = MES_NORMAL; imsg->cm_format_type = FMT_RFC822; CM_SetField(imsg, eMsgSubject, HKEY("QMSG")); CM_SetField(imsg, eAuthor, HKEY("Citadel")); CM_SetField(imsg, eJournal, HKEY("do not journal")); CM_SetAsFieldSB(imsg, eMesageText, &instr); CM_SetField(imsg, eExtnotify, recps->recp_local, strlen(recps->recp_local)); CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0); CM_Free(imsg); } return 0; } CTDL_MODULE_INIT(extnotify) { if (!threading) { create_extnotify_queue(); CtdlRegisterMessageHook(extnotify_after_mbox_save, EVT_AFTERUSRMBOXSAVE); CtdlRegisterSessionHook(do_extnotify_queue, EVT_TIMER, PRIO_SEND + 10); } /* return our module name for the log */ return "extnotify"; } citadel-9.01/modules/openid/0000755000000000000000000000000012507024051014470 5ustar rootrootcitadel-9.01/modules/openid/serv_openid_rp.c0000644000000000000000000010143312507024051017654 0ustar rootroot/* * This is an implementation of OpenID 2.0 relying party support in stateless mode. * * Copyright (c) 2007-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "sysdep.h" #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include "ctdl_module.h" #include "config.h" #include "citserver.h" #include "user_ops.h" typedef struct _ctdl_openid { StrBuf *op_url; /* OpenID Provider Endpoint URL */ StrBuf *claimed_id; /* Claimed Identifier */ int verified; HashList *sreg_keys; } ctdl_openid; enum { openid_disco_none, openid_disco_xrds, openid_disco_html }; void Free_ctdl_openid(ctdl_openid **FreeMe) { if (*FreeMe == NULL) { return; } FreeStrBuf(&(*FreeMe)->op_url); FreeStrBuf(&(*FreeMe)->claimed_id); DeleteHash(&(*FreeMe)->sreg_keys); free(*FreeMe); *FreeMe = NULL; } /* * This cleanup function blows away the temporary memory used by this module. */ void openid_cleanup_function(void) { struct CitContext *CCC = CC; /* CachedCitContext - performance boost */ if (CCC->openid_data != NULL) { syslog(LOG_DEBUG, "Clearing OpenID session state"); Free_ctdl_openid((ctdl_openid **) &CCC->openid_data); } } /**************************************************************************/ /* */ /* Functions in this section handle Citadel internal OpenID mapping stuff */ /* */ /**************************************************************************/ /* * The structure of an openid record *key* is: * * |--------------claimed_id-------------| * (actual length of claimed id) * * * The structure of an openid record *value* is: * * |-----user_number----|------------claimed_id---------------| * (sizeof long) (actual length of claimed id) * */ /* * Attach an OpenID to a Citadel account */ int attach_openid(struct ctdluser *who, StrBuf *claimed_id) { struct cdbdata *cdboi; long fetched_usernum; char *data; int data_len; char buf[2048]; if (!who) return(1); if (StrLength(claimed_id)==0) return(1); /* Check to see if this OpenID is already in the database */ cdboi = cdb_fetch(CDB_OPENID, ChrPtr(claimed_id), StrLength(claimed_id)); if (cdboi != NULL) { memcpy(&fetched_usernum, cdboi->ptr, sizeof(long)); cdb_free(cdboi); if (fetched_usernum == who->usernum) { syslog(LOG_INFO, "%s already associated; no action is taken", ChrPtr(claimed_id)); return(0); } else { syslog(LOG_INFO, "%s already belongs to another user", ChrPtr(claimed_id)); return(3); } } /* Not already in the database, so attach it now */ data_len = sizeof(long) + StrLength(claimed_id) + 1; data = malloc(data_len); memcpy(data, &who->usernum, sizeof(long)); memcpy(&data[sizeof(long)], ChrPtr(claimed_id), StrLength(claimed_id) + 1); cdb_store(CDB_OPENID, ChrPtr(claimed_id), StrLength(claimed_id), data, data_len); free(data); snprintf(buf, sizeof buf, "User <%s> (#%ld) has claimed the OpenID URL %s\n", who->fullname, who->usernum, ChrPtr(claimed_id)); CtdlAideMessage(buf, "OpenID claim"); syslog(LOG_INFO, "%s", buf); return(0); } /* * When a user is being deleted, we have to delete any OpenID associations */ void openid_purge(struct ctdluser *usbuf) { struct cdbdata *cdboi; HashList *keys = NULL; HashPos *HashPos; char *deleteme = NULL; long len; void *Value; const char *Key; long usernum = 0L; keys = NewHash(1, NULL); if (!keys) return; cdb_rewind(CDB_OPENID); while (cdboi = cdb_next_item(CDB_OPENID), cdboi != NULL) { if (cdboi->len > sizeof(long)) { memcpy(&usernum, cdboi->ptr, sizeof(long)); if (usernum == usbuf->usernum) { deleteme = strdup(cdboi->ptr + sizeof(long)), Put(keys, deleteme, strlen(deleteme), deleteme, NULL); } } cdb_free(cdboi); } /* Go through the hash list, deleting keys we stored in it */ HashPos = GetNewHashPos(keys, 0); while (GetNextHashPos(keys, HashPos, &len, &Key, &Value)!=0) { syslog(LOG_DEBUG, "Deleting associated OpenID <%s>", (char*)Value); cdb_delete(CDB_OPENID, Value, strlen(Value)); /* note: don't free(Value) -- deleting the hash list will handle this for us */ } DeleteHashPos(&HashPos); DeleteHash(&keys); } /* * List the OpenIDs associated with the currently logged in account */ void cmd_oidl(char *argbuf) { struct cdbdata *cdboi; long usernum = 0L; if (config.c_disable_newu) { cprintf("%d this system does not support openid.\n", ERROR + CMD_NOT_SUPPORTED); return; } if (CtdlAccessCheck(ac_logged_in)) return; cdb_rewind(CDB_OPENID); cprintf("%d Associated OpenIDs:\n", LISTING_FOLLOWS); while (cdboi = cdb_next_item(CDB_OPENID), cdboi != NULL) { if (cdboi->len > sizeof(long)) { memcpy(&usernum, cdboi->ptr, sizeof(long)); if (usernum == CC->user.usernum) { cprintf("%s\n", cdboi->ptr + sizeof(long)); } } cdb_free(cdboi); } cprintf("000\n"); } /* * List ALL OpenIDs in the database */ void cmd_oida(char *argbuf) { struct cdbdata *cdboi; long usernum; struct ctdluser usbuf; if (config.c_disable_newu) { cprintf("%d this system does not support openid.\n", ERROR + CMD_NOT_SUPPORTED); return; } if (CtdlAccessCheck(ac_aide)) return; cdb_rewind(CDB_OPENID); cprintf("%d List of all OpenIDs in the database:\n", LISTING_FOLLOWS); while (cdboi = cdb_next_item(CDB_OPENID), cdboi != NULL) { if (cdboi->len > sizeof(long)) { memcpy(&usernum, cdboi->ptr, sizeof(long)); if (CtdlGetUserByNumber(&usbuf, usernum) != 0) { usbuf.fullname[0] = 0; } cprintf("%s|%ld|%s\n", cdboi->ptr + sizeof(long), usernum, usbuf.fullname ); } cdb_free(cdboi); } cprintf("000\n"); } /* * Create a new user account, manually specifying the name, after successfully * verifying an OpenID (which will of course be attached to the account) */ void cmd_oidc(char *argbuf) { ctdl_openid *oiddata = (ctdl_openid *) CC->openid_data; if (config.c_disable_newu) { cprintf("%d this system does not support openid.\n", ERROR + CMD_NOT_SUPPORTED); return; } if ( (!oiddata) || (!oiddata->verified) ) { cprintf("%d You have not verified an OpenID yet.\n", ERROR); return; } /* We can make the semantics of OIDC exactly the same as NEWU, simply * by _calling_ cmd_newu() and letting it run. Very clever! */ cmd_newu(argbuf); /* Now, if this logged us in, we have to attach the OpenID */ if (CC->logged_in) { attach_openid(&CC->user, oiddata->claimed_id); } } /* * Detach an OpenID from the currently logged in account */ void cmd_oidd(char *argbuf) { struct cdbdata *cdboi; char id_to_detach[1024]; int this_is_mine = 0; long usernum = 0L; if (config.c_disable_newu) { cprintf("%d this system does not support openid.\n", ERROR + CMD_NOT_SUPPORTED); return; } if (CtdlAccessCheck(ac_logged_in)) return; extract_token(id_to_detach, argbuf, 0, '|', sizeof id_to_detach); if (IsEmptyStr(id_to_detach)) { cprintf("%d An empty OpenID URL is not allowed.\n", ERROR + ILLEGAL_VALUE); } cdb_rewind(CDB_OPENID); while (cdboi = cdb_next_item(CDB_OPENID), cdboi != NULL) { if (cdboi->len > sizeof(long)) { memcpy(&usernum, cdboi->ptr, sizeof(long)); if (usernum == CC->user.usernum) { this_is_mine = 1; } } cdb_free(cdboi); } if (!this_is_mine) { cprintf("%d That OpenID was not found or not associated with your account.\n", ERROR + ILLEGAL_VALUE); return; } cdb_delete(CDB_OPENID, id_to_detach, strlen(id_to_detach)); cprintf("%d %s detached from your account.\n", CIT_OK, id_to_detach); } /* * Attempt to auto-create a new Citadel account using the nickname from Attribute Exchange */ int openid_create_user_via_ax(StrBuf *claimed_id, HashList *sreg_keys) { char *nickname = NULL; char *firstname = NULL; char *lastname = NULL; char new_password[32]; long len; const char *Key; void *Value; if (config.c_auth_mode != AUTHMODE_NATIVE) return(1); if (config.c_disable_newu) return(2); if (CC->logged_in) return(3); HashPos *HashPos = GetNewHashPos(sreg_keys, 0); while (GetNextHashPos(sreg_keys, HashPos, &len, &Key, &Value) != 0) { syslog(LOG_DEBUG, "%s = %s", Key, (char *)Value); if (cbmstrcasestr(Key, "value.nickname") != NULL) { nickname = (char *)Value; } else if ( (nickname == NULL) && (cbmstrcasestr(Key, "value.nickname") != NULL)) { nickname = (char *)Value; } else if (cbmstrcasestr(Key, "value.firstname") != NULL) { firstname = (char *)Value; } else if (cbmstrcasestr(Key, "value.lastname") != NULL) { lastname = (char *)Value; } } DeleteHashPos(&HashPos); if (nickname == NULL) { if ((firstname != NULL) || (lastname != NULL)) { char fullname[1024] = ""; if (firstname) strcpy(fullname, firstname); if (firstname && lastname) strcat(fullname, " "); if (lastname) strcat(fullname, lastname); nickname = fullname; } } if (nickname == NULL) { return(4); } syslog(LOG_DEBUG, "The desired account name is <%s>", nickname); len = cutuserkey(nickname); if (!CtdlGetUser(&CC->user, nickname)) { syslog(LOG_DEBUG, "<%s> is already taken by another user.", nickname); memset(&CC->user, 0, sizeof(struct ctdluser)); return(5); } /* The desired account name is available. Create the account and log it in! */ if (create_user(nickname, len, 1)) return(6); /* Generate a random password. * The user doesn't care what the password is since he is using OpenID. */ snprintf(new_password, sizeof new_password, "%08lx%08lx", random(), random()); CtdlSetPassword(new_password); /* Now attach the verified OpenID to this account. */ attach_openid(&CC->user, claimed_id); return(0); } /* * If a user account exists which is associated with the Claimed ID, log it in and return zero. * Otherwise it returns nonzero. */ int login_via_openid(StrBuf *claimed_id) { struct cdbdata *cdboi; long usernum = 0; cdboi = cdb_fetch(CDB_OPENID, ChrPtr(claimed_id), StrLength(claimed_id)); if (cdboi == NULL) { return(-1); } memcpy(&usernum, cdboi->ptr, sizeof(long)); cdb_free(cdboi); if (!CtdlGetUserByNumber(&CC->user, usernum)) { /* Now become the user we just created */ safestrncpy(CC->curr_user, CC->user.fullname, sizeof CC->curr_user); do_login(); return(0); } else { memset(&CC->user, 0, sizeof(struct ctdluser)); return(-1); } } /**************************************************************************/ /* */ /* Functions in this section handle OpenID protocol */ /* */ /**************************************************************************/ /* * Locate a tag and, given its 'rel=' parameter, return its 'href' parameter */ void extract_link(StrBuf *target_buf, const char *rel, long repllen, StrBuf *source_buf) { int i; const char *ptr; const char *href_start = NULL; const char *href_end = NULL; const char *link_tag_start = NULL; const char *link_tag_end = NULL; const char *rel_start = NULL; const char *rel_end = NULL; if (!target_buf) return; if (!rel) return; if (!source_buf) return; ptr = ChrPtr(source_buf); FlushStrBuf(target_buf); while (ptr = cbmstrcasestr(ptr, "'); if (link_tag_end == NULL) break; for (i=0; i < 1; i++ ){ rel_start = cbmstrcasestr(link_tag_start, "rel="); if ((rel_start == NULL) || (rel_start > link_tag_end)) continue; rel_start = strchr(rel_start, '\"'); if ((rel_start == NULL) || (rel_start > link_tag_end)) continue; ++rel_start; rel_end = strchr(rel_start, '\"'); if ((rel_end == NULL) || (rel_end == rel_start) || (rel_end >= link_tag_end) ) continue; if (strncasecmp(rel, rel_start, repllen)!= 0) continue; /* didn't match? never mind... */ href_start = cbmstrcasestr(link_tag_start, "href="); if ((href_start == NULL) || (href_start >= link_tag_end)) continue; href_start = strchr(href_start, '\"'); if ((href_start == NULL) | (href_start >= link_tag_end)) continue; ++href_start; href_end = strchr(href_start, '\"'); if ((href_end == NULL) || (href_end == href_start) || (href_start >= link_tag_end)) continue; StrBufPlain(target_buf, href_start, href_end - href_start); } ptr = link_tag_end; } } /* * Wrapper for curl_easy_init() that includes the options common to all calls * used in this module. */ CURL *ctdl_openid_curl_easy_init(char *errmsg) { CURL *curl; curl = curl_easy_init(); if (!curl) { return(curl); } curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); if (errmsg) { curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errmsg); } curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); #ifdef CURLOPT_HTTP_CONTENT_DECODING curl_easy_setopt(curl, CURLOPT_HTTP_CONTENT_DECODING, 1); curl_easy_setopt(curl, CURLOPT_ENCODING, ""); #endif curl_easy_setopt(curl, CURLOPT_USERAGENT, CITADEL); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30); /* die after 30 seconds */ if ( (!IsEmptyStr(config.c_ip_addr)) && (strcmp(config.c_ip_addr, "*")) && (strcmp(config.c_ip_addr, "::")) && (strcmp(config.c_ip_addr, "0.0.0.0")) ) { curl_easy_setopt(curl, CURLOPT_INTERFACE, config.c_ip_addr); } return(curl); } struct xrds { StrBuf *CharData; int nesting_level; int in_xrd; int current_service_priority; int selected_service_priority; StrBuf *current_service_uri; StrBuf *selected_service_uri; int current_service_is_oid2auth; }; void xrds_xml_start(void *data, const char *supplied_el, const char **attr) { struct xrds *xrds = (struct xrds *) data; int i; ++xrds->nesting_level; if (!strcasecmp(supplied_el, "XRD")) { ++xrds->in_xrd; } else if (!strcasecmp(supplied_el, "service")) { xrds->current_service_priority = 0; xrds->current_service_is_oid2auth = 0; for (i=0; attr[i] != NULL; i+=2) { if (!strcasecmp(attr[i], "priority")) { xrds->current_service_priority = atoi(attr[i+1]); } } } FlushStrBuf(xrds->CharData); } void xrds_xml_end(void *data, const char *supplied_el) { struct xrds *xrds = (struct xrds *) data; --xrds->nesting_level; if (!strcasecmp(supplied_el, "XRD")) { --xrds->in_xrd; } else if (!strcasecmp(supplied_el, "type")) { if ( (xrds->in_xrd) && (!strcasecmp(ChrPtr(xrds->CharData), "http://specs.openid.net/auth/2.0/server")) ) { xrds->current_service_is_oid2auth = 1; } if ( (xrds->in_xrd) && (!strcasecmp(ChrPtr(xrds->CharData), "http://specs.openid.net/auth/2.0/signon")) ) { xrds->current_service_is_oid2auth = 1; /* FIXME in this case, the Claimed ID should be considered immutable */ } } else if (!strcasecmp(supplied_el, "uri")) { if (xrds->in_xrd) { FlushStrBuf(xrds->current_service_uri); StrBufAppendBuf(xrds->current_service_uri, xrds->CharData, 0); } } else if (!strcasecmp(supplied_el, "service")) { if ( (xrds->in_xrd) && (xrds->current_service_priority < xrds->selected_service_priority) && (xrds->current_service_is_oid2auth) ) { xrds->selected_service_priority = xrds->current_service_priority; FlushStrBuf(xrds->selected_service_uri); StrBufAppendBuf(xrds->selected_service_uri, xrds->current_service_uri, 0); } } FlushStrBuf(xrds->CharData); } void xrds_xml_chardata(void *data, const XML_Char *s, int len) { struct xrds *xrds = (struct xrds *) data; StrBufAppendBufPlain (xrds->CharData, s, len, 0); } /* * Parse an XRDS document. * If an OpenID Provider URL is discovered, op_url to that value and return nonzero. * If nothing useful happened, return 0. */ int parse_xrds_document(StrBuf *ReplyBuf) { ctdl_openid *oiddata = (ctdl_openid *) CC->openid_data; struct xrds xrds; int return_value = 0; memset(&xrds, 0, sizeof (struct xrds)); xrds.selected_service_priority = INT_MAX; xrds.CharData = NewStrBuf(); xrds.current_service_uri = NewStrBuf(); xrds.selected_service_uri = NewStrBuf(); XML_Parser xp = XML_ParserCreate(NULL); if (xp) { XML_SetUserData(xp, &xrds); XML_SetElementHandler(xp, xrds_xml_start, xrds_xml_end); XML_SetCharacterDataHandler(xp, xrds_xml_chardata); XML_Parse(xp, ChrPtr(ReplyBuf), StrLength(ReplyBuf), 0); XML_Parse(xp, "", 0, 1); XML_ParserFree(xp); } else { syslog(LOG_ALERT, "Cannot create XML parser"); } if (xrds.selected_service_priority < INT_MAX) { if (oiddata->op_url == NULL) { oiddata->op_url = NewStrBuf(); } FlushStrBuf(oiddata->op_url); StrBufAppendBuf(oiddata->op_url, xrds.selected_service_uri, 0); return_value = openid_disco_xrds; } FreeStrBuf(&xrds.CharData); FreeStrBuf(&xrds.current_service_uri); FreeStrBuf(&xrds.selected_service_uri); return(return_value); } /* * Callback function for perform_openid2_discovery() * We're interested in the X-XRDS-Location: header. */ size_t yadis_headerfunction(void *ptr, size_t size, size_t nmemb, void *userdata) { char hdr[1024]; StrBuf **x_xrds_location = (StrBuf **) userdata; memcpy(hdr, ptr, (size*nmemb)); hdr[size*nmemb] = 0; if (!strncasecmp(hdr, "X-XRDS-Location:", 16)) { *x_xrds_location = NewStrBufPlain(&hdr[16], ((size*nmemb)-16)); StrBufTrim(*x_xrds_location); } return(size * nmemb); } /* Attempt to perform Yadis discovery as specified in Yadis 1.0 section 6.2.5. * * If Yadis fails, we then attempt HTML discovery using the same document. * * If successful, returns nonzero and calls parse_xrds_document() to act upon the received data. * If fails, returns 0 and does nothing else. */ int perform_openid2_discovery(StrBuf *SuppliedURL) { ctdl_openid *oiddata = (ctdl_openid *) CC->openid_data; int docbytes = (-1); StrBuf *ReplyBuf = NULL; int return_value = 0; CURL *curl; CURLcode result; char errmsg[1024] = ""; struct curl_slist *my_headers = NULL; StrBuf *x_xrds_location = NULL; if (!SuppliedURL) return(0); syslog(LOG_DEBUG, "perform_openid2_discovery(%s)", ChrPtr(SuppliedURL)); if (StrLength(SuppliedURL) == 0) return(0); ReplyBuf = NewStrBuf(); if (!ReplyBuf) return(0); curl = ctdl_openid_curl_easy_init(errmsg); if (!curl) return(0); curl_easy_setopt(curl, CURLOPT_URL, ChrPtr(SuppliedURL)); curl_easy_setopt(curl, CURLOPT_WRITEDATA, ReplyBuf); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlFillStrBuf_callback); my_headers = curl_slist_append(my_headers, "Accept:"); /* disable the default Accept: header */ my_headers = curl_slist_append(my_headers, "Accept: application/xrds+xml"); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, my_headers); curl_easy_setopt(curl, CURLOPT_WRITEHEADER, &x_xrds_location); curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, yadis_headerfunction); result = curl_easy_perform(curl); if (result) { syslog(LOG_DEBUG, "libcurl error %d: %s", result, errmsg); } curl_slist_free_all(my_headers); curl_easy_cleanup(curl); docbytes = StrLength(ReplyBuf); /* * The response from the server will be one of: * * Option 1: An HTML document with a element that includes a element with http-equiv * attribute, X-XRDS-Location, * * Does any provider actually do this? If so then we will implement it in the future. */ /* * Option 2: HTTP response-headers that include an X-XRDS-Location response-header, * together with a document. * Option 3: HTTP response-headers only, which MAY include an X-XRDS-Location response-header, * a contenttype response-header specifying MIME media type, * application/xrds+xml, or both. * * If the X-XRDS-Location header was delivered, we know about it at this point... */ if ( (x_xrds_location) && (strcmp(ChrPtr(x_xrds_location), ChrPtr(SuppliedURL))) ) { syslog(LOG_DEBUG, "X-XRDS-Location: %s ... recursing!", ChrPtr(x_xrds_location)); return_value = perform_openid2_discovery(x_xrds_location); FreeStrBuf(&x_xrds_location); } /* * Option 4: the returned web page may *be* an XRDS document. Try to parse it. */ if ( (return_value == 0) && (docbytes >= 0)) { return_value = parse_xrds_document(ReplyBuf); } /* * Option 5: if all else fails, attempt HTML based discovery. */ if ( (return_value == 0) && (docbytes >= 0)) { if (oiddata->op_url == NULL) { oiddata->op_url = NewStrBuf(); } extract_link(oiddata->op_url, HKEY("openid2.provider"), ReplyBuf); if (StrLength(oiddata->op_url) > 0) { return_value = openid_disco_html; } } if (ReplyBuf != NULL) { FreeStrBuf(&ReplyBuf); } return(return_value); } /* * Setup an OpenID authentication */ void cmd_oids(char *argbuf) { struct CitContext *CCC = CC; /* CachedCitContext - performance boost */ const char *Pos = NULL; StrBuf *ArgBuf = NULL; StrBuf *ReplyBuf = NULL; StrBuf *return_to = NULL; StrBuf *RedirectUrl = NULL; ctdl_openid *oiddata; int discovery_succeeded = 0; if (config.c_disable_newu) { cprintf("%d this system does not support openid.\n", ERROR + CMD_NOT_SUPPORTED); return; } Free_ctdl_openid ((ctdl_openid**)&CCC->openid_data); CCC->openid_data = oiddata = malloc(sizeof(ctdl_openid)); if (oiddata == NULL) { syslog(LOG_ALERT, "malloc() failed: %s", strerror(errno)); cprintf("%d malloc failed\n", ERROR + INTERNAL_ERROR); return; } memset(oiddata, 0, sizeof(ctdl_openid)); ArgBuf = NewStrBufPlain(argbuf, -1); oiddata->verified = 0; oiddata->claimed_id = NewStrBufPlain(NULL, StrLength(ArgBuf)); return_to = NewStrBufPlain(NULL, StrLength(ArgBuf)); StrBufExtract_NextToken(oiddata->claimed_id, ArgBuf, &Pos, '|'); StrBufExtract_NextToken(return_to, ArgBuf, &Pos, '|'); syslog(LOG_DEBUG, "User-Supplied Identifier is: %s", ChrPtr(oiddata->claimed_id)); /********** OpenID 2.0 section 7.3 - Discovery **********/ /* Section 7.3.1 says we have to attempt XRI based discovery. * No one is using this, no one is asking for it, no one wants it. * So we're not even going to bother attempting this mode. */ /* Attempt section 7.3.2 (Yadis discovery) and section 7.3.3 (HTML discovery); */ discovery_succeeded = perform_openid2_discovery(oiddata->claimed_id); if (discovery_succeeded == 0) { cprintf("%d There is no OpenID identity provider at this location.\n", ERROR); } else { /* * If we get to this point we are in possession of a valid OpenID Provider URL. */ syslog(LOG_DEBUG, "OP URI '%s' discovered using method %d", ChrPtr(oiddata->op_url), discovery_succeeded ); /* We have to "normalize" our Claimed ID otherwise it will cause some OP's to barf */ if (cbmstrcasestr(ChrPtr(oiddata->claimed_id), "://") == NULL) { StrBuf *cid = oiddata->claimed_id; oiddata->claimed_id = NewStrBufPlain(HKEY("http://")); StrBufAppendBuf(oiddata->claimed_id, cid, 0); FreeStrBuf(&cid); } /* * OpenID 2.0 section 9: request authentication * Assemble a URL to which the user-agent will be redirected. */ RedirectUrl = NewStrBufDup(oiddata->op_url); StrBufAppendBufPlain(RedirectUrl, HKEY("?openid.ns="), 0); StrBufUrlescAppend(RedirectUrl, NULL, "http://specs.openid.net/auth/2.0"); StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.mode=checkid_setup"), 0); StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.claimed_id="), 0); StrBufUrlescAppend(RedirectUrl, oiddata->claimed_id, NULL); StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.identity="), 0); StrBufUrlescAppend(RedirectUrl, oiddata->claimed_id, NULL); /* return_to tells the provider how to complete the round trip back to our site */ StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.return_to="), 0); StrBufUrlescAppend(RedirectUrl, return_to, NULL); /* Attribute Exchange * See: * http://openid.net/specs/openid-attribute-exchange-1_0.html * http://code.google.com/apis/accounts/docs/OpenID.html#endpoint * http://test-id.net/OP/AXFetch.aspx */ StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ns.ax="), 0); StrBufUrlescAppend(RedirectUrl, NULL, "http://openid.net/srv/ax/1.0"); StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.mode=fetch_request"), 0); StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.required=firstname,lastname,friendly,nickname"), 0); StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.type.firstname="), 0); StrBufUrlescAppend(RedirectUrl, NULL, "http://axschema.org/namePerson/first"); StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.type.lastname="), 0); StrBufUrlescAppend(RedirectUrl, NULL, "http://axschema.org/namePerson/last"); StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.type.friendly="), 0); StrBufUrlescAppend(RedirectUrl, NULL, "http://axschema.org/namePerson/friendly"); StrBufAppendBufPlain(RedirectUrl, HKEY("&openid.ax.type.nickname="), 0); StrBufUrlescAppend(RedirectUrl, NULL, "http://axschema.org/namePerson/nickname"); syslog(LOG_DEBUG, "OpenID: redirecting client to %s", ChrPtr(RedirectUrl)); cprintf("%d %s\n", CIT_OK, ChrPtr(RedirectUrl)); } FreeStrBuf(&ArgBuf); FreeStrBuf(&ReplyBuf); FreeStrBuf(&return_to); FreeStrBuf(&RedirectUrl); } /* * Finalize an OpenID authentication */ void cmd_oidf(char *argbuf) { long len; char buf[2048]; char thiskey[1024]; char thisdata[1024]; HashList *keys = NULL; const char *Key; void *Value; ctdl_openid *oiddata = (ctdl_openid *) CC->openid_data; if (config.c_disable_newu) { cprintf("%d this system does not support openid.\n", ERROR + CMD_NOT_SUPPORTED); return; } if (oiddata == NULL) { cprintf("%d run OIDS first.\n", ERROR + INTERNAL_ERROR); return; } if (StrLength(oiddata->op_url) == 0){ cprintf("%d No OpenID Endpoint URL has been obtained.\n", ERROR + ILLEGAL_VALUE); return; } keys = NewHash(1, NULL); if (!keys) { cprintf("%d NewHash() failed\n", ERROR + INTERNAL_ERROR); return; } cprintf("%d Transmit OpenID data now\n", START_CHAT_MODE); while (client_getln(buf, sizeof buf), strcmp(buf, "000")) { len = extract_token(thiskey, buf, 0, '|', sizeof thiskey); if (len < 0) { len = sizeof(thiskey) - 1; } extract_token(thisdata, buf, 1, '|', sizeof thisdata); Put(keys, thiskey, len, strdup(thisdata), NULL); } /* Check to see if this is a correct response. * Start with verified=1 but then set it to 0 if anything looks wrong. */ oiddata->verified = 1; char *openid_ns = NULL; if ( (!GetHash(keys, "ns", 2, (void *) &openid_ns)) || (strcasecmp(openid_ns, "http://specs.openid.net/auth/2.0")) ) { syslog(LOG_DEBUG, "This is not an an OpenID assertion"); oiddata->verified = 0; } char *openid_mode = NULL; if ( (!GetHash(keys, "mode", 4, (void *) &openid_mode)) || (strcasecmp(openid_mode, "id_res")) ) { oiddata->verified = 0; } char *openid_claimed_id = NULL; if (GetHash(keys, "claimed_id", 10, (void *) &openid_claimed_id)) { FreeStrBuf(&oiddata->claimed_id); oiddata->claimed_id = NewStrBufPlain(openid_claimed_id, -1); syslog(LOG_DEBUG, "Provider is asserting the Claimed ID '%s'", ChrPtr(oiddata->claimed_id)); } /* Validate the assertion against the server */ syslog(LOG_DEBUG, "Validating..."); CURL *curl; CURLcode res; struct curl_httppost *formpost = NULL; struct curl_httppost *lastptr = NULL; char errmsg[1024] = ""; StrBuf *ReplyBuf = NewStrBuf(); curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "openid.mode", CURLFORM_COPYCONTENTS, "check_authentication", CURLFORM_END ); HashPos *HashPos = GetNewHashPos(keys, 0); while (GetNextHashPos(keys, HashPos, &len, &Key, &Value) != 0) { if (strcasecmp(Key, "mode")) { char k_o_keyname[1024]; snprintf(k_o_keyname, sizeof k_o_keyname, "openid.%s", (const char *)Key); curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, k_o_keyname, CURLFORM_COPYCONTENTS, (char *)Value, CURLFORM_END ); } } DeleteHashPos(&HashPos); curl = ctdl_openid_curl_easy_init(errmsg); curl_easy_setopt(curl, CURLOPT_URL, ChrPtr(oiddata->op_url)); curl_easy_setopt(curl, CURLOPT_WRITEDATA, ReplyBuf); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlFillStrBuf_callback); curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost); res = curl_easy_perform(curl); if (res) { syslog(LOG_DEBUG, "cmd_oidf() libcurl error %d: %s", res, errmsg); oiddata->verified = 0; } curl_easy_cleanup(curl); curl_formfree(formpost); /* syslog(LOG_DEBUG, "Validation reply: \n%s", ChrPtr(ReplyBuf)); */ if (cbmstrcasestr(ChrPtr(ReplyBuf), "is_valid:true") == NULL) { oiddata->verified = 0; } FreeStrBuf(&ReplyBuf); syslog(LOG_DEBUG, "OpenID authentication %s", (oiddata->verified ? "succeeded" : "failed") ); /* Respond to the client */ if (oiddata->verified) { /* If we were already logged in, attach the OpenID to the user's account */ if (CC->logged_in) { if (attach_openid(&CC->user, oiddata->claimed_id) == 0) { cprintf("attach\n"); syslog(LOG_DEBUG, "OpenID attach succeeded"); } else { cprintf("fail\n"); syslog(LOG_DEBUG, "OpenID attach failed"); } } /* Otherwise, a user is attempting to log in using the verified OpenID */ else { /* * Existing user who has claimed this OpenID? * * Note: if you think that sending the password back over the wire is insecure, * check your assumptions. If someone has successfully asserted an OpenID that * is associated with the account, they already have password equivalency and can * login, so they could just as easily change the password, etc. */ if (login_via_openid(oiddata->claimed_id) == 0) { cprintf("authenticate\n%s\n%s\n", CC->user.fullname, CC->user.password); logged_in_response(); syslog(LOG_DEBUG, "Logged in using previously claimed OpenID"); } /* * If this system does not allow self-service new user registration, the * remaining modes do not apply, so fail here and now. */ else if (config.c_disable_newu) { cprintf("fail\n"); syslog(LOG_DEBUG, "Creating user failed due to local policy"); } /* * New user whose OpenID is verified and Attribute Exchange gave us a name? */ else if (openid_create_user_via_ax(oiddata->claimed_id, keys) == 0) { cprintf("authenticate\n%s\n%s\n", CC->user.fullname, CC->user.password); logged_in_response(); syslog(LOG_DEBUG, "Successfully auto-created new user"); } /* * OpenID is verified, but the desired username either was not specified or * conflicts with an existing user. Manual account creation is required. */ else { char *desired_name = NULL; cprintf("verify_only\n"); cprintf("%s\n", ChrPtr(oiddata->claimed_id)); if (GetHash(keys, "sreg.nickname", 13, (void *) &desired_name)) { cprintf("%s\n", desired_name); } else { cprintf("\n"); } syslog(LOG_DEBUG, "The desired display name is already taken."); } } } else { cprintf("fail\n"); } cprintf("000\n"); if (oiddata->sreg_keys != NULL) { DeleteHash(&oiddata->sreg_keys); oiddata->sreg_keys = NULL; } oiddata->sreg_keys = keys; } /**************************************************************************/ /* */ /* Functions in this section handle module initialization and shutdown */ /* */ /**************************************************************************/ CTDL_MODULE_INIT(openid_rp) { if (!threading) { // evcurl call this for us. curl_global_init(CURL_GLOBAL_ALL); /* Only enable the OpenID command set when native mode authentication is in use. */ if (config.c_auth_mode == AUTHMODE_NATIVE) { CtdlRegisterProtoHook(cmd_oids, "OIDS", "Setup OpenID authentication"); CtdlRegisterProtoHook(cmd_oidf, "OIDF", "Finalize OpenID authentication"); CtdlRegisterProtoHook(cmd_oidl, "OIDL", "List OpenIDs associated with an account"); CtdlRegisterProtoHook(cmd_oidd, "OIDD", "Detach an OpenID from an account"); CtdlRegisterProtoHook(cmd_oidc, "OIDC", "Create new user after validating OpenID"); CtdlRegisterProtoHook(cmd_oida, "OIDA", "List all OpenIDs in the database"); } CtdlRegisterSessionHook(openid_cleanup_function, EVT_LOGOUT, PRIO_LOGOUT + 10); CtdlRegisterUserHook(openid_purge, EVT_PURGEUSER); openid_level_supported = 1; /* This module supports OpenID 1.0 only */ } /* return our module name for the log */ return "openid_rp"; } citadel-9.01/modules/xmpp/0000755000000000000000000000000012507024051014176 5ustar rootrootcitadel-9.01/modules/xmpp/xmpp_presence.c0000644000000000000000000002515312507024051017220 0ustar rootroot/* * Handle XMPP presence exchanges * * Copyright (c) 2007-2015 by Art Cancro and citadel.org * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include "sysdep.h" #include #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "internet_addressing.h" #include "md5.h" #include "ctdl_module.h" #include "serv_xmpp.h" /* * Indicate the presence of another user to the client * (used in several places) */ void xmpp_indicate_presence(char *presence_jid) { char xmlbuf[256]; XMPP_syslog(LOG_DEBUG, "XMPP: indicating presence of <%s> to <%s>", presence_jid, XMPP->client_jid); cprintf("", xmlesc(xmlbuf, XMPP->client_jid, sizeof xmlbuf)); } /* * Convenience function to determine whether any given session is 'visible' to any other given session, * and is capable of receiving instant messages from that session. */ int xmpp_is_visible(struct CitContext *cptr, struct CitContext *to_whom) { int aide = (to_whom->user.axlevel >= AxAideU); if ( (cptr->logged_in) && (((cptr->cs_flags&CS_STEALTH)==0) || (aide)) /* aides see everyone */ && (cptr->user.usernum != to_whom->user.usernum) /* don't show myself */ && (cptr->can_receive_im) /* IM-capable session */ ) { return(1); } else { return(0); } } /* * Initial dump of the entire wholist */ void xmpp_wholist_presence_dump(void) { struct CitContext *cptr = NULL; int nContexts, i; cptr = CtdlGetContextArray(&nContexts); if (!cptr) { return; } for (i=0; iclient_jid) return; /* Transmit non-presence information */ cprintf("", xmlesc(xmlbuf1, presence_jid, sizeof xmlbuf1), xmlesc(xmlbuf2, XMPP->client_jid, sizeof xmlbuf2) ); /* * Setting the "aggressively" flag also sends an "unsubscribed" presence update. * We only ask for this when flushing the client side roster, because if we do it * in the middle of a session when another user logs off, some clients (Jitsi) interpret * it as a rejection of a subscription request. */ if (aggressively) { cprintf("", xmlesc(xmlbuf1, presence_jid, sizeof xmlbuf1), xmlesc(xmlbuf2, XMPP->client_jid, sizeof xmlbuf2) ); } // note: we should implement xmpp_indicate_nonpresence so we can use it elsewhere /* Do an unsolicited roster update that deletes the contact. */ cprintf("", xmlesc(xmlbuf1, CC->cs_inet_email, sizeof xmlbuf1), xmlesc(xmlbuf2, XMPP->client_jid, sizeof xmlbuf2), ++unsolicited_id ); cprintf(""); cprintf("", xmlesc(xmlbuf1, presence_jid, sizeof xmlbuf1)); cprintf("%s", xmlesc(xmlbuf1, config.c_humannode, sizeof xmlbuf1)); cprintf(""); cprintf("" "" ); } /* * When a user logs in or out of the local Citadel system, notify all XMPP sessions about it. * THIS FUNCTION HAS A BUG IN IT THAT ENUMERATES THE SESSIONS WRONG. */ void xmpp_presence_notify(char *presence_jid, int event_type) { struct CitContext *cptr; static int unsolicited_id = 12345; int visible_sessions = 0; int nContexts, i; int which_cptr_is_relevant = (-1); if (IsEmptyStr(presence_jid)) return; if (CC->kill_me) return; cptr = CtdlGetContextArray(&nContexts); if (!cptr) { return; } /* Count the visible sessions for this user */ for (i=0; i are now visible to session %d\n", visible_sessions, presence_jid, CC->cs_pid); if ( (event_type == XMPP_EVT_LOGIN) && (visible_sessions == 1) ) { syslog(LOG_DEBUG, "Telling session %d that <%s> logged in\n", CC->cs_pid, presence_jid); /* Do an unsolicited roster update that adds a new contact. */ assert(which_cptr_is_relevant >= 0); cprintf("", ++unsolicited_id); cprintf(""); xmpp_roster_item(&cptr[which_cptr_is_relevant]); cprintf(""); /* Transmit presence information */ xmpp_indicate_presence(presence_jid); } if (visible_sessions == 0) { syslog(LOG_DEBUG, "Telling session %d that <%s> logged out\n", CC->cs_pid, presence_jid); xmpp_destroy_buddy(presence_jid, 0); /* non aggressive presence update */ } free(cptr); } void xmpp_fetch_mortuary_backend(long msgnum, void *userdata) { HashList *mortuary = (HashList *) userdata; struct CtdlMessage *msg; char *ptr = NULL; char *lasts = NULL; msg = CtdlFetchMessage(msgnum, 1); if (msg == NULL) { return; } /* now add anyone we find into the hashlist */ /* skip past the headers */ ptr = strstr(msg->cm_fields[eMesageText], "\n\n"); if (ptr != NULL) { ptr += 2; } else { ptr = strstr(msg->cm_fields[eMesageText], "\n\r\n"); if (ptr != NULL) { ptr += 3; } } /* the remaining lines are addresses */ if (ptr != NULL) { ptr = strtok_r(ptr, "\n", &lasts); while (ptr != NULL) { char *pch = strdup(ptr); Put(mortuary, pch, strlen(pch), pch, NULL); ptr = strtok_r(NULL, "\n", &lasts); } } CM_Free(msg); } /* * Fetch the "mortuary" - a list of dead buddies which we keep around forever * so we can remove them from any client's roster that still has them listed */ HashList *xmpp_fetch_mortuary(void) { HashList *mortuary = NewHash(1, NULL); if (!mortuary) { syslog(LOG_ALERT, "NewHash() failed!\n"); return(NULL); } if (CtdlGetRoom(&CC->room, USERCONFIGROOM) != 0) { /* no config room exists - no further processing is required. */ return(mortuary); } CtdlForEachMessage(MSGS_LAST, 1, NULL, XMPPMORTUARY, NULL, xmpp_fetch_mortuary_backend, (void *)mortuary ); return(mortuary); } /* * Fetch the "mortuary" - a list of dead buddies which we keep around forever * so we can remove them from any client's roster that still has them listed */ void xmpp_store_mortuary(HashList *mortuary) { HashPos *HashPos; long len; void *Value; const char *Key; StrBuf *themsg; themsg = NewStrBuf(); StrBufPrintf(themsg, "Content-type: " XMPPMORTUARY "\n" "Content-transfer-encoding: 7bit\n" "\n" ); HashPos = GetNewHashPos(mortuary, 0); while (GetNextHashPos(mortuary, HashPos, &len, &Key, &Value) != 0) { StrBufAppendPrintf(themsg, "%s\n", (char *)Value); } DeleteHashPos(&HashPos); /* Delete the old mortuary */ CtdlDeleteMessages(USERCONFIGROOM, NULL, 0, XMPPMORTUARY); /* And save the new one to disk */ quickie_message("Citadel", NULL, NULL, USERCONFIGROOM, ChrPtr(themsg), 4, "XMPP Mortuary"); FreeStrBuf(&themsg); } /* * Upon logout we make an attempt to delete the whole roster, in order to * try to keep "ghost" buddies from remaining in the client-side roster. * * Since the client is probably not still alive, also remember the current * roster for next time so we can delete dead buddies then. */ void xmpp_massacre_roster(void) { struct CitContext *cptr; int nContexts, i; HashList *mortuary = xmpp_fetch_mortuary(); cptr = CtdlGetContextArray(&nContexts); if (cptr) { for (i=0; i #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "user_ops.h" #include "internet_addressing.h" #include "md5.h" #include "ctdl_module.h" #include "serv_xmpp.h" /* * PLAIN authentication. Returns zero on success, nonzero on failure. */ int xmpp_auth_plain(char *authstring) { char decoded_authstring[1024]; char ident[256]; char user[256]; char pass[256]; int result; long len; /* Take apart the authentication string */ memset(pass, 0, sizeof(pass)); CtdlDecodeBase64(decoded_authstring, authstring, strlen(authstring)); safestrncpy(ident, decoded_authstring, sizeof ident); safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user); len = safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass); if (len < 0) len = -len; /* If there are underscores in either string, change them to spaces. Some clients * do not allow spaces so we can tell the user to substitute underscores if their * login name contains spaces. */ convert_spaces_to_underscores(ident); convert_spaces_to_underscores(user); /* Now attempt authentication */ if (!IsEmptyStr(ident)) { result = CtdlLoginExistingUser(user, ident); } else { result = CtdlLoginExistingUser(NULL, user); } if (result == login_ok) { if (CtdlTryPassword(pass, len) == pass_ok) { return(0); /* success */ } } return(1); /* failure */ } /* * Output the list of SASL mechanisms offered by this stream. */ void xmpp_output_auth_mechs(void) { cprintf(""); cprintf("PLAIN"); cprintf(""); } /* * Here we go ... client is trying to authenticate. */ void xmpp_sasl_auth(char *sasl_auth_mech, char *authstring) { if (strcasecmp(sasl_auth_mech, "PLAIN")) { cprintf(""); cprintf(""); cprintf(""); return; } if (CC->logged_in) CtdlUserLogout(); /* Client may try to log in twice. Handle this. */ if (CC->nologin) { cprintf(""); cprintf(""); cprintf(""); } else if (xmpp_auth_plain(authstring) == 0) { cprintf(""); } else { cprintf(""); cprintf(""); cprintf(""); } } /* * Non-SASL authentication */ void xmpp_non_sasl_authenticate(char *iq_id, char *username, char *password, char *resource) { int result; char xmlbuf[256]; if (CC->logged_in) CtdlUserLogout(); /* Client may try to log in twice. Handle this. */ result = CtdlLoginExistingUser(NULL, username); if (result == login_ok) { result = CtdlTryPassword(password, strlen(password)); if (result == pass_ok) { cprintf("", xmlesc(xmlbuf, iq_id, sizeof xmlbuf)); /* success */ return; } } /* failure */ cprintf("", xmlesc(xmlbuf, iq_id, sizeof xmlbuf)); cprintf("" "" "" "" ); } citadel-9.01/modules/xmpp/serv_xmpp.h0000644000000000000000000000605712507024051016402 0ustar rootroot/* * Copyright (c) 2007-2009 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ typedef struct _citxmpp { /* Information about the current session */ XML_Parser xp; /* XML parser instance for incoming client stream */ char server_name[256]; /* who they think we are */ char *chardata; int chardata_len; int chardata_alloc; char client_jid[256]; /* "full JID" of the client */ int last_event_processed; char iq_type[256]; /* for stanzas */ char iq_id[256]; char iq_from[256]; char iq_to[256]; char iq_client_username[256]; /* username requested by the client (NON SASL ONLY) */ char iq_client_password[256]; /* password requested by the client (NON SASL ONLY) */ char iq_client_resource[256]; /* resource name requested by the client */ int iq_session; /* nonzero == client is requesting a session */ char iq_query_xmlns[256]; /* Namespace of */ char sasl_auth_mech[32]; /* SASL auth mechanism requested by the client */ char message_to[256]; char *message_body; /* Message body in transit */ int html_tag_level; /* tag nesting level */ int bind_requested; /* In this stanza, client is asking server to bind a resource. */ int ping_requested; /* In this stanza, client is pinging the server. */ } citxmpp; #define XMPP ((citxmpp *)CC->session_specific_data) struct xmpp_event { struct xmpp_event *next; int event_seq; time_t event_time; int event_type; char event_jid[256]; int session_which_generated_this_event; }; extern struct xmpp_event *xmpp_queue; extern int queue_event_seq; enum { XMPP_EVT_LOGIN, XMPP_EVT_LOGOUT }; void xmpp_cleanup_function(void); void xmpp_greeting(void); void xmpp_command_loop(void); void xmpp_async_loop(void); void xmpp_sasl_auth(char *, char *); void xmpp_output_auth_mechs(void); void xmpp_query_namespace(char *, char *, char *, char *); void xmpp_wholist_presence_dump(void); void xmpp_output_incoming_messages(void); void xmpp_queue_event(int, char *); void xmpp_process_events(void); void xmpp_presence_notify(char *, int); void xmpp_roster_item(struct CitContext *); void xmpp_send_message(char *, char *); void xmpp_non_sasl_authenticate(char *, char *, char *, char *); void xmpp_massacre_roster(void); void xmpp_delete_old_buddies_who_no_longer_exist_from_the_client_roster(void); int xmpp_is_visible(struct CitContext *from, struct CitContext *to_whom); char *xmlesc(char *buf, char *str, int bufsiz); extern int XMPPSrvDebugEnable; #define DBGLOG(LEVEL) if ((LEVEL != LOG_DEBUG) || (XMPPSrvDebugEnable != 0)) #define XMPP_syslog(LEVEL, FORMAT, ...) \ DBGLOG(LEVEL) syslog(LEVEL, \ "XMPP: " FORMAT, __VA_ARGS__) #define XMPPM_syslog(LEVEL, FORMAT) \ DBGLOG(LEVEL) syslog(LEVEL, \ "XMPP: " FORMAT); citadel-9.01/modules/xmpp/serv_xmpp.c0000644000000000000000000004205112507024051016367 0ustar rootroot/* * XMPP (Jabber) service for the Citadel system * Copyright (c) 2007-2015 by Art Cancro and citadel.org * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "sysdep.h" #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "user_ops.h" #include "database.h" #include "msgbase.h" #include "internet_addressing.h" #include "md5.h" #include "ctdl_module.h" #include "serv_xmpp.h" /* XML_StopParser is present in expat 2.x */ #if XML_MAJOR_VERSION > 1 #define HAVE_XML_STOPPARSER #endif struct xmpp_event *xmpp_queue = NULL; int XMPPSrvDebugEnable = 0; #ifdef HAVE_XML_STOPPARSER /* Stop the parser if an entity declaration is hit. */ static void xmpp_entity_declaration(void *userData, const XML_Char *entityName, int is_parameter_entity, const XML_Char *value, int value_length, const XML_Char *base, const XML_Char *systemId, const XML_Char *publicId, const XML_Char *notationName ) { XMPPM_syslog(LOG_WARNING, "Illegal entity declaration encountered; stopping parser."); XML_StopParser(XMPP->xp, XML_FALSE); } #endif /* * Given a source string and a target buffer, returns the string * properly escaped for insertion into an XML stream. Returns a * pointer to the target buffer for convenience. */ static inline int Ctdl_GetUtf8SequenceLength(const char *CharS, const char *CharE) { int n = 0; unsigned char test = (1<<7); if ((*CharS & 0xC0) != 0xC0) return 1; while ((n < 8) && ((test & ((unsigned char)*CharS)) != 0)) { test = test >> 1; n ++; } if ((n > 6) || ((CharE - CharS) < n)) n = 0; return n; } char *xmlesc(char *buf, char *str, int bufsiz) { int IsUtf8Sequence; char *ptr, *pche; unsigned char ch; int inlen; int len = 0; if (!buf) return(NULL); buf[0] = 0; len = 0; if (!str) { return(buf); } inlen = strlen(str); pche = str + inlen; for (ptr=str; *ptr; ptr++) { ch = *ptr; if (ch == '<') { strcpy(&buf[len], "<"); len += 4; } else if (ch == '>') { strcpy(&buf[len], ">"); len += 4; } else if (ch == '&') { strcpy(&buf[len], "&"); len += 5; } else if ((ch >= 0x20) && (ch <= 0x7F)) { buf[len++] = ch; buf[len] = 0; } else if (ch < 0x20) { /* we probably shouldn't be doing this */ buf[len++] = '_'; buf[len] = 0; } else { IsUtf8Sequence = Ctdl_GetUtf8SequenceLength(ptr, pche); if (IsUtf8Sequence) { while ((IsUtf8Sequence > 0) && (ptr < pche)) { buf[len] = *ptr; ptr ++; --IsUtf8Sequence; } } else { char oct[10]; sprintf(oct, "&#%o;", ch); strcpy(&buf[len], oct); len += strlen(oct); } } if ((len + 6) > bufsiz) { return(buf); } } return(buf); } /* * We have just received a tag from the client, so send them ours */ void xmpp_stream_start(void *data, const char *supplied_el, const char **attr) { char xmlbuf[256]; while (*attr) { if (!strcasecmp(attr[0], "to")) { safestrncpy(XMPP->server_name, attr[1], sizeof XMPP->server_name); } attr += 2; } cprintf(""); cprintf("server_name, sizeof xmlbuf)); cprintf("id=\"%08x\" ", CC->cs_pid); cprintf("version=\"1.0\" "); cprintf("xmlns:stream=\"http://etherx.jabber.org/streams\" "); cprintf("xmlns=\"jabber:client\">"); /* The features of this stream are... */ cprintf(""); /* * TLS encryption (but only if it isn't already active) */ #ifdef HAVE_OPENSSL if (!CC->redirect_ssl) { cprintf(""); } #endif if (!CC->logged_in) { /* If we're not logged in yet, offer SASL as our feature set */ xmpp_output_auth_mechs(); /* Also offer non-SASL authentication */ cprintf(""); } /* Offer binding and sessions as part of our feature set */ cprintf(""); cprintf(""); cprintf(""); CC->is_async = 1; /* XMPP sessions are inherently async-capable */ } void xmpp_xml_start(void *data, const char *supplied_el, const char **attr) { char el[256]; char *sep = NULL; int i; /* Axe the namespace, we don't care about it */ safestrncpy(el, supplied_el, sizeof el); while (sep = strchr(el, ':'), sep) { strcpy(el, ++sep); } /* XMPP_syslog(LOG_DEBUG, "XMPP ELEMENT START: <%s>\n", el); for (i=0; attr[i] != NULL; i+=2) { XMPP_syslog(LOG_DEBUG, " Attribute '%s' = '%s'\n", attr[i], attr[i+1]); } uncomment for more verbosity */ if (!strcasecmp(el, "stream")) { xmpp_stream_start(data, supplied_el, attr); } else if (!strcasecmp(el, "query")) { XMPP->iq_query_xmlns[0] = 0; safestrncpy(XMPP->iq_query_xmlns, supplied_el, sizeof XMPP->iq_query_xmlns); } else if (!strcasecmp(el, "bind")) { XMPP->bind_requested = 1; } else if (!strcasecmp(el, "iq")) { for (i=0; attr[i] != NULL; i+=2) { if (!strcasecmp(attr[i], "type")) { safestrncpy(XMPP->iq_type, attr[i+1], sizeof XMPP->iq_type); } else if (!strcasecmp(attr[i], "id")) { safestrncpy(XMPP->iq_id, attr[i+1], sizeof XMPP->iq_id); } else if (!strcasecmp(attr[i], "from")) { safestrncpy(XMPP->iq_from, attr[i+1], sizeof XMPP->iq_from); } else if (!strcasecmp(attr[i], "to")) { safestrncpy(XMPP->iq_to, attr[i+1], sizeof XMPP->iq_to); } } } else if (!strcasecmp(el, "auth")) { XMPP->sasl_auth_mech[0] = 0; for (i=0; attr[i] != NULL; i+=2) { if (!strcasecmp(attr[i], "mechanism")) { safestrncpy(XMPP->sasl_auth_mech, attr[i+1], sizeof XMPP->sasl_auth_mech); } } } else if (!strcasecmp(el, "message")) { for (i=0; attr[i] != NULL; i+=2) { if (!strcasecmp(attr[i], "to")) { safestrncpy(XMPP->message_to, attr[i+1], sizeof XMPP->message_to); } } } else if (!strcasecmp(el, "html")) { ++XMPP->html_tag_level; } } void xmpp_xml_end(void *data, const char *supplied_el) { char el[256]; char *sep = NULL; char xmlbuf[256]; /* Axe the namespace, we don't care about it */ safestrncpy(el, supplied_el, sizeof el); while (sep = strchr(el, ':'), sep) { strcpy(el, ++sep); } /* XMPP_syslog(LOG_DEBUG, "XMPP ELEMENT END : <%s>\n", el); if (XMPP->chardata_len > 0) { XMPP_syslog(LOG_DEBUG, " chardata: %s\n", XMPP->chardata); } uncomment for more verbosity */ if (!strcasecmp(el, "resource")) { if (XMPP->chardata_len > 0) { safestrncpy(XMPP->iq_client_resource, XMPP->chardata, sizeof XMPP->iq_client_resource); striplt(XMPP->iq_client_resource); } } else if (!strcasecmp(el, "username")) { /* NON SASL ONLY */ if (XMPP->chardata_len > 0) { safestrncpy(XMPP->iq_client_username, XMPP->chardata, sizeof XMPP->iq_client_username); striplt(XMPP->iq_client_username); } } else if (!strcasecmp(el, "password")) { /* NON SASL ONLY */ if (XMPP->chardata_len > 0) { safestrncpy(XMPP->iq_client_password, XMPP->chardata, sizeof XMPP->iq_client_password); striplt(XMPP->iq_client_password); } } else if (!strcasecmp(el, "iq")) { /* * iq type="get" (handle queries) */ if (!strcasecmp(XMPP->iq_type, "get")) { /* * Query on a namespace */ if (!IsEmptyStr(XMPP->iq_query_xmlns)) { xmpp_query_namespace(XMPP->iq_id, XMPP->iq_from, XMPP->iq_to, XMPP->iq_query_xmlns); } /* * ping ( http://xmpp.org/extensions/xep-0199.html ) */ else if (XMPP->ping_requested) { cprintf("iq_from)) { cprintf("to=\"%s\" ", xmlesc(xmlbuf, XMPP->iq_from, sizeof xmlbuf)); } if (!IsEmptyStr(XMPP->iq_to)) { cprintf("from=\"%s\" ", xmlesc(xmlbuf, XMPP->iq_to, sizeof xmlbuf)); } cprintf("id=\"%s\"/>", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf)); } /* * Unknown query ... return the XML equivalent of a blank stare */ else { XMPP_syslog(LOG_DEBUG, "Unknown query <%s> - returning \n", el ); cprintf("", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf)); cprintf("" "" "" ); cprintf(""); } } /* * Non SASL authentication */ else if ( (!strcasecmp(XMPP->iq_type, "set")) && (!strcasecmp(XMPP->iq_query_xmlns, "jabber:iq:auth:query")) ) { xmpp_non_sasl_authenticate( XMPP->iq_id, XMPP->iq_client_username, XMPP->iq_client_password, XMPP->iq_client_resource ); } /* * If this stanza was a "bind" attempt, process it ... */ else if ( (XMPP->bind_requested) && (!IsEmptyStr(XMPP->iq_id)) && (!IsEmptyStr(XMPP->iq_client_resource)) && (CC->logged_in) ) { /* Generate the "full JID" of the client resource */ snprintf(XMPP->client_jid, sizeof XMPP->client_jid, "%s/%s", CC->cs_inet_email, XMPP->iq_client_resource ); /* Tell the client what its JID is */ cprintf("", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf)); cprintf(""); cprintf("%s", xmlesc(xmlbuf, XMPP->client_jid, sizeof xmlbuf)); cprintf(""); cprintf(""); } else if (XMPP->iq_session) { cprintf("", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf)); cprintf(""); } else { cprintf("", xmlesc(xmlbuf, XMPP->iq_id, sizeof xmlbuf)); cprintf("Don't know howto do '%s'!", xmlesc(xmlbuf, XMPP->iq_type, sizeof xmlbuf)); cprintf(""); } /* Now clear these fields out so they don't get used by a future stanza */ XMPP->iq_id[0] = 0; XMPP->iq_from[0] = 0; XMPP->iq_to[0] = 0; XMPP->iq_type[0] = 0; XMPP->iq_client_resource[0] = 0; XMPP->iq_session = 0; XMPP->iq_query_xmlns[0] = 0; XMPP->bind_requested = 0; XMPP->ping_requested = 0; } else if (!strcasecmp(el, "auth")) { /* Try to authenticate (this function is responsible for the output stanza) */ xmpp_sasl_auth(XMPP->sasl_auth_mech, (XMPP->chardata != NULL ? XMPP->chardata : "") ); /* Now clear these fields out so they don't get used by a future stanza */ XMPP->sasl_auth_mech[0] = 0; } else if (!strcasecmp(el, "session")) { XMPP->iq_session = 1; } else if (!strcasecmp(el, "presence")) { /* Respond to a update by firing back with presence information * on the entire wholist. Check this assumption, it's probably wrong. */ xmpp_wholist_presence_dump(); } else if ( (!strcasecmp(el, "body")) && (XMPP->html_tag_level == 0) ) { if (XMPP->message_body != NULL) { free(XMPP->message_body); XMPP->message_body = NULL; } if (XMPP->chardata_len > 0) { XMPP->message_body = strdup(XMPP->chardata); } } else if (!strcasecmp(el, "message")) { xmpp_send_message(XMPP->message_to, XMPP->message_body); XMPP->html_tag_level = 0; } else if (!strcasecmp(el, "html")) { --XMPP->html_tag_level; } else if (!strcasecmp(el, "starttls")) { #ifdef HAVE_OPENSSL cprintf(""); CtdlModuleStartCryptoMsgs(NULL, NULL, NULL); if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO; #else cprintf(""); CC->kill_me = KILLME_NO_CRYPTO; #endif } else if (!strcasecmp(el, "ping")) { XMPP->ping_requested = 1; } else if (!strcasecmp(el, "stream")) { XMPPM_syslog(LOG_DEBUG, "XMPP client shut down their stream\n"); xmpp_massacre_roster(); cprintf("\n"); CC->kill_me = KILLME_CLIENT_LOGGED_OUT; } else if (!strcasecmp(el, "query")) { /* already processed , no further action needed here */ } else if (!strcasecmp(el, "bind")) { /* already processed , no further action needed here */ } else { XMPP_syslog(LOG_DEBUG, "Ignoring unknown tag <%s>\n", el); } XMPP->chardata_len = 0; if (XMPP->chardata_alloc > 0) { XMPP->chardata[0] = 0; } } void xmpp_xml_chardata(void *data, const XML_Char *s, int len) { citxmpp *X = XMPP; if (X->chardata_alloc == 0) { X->chardata_alloc = SIZ; X->chardata = malloc(X->chardata_alloc); } if ((X->chardata_len + len + 1) > X->chardata_alloc) { X->chardata_alloc = X->chardata_len + len + 1024; X->chardata = realloc(X->chardata, X->chardata_alloc); } memcpy(&X->chardata[X->chardata_len], s, len); X->chardata_len += len; X->chardata[X->chardata_len] = 0; } /* * This cleanup function blows away the temporary memory and files used by the XMPP service. */ void xmpp_cleanup_function(void) { /* Don't do this stuff if this is not a XMPP session! */ if (CC->h_command_function != xmpp_command_loop) return; if (XMPP->chardata != NULL) { free(XMPP->chardata); XMPP->chardata = NULL; XMPP->chardata_len = 0; XMPP->chardata_alloc = 0; if (XMPP->message_body != NULL) { free(XMPP->message_body); } } XML_ParserFree(XMPP->xp); free(XMPP); } /* * Here's where our XMPP session begins its happy day. */ void xmpp_greeting(void) { client_set_inbound_buf(4); strcpy(CC->cs_clientname, "XMPP session"); CC->session_specific_data = malloc(sizeof(citxmpp)); memset(XMPP, 0, sizeof(citxmpp)); XMPP->last_event_processed = queue_event_seq; /* XMPP does not use a greeting, but we still have to initialize some things. */ XMPP->xp = XML_ParserCreateNS("UTF-8", ':'); if (XMPP->xp == NULL) { XMPPM_syslog(LOG_ALERT, "Cannot create XML parser!\n"); CC->kill_me = KILLME_XML_PARSER; return; } XML_SetElementHandler(XMPP->xp, xmpp_xml_start, xmpp_xml_end); XML_SetCharacterDataHandler(XMPP->xp, xmpp_xml_chardata); // XML_SetUserData(XMPP->xp, something...); /* Prevent the "billion laughs" attack against expat by disabling * internal entity expansion. With 2.x, forcibly stop the parser * if an entity is declared - this is safer and a more obvious * failure mode. With older versions, simply prevent expansion * of such entities. */ #ifdef HAVE_XML_STOPPARSER XML_SetEntityDeclHandler(XMPP->xp, xmpp_entity_declaration); #else XML_SetDefaultHandler(XMPP->xp, NULL); #endif CC->can_receive_im = 1; /* This protocol is capable of receiving instant messages */ } /* * Main command loop for XMPP sessions. */ void xmpp_command_loop(void) { int rc; StrBuf *stream_input = NewStrBuf(); time(&CC->lastcmd); rc = client_read_random_blob(stream_input, 30); if (rc > 0) { XML_Parse(XMPP->xp, ChrPtr(stream_input), rc, 0); } else { XMPPM_syslog(LOG_ERR, "client disconnected: ending session.\n"); CC->kill_me = KILLME_CLIENT_DISCONNECTED; } FreeStrBuf(&stream_input); } /* * Async loop for XMPP sessions (handles the transmission of unsolicited stanzas) */ void xmpp_async_loop(void) { xmpp_process_events(); xmpp_output_incoming_messages(); } /* * Login hook for XMPP sessions */ void xmpp_login_hook(void) { xmpp_queue_event(XMPP_EVT_LOGIN, CC->cs_inet_email); } /* * Logout hook for XMPP sessions */ void xmpp_logout_hook(void) { xmpp_queue_event(XMPP_EVT_LOGOUT, CC->cs_inet_email); } void LogXMPPSrvDebugEnable(const int n) { XMPPSrvDebugEnable = n; } const char *CitadelServiceXMPP="XMPP"; extern void xmpp_cleanup_events(void); CTDL_MODULE_INIT(xmpp) { if (!threading) { CtdlRegisterServiceHook(config.c_xmpp_c2s_port, NULL, xmpp_greeting, xmpp_command_loop, xmpp_async_loop, CitadelServiceXMPP ); CtdlRegisterDebugFlagHook(HKEY("serv_xmpp"), LogXMPPSrvDebugEnable, &XMPPSrvDebugEnable); CtdlRegisterSessionHook(xmpp_cleanup_function, EVT_STOP, PRIO_STOP + 70); CtdlRegisterSessionHook(xmpp_login_hook, EVT_LOGIN, PRIO_LOGIN + 90); CtdlRegisterSessionHook(xmpp_logout_hook, EVT_LOGOUT, PRIO_LOGOUT + 90); CtdlRegisterSessionHook(xmpp_login_hook, EVT_UNSTEALTH, PRIO_UNSTEALTH + 1); CtdlRegisterSessionHook(xmpp_logout_hook, EVT_STEALTH, PRIO_STEALTH + 1); CtdlRegisterCleanupHook(xmpp_cleanup_events); } /* return our module name for the log */ return "xmpp"; } citadel-9.01/modules/xmpp/xmpp_queue.c0000644000000000000000000000732012507024051016534 0ustar rootroot/* * XMPP event queue * * Copyright (c) 2007-2009 by Art Cancro * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * * */ #include "sysdep.h" #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "internet_addressing.h" #include "md5.h" #include "ctdl_module.h" #include "serv_xmpp.h" int queue_event_seq = 0; void xmpp_queue_event(int event_type, char *email_addr) { struct xmpp_event *xptr = NULL; struct xmpp_event *new_event = NULL; struct xmpp_event *last = NULL; int purged_something = 0; struct CitContext *cptr; syslog(LOG_DEBUG, "xmpp_queue_event(%d, %s)\n", event_type, email_addr); /* Purge events more than a minute old */ begin_critical_section(S_XMPP_QUEUE); do { purged_something = 0; if (xmpp_queue != NULL) { if ((time(NULL) - xmpp_queue->event_time) > 60) { xptr = xmpp_queue->next; free(xmpp_queue); xmpp_queue = xptr; purged_something = 1; } } } while(purged_something); end_critical_section(S_XMPP_QUEUE); /* Create a new event */ new_event = (struct xmpp_event *) malloc(sizeof(struct xmpp_event)); new_event->next = NULL; new_event->event_time = time(NULL); new_event->event_seq = ++queue_event_seq; new_event->event_type = event_type; new_event->session_which_generated_this_event = CC->cs_pid; safestrncpy(new_event->event_jid, email_addr, sizeof new_event->event_jid); /* Add it to the list */ begin_critical_section(S_XMPP_QUEUE); if (xmpp_queue == NULL) { xmpp_queue = new_event; } else { for (xptr = xmpp_queue; xptr != NULL; xptr = xptr->next) { if (xptr->next == NULL) { last = xptr; } } last->next = new_event; } end_critical_section(S_XMPP_QUEUE); /* Tell the sessions that something is happening */ begin_critical_section(S_SESSION_TABLE); for (cptr = ContextList; cptr != NULL; cptr = cptr->next) { if ((cptr->logged_in) && (cptr->h_async_function == xmpp_async_loop)) { set_async_waiting(cptr); } } end_critical_section(S_SESSION_TABLE); } /* * Are we interested in anything from the queue? (Called in async loop) */ void xmpp_process_events(void) { struct xmpp_event *xptr = NULL; int highest_event = 0; for (xptr=xmpp_queue; xptr!=NULL; xptr=xptr->next) { if (xptr->event_seq > XMPP->last_event_processed) { switch(xptr->event_type) { case XMPP_EVT_LOGIN: case XMPP_EVT_LOGOUT: if (xptr->session_which_generated_this_event != CC->cs_pid) { xmpp_presence_notify(xptr->event_jid, xptr->event_type); } break; } if (xptr->event_seq > highest_event) { highest_event = xptr->event_seq; } } } XMPP->last_event_processed = highest_event; } void xmpp_cleanup_events(void) { struct xmpp_event *ptr, *ptr2; begin_critical_section(S_XMPP_QUEUE); ptr = xmpp_queue; xmpp_queue = NULL; while (ptr != NULL) { ptr2 = ptr->next; free(ptr); ptr = ptr2; } end_critical_section(S_XMPP_QUEUE); } citadel-9.01/modules/xmpp/xmpp_messages.c0000644000000000000000000000534212507024051017221 0ustar rootroot/* * Handle messages sent and received using XMPP (Jabber) protocol * * Copyright (c) 2007-2010 by Art Cancro * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * * */ #include "sysdep.h" #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "internet_addressing.h" #include "md5.h" #include "ctdl_module.h" #include "serv_xmpp.h" /* * This function is called by the XMPP service's async loop. * If the client session has instant messages waiting, it outputs * unsolicited XML stanzas containing them. */ void xmpp_output_incoming_messages(void) { struct ExpressMessage *ptr; char xmlbuf1[4096]; char xmlbuf2[4096]; while (CC->FirstExpressMessage != NULL) { begin_critical_section(S_SESSION_TABLE); ptr = CC->FirstExpressMessage; CC->FirstExpressMessage = CC->FirstExpressMessage->next; end_critical_section(S_SESSION_TABLE); cprintf("", xmlesc(xmlbuf1, XMPP->client_jid, sizeof xmlbuf1), xmlesc(xmlbuf2, ptr->sender_email, sizeof xmlbuf2) ); if (ptr->text != NULL) { striplt(ptr->text); cprintf("%s", xmlesc(xmlbuf1, ptr->text, sizeof xmlbuf1)); free(ptr->text); } cprintf(""); free(ptr); } } /* * Client is sending a message. */ void xmpp_send_message(char *message_to, char *message_body) { char *recp = NULL; struct CitContext *cptr; if (message_body == NULL) return; if (message_to == NULL) return; if (IsEmptyStr(message_to)) return; if (!CC->logged_in) return; for (cptr = ContextList; cptr != NULL; cptr = cptr->next) { if ( (cptr->logged_in) && (cptr->can_receive_im) && (!strcasecmp(cptr->cs_inet_email, message_to)) ) { recp = cptr->user.fullname; } } if (recp) { PerformXmsgHooks(CC->user.fullname, CC->cs_inet_email, recp, message_body); } free(XMPP->message_body); XMPP->message_body = NULL; XMPP->message_to[0] = 0; time(&CC->lastidle); } citadel-9.01/modules/xmpp/xmpp_query_namespace.c0000644000000000000000000001371012507024051020571 0ustar rootroot/* * Handle type situations (namespace queries) * * Copyright (c) 2007-2015 by Art Cancro and citadel.org * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "sysdep.h" #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "internet_addressing.h" #include "md5.h" #include "ctdl_module.h" #include "serv_xmpp.h" /* * Output a single roster item, for roster queries or pushes */ void xmpp_roster_item(struct CitContext *cptr) { char xmlbuf1[256]; char xmlbuf2[256]; cprintf("", xmlesc(xmlbuf1, cptr->cs_inet_email, sizeof xmlbuf1), xmlesc(xmlbuf2, cptr->user.fullname, sizeof xmlbuf2) ); cprintf("%s", xmlesc(xmlbuf1, config.c_humannode, sizeof xmlbuf1)); cprintf(""); } /* * Return the results for a "jabber:iq:roster:query" * * Since we are not yet managing a roster, we simply return the entire wholist * (minus any entries for this user -- don't tell me about myself) * */ void xmpp_iq_roster_query(void) { struct CitContext *cptr; int nContexts, i; syslog(LOG_DEBUG, "Roster push!"); cprintf(""); cptr = CtdlGetContextArray(&nContexts); if (cptr) { for (i=0; i"); } /* * Client is doing a namespace query. These are all handled differently. * A "rumplestiltskin lookup" is the most efficient way to handle this. Please do not refactor this code. */ void xmpp_query_namespace(char *iq_id, char *iq_from, char *iq_to, char *query_xmlns) { int supported_namespace = 0; int roster_query = 0; char xmlbuf[256]; int reply_must_be_from_my_jid = 0; /* We need to know before we begin the response whether this is a supported namespace, so * unfortunately all supported namespaces need to be defined here *and* down below where * they are handled. */ if ( (!strcasecmp(query_xmlns, "jabber:iq:roster:query")) || (!strcasecmp(query_xmlns, "jabber:iq:auth:query")) || (!strcasecmp(query_xmlns, "http://jabber.org/protocol/disco#items:query")) || (!strcasecmp(query_xmlns, "http://jabber.org/protocol/disco#info:query")) ) { supported_namespace = 1; } XMPP_syslog(LOG_DEBUG, "xmpp_query_namespace(id=%s, from=%s, to=%s, xmlns=%s)\n", iq_id, iq_from, iq_to, query_xmlns); /* * Beginning of query result. */ if (!strcasecmp(query_xmlns, "jabber:iq:roster:query")) { reply_must_be_from_my_jid = 1; } char dom[1024]; // client is expecting to see the reply if (reply_must_be_from_my_jid) { // coming "from" the user's jid safestrncpy(dom, XMPP->client_jid, sizeof(dom)); char *slash = strchr(dom, '/'); if (slash) { *slash = 0; } } else { safestrncpy(dom, XMPP->client_jid, sizeof(dom)); // client is expecting to see the reply if (IsEmptyStr(dom)) { // coming "from" the domain of the user's jid safestrncpy(dom, XMPP->server_name, sizeof(dom)); } char *at = strrchr(dom, '@'); if (at) { strcpy(dom, ++at); } char *slash = strchr(dom, '/'); if (slash) { *slash = 0; } } if (supported_namespace) { cprintf("", xmlesc(xmlbuf, iq_id, sizeof xmlbuf)); /* * Is this a query we know how to handle? */ if (!strcasecmp(query_xmlns, "jabber:iq:roster:query")) { roster_query = 1; xmpp_iq_roster_query(); } else if (!strcasecmp(query_xmlns, "jabber:iq:auth:query")) { cprintf("" "" "" ); } // Extension "xep-0030" (http://xmpp.org/extensions/xep-0030.html) (return an empty set of results) else if (!strcasecmp(query_xmlns, "http://jabber.org/protocol/disco#items:query")) { cprintf("", xmlesc(xmlbuf, query_xmlns, sizeof xmlbuf)); } // Extension "xep-0030" (http://xmpp.org/extensions/xep-0030.html) (return an empty set of results) else if (!strcasecmp(query_xmlns, "http://jabber.org/protocol/disco#info:query")) { cprintf("", xmlesc(xmlbuf, query_xmlns, sizeof xmlbuf)); } /* * If we didn't hit any known query namespaces then we should deliver a * "service unavailable" error (see RFC3921 section 2.4 and 11.1.5.4) */ else { XMPP_syslog(LOG_DEBUG, "Unknown query namespace '%s' - returning \n", query_xmlns); cprintf("" "" "" ); } cprintf(""); /* If we told the client who is on the roster, we also need to tell the client * who is *not* on the roster. (It's down here because we can't do it in the same * stanza; this will be an unsolicited push.) */ if (roster_query) { xmpp_delete_old_buddies_who_no_longer_exist_from_the_client_roster(); } } citadel-9.01/modules/expire/0000755000000000000000000000000012507024051014506 5ustar rootrootcitadel-9.01/modules/expire/serv_expire.c0000644000000000000000000006114512507024051017214 0ustar rootroot/* * This module handles the expiry of old messages and the purging of old users. * * You might also see this module affectionately referred to as the DAP (the Dreaded Auto-Purger). * * Copyright (c) 1988-2011 by citadel.org (Art Cancro, Wilifried Goesgens, and others) * * This program is open source software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * * A brief technical discussion: * * Several of the purge operations found in this module operate in two * stages: the first stage generates a linked list of objects to be deleted, * then the second stage deletes all listed objects from the database. * * At first glance this may seem cumbersome and unnecessary. The reason it is * implemented in this way is because Berkeley DB, and possibly other backends * we may hook into in the future, explicitly do _not_ support the deletion of * records from a file while the file is being traversed. The delete operation * will succeed, but the traversal is not guaranteed to visit every object if * this is done. Therefore we utilize the two-stage purge. * * When using Berkeley DB, there's another reason for the two-phase purge: we * don't want the entire thing being done as one huge transaction. * * You'll also notice that we build the in-memory list of records to be deleted * sometimes with a linked list and sometimes with a hash table. There is no * reason for this aside from the fact that the linked list ones were written * before we had the hash table library available. */ #include "sysdep.h" #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "policy.h" #include "database.h" #include "msgbase.h" #include "user_ops.h" #include "control.h" #include "threads.h" #include "context.h" #include "ctdl_module.h" struct PurgeList { struct PurgeList *next; char name[ROOMNAMELEN]; /* use the larger of username or roomname */ }; struct VPurgeList { struct VPurgeList *next; long vp_roomnum; long vp_roomgen; long vp_usernum; }; struct ValidRoom { struct ValidRoom *next; long vr_roomnum; long vr_roomgen; }; struct ValidUser { struct ValidUser *next; long vu_usernum; }; struct ctdlroomref { struct ctdlroomref *next; long msgnum; }; struct UPurgeList { struct UPurgeList *next; char up_key[256]; }; struct EPurgeList { struct EPurgeList *next; int ep_keylen; char *ep_key; }; struct PurgeList *UserPurgeList = NULL; struct PurgeList *RoomPurgeList = NULL; struct ValidRoom *ValidRoomList = NULL; struct ValidUser *ValidUserList = NULL; int messages_purged; int users_not_purged; char *users_corrupt_msg = NULL; char *users_zero_msg = NULL; struct ctdlroomref *rr = NULL; int force_purge_now = 0; /* set to nonzero to force a run right now */ /* * First phase of message purge -- gather the locations of messages which * qualify for purging and write them to a temp file. */ void GatherPurgeMessages(struct ctdlroom *qrbuf, void *data) { struct ExpirePolicy epbuf; long delnum; time_t xtime, now; struct CtdlMessage *msg = NULL; int a; struct cdbdata *cdbfr; long *msglist = NULL; int num_msgs = 0; FILE *purgelist; purgelist = (FILE *)data; fprintf(purgelist, "r=%s\n", qrbuf->QRname); time(&now); GetExpirePolicy(&epbuf, qrbuf); /* If the room is set to never expire messages ... do nothing */ if (epbuf.expire_mode == EXPIRE_NEXTLEVEL) return; if (epbuf.expire_mode == EXPIRE_MANUAL) return; /* Don't purge messages containing system configuration, dumbass. */ if (!strcasecmp(qrbuf->QRname, SYSCONFIGROOM)) return; /* Ok, we got this far ... now let's see what's in the room */ cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf->QRnumber, sizeof(long)); if (cdbfr != NULL) { msglist = malloc(cdbfr->len); memcpy(msglist, cdbfr->ptr, cdbfr->len); num_msgs = cdbfr->len / sizeof(long); cdb_free(cdbfr); } /* Nothing to do if there aren't any messages */ if (num_msgs == 0) { if (msglist != NULL) free(msglist); return; } /* If the room is set to expire by count, do that */ if (epbuf.expire_mode == EXPIRE_NUMMSGS) { if (num_msgs > epbuf.expire_value) { for (a=0; a<(num_msgs - epbuf.expire_value); ++a) { fprintf(purgelist, "m=%ld\n", msglist[a]); ++messages_purged; } } } /* If the room is set to expire by age... */ if (epbuf.expire_mode == EXPIRE_AGE) { for (a=0; acm_fields[eTimestamp]); CM_Free(msg); } else { xtime = 0L; } if ((xtime > 0L) && (now - xtime > (time_t)(epbuf.expire_value * 86400L))) { fprintf(purgelist, "m=%ld\n", delnum); ++messages_purged; } } } if (msglist != NULL) free(msglist); } /* * Second phase of message purge -- read list of msgs from temp file and * delete them. */ void DoPurgeMessages(FILE *purgelist) { char roomname[ROOMNAMELEN]; long msgnum; char buf[SIZ]; rewind(purgelist); strcpy(roomname, "nonexistent room ___ ___"); while (fgets(buf, sizeof buf, purgelist) != NULL) { buf[strlen(buf)-1]=0; if (!strncasecmp(buf, "r=", 2)) { strcpy(roomname, &buf[2]); } if (!strncasecmp(buf, "m=", 2)) { msgnum = atol(&buf[2]); if (msgnum > 0L) { CtdlDeleteMessages(roomname, &msgnum, 1, ""); } } } } void PurgeMessages(void) { FILE *purgelist; syslog(LOG_DEBUG, "PurgeMessages() called"); messages_purged = 0; purgelist = tmpfile(); if (purgelist == NULL) { syslog(LOG_CRIT, "Can't create purgelist temp file: %s", strerror(errno)); return; } CtdlForEachRoom(GatherPurgeMessages, (void *)purgelist ); DoPurgeMessages(purgelist); fclose(purgelist); } void AddValidUser(struct ctdluser *usbuf, void *data) { struct ValidUser *vuptr; vuptr = (struct ValidUser *)malloc(sizeof(struct ValidUser)); vuptr->next = ValidUserList; vuptr->vu_usernum = usbuf->usernum; ValidUserList = vuptr; } void AddValidRoom(struct ctdlroom *qrbuf, void *data) { struct ValidRoom *vrptr; vrptr = (struct ValidRoom *)malloc(sizeof(struct ValidRoom)); vrptr->next = ValidRoomList; vrptr->vr_roomnum = qrbuf->QRnumber; vrptr->vr_roomgen = qrbuf->QRgen; ValidRoomList = vrptr; } void DoPurgeRooms(struct ctdlroom *qrbuf, void *data) { time_t age, purge_secs; struct PurgeList *pptr; struct ValidUser *vuptr; int do_purge = 0; /* For mailbox rooms, there's only one purging rule: if the user who * owns the room still exists, we keep the room; otherwise, we purge * it. Bypass any other rules. */ if (qrbuf->QRflags & QR_MAILBOX) { /* if user not found, do_purge will be 1 */ do_purge = 1; for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) { if (vuptr->vu_usernum == atol(qrbuf->QRname)) { do_purge = 0; } } } else { /* Any of these attributes render a room non-purgable */ if (qrbuf->QRflags & QR_PERMANENT) return; if (qrbuf->QRflags & QR_DIRECTORY) return; if (qrbuf->QRflags & QR_NETWORK) return; if (qrbuf->QRflags2 & QR2_SYSTEM) return; if (!strcasecmp(qrbuf->QRname, SYSCONFIGROOM)) return; if (CtdlIsNonEditable(qrbuf)) return; /* If we don't know the modification date, be safe and don't purge */ if (qrbuf->QRmtime <= (time_t)0) return; /* If no room purge time is set, be safe and don't purge */ if (config.c_roompurge < 0) return; /* Otherwise, check the date of last modification */ age = time(NULL) - (qrbuf->QRmtime); purge_secs = (time_t)config.c_roompurge * (time_t)86400; if (purge_secs <= (time_t)0) return; syslog(LOG_DEBUG, "<%s> is <%ld> seconds old", qrbuf->QRname, (long)age); if (age > purge_secs) do_purge = 1; } /* !QR_MAILBOX */ if (do_purge) { pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList)); pptr->next = RoomPurgeList; strcpy(pptr->name, qrbuf->QRname); RoomPurgeList = pptr; } } int PurgeRooms(void) { struct PurgeList *pptr; int num_rooms_purged = 0; struct ctdlroom qrbuf; struct ValidUser *vuptr; char *transcript = NULL; syslog(LOG_DEBUG, "PurgeRooms() called"); /* Load up a table full of valid user numbers so we can delete * user-owned rooms for users who no longer exist */ ForEachUser(AddValidUser, NULL); /* Then cycle through the room file */ CtdlForEachRoom(DoPurgeRooms, NULL); /* Free the valid user list */ while (ValidUserList != NULL) { vuptr = ValidUserList->next; free(ValidUserList); ValidUserList = vuptr; } transcript = malloc(SIZ); strcpy(transcript, "The following rooms have been auto-purged:\n"); while (RoomPurgeList != NULL) { if (CtdlGetRoom(&qrbuf, RoomPurgeList->name) == 0) { transcript=realloc(transcript, strlen(transcript)+SIZ); snprintf(&transcript[strlen(transcript)], SIZ, " %s\n", qrbuf.QRname); CtdlDeleteRoom(&qrbuf); } pptr = RoomPurgeList->next; free(RoomPurgeList); RoomPurgeList = pptr; ++num_rooms_purged; } if (num_rooms_purged > 0) CtdlAideMessage(transcript, "Room Autopurger Message"); free(transcript); syslog(LOG_DEBUG, "Purged %d rooms.", num_rooms_purged); return(num_rooms_purged); } /* * Back end function to check user accounts for associated Unix accounts * which no longer exist. (Only relevant for host auth mode.) */ void do_uid_user_purge(struct ctdluser *us, void *data) { struct PurgeList *pptr; if ((us->uid != (-1)) && (us->uid != CTDLUID)) { if (getpwuid(us->uid) == NULL) { pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList)); pptr->next = UserPurgeList; strcpy(pptr->name, us->fullname); UserPurgeList = pptr; } } else { ++users_not_purged; } } /* * Back end function to check user accounts for expiration. */ void do_user_purge(struct ctdluser *us, void *data) { int purge; time_t now; time_t purge_time; struct PurgeList *pptr; /* Set purge time; if the user overrides the system default, use it */ if (us->USuserpurge > 0) { purge_time = ((time_t)us->USuserpurge) * 86400L; } else { purge_time = ((time_t)config.c_userpurge) * 86400L; } /* The default rule is to not purge. */ purge = 0; /* If the user hasn't called in two months and expiring of accounts is turned on, his/her account * has expired, so purge the record. */ if (config.c_userpurge > 0) { now = time(NULL); if ((now - us->lastcall) > purge_time) purge = 1; } /* If the record is marked as permanent, don't purge it. */ if (us->flags & US_PERM) purge = 0; /* If the user is an Aide, don't purge him/her/it. */ if (us->axlevel == 6) purge = 0; /* If the access level is 0, the record should already have been * deleted, but maybe the user was logged in at the time or something. * Delete the record now. */ if (us->axlevel == 0) purge = 1; /* If the user set his/her password to 'deleteme', he/she * wishes to be deleted, so purge the record. * Moved this lower down so that aides and permanent users get purged if they ask to be. */ if (!strcasecmp(us->password, "deleteme")) purge = 1; /* 0 calls is impossible. If there are 0 calls, it must * be a corrupted record, so purge it. * Actually it is possible if an Aide created the user so now we check for less than 0 (DRW) */ if (us->timescalled < 0) purge = 1; /* any negative user number, is * also impossible. */ if (us->usernum < 0L) purge = 1; /* Don't purge user 0. That user is there for the system */ if (us->usernum == 0L) { /* FIXME: Temporary log message. Until we do unauth access with user 0 we should * try to get rid of all user 0 occurences. Many will be remnants from old code so * we will need to try and purge them from users data bases.Some will not have names but * those with names should be purged. */ syslog(LOG_DEBUG, "Auto purger found a user 0 with name <%s>", us->fullname); // purge = 0; } /* If the user has no full name entry then we can't purge them * since the actual purge can't find them. * This shouldn't happen but does somehow. */ if (IsEmptyStr(us->fullname)) { purge = 0; if (us->usernum > 0L) { purge=0; if (users_corrupt_msg == NULL) { users_corrupt_msg = malloc(SIZ); strcpy(users_corrupt_msg, "The auto-purger found the following user numbers with no name.\n" "The system has no way to purge a user with no name," " and should not be able to create them either.\n" "This indicates corruption of the user DB or possibly a bug.\n" "It may be a good idea to restore your DB from a backup.\n" ); } users_corrupt_msg=realloc(users_corrupt_msg, strlen(users_corrupt_msg)+30); snprintf(&users_corrupt_msg[strlen(users_corrupt_msg)], 29, " %ld\n", us->usernum); } } if (purge == 1) { pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList)); pptr->next = UserPurgeList; strcpy(pptr->name, us->fullname); UserPurgeList = pptr; } else { ++users_not_purged; } } int PurgeUsers(void) { struct PurgeList *pptr; int num_users_purged = 0; char *transcript = NULL; syslog(LOG_DEBUG, "PurgeUsers() called"); users_not_purged = 0; switch(config.c_auth_mode) { case AUTHMODE_NATIVE: ForEachUser(do_user_purge, NULL); break; case AUTHMODE_HOST: ForEachUser(do_uid_user_purge, NULL); break; default: syslog(LOG_DEBUG, "User purge for auth mode %d is not implemented.", config.c_auth_mode); break; } transcript = malloc(SIZ); if (users_not_purged == 0) { strcpy(transcript, "The auto-purger was told to purge every user. It is\n" "refusing to do this because it usually indicates a problem\n" "such as an inability to communicate with a name service.\n" ); while (UserPurgeList != NULL) { pptr = UserPurgeList->next; free(UserPurgeList); UserPurgeList = pptr; ++num_users_purged; } } else { strcpy(transcript, "The following users have been auto-purged:\n"); while (UserPurgeList != NULL) { transcript=realloc(transcript, strlen(transcript)+SIZ); snprintf(&transcript[strlen(transcript)], SIZ, " %s\n", UserPurgeList->name); purge_user(UserPurgeList->name); pptr = UserPurgeList->next; free(UserPurgeList); UserPurgeList = pptr; ++num_users_purged; } } if (num_users_purged > 0) CtdlAideMessage(transcript, "User Purge Message"); free(transcript); if(users_corrupt_msg) { CtdlAideMessage(users_corrupt_msg, "User Corruption Message"); free (users_corrupt_msg); users_corrupt_msg = NULL; } if(users_zero_msg) { CtdlAideMessage(users_zero_msg, "User Zero Message"); free (users_zero_msg); users_zero_msg = NULL; } syslog(LOG_DEBUG, "Purged %d users.", num_users_purged); return(num_users_purged); } /* * Purge visits * * This is a really cumbersome "garbage collection" function. We have to * delete visits which refer to rooms and/or users which no longer exist. In * order to prevent endless traversals of the room and user files, we first * build linked lists of rooms and users which _do_ exist on the system, then * traverse the visit file, checking each record against those two lists and * purging the ones that do not have a match on _both_ lists. (Remember, if * either the room or user being referred to is no longer on the system, the * record is completely useless.) */ int PurgeVisits(void) { struct cdbdata *cdbvisit; visit vbuf; struct VPurgeList *VisitPurgeList = NULL; struct VPurgeList *vptr; int purged = 0; char IndexBuf[32]; int IndexLen; struct ValidRoom *vrptr; struct ValidUser *vuptr; int RoomIsValid, UserIsValid; /* First, load up a table full of valid room/gen combinations */ CtdlForEachRoom(AddValidRoom, NULL); /* Then load up a table full of valid user numbers */ ForEachUser(AddValidUser, NULL); /* Now traverse through the visits, purging irrelevant records... */ cdb_rewind(CDB_VISIT); while(cdbvisit = cdb_next_item(CDB_VISIT), cdbvisit != NULL) { memset(&vbuf, 0, sizeof(visit)); memcpy(&vbuf, cdbvisit->ptr, ( (cdbvisit->len > sizeof(visit)) ? sizeof(visit) : cdbvisit->len) ); cdb_free(cdbvisit); RoomIsValid = 0; UserIsValid = 0; /* Check to see if the room exists */ for (vrptr=ValidRoomList; vrptr!=NULL; vrptr=vrptr->next) { if ( (vrptr->vr_roomnum==vbuf.v_roomnum) && (vrptr->vr_roomgen==vbuf.v_roomgen)) RoomIsValid = 1; } /* Check to see if the user exists */ for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) { if (vuptr->vu_usernum == vbuf.v_usernum) UserIsValid = 1; } /* Put the record on the purge list if it's dead */ if ((RoomIsValid==0) || (UserIsValid==0)) { vptr = (struct VPurgeList *) malloc(sizeof(struct VPurgeList)); vptr->next = VisitPurgeList; vptr->vp_roomnum = vbuf.v_roomnum; vptr->vp_roomgen = vbuf.v_roomgen; vptr->vp_usernum = vbuf.v_usernum; VisitPurgeList = vptr; } } /* Free the valid room/gen combination list */ while (ValidRoomList != NULL) { vrptr = ValidRoomList->next; free(ValidRoomList); ValidRoomList = vrptr; } /* Free the valid user list */ while (ValidUserList != NULL) { vuptr = ValidUserList->next; free(ValidUserList); ValidUserList = vuptr; } /* Now delete every visit on the purged list */ while (VisitPurgeList != NULL) { IndexLen = GenerateRelationshipIndex(IndexBuf, VisitPurgeList->vp_roomnum, VisitPurgeList->vp_roomgen, VisitPurgeList->vp_usernum); cdb_delete(CDB_VISIT, IndexBuf, IndexLen); vptr = VisitPurgeList->next; free(VisitPurgeList); VisitPurgeList = vptr; ++purged; } return(purged); } /* * Purge the use table of old entries. * */ int PurgeUseTable(StrBuf *ErrMsg) { int purged = 0; struct cdbdata *cdbut; struct UseTable ut; struct UPurgeList *ul = NULL; struct UPurgeList *uptr; /* Phase 1: traverse through the table, discovering old records... */ if (CheckTDAPVeto(CDB_USETABLE, ErrMsg)) { syslog(LOG_DEBUG, "Purge use table: VETO!"); return 0; } syslog(LOG_DEBUG, "Purge use table: phase 1"); cdb_rewind(CDB_USETABLE); while(cdbut = cdb_next_item(CDB_USETABLE), cdbut != NULL) { /* * TODODRW: change this to create a new function time_t cdb_get_timestamp( struct cdbdata *) * this will release this file from the serv_network.h * Maybe it could be a macro that extracts and casts the reult */ if (cdbut->len > sizeof(struct UseTable)) memcpy(&ut, cdbut->ptr, sizeof(struct UseTable)); else { memset(&ut, 0, sizeof(struct UseTable)); memcpy(&ut, cdbut->ptr, cdbut->len); } cdb_free(cdbut); if ( (time(NULL) - ut.ut_timestamp) > USETABLE_RETAIN ) { uptr = (struct UPurgeList *) malloc(sizeof(struct UPurgeList)); if (uptr != NULL) { uptr->next = ul; safestrncpy(uptr->up_key, ut.ut_msgid, sizeof uptr->up_key); ul = uptr; } ++purged; } } /* Phase 2: delete the records */ syslog(LOG_DEBUG, "Purge use table: phase 2"); while (ul != NULL) { cdb_delete(CDB_USETABLE, ul->up_key, strlen(ul->up_key)); uptr = ul->next; free(ul); ul = uptr; } syslog(LOG_DEBUG, "Purge use table: finished (purged %d records)", purged); return(purged); } /* * Purge the EUID Index of old records. * */ int PurgeEuidIndexTable(void) { int purged = 0; struct cdbdata *cdbei; struct EPurgeList *el = NULL; struct EPurgeList *eptr; long msgnum; struct CtdlMessage *msg = NULL; /* Phase 1: traverse through the table, discovering old records... */ syslog(LOG_DEBUG, "Purge EUID index: phase 1"); cdb_rewind(CDB_EUIDINDEX); while(cdbei = cdb_next_item(CDB_EUIDINDEX), cdbei != NULL) { memcpy(&msgnum, cdbei->ptr, sizeof(long)); msg = CtdlFetchMessage(msgnum, 0); if (msg != NULL) { CM_Free(msg); /* it still exists, so do nothing */ } else { eptr = (struct EPurgeList *) malloc(sizeof(struct EPurgeList)); if (eptr != NULL) { eptr->next = el; eptr->ep_keylen = cdbei->len - sizeof(long); eptr->ep_key = malloc(cdbei->len); memcpy(eptr->ep_key, &cdbei->ptr[sizeof(long)], eptr->ep_keylen); el = eptr; } ++purged; } cdb_free(cdbei); } /* Phase 2: delete the records */ syslog(LOG_DEBUG, "Purge euid index: phase 2"); while (el != NULL) { cdb_delete(CDB_EUIDINDEX, el->ep_key, el->ep_keylen); free(el->ep_key); eptr = el->next; free(el); el = eptr; } syslog(LOG_DEBUG, "Purge euid index: finished (purged %d records)", purged); return(purged); } /* * Purge OpenID assocations for missing users (theoretically this will never delete anything) */ int PurgeStaleOpenIDassociations(void) { struct cdbdata *cdboi; struct ctdluser usbuf; HashList *keys = NULL; HashPos *HashPos; char *deleteme = NULL; long len; void *Value; const char *Key; int num_deleted = 0; long usernum = 0L; keys = NewHash(1, NULL); if (!keys) return(0); cdb_rewind(CDB_OPENID); while (cdboi = cdb_next_item(CDB_OPENID), cdboi != NULL) { if (cdboi->len > sizeof(long)) { memcpy(&usernum, cdboi->ptr, sizeof(long)); if (CtdlGetUserByNumber(&usbuf, usernum) != 0) { deleteme = strdup(cdboi->ptr + sizeof(long)), Put(keys, deleteme, strlen(deleteme), deleteme, NULL); } } cdb_free(cdboi); } /* Go through the hash list, deleting keys we stored in it */ HashPos = GetNewHashPos(keys, 0); while (GetNextHashPos(keys, HashPos, &len, &Key, &Value)!=0) { syslog(LOG_DEBUG, "Deleting associated OpenID <%s>", (char*)Value); cdb_delete(CDB_OPENID, Value, strlen(Value)); /* note: don't free(Value) -- deleting the hash list will handle this for us */ ++num_deleted; } DeleteHashPos(&HashPos); DeleteHash(&keys); return num_deleted; } void purge_databases(void) { int retval; static time_t last_purge = 0; time_t now; struct tm tm; /* Do the auto-purge if the current hour equals the purge hour, * but not if the operation has already been performed in the * last twelve hours. This is usually enough granularity. */ now = time(NULL); localtime_r(&now, &tm); if ( ((tm.tm_hour != config.c_purge_hour) || ((now - last_purge) < 43200)) && (force_purge_now == 0) ) { return; } syslog(LOG_INFO, "Auto-purger: starting."); if (!server_shutting_down) { retval = PurgeUsers(); syslog(LOG_NOTICE, "Purged %d users.", retval); } if (!server_shutting_down) { PurgeMessages(); syslog(LOG_NOTICE, "Expired %d messages.", messages_purged); } if (!server_shutting_down) { retval = PurgeRooms(); syslog(LOG_NOTICE, "Expired %d rooms.", retval); } if (!server_shutting_down) { retval = PurgeVisits(); syslog(LOG_NOTICE, "Purged %d visits.", retval); } if (!server_shutting_down) { StrBuf *ErrMsg; ErrMsg = NewStrBuf (); retval = PurgeUseTable(ErrMsg); syslog(LOG_NOTICE, "Purged %d entries from the use table.", retval); ////TODO: fix errmsg FreeStrBuf(&ErrMsg); } if (!server_shutting_down) { retval = PurgeEuidIndexTable(); syslog(LOG_NOTICE, "Purged %d entries from the EUID index.", retval); } if (!server_shutting_down) { retval = PurgeStaleOpenIDassociations(); syslog(LOG_NOTICE, "Purged %d stale OpenID associations.", retval); } if (!server_shutting_down) { retval = TDAP_ProcessAdjRefCountQueue(); syslog(LOG_NOTICE, "Processed %d message reference count adjustments.", retval); } if (!server_shutting_down) { syslog(LOG_INFO, "Auto-purger: finished."); last_purge = now; /* So we don't do it again soon */ force_purge_now = 0; } else { syslog(LOG_INFO, "Auto-purger: STOPPED."); } } /* * Manually initiate a run of The Dreaded Auto-Purger (tm) */ void cmd_tdap(char *argbuf) { if (CtdlAccessCheck(ac_aide)) return; force_purge_now = 1; cprintf("%d Manually initiating a purger run now.\n", CIT_OK); } CTDL_MODULE_INIT(expire) { if (!threading) { CtdlRegisterProtoHook(cmd_tdap, "TDAP", "Manually initiate auto-purger"); CtdlRegisterProtoHook(cmd_gpex, "GPEX", "Get expire policy"); CtdlRegisterProtoHook(cmd_spex, "SPEX", "Set expire policy"); CtdlRegisterSessionHook(purge_databases, EVT_TIMER, PRIO_CLEANUP + 20); } /* return our module name for the log */ return "expire"; } citadel-9.01/modules/expire/policy.h0000644000000000000000000000020412507024051016152 0ustar rootrootvoid GetExpirePolicy(struct ExpirePolicy *epbuf, struct ctdlroom *qrbuf); void cmd_gpex(char *argbuf); void cmd_spex(char *argbuf); citadel-9.01/modules/expire/expire_policy.c0000644000000000000000000001222012507024051017522 0ustar rootroot/* * Functions which manage expire policy for rooms * Copyright (c) 1987-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "sysdep.h" #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include "citadel.h" #include "server.h" #include "database.h" #include "config.h" #include "room_ops.h" #include "sysdep_decls.h" #include "support.h" #include "msgbase.h" #include "citserver.h" #include "ctdl_module.h" #include "user_ops.h" /* * Retrieve the applicable expire policy for a specific room */ void GetExpirePolicy(struct ExpirePolicy *epbuf, struct ctdlroom *qrbuf) { struct floor *fl; /* If the room has its own policy, return it */ if (qrbuf->QRep.expire_mode != 0) { memcpy(epbuf, &qrbuf->QRep, sizeof(struct ExpirePolicy)); return; } /* (non-mailbox rooms) * If the floor has its own policy, return it */ if ( (qrbuf->QRflags & QR_MAILBOX) == 0) { fl = CtdlGetCachedFloor(qrbuf->QRfloor); if (fl->f_ep.expire_mode != 0) { memcpy(epbuf, &fl->f_ep, sizeof(struct ExpirePolicy)); return; } } /* (Mailbox rooms) * If there is a default policy for mailbox rooms, return it */ if (qrbuf->QRflags & QR_MAILBOX) { if (config.c_mbxep.expire_mode != 0) { memcpy(epbuf, &config.c_mbxep, sizeof(struct ExpirePolicy)); return; } } /* Otherwise, fall back on the system default */ memcpy(epbuf, &config.c_ep, sizeof(struct ExpirePolicy)); } /* * Get Policy EXpire */ void cmd_gpex(char *argbuf) { struct ExpirePolicy exp; struct floor *fl; char which[128]; extract_token(which, argbuf, 0, '|', sizeof which); if (!strcasecmp(which, strof(roompolicy))|| !strcasecmp(which, "room")) { /* Deprecated version */ memcpy(&exp, &CC->room.QRep, sizeof(struct ExpirePolicy)); } else if (!strcasecmp(which, strof(floorpolicy))|| !strcasecmp(which, "floor")) { /* Deprecated version */ fl = CtdlGetCachedFloor(CC->room.QRfloor); memcpy(&exp, &fl->f_ep, sizeof(struct ExpirePolicy)); } else if (!strcasecmp(which, strof(mailboxespolicy))|| !strcasecmp(which, "mailboxes")) {/* Deprecated version */ memcpy(&exp, &config.c_mbxep, sizeof(struct ExpirePolicy)); } else if (!strcasecmp(which, strof(sitepolicy))|| !strcasecmp(which, "site")) {/* Deprecated version */ memcpy(&exp, &config.c_ep, sizeof(struct ExpirePolicy)); } else { cprintf("%d Invalid keyword \"%s\"\n", ERROR + ILLEGAL_VALUE, which); return; } cprintf("%d %d|%d\n", CIT_OK, exp.expire_mode, exp.expire_value); } /* * Set Policy EXpire */ void cmd_spex(char *argbuf) { struct ExpirePolicy exp; struct floor flbuf; char which[128]; memset(&exp, 0, sizeof(struct ExpirePolicy)); extract_token(which, argbuf, 0, '|', sizeof which); exp.expire_mode = extract_int(argbuf, 1); exp.expire_value = extract_int(argbuf, 2); if ((exp.expire_mode < 0) || (exp.expire_mode > 3)) { cprintf("%d Invalid policy.\n", ERROR + ILLEGAL_VALUE); return; } if ( (!strcasecmp(which, strof(roompolicy))) || (!strcasecmp(which, "room")) ) { if (!is_room_aide()) { cprintf("%d Higher access required.\n", ERROR + HIGHER_ACCESS_REQUIRED); return; } CtdlGetRoomLock(&CC->room, CC->room.QRname); memcpy(&CC->room.QRep, &exp, sizeof(struct ExpirePolicy)); CtdlPutRoomLock(&CC->room); cprintf("%d Room expire policy for '%s' has been updated.\n", CIT_OK, CC->room.QRname); syslog(LOG_DEBUG, "Room: %s , Policy: %d , Value: %d", CC->room.QRname, exp.expire_mode, exp.expire_value ); return; } if (CC->user.axlevel < AxAideU) { cprintf("%d Higher access required.\n", ERROR + HIGHER_ACCESS_REQUIRED); return; } if ( (!strcasecmp(which, strof(floorpolicy))) || (!strcasecmp(which, "floor")) ) { lgetfloor(&flbuf, CC->room.QRfloor); memcpy(&flbuf.f_ep, &exp, sizeof(struct ExpirePolicy)); lputfloor(&flbuf, CC->room.QRfloor); cprintf("%d Floor expire policy has been updated.\n", CIT_OK); return; } else if ( (!strcasecmp(which, strof(mailboxespolicy))) || (!strcasecmp(which, "mailboxes")) ) { memcpy(&config.c_mbxep, &exp, sizeof(struct ExpirePolicy)); put_config(); cprintf("%d Default expire policy for mailboxes set.\n", CIT_OK); return; } else if ( (!strcasecmp(which, strof(sitepolicy))) || (!strcasecmp(which, "site")) ) { if (exp.expire_mode == EXPIRE_NEXTLEVEL) { cprintf("%d Invalid policy (no higher level)\n", ERROR + ILLEGAL_VALUE); return; } memcpy(&config.c_ep, &exp, sizeof(struct ExpirePolicy)); put_config(); cprintf("%d Site expire policy has been updated.\n", CIT_OK); return; } cprintf("%d Invalid keyword '%s'\n", ERROR + ILLEGAL_VALUE, which); } citadel-9.01/modules/wiki/0000755000000000000000000000000012507024051014155 5ustar rootrootcitadel-9.01/modules/wiki/serv_wiki.c0000644000000000000000000004746412507024051016342 0ustar rootroot/* * Server-side module for Wiki rooms. This handles things like version control. * * Copyright (c) 2009-2012 by the citadel.org team * * This program is open source software. You can redistribute it and/or * modify it under the terms of the GNU General Public License, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "sysdep.h" #include #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "control.h" #include "user_ops.h" #include "room_ops.h" #include "database.h" #include "msgbase.h" #include "euidindex.h" #include "ctdl_module.h" /* * Data passed back and forth between wiki_rev() and its MIME parser callback */ struct HistoryEraserCallBackData { char *tempfilename; /* name of temp file being patched */ char *stop_when; /* stop when we hit this uuid */ int done; /* set to nonzero when we're done patching */ }; /* * Name of the temporary room we create to store old revisions when someone requests them. * We put it in an invalid namespace so the DAP cleans up after us later. */ char *wwm = "9999999999.WikiWaybackMachine"; /* * Before allowing a wiki page save to execute, we have to perform version control. * This involves fetching the old version of the page if it exists. */ int wiki_upload_beforesave(struct CtdlMessage *msg, recptypes *recp) { struct CitContext *CCC = CC; long old_msgnum = (-1L); struct CtdlMessage *old_msg = NULL; long history_msgnum = (-1L); struct CtdlMessage *history_msg = NULL; char diff_old_filename[PATH_MAX]; char diff_new_filename[PATH_MAX]; char diff_out_filename[PATH_MAX]; char diff_cmd[PATH_MAX]; FILE *fp; int rv; char history_page[1024]; long history_page_len; char boundary[256]; char prefixed_boundary[258]; char buf[1024]; char *diffbuf = NULL; size_t diffbuf_len = 0; char *ptr = NULL; if (!CCC->logged_in) return(0); /* Only do this if logged in. */ /* Is this a room with a Wiki in it, don't run this hook. */ if ((CCC->room.QRdefaultview != VIEW_WIKI) && (CCC->room.QRdefaultview != VIEW_WIKIMD)) { return(0); } /* If this isn't a MIME message, don't bother. */ if (msg->cm_format_type != 4) return(0); /* If there's no EUID we can't do this. Reject the post. */ if (CM_IsEmpty(msg, eExclusiveID)) return(1); history_page_len = snprintf(history_page, sizeof history_page, "%s_HISTORY_", msg->cm_fields[eExclusiveID]); /* Make sure we're saving a real wiki page rather than a wiki history page. * This is important in order to avoid recursing infinitely into this hook. */ if ( (msg->cm_lengths[eExclusiveID] >= 9) && (!strcasecmp(&msg->cm_fields[eExclusiveID][msg->cm_lengths[eExclusiveID]-9], "_HISTORY_")) ) { syslog(LOG_DEBUG, "History page not being historied\n"); return(0); } /* If there's no message text, obviously this is all b0rken and shouldn't happen at all */ if (CM_IsEmpty(msg, eMesageText)) return(0); /* Set the message subject identical to the page name */ CM_CopyField(msg, eMsgSubject, eExclusiveID); /* See if we can retrieve the previous version. */ old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CCC->room); if (old_msgnum > 0L) { old_msg = CtdlFetchMessage(old_msgnum, 1); } else { old_msg = NULL; } if ((old_msg != NULL) && (CM_IsEmpty(old_msg, eMesageText))) { /* old version is corrupt? */ CM_Free(old_msg); old_msg = NULL; } /* If no changes were made, don't bother saving it again */ if ((old_msg != NULL) && (!strcmp(msg->cm_fields[eMesageText], old_msg->cm_fields[eMesageText]))) { CM_Free(old_msg); return(1); } /* * Generate diffs */ CtdlMakeTempFileName(diff_old_filename, sizeof diff_old_filename); CtdlMakeTempFileName(diff_new_filename, sizeof diff_new_filename); CtdlMakeTempFileName(diff_out_filename, sizeof diff_out_filename); if (old_msg != NULL) { fp = fopen(diff_old_filename, "w"); rv = fwrite(old_msg->cm_fields[eMesageText], old_msg->cm_lengths[eMesageText], 1, fp); fclose(fp); CM_Free(old_msg); } fp = fopen(diff_new_filename, "w"); rv = fwrite(msg->cm_fields[eMesageText], msg->cm_lengths[eMesageText], 1, fp); fclose(fp); snprintf(diff_cmd, sizeof diff_cmd, DIFF " -u %s %s >%s", diff_new_filename, ((old_msg != NULL) ? diff_old_filename : "/dev/null"), diff_out_filename ); syslog(LOG_DEBUG, "diff cmd: %s", diff_cmd); rv = system(diff_cmd); syslog(LOG_DEBUG, "diff cmd returned %d", rv); diffbuf_len = 0; diffbuf = NULL; fp = fopen(diff_out_filename, "r"); if (fp == NULL) { fp = fopen("/dev/null", "r"); } if (fp != NULL) { fseek(fp, 0L, SEEK_END); diffbuf_len = ftell(fp); fseek(fp, 0L, SEEK_SET); diffbuf = malloc(diffbuf_len + 1); fread(diffbuf, diffbuf_len, 1, fp); diffbuf[diffbuf_len] = '\0'; fclose(fp); } syslog(LOG_DEBUG, "diff length is "SIZE_T_FMT" bytes", diffbuf_len); unlink(diff_old_filename); unlink(diff_new_filename); unlink(diff_out_filename); /* Determine whether this was a bogus (empty) edit */ if ((diffbuf_len = 0) && (diffbuf != NULL)) { free(diffbuf); diffbuf = NULL; } if (diffbuf == NULL) { return(1); /* No changes at all? Abandon the post entirely! */ } /* Now look for the existing edit history */ history_msgnum = CtdlLocateMessageByEuid(history_page, &CCC->room); history_msg = NULL; if (history_msgnum > 0L) { history_msg = CtdlFetchMessage(history_msgnum, 1); } /* Create a new history message if necessary */ if (history_msg == NULL) { char *buf; long len; history_msg = malloc(sizeof(struct CtdlMessage)); memset(history_msg, 0, sizeof(struct CtdlMessage)); history_msg->cm_magic = CTDLMESSAGE_MAGIC; history_msg->cm_anon_type = MES_NORMAL; history_msg->cm_format_type = FMT_RFC822; CM_SetField(history_msg, eAuthor, HKEY("Citadel")); CM_SetField(history_msg, eRecipient, CCC->room.QRname, strlen(CCC->room.QRname)); CM_SetField(history_msg, eExclusiveID, history_page, history_page_len); CM_SetField(history_msg, eMsgSubject, history_page, history_page_len); CM_SetField(history_msg, eSuppressIdx, HKEY("1")); /* suppress full text indexing */ snprintf(boundary, sizeof boundary, "Citadel--Multipart--%04x--%08lx", getpid(), time(NULL)); buf = (char*) malloc(1024); len = snprintf(buf, 1024, "Content-type: multipart/mixed; boundary=\"%s\"\n\n" "This is a Citadel wiki history encoded as multipart MIME.\n" "Each part is comprised of a diff script representing one change set.\n" "\n" "--%s--\n", boundary, boundary ); CM_SetAsField(history_msg, eMesageText, &buf, len); } /* Update the history message (regardless of whether it's new or existing) */ /* Remove the Message-ID from the old version of the history message. This will cause a brand * new one to be generated, avoiding an uninitentional hit of the loop zapper when we replicate. */ CM_FlushField(history_msg, emessageId); /* Figure out the boundary string. We do this even when we generated the * boundary string in the above code, just to be safe and consistent. */ *boundary = '\0'; ptr = history_msg->cm_fields[eMesageText]; do { ptr = memreadline(ptr, buf, sizeof buf); if (*ptr != 0) { striplt(buf); if (!IsEmptyStr(buf) && (!strncasecmp(buf, "Content-type:", 13))) { if ( (bmstrcasestr(buf, "multipart") != NULL) && (bmstrcasestr(buf, "boundary=") != NULL) ) { safestrncpy(boundary, bmstrcasestr(buf, "\""), sizeof boundary); char *qu; qu = strchr(boundary, '\"'); if (qu) { strcpy(boundary, ++qu); } qu = strchr(boundary, '\"'); if (qu) { *qu = 0; } } } } } while ( (IsEmptyStr(boundary)) && (*ptr != 0) ); /* * Now look for the first boundary. That is where we need to insert our fun. */ if (!IsEmptyStr(boundary)) { char *MsgText; long MsgTextLen; time_t Now = time(NULL); snprintf(prefixed_boundary, sizeof(prefixed_boundary), "--%s", boundary); CM_GetAsField(history_msg, eMesageText, &MsgText, &MsgTextLen); ptr = bmstrcasestr(MsgText, prefixed_boundary); if (ptr != NULL) { StrBuf *NewMsgText; char uuid[64]; char memo[512]; long memolen; char encoded_memo[1024]; NewMsgText = NewStrBufPlain(NULL, MsgTextLen + diffbuf_len + 1024); generate_uuid(uuid); memolen = snprintf(memo, sizeof(memo), "%s|%ld|%s|%s", uuid, Now, CCC->user.fullname, config.c_nodename); memolen = CtdlEncodeBase64(encoded_memo, memo, memolen, 0); StrBufAppendBufPlain(NewMsgText, HKEY("--"), 0); StrBufAppendBufPlain(NewMsgText, boundary, -1, 0); StrBufAppendBufPlain( NewMsgText, HKEY("\n" "Content-type: text/plain\n" "Content-Disposition: inline; filename=\""), 0); StrBufAppendBufPlain(NewMsgText, encoded_memo, memolen, 0); StrBufAppendBufPlain( NewMsgText, HKEY("\"\n" "Content-Transfer-Encoding: 8bit\n" "\n"), 0); StrBufAppendBufPlain(NewMsgText, diffbuf, diffbuf_len, 0); StrBufAppendBufPlain(NewMsgText, HKEY("\n"), 0); StrBufAppendBufPlain(NewMsgText, ptr, MsgTextLen - (ptr - MsgText), 0); free(MsgText); CM_SetAsFieldSB(history_msg, eMesageText, &NewMsgText); } else { CM_SetAsField(history_msg, eMesageText, &MsgText, MsgTextLen); } CM_SetFieldLONG(history_msg, eTimestamp, Now); CtdlSubmitMsg(history_msg, NULL, "", 0); } else { syslog(LOG_ALERT, "Empty boundary string in history message. No history!\n"); } free(diffbuf); CM_Free(history_msg); return(0); } /* * MIME Parser callback for wiki_history() * * The "filename" field will contain a memo field. All we have to do is decode * the base64 and output it. The data is already in a delimited format suitable * for our client protocol. */ void wiki_history_callback(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata) { char memo[1024]; CtdlDecodeBase64(memo, filename, strlen(filename)); cprintf("%s\n", memo); } /* * Fetch a list of revisions for a particular wiki page */ void wiki_history(char *pagename) { int r; char history_page_name[270]; long msgnum; struct CtdlMessage *msg; r = CtdlDoIHavePermissionToReadMessagesInThisRoom(); if (r != om_ok) { if (r == om_not_logged_in) { cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN); } else { cprintf("%d An unknown error has occurred.\n", ERROR); } return; } snprintf(history_page_name, sizeof history_page_name, "%s_HISTORY_", pagename); msgnum = CtdlLocateMessageByEuid(history_page_name, &CC->room); if (msgnum > 0L) { msg = CtdlFetchMessage(msgnum, 1); } else { msg = NULL; } if ((msg != NULL) && CM_IsEmpty(msg, eMesageText)) { CM_Free(msg); msg = NULL; } if (msg == NULL) { cprintf("%d Revision history for '%s' was not found.\n", ERROR+MESSAGE_NOT_FOUND, pagename); return; } cprintf("%d Revision history for '%s'\n", LISTING_FOLLOWS, pagename); mime_parser(CM_RANGE(msg, eMesageText), *wiki_history_callback, NULL, NULL, NULL, 0); cprintf("000\n"); CM_Free(msg); return; } /* * MIME Parser callback for wiki_rev() * * The "filename" field will contain a memo field, which includes (among other things) * the uuid of this revision. After we hit the desired revision, we stop processing. * * The "content" filed will contain "diff" output suitable for applying via "patch" * to our temporary file. */ void wiki_rev_callback(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata) { struct HistoryEraserCallBackData *hecbd = (struct HistoryEraserCallBackData *)cbuserdata; char memo[1024]; char this_rev[256]; FILE *fp; char *ptr = NULL; char buf[1024]; /* Did a previous callback already indicate that we've reached our target uuid? * If so, don't process anything else. */ if (hecbd->done) { return; } CtdlDecodeBase64(memo, filename, strlen(filename)); extract_token(this_rev, memo, 0, '|', sizeof this_rev); striplt(this_rev); /* Perform the patch */ fp = popen(PATCH " -f -s -p0 -r /dev/null >/dev/null 2>/dev/null", "w"); if (fp) { /* Replace the filenames in the patch with the tempfilename we're actually tweaking */ fprintf(fp, "--- %s\n", hecbd->tempfilename); fprintf(fp, "+++ %s\n", hecbd->tempfilename); ptr = (char *)content; int linenum = 0; do { ++linenum; ptr = memreadline(ptr, buf, sizeof buf); if (*ptr != 0) { if (linenum <= 2) { /* skip the first two lines; they contain bogus filenames */ } else { fprintf(fp, "%s\n", buf); } } } while ((*ptr != 0) && (ptr < ((char*)content + length))); if (pclose(fp) != 0) { syslog(LOG_ERR, "pclose() returned an error - patch failed\n"); } } if (!strcasecmp(this_rev, hecbd->stop_when)) { /* Found our target rev. Tell any subsequent callbacks to suppress processing. */ syslog(LOG_DEBUG, "Target revision has been reached -- stop patching.\n"); hecbd->done = 1; } } /* * Fetch a specific revision of a wiki page. The "operation" string may be set to "fetch" in order * to simply fetch the desired revision and store it in a temporary location for viewing, or "revert" * to revert the currently active page to that revision. */ void wiki_rev(char *pagename, char *rev, char *operation) { struct CitContext *CCC = CC; int r; char history_page_name[270]; long msgnum; char temp[PATH_MAX]; struct CtdlMessage *msg; FILE *fp; struct HistoryEraserCallBackData hecbd; long len = 0L; int rv; r = CtdlDoIHavePermissionToReadMessagesInThisRoom(); if (r != om_ok) { if (r == om_not_logged_in) { cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN); } else { cprintf("%d An unknown error has occurred.\n", ERROR); } return; } if (!strcasecmp(operation, "revert")) { r = CtdlDoIHavePermissionToPostInThisRoom(temp, sizeof temp, NULL, POST_LOGGED_IN, 0); if (r != 0) { cprintf("%d %s\n", r, temp); return; } } /* Begin by fetching the current version of the page. We're going to patch * backwards through the diffs until we get the one we want. */ msgnum = CtdlLocateMessageByEuid(pagename, &CCC->room); if (msgnum > 0L) { msg = CtdlFetchMessage(msgnum, 1); } else { msg = NULL; } if ((msg != NULL) && CM_IsEmpty(msg, eMesageText)) { CM_Free(msg); msg = NULL; } if (msg == NULL) { cprintf("%d Page '%s' was not found.\n", ERROR+MESSAGE_NOT_FOUND, pagename); return; } /* Output it to a temporary file */ CtdlMakeTempFileName(temp, sizeof temp); fp = fopen(temp, "w"); if (fp != NULL) { r = fwrite(msg->cm_fields[eMesageText], msg->cm_lengths[eMesageText], 1, fp); fclose(fp); } else { syslog(LOG_ALERT, "Cannot open %s: %s\n", temp, strerror(errno)); } CM_Free(msg); /* Get the revision history */ snprintf(history_page_name, sizeof history_page_name, "%s_HISTORY_", pagename); msgnum = CtdlLocateMessageByEuid(history_page_name, &CCC->room); if (msgnum > 0L) { msg = CtdlFetchMessage(msgnum, 1); } else { msg = NULL; } if ((msg != NULL) && CM_IsEmpty(msg, eMesageText)) { CM_Free(msg); msg = NULL; } if (msg == NULL) { cprintf("%d Revision history for '%s' was not found.\n", ERROR+MESSAGE_NOT_FOUND, pagename); return; } /* Start patching backwards (newest to oldest) through the revision history until we * hit the revision uuid requested by the user. (The callback will perform each one.) */ memset(&hecbd, 0, sizeof(struct HistoryEraserCallBackData)); hecbd.tempfilename = temp; hecbd.stop_when = rev; striplt(hecbd.stop_when); mime_parser(CM_RANGE(msg, eMesageText), *wiki_rev_callback, NULL, NULL, (void *)&hecbd, 0); CM_Free(msg); /* Were we successful? */ if (hecbd.done == 0) { cprintf("%d Revision '%s' of page '%s' was not found.\n", ERROR + MESSAGE_NOT_FOUND, rev, pagename ); } /* We have the desired revision on disk. Now do something with it. */ else if ( (!strcasecmp(operation, "fetch")) || (!strcasecmp(operation, "revert")) ) { msg = malloc(sizeof(struct CtdlMessage)); memset(msg, 0, sizeof(struct CtdlMessage)); msg->cm_magic = CTDLMESSAGE_MAGIC; msg->cm_anon_type = MES_NORMAL; msg->cm_format_type = FMT_RFC822; fp = fopen(temp, "r"); if (fp) { char *msgbuf; fseek(fp, 0L, SEEK_END); len = ftell(fp); fseek(fp, 0L, SEEK_SET); msgbuf = malloc(len + 1); rv = fread(msgbuf, len, 1, fp); syslog(LOG_DEBUG, "did %d blocks of %ld bytes\n", rv, len); msgbuf[len] = '\0'; CM_SetAsField(msg, eMesageText, &msgbuf, len); fclose(fp); } if (len <= 0) { msgnum = (-1L); } else if (!strcasecmp(operation, "fetch")) { CM_SetField(msg, eAuthor, HKEY("Citadel")); CtdlCreateRoom(wwm, 5, "", 0, 1, 1, VIEW_BBS); /* Not an error if already exists */ msgnum = CtdlSubmitMsg(msg, NULL, wwm, 0); /* Store the revision here */ /* * WARNING: VILE SLEAZY HACK * This will avoid the 'message xxx is not in this room' security error, * but only if the client fetches the message we just generated immediately * without first trying to perform other fetch operations. */ if (CCC->cached_msglist != NULL) { free(CCC->cached_msglist); CCC->cached_msglist = NULL; CCC->cached_num_msgs = 0; } CCC->cached_msglist = malloc(sizeof(long)); if (CCC->cached_msglist != NULL) { CCC->cached_num_msgs = 1; CCC->cached_msglist[0] = msgnum; } } else if (!strcasecmp(operation, "revert")) { CM_SetFieldLONG(msg, eTimestamp, time(NULL)); CM_SetField(msg, eAuthor, CCC->user.fullname, strlen(CCC->user.fullname)); CM_SetField(msg, erFc822Addr, CCC->cs_inet_email, strlen(CCC->cs_inet_email)); CM_SetField(msg, eOriginalRoom, CCC->room.QRname, strlen(CCC->room.QRname)); CM_SetField(msg, eNodeName, CFG_KEY(c_nodename)); CM_SetField(msg, eExclusiveID, pagename, strlen(pagename)); msgnum = CtdlSubmitMsg(msg, NULL, "", 0); /* Replace the current revision */ } else { /* Theoretically it is impossible to get here, but throw an error anyway */ msgnum = (-1L); } CM_Free(msg); if (msgnum >= 0L) { cprintf("%d %ld\n", CIT_OK, msgnum); /* Give the client a msgnum */ } else { cprintf("%d Error %ld has occurred.\n", ERROR+INTERNAL_ERROR, msgnum); } } /* We did all this work for nothing. Express anguish to the caller. */ else { cprintf("%d An unknown operation was requested.\n", ERROR+CMD_NOT_SUPPORTED); } unlink(temp); return; } /* * commands related to wiki management */ void cmd_wiki(char *argbuf) { char subcmd[32]; char pagename[256]; char rev[128]; char operation[16]; extract_token(subcmd, argbuf, 0, '|', sizeof subcmd); if (!strcasecmp(subcmd, "history")) { extract_token(pagename, argbuf, 1, '|', sizeof pagename); wiki_history(pagename); return; } if (!strcasecmp(subcmd, "rev")) { extract_token(pagename, argbuf, 1, '|', sizeof pagename); extract_token(rev, argbuf, 2, '|', sizeof rev); extract_token(operation, argbuf, 3, '|', sizeof operation); wiki_rev(pagename, rev, operation); return; } cprintf("%d Invalid subcommand\n", ERROR + CMD_NOT_SUPPORTED); } /* * Module initialization */ CTDL_MODULE_INIT(wiki) { if (!threading) { CtdlRegisterMessageHook(wiki_upload_beforesave, EVT_BEFORESAVE); CtdlRegisterProtoHook(cmd_wiki, "WIKI", "Commands related to Wiki management"); } /* return our module name for the log */ return "wiki"; } citadel-9.01/modules/newuser/0000755000000000000000000000000012507024051014702 5ustar rootrootcitadel-9.01/modules/newuser/serv_newuser.c0000644000000000000000000000473112507024051017602 0ustar rootroot/* * Automatically copies the contents of a "New User Greetings" room to the * inbox of any new user upon account creation. * * Copyright (c) 1987-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * */ /* * Name of the New User Greetings room. */ #define NEWUSERGREETINGS "New User Greetings" #include "sysdep.h" #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include "ctdl_module.h" #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "user_ops.h" #include "database.h" #include "msgbase.h" /* * Copy the contents of the New User Greetings> room to the user's Mail> room. */ void CopyNewUserGreetings(void) { struct cdbdata *cdbfr; long *msglist = NULL; int num_msgs = 0; char mailboxname[ROOMNAMELEN]; /* Only do this for new users. */ if (CC->user.timescalled != 1) return; /* This user's mailbox. */ CtdlMailboxName(mailboxname, sizeof mailboxname, &CC->user, MAILROOM); /* Go to the source room ... bail out silently if it's not there, * or if it's not private. */ if (CtdlGetRoom(&CC->room, NEWUSERGREETINGS) != 0) return; if ((CC->room.QRflags & QR_PRIVATE) == 0) return; cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long)); if (cdbfr != NULL) { msglist = malloc(cdbfr->len); memcpy(msglist, cdbfr->ptr, cdbfr->len); num_msgs = cdbfr->len / sizeof(long); cdb_free(cdbfr); } if (num_msgs > 0) { CtdlSaveMsgPointersInRoom(mailboxname, msglist, num_msgs, 1, NULL, 0); } /* Now free the memory we used, and go away. */ if (msglist != NULL) free(msglist); } CTDL_MODULE_INIT(newuser) { if (!threading) { CtdlRegisterSessionHook(CopyNewUserGreetings, EVT_LOGIN, PRIO_LOGIN + 1); } /* return our module name for the log */ return "newuser"; } citadel-9.01/modules/rwho/0000755000000000000000000000000012507024051014171 5ustar rootrootcitadel-9.01/modules/rwho/serv_rwho.c0000644000000000000000000001531012507024051016353 0ustar rootroot/* * This module implements server commands related to the display and * manipulation of the "Who's online" list. * * Copyright (c) 1987-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #include "sysdep.h" #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "control.h" #include "user_ops.h" #include "database.h" #include "msgbase.h" #include "ctdl_module.h" /* Don't show the names of private rooms unless the viewing * user also knows the rooms. */ void GenerateRoomDisplay(char *real_room, CitContext *viewed, CitContext *viewer) { int ra; strcpy(real_room, viewed->room.QRname); if (viewed->room.QRflags & QR_MAILBOX) { strcpy(real_room, &real_room[11]); } if (viewed->room.QRflags & QR_PRIVATE) { CtdlRoomAccess(&viewed->room, &viewer->user, &ra, NULL); if ( (ra & UA_KNOWN) == 0) { strcpy(real_room, " "); } } if (viewed->cs_flags & CS_CHAT) { while (strlen(real_room) < 14) { strcat(real_room, " "); } strcpy(&real_room[14], ""); } } /* * display who's online */ void cmd_rwho(char *argbuf) { struct CitContext *nptr; int nContexts, i; int spoofed = 0; int user_spoofed = 0; int room_spoofed = 0; int host_spoofed = 0; int aide; char un[40]; char real_room[ROOMNAMELEN], room[ROOMNAMELEN]; char host[64], flags[5]; /* So that we don't keep the context list locked for a long time * we create a copy of it first */ nptr = CtdlGetContextArray(&nContexts) ; if (!nptr) { /* Couldn't malloc so we have to bail but stick to the protocol */ cprintf("%d%c \n", LISTING_FOLLOWS, CtdlCheckExpress() ); cprintf("000\n"); return; } aide = ( (CC->user.axlevel >= AxAideU) || (CC->internal_pgm) ) ; cprintf("%d%c \n", LISTING_FOLLOWS, CtdlCheckExpress() ); for (i=0; ifake_roomname, newroomname, sizeof(CC->fake_roomname) ); } else { safestrncpy(CC->fake_roomname, "", sizeof CC->fake_roomname); } cprintf("%d OK\n", CIT_OK); } /* * Masquerade hostname */ void cmd_hchg(char *argbuf) { char newhostname[64]; extract_token(newhostname, argbuf, 0, '|', sizeof newhostname); if (!IsEmptyStr(newhostname)) { safestrncpy(CC->fake_hostname, newhostname, sizeof(CC->fake_hostname) ); } else { safestrncpy(CC->fake_hostname, "", sizeof CC->fake_hostname); } cprintf("%d OK\n", CIT_OK); } /* * Masquerade username (aides only) */ void cmd_uchg(char *argbuf) { char newusername[USERNAME_SIZE]; extract_token(newusername, argbuf, 0, '|', sizeof newusername); if (CtdlAccessCheck(ac_aide)) return; if (!IsEmptyStr(newusername)) { CC->cs_flags &= ~CS_STEALTH; memset(CC->fake_username, 0, 32); if (strncasecmp(newusername, CC->curr_user, strlen(CC->curr_user))) safestrncpy(CC->fake_username, newusername, sizeof(CC->fake_username)); } else { CC->fake_username[0] = '\0'; CC->cs_flags |= CS_STEALTH; } cprintf("%d\n",CIT_OK); } /* * enter or exit "stealth mode" */ void cmd_stel(char *cmdbuf) { int requested_mode; requested_mode = extract_int(cmdbuf,0); if (CtdlAccessCheck(ac_logged_in)) return; if (requested_mode == 1) { CC->cs_flags = CC->cs_flags | CS_STEALTH; PerformSessionHooks(EVT_STEALTH); } if (requested_mode == 0) { CC->cs_flags = CC->cs_flags & ~CS_STEALTH; PerformSessionHooks(EVT_UNSTEALTH); } cprintf("%d %d\n", CIT_OK, ((CC->cs_flags & CS_STEALTH) ? 1 : 0) ); } CTDL_MODULE_INIT(rwho) { if(!threading) { CtdlRegisterProtoHook(cmd_rwho, "RWHO", "Display who is online"); CtdlRegisterProtoHook(cmd_hchg, "HCHG", "Masquerade hostname"); CtdlRegisterProtoHook(cmd_rchg, "RCHG", "Masquerade roomname"); CtdlRegisterProtoHook(cmd_uchg, "UCHG", "Masquerade username"); CtdlRegisterProtoHook(cmd_stel, "STEL", "Enter/exit stealth mode"); } /* return our module name for the log */ return "rwho"; } citadel-9.01/modules/spam/0000755000000000000000000000000012507024051014152 5ustar rootrootcitadel-9.01/modules/spam/serv_spam.c0000644000000000000000000001167512507024051016327 0ustar rootroot/* * This module allows Citadel to use SpamAssassin to filter incoming messages * arriving via SMTP. For more information on SpamAssassin, visit * http://www.spamassassin.org (the SpamAssassin project is not in any way * affiliated with the Citadel project). * * Copyright (c) 1998-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * */ #define SPAMASSASSIN_PORT "783" #include "sysdep.h" #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "control.h" #include "user_ops.h" #include "database.h" #include "msgbase.h" #include "internet_addressing.h" #include "domain.h" #include "clientsocket.h" #include "ctdl_module.h" /* * Connect to the SpamAssassin server and scan a message. */ int spam_assassin(struct CtdlMessage *msg, recptypes *recp) { int sock = (-1); char sahosts[SIZ]; int num_sahosts; char buf[SIZ]; int is_spam = 0; int sa; StrBuf *msgtext; CitContext *CCC=CC; /* For users who have authenticated to this server we never want to * apply spam filtering, because presumably they're trustworthy. */ if (CC->logged_in) return(0); /* See if we have any SpamAssassin hosts configured */ num_sahosts = get_hosts(sahosts, "spamassassin"); if (num_sahosts < 1) return(0); /* Try them one by one until we get a working one */ for (sa=0; sa\n", buf); sock = sock_connect(buf, SPAMASSASSIN_PORT); if (sock >= 0) syslog(LOG_DEBUG, "Connected!\n"); } if (sock < 0) { /* If the service isn't running, just pass the mail * through. Potentially throwing away mails isn't good. */ return(0); } CCC->SBuf.Buf = NewStrBuf(); CCC->sMigrateBuf = NewStrBuf(); CCC->SBuf.ReadWritePointer = NULL; /* Command */ syslog(LOG_DEBUG, "Transmitting command\n"); sprintf(buf, "CHECK SPAMC/1.2\r\n\r\n"); sock_write(&sock, buf, strlen(buf)); /* Message */ CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ); CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, 0); msgtext = CC->redirect_buffer; CC->redirect_buffer = NULL; sock_write(&sock, SKEY(msgtext)); FreeStrBuf(&msgtext); /* Close one end of the socket connection; this tells SpamAssassin * that we're done. */ if (sock != -1) sock_shutdown(sock, SHUT_WR); /* Response */ syslog(LOG_DEBUG, "Awaiting response\n"); if (sock_getln(&sock, buf, sizeof buf) < 0) { goto bail; } syslog(LOG_DEBUG, "<%s\n", buf); if (strncasecmp(buf, "SPAMD", 5)) { goto bail; } if (sock_getln(&sock, buf, sizeof buf) < 0) { goto bail; } syslog(LOG_DEBUG, "<%s\n", buf); syslog(LOG_DEBUG, "c_spam_flag_only setting %d\n", config.c_spam_flag_only); if (config.c_spam_flag_only) { int headerlen; char *cur; char sastatus[10]; char sascore[10]; char saoutof[10]; int numscore; syslog(LOG_DEBUG, "flag spam code used"); extract_token(sastatus, buf, 1, ' ', sizeof sastatus); extract_token(sascore, buf, 3, ' ', sizeof sascore); extract_token(saoutof, buf, 5, ' ', sizeof saoutof); memcpy(buf, HKEY("X-Spam-Level: ")); cur = buf + 14; for (numscore = atoi(sascore); numscore>0; numscore--) *(cur++) = '*'; *cur = '\0'; headerlen = cur - buf; headerlen += snprintf(cur, (sizeof(buf) - headerlen), "\r\nX-Spam-Status: %s, score=%s required=%s\r\n", sastatus, sascore, saoutof); CM_PrependToField(msg, eMesageText, buf, headerlen); } else { syslog(LOG_DEBUG, "reject spam code used"); if (!strncasecmp(buf, "Spam: True", 10)) { is_spam = 1; } if (is_spam) { CM_SetField(msg, eErrorMsg, HKEY("message rejected by spam filter")); } } bail: close(sock); FreeStrBuf(&CCC->SBuf.Buf); FreeStrBuf(&CCC->sMigrateBuf); return(is_spam); } CTDL_MODULE_INIT(spam) { if (!threading) { CtdlRegisterMessageHook(spam_assassin, EVT_SMTPSCAN); } /* return our module name for the log */ return "spam"; } citadel-9.01/modules/c-ares-dns/0000755000000000000000000000000012507024051015146 5ustar rootrootcitadel-9.01/modules/c-ares-dns/serv_c-ares-dns.c0000644000000000000000000003747312507024051020323 0ustar rootroot/* * Copyright (c) 1998-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * * * Inspired by NodeJS.org; thanks for the MX-Parser ;-) */ #include "sysdep.h" #include #include #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "ctdl_module.h" #include "event_client.h" int DebugCAres = 0; extern struct ev_loop *event_base; void SockStateCb(void *data, int sock, int read, int write); static void HostByAddrCb(void *data, int status, int timeouts, struct hostent *hostent) { AsyncIO *IO = data; EV_DNS_syslog(LOG_DEBUG, "C-ARES: %s\n", __FUNCTION__); EV_DNS_LOGT_STOP(DNS.timeout); ev_timer_stop (event_base, &IO->DNS.timeout); IO->DNS.Query->DNSStatus = status; if (status != ARES_SUCCESS) { StrBufPlain(IO->ErrMsg, ares_strerror(status), -1); return; } IO->DNS.Query->Data = hostent; } static void ParseAnswerA(AsyncIO *IO, unsigned char* abuf, int alen) { struct hostent* host = NULL; EV_DNS_syslog(LOG_DEBUG, "C-ARES: %s\n", __FUNCTION__); if (IO->DNS.Query->VParsedDNSReply != NULL) IO->DNS.Query->DNSReplyFree(IO->DNS.Query->VParsedDNSReply); IO->DNS.Query->VParsedDNSReply = NULL; IO->DNS.Query->DNSStatus = ares_parse_a_reply(abuf, alen, &host, NULL, NULL); if (IO->DNS.Query->DNSStatus != ARES_SUCCESS) { if (host != NULL) ares_free_hostent(host); StrBufPlain(IO->ErrMsg, ares_strerror(IO->DNS.Query->DNSStatus), -1); return; } IO->DNS.Query->VParsedDNSReply = host; IO->DNS.Query->DNSReplyFree = (FreeDNSReply) ares_free_hostent; } static void ParseAnswerAAAA(AsyncIO *IO, unsigned char* abuf, int alen) { struct hostent* host = NULL; EV_DNS_syslog(LOG_DEBUG, "C-ARES: %s\n", __FUNCTION__); if (IO->DNS.Query->VParsedDNSReply != NULL) IO->DNS.Query->DNSReplyFree(IO->DNS.Query->VParsedDNSReply); IO->DNS.Query->VParsedDNSReply = NULL; IO->DNS.Query->DNSStatus = ares_parse_aaaa_reply(abuf, alen, &host, NULL, NULL); if (IO->DNS.Query->DNSStatus != ARES_SUCCESS) { if (host != NULL) ares_free_hostent(host); StrBufPlain(IO->ErrMsg, ares_strerror(IO->DNS.Query->DNSStatus), -1); return; } IO->DNS.Query->VParsedDNSReply = host; IO->DNS.Query->DNSReplyFree = (FreeDNSReply) ares_free_hostent; } static void ParseAnswerCNAME(AsyncIO *IO, unsigned char* abuf, int alen) { struct hostent* host = NULL; EV_DNS_syslog(LOG_DEBUG, "C-ARES: %s\n", __FUNCTION__); if (IO->DNS.Query->VParsedDNSReply != NULL) IO->DNS.Query->DNSReplyFree(IO->DNS.Query->VParsedDNSReply); IO->DNS.Query->VParsedDNSReply = NULL; IO->DNS.Query->DNSStatus = ares_parse_a_reply(abuf, alen, &host, NULL, NULL); if (IO->DNS.Query->DNSStatus != ARES_SUCCESS) { if (host != NULL) ares_free_hostent(host); StrBufPlain(IO->ErrMsg, ares_strerror(IO->DNS.Query->DNSStatus), -1); return; } // a CNAME lookup always returns a single record but IO->DNS.Query->VParsedDNSReply = host; IO->DNS.Query->DNSReplyFree = (FreeDNSReply) ares_free_hostent; } static void ParseAnswerMX(AsyncIO *IO, unsigned char* abuf, int alen) { struct ares_mx_reply *mx_out = NULL; EV_DNS_syslog(LOG_DEBUG, "C-ARES: %s\n", __FUNCTION__); if (IO->DNS.Query->VParsedDNSReply != NULL) IO->DNS.Query->DNSReplyFree(IO->DNS.Query->VParsedDNSReply); IO->DNS.Query->VParsedDNSReply = NULL; IO->DNS.Query->DNSStatus = ares_parse_mx_reply(abuf, alen, &mx_out); if (IO->DNS.Query->DNSStatus != ARES_SUCCESS) { if (mx_out != NULL) ares_free_data(mx_out); StrBufPlain(IO->ErrMsg, ares_strerror(IO->DNS.Query->DNSStatus), -1); return; } IO->DNS.Query->VParsedDNSReply = mx_out; IO->DNS.Query->DNSReplyFree = (FreeDNSReply) ares_free_data; } static void ParseAnswerNS(AsyncIO *IO, unsigned char* abuf, int alen) { struct hostent* host = NULL; EV_DNS_syslog(LOG_DEBUG, "C-ARES: %s\n", __FUNCTION__); if (IO->DNS.Query->VParsedDNSReply != NULL) IO->DNS.Query->DNSReplyFree(IO->DNS.Query->VParsedDNSReply); IO->DNS.Query->VParsedDNSReply = NULL; IO->DNS.Query->DNSStatus = ares_parse_ns_reply(abuf, alen, &host); if (IO->DNS.Query->DNSStatus != ARES_SUCCESS) { if (host != NULL) ares_free_hostent(host); StrBufPlain(IO->ErrMsg, ares_strerror(IO->DNS.Query->DNSStatus), -1); return; } IO->DNS.Query->VParsedDNSReply = host; IO->DNS.Query->DNSReplyFree = (FreeDNSReply) ares_free_hostent; } static void ParseAnswerSRV(AsyncIO *IO, unsigned char* abuf, int alen) { struct ares_srv_reply *srv_out = NULL; EV_DNS_syslog(LOG_DEBUG, "C-ARES: %s\n", __FUNCTION__); if (IO->DNS.Query->VParsedDNSReply != NULL) IO->DNS.Query->DNSReplyFree(IO->DNS.Query->VParsedDNSReply); IO->DNS.Query->VParsedDNSReply = NULL; IO->DNS.Query->DNSStatus = ares_parse_srv_reply(abuf, alen, &srv_out); if (IO->DNS.Query->DNSStatus != ARES_SUCCESS) { if (srv_out != NULL) ares_free_data(srv_out); StrBufPlain(IO->ErrMsg, ares_strerror(IO->DNS.Query->DNSStatus), -1); return; } IO->DNS.Query->VParsedDNSReply = srv_out; IO->DNS.Query->DNSReplyFree = (FreeDNSReply) ares_free_data; } static void ParseAnswerTXT(AsyncIO *IO, unsigned char* abuf, int alen) { struct ares_txt_reply *txt_out; EV_DNS_syslog(LOG_DEBUG, "C-ARES: %s\n", __FUNCTION__); if (IO->DNS.Query->VParsedDNSReply != NULL) IO->DNS.Query->DNSReplyFree(IO->DNS.Query->VParsedDNSReply); IO->DNS.Query->VParsedDNSReply = NULL; IO->DNS.Query->DNSStatus = ares_parse_txt_reply(abuf, alen, &txt_out); if (IO->DNS.Query->DNSStatus != ARES_SUCCESS) { if (txt_out != NULL) ares_free_data(txt_out); StrBufPlain(IO->ErrMsg, ares_strerror(IO->DNS.Query->DNSStatus), -1); return; } IO->DNS.Query->VParsedDNSReply = txt_out; IO->DNS.Query->DNSReplyFree = (FreeDNSReply) ares_free_data; } void QueryCb(void *arg, int status, int timeouts, unsigned char* abuf, int alen) { AsyncIO *IO = arg; SetEVState(IO, eCaresStart); EV_DNS_syslog(LOG_DEBUG, "C-ARES: %s\n", __FUNCTION__); EV_DNS_LOGT_STOP(DNS.timeout); ev_timer_stop (event_base, &IO->DNS.timeout); IO->DNS.Query->DNSStatus = status; if (status == ARES_SUCCESS) IO->DNS.Query->DNS_CB(arg, abuf, alen); else { EV_DNS_syslog(LOG_DEBUG, "C-ARES: Failed by: %s error %s\n", __FUNCTION__, ares_strerror(status)); StrBufPrintf(IO->ErrMsg, "%s-lookup %s - %s", IO->DNS.Query->QueryTYPE, (IO->DNS.Query->QStr != NULL)? IO->DNS.Query->QStr : "", ares_strerror(status)); IO->DNS.Query->DNSStatus = status; } ev_idle_init(&IO->unwind_stack, IO_postdns_callback); IO->unwind_stack.data = IO; EV_DNS_LOGT_INIT(unwind_stack); EV_DNS_LOGT_START(unwind_stack); ev_idle_start(event_base, &IO->unwind_stack); } void QueryCbDone(AsyncIO *IO) { SetEVState(IO, eCaresDoneIO); EV_DNS_syslog(LOG_DEBUG, "C-ARES: %s\n", __FUNCTION__); EV_DNS_LOGT_STOP(DNS.timeout); ev_timer_stop (event_base, &IO->DNS.timeout); EV_DNS_LOGT_STOP(unwind_stack); ev_idle_stop(event_base, &IO->unwind_stack); } void DestructCAres(AsyncIO *IO) { SetEVState(IO, eCaresX); EVNC_syslog(LOG_DEBUG, "C-ARES: %s\n", __FUNCTION__); EVNC_syslog(LOG_DEBUG, "C-ARES: - stopping %s %d %p \n", "DNS.recv_event", IO->DNS.recv_event.fd, &IO->DNS.recv_event); ev_io_stop(event_base, &IO->DNS.recv_event); EVNC_syslog(LOG_DEBUG, "C-ARES: - stopping %s %d %p\n", "DNS.send_event", IO->DNS.send_event.fd, &IO->DNS.send_event); ev_io_stop(event_base, &IO->DNS.send_event); EVNC_syslog(LOG_DEBUG, "C-ARES: - stopping %s %p\n", "DNS.timeout", &IO->DNS.send_event); ev_timer_stop (event_base, &IO->DNS.timeout); EVNC_syslog(LOG_DEBUG, "C-ARES: - stopping %s %p\n", "DNS.unwind_stack", &IO->unwind_stack); ev_idle_stop(event_base, &IO->unwind_stack); ares_destroy_options(&IO->DNS.Options); } void InitC_ares_dns(AsyncIO *IO) { int optmask = 0; EV_DNS_syslog(LOG_DEBUG, "C-ARES: %s %p\n", __FUNCTION__, IO->DNS.Channel); if (IO->DNS.Channel == NULL) { optmask |= ARES_OPT_SOCK_STATE_CB; IO->DNS.Options.sock_state_cb = SockStateCb; IO->DNS.Options.sock_state_cb_data = IO; ares_init_options(&IO->DNS.Channel, &IO->DNS.Options, optmask); } IO->DNS.Query->DNSStatus = 0; } static void DNStimeouttrigger_callback(struct ev_loop *loop, ev_timer *watcher, int revents) { AsyncIO *IO = watcher->data; struct timeval tv, MaxTV; struct timeval *NextTV; memset(&MaxTV, 0, sizeof(MaxTV)); memset(&tv, 0, sizeof(tv)); MaxTV.tv_sec = 30; NextTV = ares_timeout(IO->DNS.Channel, &MaxTV, &tv); if ((NextTV->tv_sec != MaxTV.tv_sec) || (NextTV->tv_usec != MaxTV.tv_usec)) { fd_set readers, writers; EV_DNS_syslog(LOG_DEBUG, "C-ARES: %s Timeout!\n", __FUNCTION__); FD_ZERO(&readers); FD_ZERO(&writers); ares_fds(IO->DNS.Channel, &readers, &writers); ares_process(IO->DNS.Channel, &readers, &writers); } } void QueueGetHostByNameDone(void *Ctx, int status, int timeouts, struct hostent *hostent) { AsyncIO *IO = (AsyncIO *) Ctx; EV_DNS_syslog(LOG_DEBUG, "C-ARES: %s\n", __FUNCTION__); IO->DNS.Query->DNSStatus = status; IO->DNS.Query->VParsedDNSReply = hostent; IO->DNS.Query->DNSReplyFree = (FreeDNSReply) ares_free_hostent; EV_DNS_LOGT_STOP(DNS.timeout); ev_timer_stop (event_base, &IO->DNS.timeout); ev_idle_init(&IO->unwind_stack, IO_postdns_callback); IO->unwind_stack.data = IO; EV_DNS_LOGT_INIT(unwind_stack); EV_DNS_LOGT_START(unwind_stack); ev_idle_start(event_base, &IO->unwind_stack); } void QueueGetHostByName(AsyncIO *IO, const char *Hostname, DNSQueryParts *QueryParts, IO_CallBack PostDNS) { EV_DNS_syslog(LOG_DEBUG, "C-ARES: %s\n", __FUNCTION__); IO->DNS.SourcePort = 0; IO->DNS.Query = QueryParts; IO->DNS.Query->PostDNS = PostDNS; InitC_ares_dns(IO); ev_timer_init(&IO->DNS.timeout, DNStimeouttrigger_callback, 10, 1); EV_DNS_LOGT_INIT(DNS.timeout); IO->DNS.timeout.data = IO; ares_gethostbyname(IO->DNS.Channel, Hostname, AF_INET6, /* it falls back to ipv4 in doubt... */ QueueGetHostByNameDone, IO); EV_DNS_LOGT_START(DNS.timeout); ev_timer_start(event_base, &IO->DNS.timeout); } const char* QT[] = { "A", "AAAA", "MX", "NS", "TXT", "SRV", "CNAME", "PTR" }; int QueueQuery(ns_type Type, const char *name, AsyncIO *IO, DNSQueryParts *QueryParts, IO_CallBack PostDNS) { int length, family; char address_b[sizeof(struct in6_addr)]; IO->DNS.SourcePort = 0; IO->DNS.Query = QueryParts; IO->DNS.Query->PostDNS = PostDNS; IO->DNS.Start = IO->Now; IO->DNS.Query->QStr = name; InitC_ares_dns(IO); ev_timer_init(&IO->DNS.timeout, DNStimeouttrigger_callback, 10, 1); IO->DNS.timeout.data = IO; EV_DNS_LOGT_INIT(DNS.timeout); switch(Type) { case ns_t_a: IO->DNS.Query->DNS_CB = ParseAnswerA; IO->DNS.Query->QueryTYPE = QT[0]; break; case ns_t_aaaa: IO->DNS.Query->DNS_CB = ParseAnswerAAAA; IO->DNS.Query->QueryTYPE = QT[1]; break; case ns_t_mx: IO->DNS.Query->DNS_CB = ParseAnswerMX; IO->DNS.Query->QueryTYPE = QT[2]; break; case ns_t_ns: IO->DNS.Query->DNS_CB = ParseAnswerNS; IO->DNS.Query->QueryTYPE = QT[3]; break; case ns_t_txt: IO->DNS.Query->DNS_CB = ParseAnswerTXT; IO->DNS.Query->QueryTYPE = QT[4]; break; case ns_t_srv: IO->DNS.Query->DNS_CB = ParseAnswerSRV; IO->DNS.Query->QueryTYPE = QT[5]; break; case ns_t_cname: IO->DNS.Query->DNS_CB = ParseAnswerCNAME; IO->DNS.Query->QueryTYPE = QT[6]; break; case ns_t_ptr: IO->DNS.Query->QueryTYPE = QT[7]; if (inet_pton(AF_INET, name, &address_b) == 1) { length = sizeof(struct in_addr); family = AF_INET; } else if (inet_pton(AF_INET6, name, &address_b) == 1) { length = sizeof(struct in6_addr); family = AF_INET6; } else { return -1; } ares_gethostbyaddr(IO->DNS.Channel, address_b, length, family, HostByAddrCb, IO); EV_DNS_LOGT_START(DNS.timeout); ev_timer_start(event_base, &IO->DNS.timeout); EV_DNS_syslog(LOG_DEBUG, "C-ARES: %s X1\n", __FUNCTION__); return 1; default: EV_DNS_syslog(LOG_DEBUG, "C-ARES: %sX2\n", __FUNCTION__); return 0; } EV_DNS_syslog(LOG_DEBUG, "C-ARES: %s\n", __FUNCTION__); ares_query(IO->DNS.Channel, name, ns_c_in, Type, QueryCb, IO); EV_DNS_LOGT_START(DNS.timeout); ev_timer_start(event_base, &IO->DNS.timeout); return 1; } /***************************************************************************** * libev / c-ares integration * *****************************************************************************/ static void DNS_send_callback(struct ev_loop *loop, ev_io *watcher, int revents) { AsyncIO *IO = watcher->data; IO->Now = ev_now(event_base); EV_DNS_syslog(LOG_DEBUG, "C-ARES: %s\n", __FUNCTION__); ares_process_fd(IO->DNS.Channel, ARES_SOCKET_BAD, IO->DNS.send_event.fd); } static void DNS_recv_callback(struct ev_loop *loop, ev_io *watcher, int revents) { AsyncIO *IO = watcher->data; IO->Now = ev_now(event_base); EV_DNS_syslog(LOG_DEBUG, "C-ARES: %s\n", __FUNCTION__); ares_process_fd(IO->DNS.Channel, IO->DNS.recv_event.fd, ARES_SOCKET_BAD); } void SockStateCb(void *data, int sock, int read, int write) { AsyncIO *IO = data; /* already inside of the event queue. */ if (DebugCAres) { struct sockaddr_in sin; socklen_t slen; memset(&sin, 0, sizeof(sin)); slen = sizeof(sin); if ((IO->DNS.SourcePort == 0) && (getsockname(sock, &sin, &slen) == 0)) { IO->DNS.SourcePort = ntohs(sin.sin_port); } EV_DNS_syslog(LOG_DEBUG, "C-ARES: %s %d|%d Sock %d port %hu\n", __FUNCTION__, read, write, sock, IO->DNS.SourcePort); } IO->Now = ev_now(event_base); if (read) { if ((IO->DNS.recv_event.fd != sock) && (IO->DNS.recv_event.fd != 0)) { EV_DNS_LOG_STOP(DNS.recv_event); ev_io_stop(event_base, &IO->DNS.recv_event); } IO->DNS.recv_event.fd = sock; ev_io_init(&IO->DNS.recv_event, DNS_recv_callback, IO->DNS.recv_event.fd, EV_READ); EV_DNS_LOG_INIT(DNS.recv_event); IO->DNS.recv_event.data = IO; EV_DNS_LOG_START(DNS.recv_event); ev_io_start(event_base, &IO->DNS.recv_event); } if (write) { if ((IO->DNS.send_event.fd != sock) && (IO->DNS.send_event.fd != 0)) { EV_DNS_LOG_STOP(DNS.send_event); ev_io_stop(event_base, &IO->DNS.send_event); } IO->DNS.send_event.fd = sock; ev_io_init(&IO->DNS.send_event, DNS_send_callback, IO->DNS.send_event.fd, EV_WRITE); IO->DNS.send_event.data = IO; EV_DNS_LOG_INIT(DNS.send_event); EV_DNS_LOG_START(DNS.send_event); ev_io_start(event_base, &IO->DNS.send_event); } if ((read == 0) && (write == 0)) { EV_DNS_LOG_STOP(DNS.recv_event); EV_DNS_LOG_STOP(DNS.send_event); ev_io_stop(event_base, &IO->DNS.recv_event); ev_io_stop(event_base, &IO->DNS.send_event); } } void EnableDebugCAres(const int n) { DebugCAres = n; } CTDL_MODULE_INIT(c_ares_client) { if (!threading) { CtdlRegisterDebugFlagHook(HKEY("cares"), EnableDebugCAres, &DebugCAres); int r = ares_library_init(ARES_LIB_INIT_ALL); if (0 != r) { } } return "c-ares"; } citadel-9.01/modules/blog/0000755000000000000000000000000012507024051014135 5ustar rootrootcitadel-9.01/modules/blog/serv_blog.c0000644000000000000000000000456412507024051016274 0ustar rootroot/* * Support for blog rooms * * Copyright (c) 1999-2011 by the citadel.org team * * This program is open source software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "sysdep.h" #include #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "control.h" #include "user_ops.h" #include "database.h" #include "msgbase.h" #include "internet_addressing.h" #include "serv_vcard.h" #include "citadel_ldap.h" #include "ctdl_module.h" /* * Pre-save hook for saving a message in a blog room. * (Do we want to only do this for top-level messages?) */ int blog_upload_beforesave(struct CtdlMessage *msg, recptypes *recp) { /* Only run this hook for blog rooms */ if (CC->room.QRdefaultview != VIEW_BLOG) { return(0); } /* * If the message doesn't have an EUID, give it one. */ if (CM_IsEmpty(msg, eExclusiveID)) { char uuid[SIZ]; generate_uuid(uuid); CM_SetField(msg, eExclusiveID, uuid, strlen(uuid)); } /* * We also want to define a maximum length, whether we generated it or not. */ CM_CutFieldAt(msg, eExclusiveID, BLOG_EUIDBUF_SIZE - 1); /* Now allow the save to complete. */ return(0); } CTDL_MODULE_INIT(blog) { if (!threading) { CtdlRegisterMessageHook(blog_upload_beforesave, EVT_BEFORESAVE); } /* return our module id for the Log */ return "blog"; } citadel-9.01/modules/upgrade/0000755000000000000000000000000012507024051014641 5ustar rootrootcitadel-9.01/modules/upgrade/serv_upgrade.c0000644000000000000000000002134212507024051017475 0ustar rootroot/* * Transparently handle the upgrading of server data formats. * * Copyright (c) 1987-2014 by the citadel.org team * * This program is open source software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "sysdep.h" #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "control.h" #include "database.h" #include "user_ops.h" #include "msgbase.h" #include "serv_upgrade.h" #include "euidindex.h" #include "ctdl_module.h" /* * Fix up the name for Citadel user 0 and try to remove any extra users with number 0 */ void fix_sys_user_name(void) { struct ctdluser usbuf; char usernamekey[USERNAME_SIZE]; /** If we have a user called Citadel rename them to SYS_Citadel */ if (CtdlGetUser(&usbuf, "Citadel") == 0) { rename_user("Citadel", "SYS_Citadel"); } while (CtdlGetUserByNumber(&usbuf, 0) == 0) { /* delete user with number 0 and no name */ if (IsEmptyStr(usbuf.fullname)) { cdb_delete(CDB_USERS, "", 0); } else { /* temporarily set this user to -1 */ usbuf.usernum = -1; CtdlPutUser(&usbuf); } } /* Make sure user SYS_* is user 0 */ while (CtdlGetUserByNumber(&usbuf, -1) == 0) { if (strncmp(usbuf.fullname, "SYS_", 4)) { /* Delete any user 0 that doesn't start with SYS_ */ makeuserkey(usernamekey, usbuf.fullname, cutuserkey(usbuf.fullname)); cdb_delete(CDB_USERS, usernamekey, strlen(usernamekey)); } else { usbuf.usernum = 0; CtdlPutUser(&usbuf); } } } /* * Back end processing function for cmd_bmbx */ void cmd_bmbx_backend(struct ctdlroom *qrbuf, void *data) { static struct RoomProcList *rplist = NULL; struct RoomProcList *ptr; struct ctdlroom qr; /* Lazy programming here. Call this function as a CtdlForEachRoom backend * in order to queue up the room names, or call it with a null room * to make it do the processing. */ if (qrbuf != NULL) { ptr = (struct RoomProcList *) malloc(sizeof (struct RoomProcList)); if (ptr == NULL) return; safestrncpy(ptr->name, qrbuf->QRname, sizeof ptr->name); ptr->next = rplist; rplist = ptr; return; } while (rplist != NULL) { if (CtdlGetRoomLock(&qr, rplist->name) == 0) { syslog(LOG_DEBUG, "Processing <%s>...", rplist->name); if ( (qr.QRflags & QR_MAILBOX) == 0) { syslog(LOG_DEBUG, " -- not a mailbox"); } else { qr.QRgen = time(NULL); syslog(LOG_DEBUG, " -- fixed!"); } CtdlPutRoomLock(&qr); } ptr = rplist; rplist = rplist->next; free(ptr); } } /* * quick fix to bump mailbox generation numbers */ void bump_mailbox_generation_numbers(void) { syslog(LOG_WARNING, "Applying security fix to mailbox rooms"); CtdlForEachRoom(cmd_bmbx_backend, NULL); cmd_bmbx_backend(NULL, NULL); return; } /* * Back end processing function for convert_ctdluid_to_minusone() */ void cbtm_backend(struct ctdluser *usbuf, void *data) { static struct UserProcList *uplist = NULL; struct UserProcList *ptr; struct ctdluser us; /* Lazy programming here. Call this function as a ForEachUser backend * in order to queue up the room names, or call it with a null user * to make it do the processing. */ if (usbuf != NULL) { ptr = (struct UserProcList *) malloc(sizeof (struct UserProcList)); if (ptr == NULL) return; safestrncpy(ptr->user, usbuf->fullname, sizeof ptr->user); ptr->next = uplist; uplist = ptr; return; } while (uplist != NULL) { if (CtdlGetUserLock(&us, uplist->user) == 0) { syslog(LOG_DEBUG, "Processing <%s>...", uplist->user); if (us.uid == CTDLUID) { us.uid = (-1); } CtdlPutUserLock(&us); } ptr = uplist; uplist = uplist->next; free(ptr); } } /* * quick fix to change all CTDLUID users to (-1) */ void convert_ctdluid_to_minusone(void) { syslog(LOG_WARNING, "Applying uid changes"); ForEachUser(cbtm_backend, NULL); cbtm_backend(NULL, NULL); return; } /* * These accounts may have been created by code that ran between mid 2008 and early 2011. * If present they are no longer in use and may be deleted. */ void remove_thread_users(void) { char *deleteusers[] = { "SYS_checkpoint", "SYS_extnotify", "SYS_IGnet Queue", "SYS_indexer", "SYS_network", "SYS_popclient", "SYS_purger", "SYS_rssclient", "SYS_select_on_master", "SYS_SMTP Send" }; int i; struct ctdluser usbuf; for (i=0; i<(sizeof(deleteusers)/sizeof(char *)); ++i) { if (CtdlGetUser(&usbuf, deleteusers[i]) == 0) { usbuf.axlevel = 0; strcpy(usbuf.password, "deleteme"); CtdlPutUser(&usbuf); syslog(LOG_INFO, "System user account <%s> is no longer in use and will be deleted.", deleteusers[i] ); } } } /* * Attempt to guess the name of the time zone currently in use * on the underlying host system. */ void guess_time_zone(void) { FILE *fp; char buf[PATH_MAX]; fp = popen(file_guesstimezone, "r"); if (fp) { if (fgets(buf, sizeof buf, fp) && (strlen(buf) > 2)) { buf[strlen(buf)-1] = 0; safestrncpy(config.c_default_cal_zone, buf, sizeof config.c_default_cal_zone); syslog(LOG_INFO, "Configuring timezone: %s", config.c_default_cal_zone); } fclose(fp); } } /* * Perform any upgrades that can be done automatically based on our knowledge of the previous * version of Citadel server that was running here. * * Note that if the previous version was 0 then this is a new installation running for the first time. */ void update_config(void) { get_config(); if (CitControl.version < 606) { config.c_rfc822_strict_from = 0; } if (CitControl.version < 609) { config.c_purge_hour = 3; } if (CitControl.version < 615) { config.c_ldap_port = 389; } if (CitControl.version < 623) { strcpy(config.c_ip_addr, "*"); } if (CitControl.version < 650) { config.c_enable_fulltext = 1; } if (CitControl.version < 652) { config.c_auto_cull = 1; } if (CitControl.version < 725) { config.c_xmpp_c2s_port = 5222; config.c_xmpp_s2s_port = 5269; } if (CitControl.version < 830) { config.c_nntp_port = 119; config.c_nntps_port = 563; } if (IsEmptyStr(config.c_default_cal_zone)) { guess_time_zone(); } put_config(); } /* * Based on the server version number reported by the existing database, * run in-place data format upgrades until everything is up to date. */ void check_server_upgrades(void) { get_control(); syslog(LOG_INFO, "Existing database version on disk is %d.%02d", (CitControl.version / 100), (CitControl.version % 100) ); if (CitControl.version < REV_LEVEL) { syslog(LOG_WARNING, "Server hosted updates need to be processed at this time. Please wait..." ); } else { return; } update_config(); if ((CitControl.version > 000) && (CitControl.version < 555)) { syslog(LOG_EMERG, "This database is too old to be upgraded. Citadel server will exit."); exit(EXIT_FAILURE); } if ((CitControl.version > 000) && (CitControl.version < 591)) { bump_mailbox_generation_numbers(); } if ((CitControl.version > 000) && (CitControl.version < 608)) { convert_ctdluid_to_minusone(); } if ((CitControl.version > 000) && (CitControl.version < 659)) { rebuild_euid_index(); } if (CitControl.version < 735) { fix_sys_user_name(); } if (CitControl.version < 736) { rebuild_usersbynumber(); } if (CitControl.version < 790) { remove_thread_users(); } if (CitControl.version < 810) { struct ctdlroom QRoom; if (!CtdlGetRoom(&QRoom, SMTP_SPOOLOUT_ROOM)) { QRoom.QRdefaultview = VIEW_QUEUE; CtdlPutRoom(&QRoom); } if (!CtdlGetRoom(&QRoom, FNBL_QUEUE_ROOM)) { QRoom.QRdefaultview = VIEW_QUEUE; CtdlPutRoom(&QRoom); } } CitControl.version = REV_LEVEL; /* * Negative values for maxsessions are not allowed. */ if (config.c_maxsessions < 0) { config.c_maxsessions = 0; } /* We need a system default message expiry policy, because this is * the top level and there's no 'higher' policy to fall back on. * By default, do not expire messages at all. */ if (config.c_ep.expire_mode == 0) { config.c_ep.expire_mode = EXPIRE_MANUAL; config.c_ep.expire_value = 0; } put_control(); } CTDL_MODULE_UPGRADE(upgrade) { check_server_upgrades(); /* return our module id for the Log */ return "upgrade"; } citadel-9.01/modules/upgrade/serv_upgrade.h0000644000000000000000000000074012507024051017501 0ustar rootroot/* * Copyright (c) 1987-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * */ citadel-9.01/modules/test/0000755000000000000000000000000012507024051014171 5ustar rootrootcitadel-9.01/modules/test/serv_test.c0000644000000000000000000000375012507024051016360 0ustar rootroot/* * This is an empty skeleton of a Citadel server module, included to demonstrate * how to add a new module to the system and how to activate it in the server. * * Copyright (c) 1998-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "sysdep.h" #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include "ctdl_module.h" void CleanupTest(void) { syslog(LOG_DEBUG, "--- test of adding an unload hook --- \n"); } void NewRoomTest(void) { syslog(LOG_DEBUG, "--- test module was told we're now in a new room ---\n"); } void SessionStartTest(void) { syslog(LOG_DEBUG, "--- starting up session %d ---\n", CC->cs_pid); } void SessionStopTest(void) { syslog(LOG_DEBUG, "--- ending session %d ---\n", CC->cs_pid); } void LoginTest(void) { syslog(LOG_DEBUG, "--- Hello, %s ---\n", CC->curr_user); } /* To insert this module into the server activate the next block by changing the #if 0 to #if 1 */ CTDL_MODULE_INIT(test) { #if 0 if (!threading) { CtdlRegisterCleanupHook(CleanupTest); CtdlRegisterSessionHook(NewRoomTest, EVT_NEWROOM, 1); CtdlRegisterSessionHook(SessionStartTest, EVT_START, 1); CtdlRegisterSessionHook(SessionStopTest, EVT_STOP, 1); CtdlRegisterSessionHook(LoginTest, EVT_LOGIN, 1); } #endif /* return our module name for the log */ return "test"; } citadel-9.01/modules/roomchat/0000755000000000000000000000000012507024051015026 5ustar rootrootcitadel-9.01/modules/roomchat/serv_roomchat.c0000644000000000000000000001305712507024051020053 0ustar rootroot/* * This module handles instant messaging between users. * * Copyright (c) 2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "sysdep.h" #include #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "msgbase.h" #include "user_ops.h" #include "ctdl_module.h" struct chatmsg { struct chatmsg *next; time_t timestamp; int seq; long roomnum; char *sender; char *msgtext; }; struct chatmsg *first_chat_msg = NULL; struct chatmsg *last_chat_msg = NULL; /* * Periodically called for housekeeping. Expire old chat messages so they don't take up memory forever. */ void roomchat_timer(void) { struct chatmsg *ptr; begin_critical_section(S_CHATQUEUE); while ((first_chat_msg != NULL) && ((time(NULL) - first_chat_msg->timestamp) > 300)) { ptr = first_chat_msg->next; free(first_chat_msg->sender); free(first_chat_msg->msgtext); free(first_chat_msg); first_chat_msg = ptr; if (first_chat_msg == NULL) { last_chat_msg = NULL; } } end_critical_section(S_CHATQUEUE); } /* * Perform shutdown-related activities... */ void roomchat_shutdown(void) { /* if we ever start logging chats, we have to flush them to disk here .*/ } /* * Add a message into the chat queue */ void add_to_chat_queue(char *msg) { static int seq = 0; struct chatmsg *m = malloc(sizeof(struct chatmsg)); if (!m) return; m->next = NULL; m->timestamp = time(NULL); m->roomnum = CC->room.QRnumber; m->sender = strdup(CC->user.fullname); m->msgtext = strdup(msg); if ((m->sender == NULL) || (m->msgtext == NULL)) { free(m->sender); free(m->msgtext); free(m); return; } begin_critical_section(S_CHATQUEUE); m->seq = ++seq; if (first_chat_msg == NULL) { assert(last_chat_msg == NULL); first_chat_msg = m; last_chat_msg = m; } else { assert(last_chat_msg != NULL); assert(last_chat_msg->next == NULL); last_chat_msg->next = m; last_chat_msg = m; } end_critical_section(S_CHATQUEUE); } /* * Transmit a message into a room chat */ void roomchat_send(char *argbuf) { char buf[1024]; if ((CC->cs_flags & CS_CHAT) == 0) { cprintf("%d Session is not in chat mode.\n", ERROR); return; } cprintf("%d send now\n", SEND_LISTING); while (client_getln(buf, sizeof buf) >= 0 && strcmp(buf, "000")) { add_to_chat_queue(buf); } } /* * Poll room for incoming chat messages */ void roomchat_poll(char *argbuf) { int newer_than = 0; struct chatmsg *found = NULL; struct chatmsg *ptr = NULL; newer_than = extract_int(argbuf, 1); if ((CC->cs_flags & CS_CHAT) == 0) { cprintf("%d Session is not in chat mode.\n", ERROR); return; } begin_critical_section(S_CHATQUEUE); for (ptr = first_chat_msg; ((ptr != NULL) && (found == NULL)); ptr = ptr->next) { if ((ptr->seq > newer_than) && (ptr->roomnum == CC->room.QRnumber)) { found = ptr; } } end_critical_section(S_CHATQUEUE); if (found == NULL) { cprintf("%d no messages\n", ERROR + MESSAGE_NOT_FOUND); return; } cprintf("%d %d|%ld|%s\n", LISTING_FOLLOWS, found->seq, found->timestamp, found->sender); cprintf("%s\n", found->msgtext); cprintf("000\n"); } /* * list users in chat in this room */ void roomchat_rwho(char *argbuf) { struct CitContext *nptr; int nContexts, i; if ((CC->cs_flags & CS_CHAT) == 0) { cprintf("%d Session is not in chat mode.\n", ERROR); return; } cprintf("%d%c \n", LISTING_FOLLOWS, CtdlCheckExpress() ); nptr = CtdlGetContextArray(&nContexts) ; // grab a copy of the wholist if (nptr) { for (i=0; iroom.QRnumber) && (nptr[i].cs_flags & CS_CHAT) ) { cprintf("%s\n", nptr[i].user.fullname); } } free(nptr); // free our copy } cprintf("000\n"); } /* * Participate in real time chat in a room */ void cmd_rcht(char *argbuf) { char subcmd[16]; if (CtdlAccessCheck(ac_logged_in)) return; extract_token(subcmd, argbuf, 0, '|', sizeof subcmd); if (!strcasecmp(subcmd, "enter")) { CC->cs_flags |= CS_CHAT; cprintf("%d Entering chat mode.\n", CIT_OK); } else if (!strcasecmp(subcmd, "exit")) { CC->cs_flags &= ~CS_CHAT; cprintf("%d Exiting chat mode.\n", CIT_OK); } else if (!strcasecmp(subcmd, "send")) { roomchat_send(argbuf); } else if (!strcasecmp(subcmd, "poll")) { roomchat_poll(argbuf); } else if (!strcasecmp(subcmd, "rwho")) { roomchat_rwho(argbuf); } else { cprintf("%d Invalid subcommand\n", ERROR + CMD_NOT_SUPPORTED); } } CTDL_MODULE_INIT(roomchat) { if (!threading) { CtdlRegisterProtoHook(cmd_rcht, "RCHT", "Participate in real time chat in a room"); CtdlRegisterSessionHook(roomchat_timer, EVT_TIMER, PRIO_CLEANUP + 400); CtdlRegisterSessionHook(roomchat_shutdown, EVT_SHUTDOWN, PRIO_SHUTDOWN + 55); } /* return our module name for the log */ return "roomchat"; } citadel-9.01/modules/clamav/0000755000000000000000000000000012507024051014455 5ustar rootrootcitadel-9.01/modules/clamav/serv_virus.c0000644000000000000000000001173712507024051017041 0ustar rootroot/* * This module allows Citadel to use clamd to filter incoming messages * arriving via SMTP. For more information on clamd, visit * http://clamav.net (the ClamAV project is not in any way * affiliated with the Citadel project). * * Copyright (c) 1987-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * */ #define CLAMD_PORT "3310" #include "sysdep.h" #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "control.h" #include "user_ops.h" #include "database.h" #include "msgbase.h" #include "internet_addressing.h" #include "domain.h" #include "clientsocket.h" #include "ctdl_module.h" /* * Connect to the clamd server and scan a message. */ int clamd(struct CtdlMessage *msg, recptypes *recp) { int sock = (-1); int streamsock = (-1); char clamhosts[SIZ]; int num_clamhosts; char buf[SIZ]; char hostbuf[SIZ]; char portbuf[SIZ]; int is_virus = 0; int clamhost; StrBuf *msgtext; CitContext *CCC; /* Don't care if you're logged in. You can still spread viruses. */ /* if (CC->logged_in) return(0); */ /* See if we have any clamd hosts configured */ num_clamhosts = get_hosts(clamhosts, "clamav"); if (num_clamhosts < 1) return(0); /* Try them one by one until we get a working one */ for (clamhost=0; clamhost\n", buf); /* Assuming a host:port entry */ extract_token(hostbuf, buf, 0, ':', sizeof hostbuf); if (extract_token(portbuf, buf, 1, ':', sizeof portbuf)==-1) /* Didn't specify a port so we'll try the psuedo-standard 3310 */ sock = sock_connect(hostbuf, CLAMD_PORT); else /* Port specified lets try connecting to it! */ sock = sock_connect(hostbuf, portbuf); if (sock >= 0) syslog(LOG_DEBUG, "Connected!\n"); } if (sock < 0) { /* If the service isn't running, just pass the mail * through. Potentially throwing away mails isn't good. */ return(0); } CCC=CC; CCC->SBuf.Buf = NewStrBuf(); CCC->sMigrateBuf = NewStrBuf(); CCC->SBuf.ReadWritePointer = NULL; /* Command */ syslog(LOG_DEBUG, "Transmitting STREAM command\n"); sprintf(buf, "STREAM\r\n"); sock_write(&sock, buf, strlen(buf)); syslog(LOG_DEBUG, "Waiting for PORT number\n"); if (sock_getln(&sock, buf, sizeof buf) < 0) { goto bail; } syslog(LOG_DEBUG, "<%s\n", buf); if (strncasecmp(buf, "PORT", 4)!=0) { goto bail; } /* Should have received a port number to connect to */ extract_token(portbuf, buf, 1, ' ', sizeof portbuf); /* Attempt to establish connection to STREAM socket */ streamsock = sock_connect(hostbuf, portbuf); if (streamsock < 0) { /* If the service isn't running, just pass the mail * through. Potentially throwing away mails isn't good. */ FreeStrBuf(&CCC->SBuf.Buf); FreeStrBuf(&CCC->sMigrateBuf); return(0); } else { syslog(LOG_DEBUG, "STREAM socket connected!\n"); } /* Message */ CC->redirect_buffer = NewStrBufPlain(NULL, SIZ); CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, 0); msgtext = CC->redirect_buffer; CC->redirect_buffer = NULL; sock_write(&streamsock, SKEY(msgtext)); FreeStrBuf(&msgtext); /* Close the streamsocket connection; this tells clamd * that we're done. */ if (streamsock != -1) close(streamsock); /* Response */ syslog(LOG_DEBUG, "Awaiting response\n"); if (sock_getln(&sock, buf, sizeof buf) < 0) { goto bail; } syslog(LOG_DEBUG, "<%s\n", buf); if (strncasecmp(buf, "stream: OK", 10)!=0) { is_virus = 1; } if (is_virus) { CM_SetField(msg, eErrorMsg, HKEY("message rejected by virus filter")); } bail: close(sock); FreeStrBuf(&CCC->SBuf.Buf); FreeStrBuf(&CCC->sMigrateBuf); return(is_virus); } CTDL_MODULE_INIT(virus) { if (!threading) { CtdlRegisterMessageHook(clamd, EVT_SMTPSCAN); } /* return our module name for the log */ return "virus"; } citadel-9.01/modules/managesieve/0000755000000000000000000000000012507024051015476 5ustar rootrootcitadel-9.01/modules/managesieve/serv_managesieve.c0000644000000000000000000003460512507024051021175 0ustar rootroot/* * This module is an managesieve implementation for the Citadel system. * It is compliant with all of the following: * * http://tools.ietf.org/html/draft-martin-managesieve-06 * as this draft expires with this writing, you might need to search for * the new one. * * Copyright (c) 2007-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "sysdep.h" #include #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "control.h" #include "user_ops.h" #include "database.h" #include "msgbase.h" #include "internet_addressing.h" #include "genstamp.h" #include "domain.h" #include "clientsocket.h" #include "locate_host.h" #include "citadel_dirs.h" #include "ctdl_module.h" #include "serv_sieve.h" /* * http://tools.ietf.org/html/draft-martin-managesieve-06 * * this is the draft this code tries to implement. */ struct citmgsve { int command_state; /**< Information about the current session */ char *transmitted_message; size_t transmitted_length; char *imap_format_outstring; int imap_outstring_length; }; enum { /** Command states for login authentication */ mgsve_command, mgsve_tls, mgsve_user, mgsve_password, mgsve_plain }; #define MGSVE ((struct citmgsve *)CC->session_specific_data) int old_imap_parameterize(char** args, char *in) { char* out = in; int num = 0; for (;;) { /* Skip whitespace. */ while (isspace(*in)) in++; if (*in == 0) break; /* Found the start of a token. */ args[num++] = out; /* Read in the token. */ for (;;) { int c = *in++; if (isspace(c)) break; if (c == '\"') { /* Found a quoted section. */ for (;;) { c = *in++; if (c == '\"') break; else if (c == '\\') c = *in++; *out++ = c; if (c == 0) return num; } } else if (c == '\\') { c = *in++; *out++ = c; } else *out++ = c; if (c == 0) return num; } *out++ = '\0'; } return num; } /*****************************************************************************/ /* MANAGESIEVE Server */ /*****************************************************************************/ void sieve_outbuf_append(char *str) { size_t newlen = strlen(str)+1; size_t oldlen = (MGSVE->imap_format_outstring==NULL)? 0 : strlen(MGSVE->imap_format_outstring)+2; char *buf = malloc ( newlen + oldlen + 10 ); buf[0]='\0'; if (oldlen!=0) sprintf(buf,"%s%s",MGSVE->imap_format_outstring, str); else memcpy(buf, str, newlen); if (oldlen != 0) free (MGSVE->imap_format_outstring); MGSVE->imap_format_outstring = buf; } /** * Capability listing. Printed as greeting or on "CAPABILITIES" * see Section 1.8 ; 2.4 */ void cmd_mgsve_caps(void) { cprintf("\"IMPLEMENTATION\" \"CITADEL Sieve " PACKAGE_VERSION "\"\r\n" "\"SASL\" \"PLAIN\"\r\n" /*DIGEST-MD5 GSSAPI SASL sucks.*/ #ifdef HAVE_OPENSSL /* if TLS is already there, should we say that again? */ "\"STARTTLS\"\r\n" #endif "\"SIEVE\" \"%s\"\r\n" "OK\r\n", msiv_extensions); } /* * Here's where our managesieve session begins its happy day. */ void managesieve_greeting(void) { strcpy(CC->cs_clientname, "Managesieve session"); CC->internal_pgm = 0; CC->cs_flags |= CS_STEALTH; CC->session_specific_data = malloc(sizeof(struct citmgsve)); memset(MGSVE, 0, sizeof(struct citmgsve)); cmd_mgsve_caps(); } long GetSizeToken(char * token) { char *cursor = token; char *number; while (!IsEmptyStr(cursor) && (*cursor != '{')) { cursor++; } if (IsEmptyStr(cursor)) return -1; number = cursor + 1; while ((*cursor != '\0') && (*cursor != '}')) { cursor++; } if (cursor[-1] == '+') cursor--; if (IsEmptyStr(cursor)) return -1; return atol(number); } char *ReadString(long size, char *command) { long ret; if (size < 1) { cprintf("NO %s: %ld BAD Message length must be at least 1.\r\n", command, size); CC->kill_me = KILLME_READSTRING_FAILED; return NULL; } MGSVE->transmitted_message = malloc(size + 2); if (MGSVE->transmitted_message == NULL) { cprintf("NO %s Cannot allocate memory.\r\n", command); CC->kill_me = KILLME_MALLOC_FAILED; return NULL; } MGSVE->transmitted_length = size; ret = client_read(MGSVE->transmitted_message, size); MGSVE->transmitted_message[size] = '\0'; if (ret != 1) { cprintf("%s NO Read failed.\r\n", command); return NULL; } return MGSVE->transmitted_message; } /* AUTHENTICATE command; 2.1 */ void cmd_mgsve_auth(int num_parms, char **parms, struct sdm_userdata *u) { if ((num_parms == 3) && !strncasecmp(parms[1], "PLAIN", 5)) /* todo, check length*/ { char auth[SIZ]; char *message; char *username; message = NULL; memset (auth, 0, SIZ); if (parms[2][0] == '{') message = ReadString(GetSizeToken(parms[2]), parms[0]); if (message != NULL) {/**< do we have tokenized login? */ CtdlDecodeBase64(auth, MGSVE->transmitted_message, strlen(MGSVE->transmitted_message)); } else CtdlDecodeBase64(auth, parms[2], strlen(parms[2])); username = auth; if ((*username == '\0') && (*(username + 1) != '\0')) username ++; if (login_ok == CtdlLoginExistingUser(NULL, username)) { char *pass; pass = &(auth[strlen(auth)+1]); /* for some reason the php script sends us the username twice. y? */ pass = &(pass[strlen(pass)+1]); if (pass_ok == CtdlTryPassword(pass, strlen(pass))) { MGSVE->command_state = mgsve_password; cprintf("OK\r\n"); return; } } } cprintf("NO \"Authentication Failure.\"\r\n");/* we just support auth plain. */ CC->kill_me = KILLME_AUTHFAILED; } /** * STARTTLS command chapter 2.2 */ void cmd_mgsve_starttls(void) { /** answer with OK, and fire off tls session. */ cprintf("OK\r\n"); CtdlModuleStartCryptoMsgs(NULL, NULL, NULL); cmd_mgsve_caps(); } /* * LOGOUT command, see chapter 2.3 */ void cmd_mgsve_logout(struct sdm_userdata *u) { cprintf("OK\r\n"); syslog(LOG_NOTICE, "MgSve bye."); CC->kill_me = KILLME_CLIENT_LOGGED_OUT; } /* * HAVESPACE command. see chapter 2.5 */ void cmd_mgsve_havespace(void) { /* as we don't have quotas in citadel we should always answer with OK; * pherhaps we should have a max-scriptsize. */ if (MGSVE->command_state != mgsve_password) { cprintf("NO\r\n"); CC->kill_me = KILLME_QUOTA; } else { cprintf("OK"); /* citadel doesn't have quotas. in case of change, please add code here. */ } } /* * PUTSCRIPT command, see chapter 2.6 */ void cmd_mgsve_putscript(int num_parms, char **parms, struct sdm_userdata *u) { /* "scriptname" {nnn+} */ /* AFTER we have the whole script overwrite existing scripts */ /* spellcheck the script before overwrite old ones, and reply with "no" */ if (num_parms == 3) { char *ScriptName; char *Script; long slength; if (parms[1][0]=='"') ScriptName = &parms[1][1]; else ScriptName = parms[1]; slength = strlen (ScriptName); if (ScriptName[slength] == '"') ScriptName[slength] = '\0'; Script = ReadString(GetSizeToken(parms[2]),parms[0]); if (Script == NULL) return; // TODO: do we spellcheck? msiv_putscript(u, ScriptName, Script); cprintf("OK\r\n"); } else { cprintf("%s NO Read failed.\r\n", parms[0]); CC->kill_me = KILLME_READ_FAILED; return; } } /** * LISTSCRIPT command. see chapter 2.7 */ void cmd_mgsve_listscript(int num_parms, char **parms, struct sdm_userdata *u) { struct sdm_script *s; long nScripts = 0; MGSVE->imap_format_outstring = NULL; for (s=u->first_script; s!=NULL; s=s->next) { if (s->script_content != NULL) { cprintf("\"%s\"%s\r\n", s->script_name, (s->script_active)?" ACTIVE":""); nScripts++; } } cprintf("OK\r\n"); } /** * \brief SETACTIVE command. see chapter 2.8 */ void cmd_mgsve_setactive(int num_parms, char **parms, struct sdm_userdata *u) { if (num_parms == 2) { if (msiv_setactive(u, parms[1]) == 0) { cprintf("OK\r\n"); } else cprintf("No \"there is no script by that name %s \"\r\n", parms[1]); } else cprintf("NO \"unexpected parameters.\"\r\n"); } /** * \brief GETSCRIPT command. see chapter 2.9 */ void cmd_mgsve_getscript(int num_parms, char **parms, struct sdm_userdata *u) { if (num_parms == 2){ char *script_content; long slen; script_content = msiv_getscript(u, parms[1]); if (script_content != NULL){ char *outbuf; slen = strlen(script_content); outbuf = malloc (slen + 64); snprintf(outbuf, slen + 64, "{%ld+}\r\n%s\r\nOK\r\n",slen, script_content); cprintf("%s", outbuf); } else cprintf("No \"there is no script by that name %s \"\r\n", parms[1]); } else cprintf("NO \"unexpected parameters.\"\r\n"); } /** * \brief DELETESCRIPT command. see chapter 2.10 */ void cmd_mgsve_deletescript(int num_parms, char **parms, struct sdm_userdata *u) { int i=-1; if (num_parms == 2) i = msiv_deletescript(u, parms[1]); switch (i){ case 0: cprintf("OK\r\n"); break; case 1: cprintf("NO \"no script by that name: %s\"\r\n", parms[1]); break; case 2: cprintf("NO \"can't delete active Script: %s\"\r\n", parms[1]); break; default: case -1: cprintf("NO \"unexpected parameters.\"\r\n"); break; } } /** * \brief Attempt to perform authenticated managesieve */ void mgsve_auth(char *argbuf) { char username_prompt[64]; char method[64]; char encoded_authstring[1024]; if (CC->logged_in) { cprintf("NO \"Already logged in.\"\r\n"); return; } extract_token(method, argbuf, 0, ' ', sizeof method); if (!strncasecmp(method, "login", 5) ) { if (strlen(argbuf) >= 7) { } else { size_t len = CtdlEncodeBase64(username_prompt, "Username:", 9, 0); if (username_prompt[len - 1] == '\n') { username_prompt[len - 1] = '\0'; } cprintf("334 %s\r\n", username_prompt); } return; } if (!strncasecmp(method, "plain", 5) ) { if (num_tokens(argbuf, ' ') < 2) { cprintf("334 \r\n"); return; } extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring); return; } if (strncasecmp(method, "login", 5) ) { cprintf("NO \"Unknown authentication method.\"\r\n"); return; } } /* * implements the STARTTLS command (Citadel API version) */ void _mgsve_starttls(void) { char ok_response[SIZ]; char nosup_response[SIZ]; char error_response[SIZ]; sprintf(ok_response, "200 2.0.0 Begin TLS negotiation now\r\n"); sprintf(nosup_response, "554 5.7.3 TLS not supported here\r\n"); sprintf(error_response, "554 5.7.3 Internal error\r\n"); CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response); } /* * Main command loop for managesieve sessions. */ void managesieve_command_loop(void) { char cmdbuf[SIZ]; char *parms[SIZ]; int length; int num_parms; struct sdm_userdata u; int changes_made = 0; memset(&u, 0, sizeof(struct sdm_userdata)); time(&CC->lastcmd); memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */ length = client_getln(cmdbuf, sizeof cmdbuf); if (length >= 1) { num_parms = old_imap_parameterize(parms, cmdbuf); if (num_parms == 0) return; length = strlen(parms[0]); } if (length < 1) { syslog(LOG_CRIT, "managesieve: client disconnected: ending session.\n"); CC->kill_me = KILLME_CLIENT_DISCONNECTED; return; } syslog(LOG_INFO, "MANAGESIEVE: %s\n", cmdbuf); if ((length>= 12) && (!strncasecmp(parms[0], "AUTHENTICATE", 12))){ cmd_mgsve_auth(num_parms, parms, &u); } #ifdef HAVE_OPENSSL else if ((length>= 8) && (!strncasecmp(parms[0], "STARTTLS", 8))){ cmd_mgsve_starttls(); } #endif else if ((length>= 6) && (!strncasecmp(parms[0], "LOGOUT", 6))){ cmd_mgsve_logout(&u); } else if ((length>= 6) && (!strncasecmp(parms[0], "CAPABILITY", 10))){ cmd_mgsve_caps(); } /** these commands need to be authenticated. throw it out if it tries. */ else if (CC->logged_in != 0) { msiv_load(&u); if ((length>= 9) && (!strncasecmp(parms[0], "HAVESPACE", 9))){ cmd_mgsve_havespace(); } else if ((length>= 6) && (!strncasecmp(parms[0], "PUTSCRIPT", 9))){ cmd_mgsve_putscript(num_parms, parms, &u); changes_made = 1; } else if ((length>= 6) && (!strncasecmp(parms[0], "LISTSCRIPT", 10))){ cmd_mgsve_listscript(num_parms, parms,&u); } else if ((length>= 6) && (!strncasecmp(parms[0], "SETACTIVE", 9))){ cmd_mgsve_setactive(num_parms, parms,&u); changes_made = 1; } else if ((length>= 6) && (!strncasecmp(parms[0], "GETSCRIPT", 9))){ cmd_mgsve_getscript(num_parms, parms, &u); } else if ((length>= 6) && (!strncasecmp(parms[0], "DELETESCRIPT", 11))){ cmd_mgsve_deletescript(num_parms, parms, &u); changes_made = 1; } msiv_store(&u, changes_made); } else { cprintf("No Invalid access or command.\r\n"); syslog(LOG_INFO, "illegal Managesieve command: %s", parms[0]); CC->kill_me = KILLME_ILLEGAL_MANAGESIEVE_COMMAND; } } /* * This cleanup function blows away the temporary memory and files used by * the server. */ void managesieve_cleanup_function(void) { /* Don't do this stuff if this is not a managesieve session! */ if (CC->h_command_function != managesieve_command_loop) return; syslog(LOG_DEBUG, "Performing managesieve cleanup hook\n"); free(MGSVE); } const char* CitadelServiceManageSieve = "ManageSieve"; CTDL_MODULE_INIT(managesieve) { if (!threading) { CtdlRegisterServiceHook(config.c_managesieve_port, NULL, managesieve_greeting, managesieve_command_loop, NULL, CitadelServiceManageSieve); CtdlRegisterSessionHook(managesieve_cleanup_function, EVT_STOP, PRIO_STOP + 30); } /* return our module name for the log */ return "managesieve"; } citadel-9.01/modules/checkpoint/0000755000000000000000000000000012507024051015341 5ustar rootrootcitadel-9.01/modules/checkpoint/serv_checkpoint.c0000644000000000000000000000204212507024051020671 0ustar rootroot/* * checkpointing module for the database * * Copyright (c) 1987-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or * modify it under the terms of the GNU General Public License, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "sysdep.h" #include #include #include #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "msgbase.h" #include "sysdep_decls.h" #include "config.h" #include "threads.h" #include "ctdl_module.h" #include "context.h" CTDL_MODULE_INIT(checkpoint) { if (threading) { CtdlRegisterSessionHook(cdb_checkpoint, EVT_TIMER, PRIO_CLEANUP + 10); } /* return our module name for the log */ return "checkpoint"; } citadel-9.01/modules/nntp/0000755000000000000000000000000012507024051014171 5ustar rootrootcitadel-9.01/modules/nntp/serv_nntp.h0000644000000000000000000000227112507024051016362 0ustar rootroot// // Header file for NNTP server module // // Copyright (c) 2014 by the citadel.org team // // This program is open source software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 3. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // data returned by a message list fetch struct nntp_msglist { int num_msgs; long *msgnums; }; // data passed by the LIST commands to its helper function struct nntp_list_data { int list_format; char *wildmat_pattern; }; // // data passed between nntp_listgroup() and nntp_listgroup_backend() // struct listgroup_range { long lo; long hi; }; typedef struct _citnntp { // Information about the current session long current_article_number; } citnntp; // // Various output formats for the LIST commands // enum { NNTP_LIST_ACTIVE, NNTP_LIST_ACTIVE_TIMES, NNTP_LIST_DISTRIB_PATS, NNTP_LIST_HEADERS, NNTP_LIST_NEWSGROUPS, NNTP_LIST_OVERVIEW_FMT }; int wildmat(const char *text, const char *p); citadel-9.01/modules/nntp/wildmat.c0000644000000000000000000001343612507024051016005 0ustar rootroot/* wildmat.h - NNTP wildmat processing functions * * Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The name "Carnegie Mellon University" must not be used to * endorse or promote products derived from this software without * prior written permission. For permission or any legal * details, please contact * Carnegie Mellon University * Center for Technology Transfer and Enterprise Creation * 4615 Forbes Avenue * Suite 302 * Pittsburgh, PA 15213 * (412) 268-7393, fax: (412) 268-7395 * innovation@andrew.cmu.edu * * 4. Redistributions of any form whatsoever must retain the following * acknowledgment: * "This product includes software developed by Computing Services * at Carnegie Mellon University (http://www.cmu.edu/computing/)." * * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * $Id: wildmat.c,v 1.4 2010/01/06 17:01:47 murch Exp $ */ /* ** ** Do shell-style pattern matching for ?, \, [], and * characters. ** Might not be robust in face of malformed patterns; e.g., "foo[a-" ** could cause a segmentation violation. It is 8bit clean. ** ** Written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986. ** Rich $alz is now . ** April, 1991: Replaced mutually-recursive calls with in-line code ** for the star character. ** ** Special thanks to Lars Mathiesen for the ABORT code. ** This can greatly speed up failing wildcard patterns. For example: ** pattern: -*-*-*-*-*-*-12-*-*-*-m-*-*-* ** text 1: -adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1 ** text 2: -adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1 ** Text 1 matches with 51 calls, while text 2 fails with 54 calls. Without ** the ABORT code, it takes 22310 calls to fail. Ugh. The following ** explanation is from Lars: ** The precondition that must be fulfilled is that DoMatch will consume ** at least one character in text. This is true if *p is neither '*' nor ** '\0'.) The last return has ABORT instead of FALSE to avoid quadratic ** behaviour in cases like pattern "*a*b*c*d" with text "abcxxxxx". With ** FALSE, each star-loop has to run to the end of the text; with ABORT ** only the last one does. ** ** Once the control of one instance of DoMatch enters the star-loop, that ** instance will return either TRUE or ABORT, and any calling instance ** will therefore return immediately after (without calling recursively ** again). In effect, only one star-loop is ever active. It would be ** possible to modify the code to maintain this context explicitly, ** eliminating all recursive calls at the cost of some complication and ** loss of clarity (and the ABORT stuff seems to be unclear enough by ** itself). I think it would be unwise to try to get this into a ** released version unless you have a good test data base to try it out ** on. */ #include #include #define TRUE 1 #define FALSE 0 #define ABORT -1 /* What character marks an inverted character class? */ #define NEGATE_CLASS '^' /* Is "*" a common pattern? */ #define OPTIMIZE_JUST_STAR /* Do tar(1) matching rules, which ignore a trailing slash? */ #undef MATCH_TAR_PATTERN /* ** Match text and p, return TRUE, FALSE, or ABORT. */ static int DoMatch(const char *text, const char *p) { int last; int matched; int reverse; for ( ; *p; text++, p++) { if (*text == '\0' && *p != '*') return ABORT; switch (*p) { case '\\': /* Literal match with following character. */ p++; /* FALLTHROUGH */ default: if (*text != *p) return FALSE; continue; case '?': /* Match anything. */ continue; case '*': while (*++p == '*') /* Consecutive stars act just like one. */ continue; if (*p == '\0') /* Trailing star matches everything. */ return TRUE; while (*text) if ((matched = DoMatch(text++, p)) != FALSE) return matched; return ABORT; case '[': reverse = p[1] == NEGATE_CLASS ? TRUE : FALSE; if (reverse) /* Inverted character class. */ p++; matched = FALSE; if (p[1] == ']' || p[1] == '-') if (*++p == *text) matched = TRUE; for (last = *p; *++p && *p != ']'; last = *p) /* This next line requires a good C compiler. */ if (*p == '-' && p[1] != ']' ? *text <= *++p && *text >= last : *text == *p) matched = TRUE; if (matched == reverse) return FALSE; continue; } } #ifdef MATCH_TAR_PATTERN if (*text == '/') return TRUE; #endif /* MATCH_TAR_ATTERN */ return *text == '\0'; } /* ** User-level routine. Returns TRUE or FALSE. */ int wildmat(const char *text, const char *p) { #ifdef OPTIMIZE_JUST_STAR if (p[0] == '*' && p[1] == '\0') return TRUE; #endif /* OPTIMIZE_JUST_STAR */ return DoMatch(text, p) == TRUE; } citadel-9.01/modules/nntp/serv_nntp.c0000644000000000000000000007154412507024051016366 0ustar rootroot// // NNTP server module (RFC 3977) // // Copyright (c) 2014 by the citadel.org team // // This program is open source software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 3. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // #include "sysdep.h" #include #include #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "control.h" #include "user_ops.h" #include "room_ops.h" #include "database.h" #include "msgbase.h" #include "internet_addressing.h" #include "genstamp.h" #include "domain.h" #include "clientsocket.h" #include "locate_host.h" #include "citadel_dirs.h" #include "ctdl_module.h" #include "serv_nntp.h" extern long timezone; // ***************** BEGIN UTILITY FUNCTIONS THAT COULD BE MOVED ELSEWHERE LATER ************** // // Tests whether the supplied string is a valid newsgroup name // Returns true (nonzero) or false (0) // int is_valid_newsgroup_name(char *name) { char *ptr = name; int has_a_letter = 0; int num_dots = 0; if (!ptr) return(0); if (!strncasecmp(name, "ctdl.", 5)) return(0); while (*ptr != 0) { if (isalpha(ptr[0])) { has_a_letter = 1; } if (ptr[0] == '.') { ++num_dots; } if ( (isalnum(ptr[0])) || (ptr[0] == '.') || (ptr[0] == '+') || (ptr[0] == '-') ) { ++ptr; } else { return(0); } } return( (has_a_letter) && (num_dots >= 1) ) ; } // // Convert a Citadel room name to a valid newsgroup name // void room_to_newsgroup(char *target, char *source, size_t target_size) { if (!target) return; if (!source) return; if (is_valid_newsgroup_name(source)) { strncpy(target, source, target_size); return; } strcpy(target, "ctdl."); int len = 5; char *ptr = source; char ch; while (ch=*ptr++, ch!=0) { if (len >= target_size) return; if ( (isalnum(ch)) || (ch == '.') || (ch == '-') ) { target[len++] = tolower(ch); target[len] = 0; } else { target[len++] = '+' ; sprintf(&target[len], "%02x", ch); len += 2; target[len] = 0; } } } // // Convert a newsgroup name to a Citadel room name. // This function recognizes names converted with room_to_newsgroup() and restores them with full fidelity. // void newsgroup_to_room(char *target, char *source, size_t target_size) { if (!target) return; if (!source) return; if (strncasecmp(source, "ctdl.", 5)) { // not a converted room name; pass through as-is strncpy(target, source, target_size); return; } target[0] = 0; int len = 0; char *ptr = &source[5]; char ch; while (ch=*ptr++, ch!=0) { if (len >= target_size) return; if (ch == '+') { char hex[3]; long digit; hex[0] = *ptr++; hex[1] = *ptr++; hex[2] = 0; digit = strtol(hex, NULL, 16); ch = (char)digit; } target[len++] = ch; target[len] = 0; } } // ***************** END UTILITY FUNCTIONS THAT COULD BE MOVED ELSEWHERE LATER ************** // // Here's where our NNTP session begins its happy day. // void nntp_greeting(void) { strcpy(CC->cs_clientname, "NNTP session"); CC->cs_flags |= CS_STEALTH; CC->session_specific_data = malloc(sizeof(citnntp)); citnntp *nntpstate = (citnntp *) CC->session_specific_data; memset(nntpstate, 0, sizeof(citnntp)); if (CC->nologin==1) { cprintf("451 Too many connections are already open; please try again later.\r\n"); CC->kill_me = KILLME_MAX_SESSIONS_EXCEEDED; return; } // Display the standard greeting cprintf("200 %s NNTP Citadel server is not finished yet\r\n", config.c_fqdn); } // // NNTPS is just like NNTP, except it goes crypto right away. // void nntps_greeting(void) { CtdlModuleStartCryptoMsgs(NULL, NULL, NULL); #ifdef HAVE_OPENSSL if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO; /* kill session if no crypto */ #endif nntp_greeting(); } // // implements the STARTTLS command // void nntp_starttls(void) { char ok_response[SIZ]; char nosup_response[SIZ]; char error_response[SIZ]; sprintf(ok_response, "382 Begin TLS negotiation now\r\n"); sprintf(nosup_response, "502 Can not initiate TLS negotiation\r\n"); sprintf(error_response, "580 Internal error\r\n"); CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response); } // // Implements the CAPABILITY command // void nntp_capabilities(void) { cprintf("101 Capability list:\r\n"); cprintf("IMPLEMENTATION Citadel v%d.%02d\r\n", (REV_LEVEL/100), (REV_LEVEL%100)); cprintf("VERSION 2\r\n"); cprintf("READER\r\n"); cprintf("MODE-READER\r\n"); cprintf("LIST ACTIVE NEWSGROUPS\r\n"); cprintf("OVER\r\n"); #ifdef HAVE_OPENSSL cprintf("STARTTLS\r\n"); #endif if (!CC->logged_in) { cprintf("AUTHINFO USER\r\n"); } cprintf(".\r\n"); } // // Implements the QUIT command // void nntp_quit(void) { cprintf("221 Goodbye...\r\n"); CC->kill_me = KILLME_CLIENT_LOGGED_OUT; } // // Cleanup hook for this module // void nntp_cleanup(void) { /* nothing here yet */ } // // Implements the AUTHINFO USER command (RFC 4643) // void nntp_authinfo_user(const char *username) { int a = CtdlLoginExistingUser(NULL, username); switch (a) { case login_already_logged_in: cprintf("482 Already logged in\r\n"); return; case login_too_many_users: cprintf("481 Too many users are already online (maximum is %d)\r\n", config.c_maxsessions); return; case login_ok: cprintf("381 Password required for %s\r\n", CC->curr_user); return; case login_not_found: cprintf("481 %s not found\r\n", username); return; default: cprintf("502 Internal error\r\n"); } } // // Implements the AUTHINFO PASS command (RFC 4643) // void nntp_authinfo_pass(const char *buf) { int a; a = CtdlTryPassword(buf, strlen(buf)); switch (a) { case pass_already_logged_in: cprintf("482 Already logged in\r\n"); return; case pass_no_user: cprintf("482 Authentication commands issued out of sequence\r\n"); return; case pass_wrong_password: cprintf("481 Authentication failed\r\n"); return; case pass_ok: cprintf("281 Authentication accepted\r\n"); return; } } // // Implements the AUTHINFO extension (RFC 4643) in USER/PASS mode // void nntp_authinfo(const char *cmd) { if (!strncasecmp(cmd, "authinfo user ", 14)) { nntp_authinfo_user(&cmd[14]); } else if (!strncasecmp(cmd, "authinfo pass ", 14)) { nntp_authinfo_pass(&cmd[14]); } else { cprintf("502 command unavailable\r\n"); } } // // Utility function to fetch the current list of message numbers in a room // struct nntp_msglist nntp_fetch_msglist(struct ctdlroom *qrbuf) { struct nntp_msglist nm; struct cdbdata *cdbfr; cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf->QRnumber, sizeof(long)); if (cdbfr != NULL) { nm.msgnums = (long*)cdbfr->ptr; cdbfr->ptr = NULL; nm.num_msgs = cdbfr->len / sizeof(long); cdbfr->len = 0; cdb_free(cdbfr); } else { nm.num_msgs = 0; nm.msgnums = NULL; } return(nm); } // // Output a room name (newsgroup name) in formats required for LIST and NEWGROUPS command // void output_roomname_in_list_format(struct ctdlroom *qrbuf, int which_format, char *wildmat_pattern) { char n_name[1024]; struct nntp_msglist nm; long low_water_mark = 0; long high_water_mark = 0; room_to_newsgroup(n_name, qrbuf->QRname, sizeof n_name); if ((wildmat_pattern != NULL) && (!IsEmptyStr(wildmat_pattern))) { if (!wildmat(n_name, wildmat_pattern)) { return; } } nm = nntp_fetch_msglist(qrbuf); if ((nm.num_msgs > 0) && (nm.msgnums != NULL)) { low_water_mark = nm.msgnums[0]; high_water_mark = nm.msgnums[nm.num_msgs - 1]; } // Only the mandatory formats are supported switch(which_format) { case NNTP_LIST_ACTIVE: // FIXME we have hardcoded "n" for "no posting allowed" -- fix when we add posting cprintf("%s %ld %ld n\r\n", n_name, high_water_mark, low_water_mark); break; case NNTP_LIST_NEWSGROUPS: cprintf("%s %s\r\n", n_name, qrbuf->QRname); break; } if (nm.msgnums != NULL) { free(nm.msgnums); } } // // Called once per room by nntp_newgroups() to qualify and possibly output a single room // void nntp_newgroups_backend(struct ctdlroom *qrbuf, void *data) { int ra; int view; time_t thetime = *(time_t *)data; CtdlRoomAccess(qrbuf, &CC->user, &ra, &view); /* * The "created after " heuristics depend on the happy coincidence * that for a very long time we have used a unix timestamp as the room record's * generation number (QRgen). When this module is merged into the master * source tree we should rename QRgen to QR_create_time or something like that. */ if (ra & UA_KNOWN) { if (qrbuf->QRgen >= thetime) { output_roomname_in_list_format(qrbuf, NNTP_LIST_ACTIVE, NULL); } } } // // Implements the NEWGROUPS command // void nntp_newgroups(const char *cmd) { if (CtdlAccessCheck(ac_logged_in_or_guest)) return; char stringy_date[16]; char stringy_time[16]; char stringy_gmt[16]; struct tm tm; time_t thetime; extract_token(stringy_date, cmd, 1, ' ', sizeof stringy_date); extract_token(stringy_time, cmd, 2, ' ', sizeof stringy_time); extract_token(stringy_gmt, cmd, 3, ' ', sizeof stringy_gmt); memset(&tm, 0, sizeof tm); if (strlen(stringy_date) == 6) { sscanf(stringy_date, "%2d%2d%2d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday); tm.tm_year += 100; } else { sscanf(stringy_date, "%4d%2d%2d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday); tm.tm_year -= 1900; } tm.tm_mon -= 1; // tm_mon is zero based (0=January) tm.tm_isdst = (-1); // let the C library figure out whether DST is in effect sscanf(stringy_time, "%2d%2d%2d", &tm.tm_hour, &tm.tm_min ,&tm.tm_sec); thetime = mktime(&tm); if (!strcasecmp(stringy_gmt, "GMT")) { tzset(); thetime += timezone; } cprintf("231 list of new newsgroups follows\r\n"); CtdlGetUser(&CC->user, CC->curr_user); CtdlForEachRoom(nntp_newgroups_backend, &thetime); cprintf(".\r\n"); } // // Called once per room by nntp_list() to qualify and possibly output a single room // void nntp_list_backend(struct ctdlroom *qrbuf, void *data) { int ra; int view; struct nntp_list_data *nld = (struct nntp_list_data *)data; CtdlRoomAccess(qrbuf, &CC->user, &ra, &view); if (ra & UA_KNOWN) { output_roomname_in_list_format(qrbuf, nld->list_format, nld->wildmat_pattern); } } // // Implements the LIST commands // void nntp_list(const char *cmd) { if (CtdlAccessCheck(ac_logged_in_or_guest)) return; char list_format[64]; char wildmat_pattern[1024]; struct nntp_list_data nld; extract_token(list_format, cmd, 1, ' ', sizeof list_format); extract_token(wildmat_pattern, cmd, 2, ' ', sizeof wildmat_pattern); if (strlen(wildmat_pattern) > 0) { nld.wildmat_pattern = wildmat_pattern; } else { nld.wildmat_pattern = NULL; } if ( (strlen(cmd) < 6) || (!strcasecmp(list_format, "ACTIVE")) ) { nld.list_format = NNTP_LIST_ACTIVE; } else if (!strcasecmp(list_format, "NEWSGROUPS")) { nld.list_format = NNTP_LIST_NEWSGROUPS; } else if (!strcasecmp(list_format, "OVERVIEW.FMT")) { nld.list_format = NNTP_LIST_OVERVIEW_FMT; } else { cprintf("501 syntax error , unsupported list format\r\n"); return; } // OVERVIEW.FMT delivers a completely different type of data than all of the // other LIST commands. It's a stupid place to put it. But that's how it's // written into RFC3977, so we have to handle it here. if (nld.list_format == NNTP_LIST_OVERVIEW_FMT) { cprintf("215 Order of fields in overview database.\r\n"); cprintf("Subject:\r\n"); cprintf("From:\r\n"); cprintf("Date:\r\n"); cprintf("Message-ID:\r\n"); cprintf("References:\r\n"); cprintf("Bytes:\r\n"); cprintf("Lines:\r\n"); cprintf(".\r\n"); return; } cprintf("215 list of newsgroups follows\r\n"); CtdlGetUser(&CC->user, CC->curr_user); CtdlForEachRoom(nntp_list_backend, &nld); cprintf(".\r\n"); } // // Implement HELP command. // void nntp_help(void) { cprintf("100 This is the Citadel NNTP service.\r\n"); cprintf("RTFM http://www.ietf.org/rfc/rfc3977.txt\r\n"); cprintf(".\r\n"); } // // Implement DATE command. // void nntp_date(void) { time_t now; struct tm nowLocal; struct tm nowUtc; char tsFromUtc[32]; now = time(NULL); localtime_r(&now, &nowLocal); gmtime_r(&now, &nowUtc); strftime(tsFromUtc, sizeof(tsFromUtc), "%Y%m%d%H%M%S", &nowUtc); cprintf("111 %s\r\n", tsFromUtc); } // // back end for the LISTGROUP command , called for each message number // void nntp_listgroup_backend(long msgnum, void *userdata) { struct listgroup_range *lr = (struct listgroup_range *)userdata; // check range if supplied if (msgnum < lr->lo) return; if ((lr->hi != 0) && (msgnum > lr->hi)) return; cprintf("%ld\r\n", msgnum); } // // Implements the GROUP and LISTGROUP commands // void nntp_group(const char *cmd) { if (CtdlAccessCheck(ac_logged_in_or_guest)) return; citnntp *nntpstate = (citnntp *) CC->session_specific_data; char verb[16]; char requested_group[1024]; char message_range[256]; char range_lo[256]; char range_hi[256]; char requested_room[ROOMNAMELEN]; char augmented_roomname[ROOMNAMELEN]; int c = 0; int ok = 0; int ra = 0; struct ctdlroom QRscratch; int msgs, new; long oldest,newest; struct listgroup_range lr; extract_token(verb, cmd, 0, ' ', sizeof verb); extract_token(requested_group, cmd, 1, ' ', sizeof requested_group); extract_token(message_range, cmd, 2, ' ', sizeof message_range); extract_token(range_lo, message_range, 0, '-', sizeof range_lo); extract_token(range_hi, message_range, 1, '-', sizeof range_hi); lr.lo = atoi(range_lo); lr.hi = atoi(range_hi); /* In LISTGROUP mode we can specify an empty name for 'currently selected' */ if ((!strcasecmp(verb, "LISTGROUP")) && (IsEmptyStr(requested_group))) { room_to_newsgroup(requested_group, CC->room.QRname, sizeof requested_group); } /* First try a regular match */ newsgroup_to_room(requested_room, requested_group, sizeof requested_room); c = CtdlGetRoom(&QRscratch, requested_room); /* Then try a mailbox name match */ if (c != 0) { CtdlMailboxName(augmented_roomname, sizeof augmented_roomname, &CC->user, requested_room); c = CtdlGetRoom(&QRscratch, augmented_roomname); if (c == 0) { safestrncpy(requested_room, augmented_roomname, sizeof(requested_room)); } } /* If the room exists, check security/access */ if (c == 0) { /* See if there is an existing user/room relationship */ CtdlRoomAccess(&QRscratch, &CC->user, &ra, NULL); /* normal clients have to pass through security */ if (ra & UA_KNOWN) { ok = 1; } } /* Fail here if no such room */ if (!ok) { cprintf("411 no such newsgroup\r\n"); return; } /* * CtdlUserGoto() formally takes us to the desired room, happily returning * the number of messages and number of new messages. */ memcpy(&CC->room, &QRscratch, sizeof(struct ctdlroom)); CtdlUserGoto(NULL, 0, 0, &msgs, &new, &oldest, &newest); cprintf("211 %d %ld %ld %s\r\n", msgs, oldest, newest, requested_group); // If this is a GROUP command, set the "current article number" to zero, and then stop here. if (!strcasecmp(verb, "GROUP")) { nntpstate->current_article_number = oldest; return; } // If we get to this point we are running a LISTGROUP command. Fetch those message numbers. CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, nntp_listgroup_backend, &lr); cprintf(".\r\n"); } // // Implements the MODE command // void nntp_mode(const char *cmd) { char which_mode[16]; extract_token(which_mode, cmd, 1, ' ', sizeof which_mode); if (!strcasecmp(which_mode, "reader")) { // FIXME implement posting and change to 200 cprintf("201 Reader mode activated\r\n"); } else { cprintf("501 unknown mode\r\n"); } } // // Implements the ARTICLE, HEAD, BODY, and STAT commands. // (These commands all accept the same parameters; they differ only in how they output the retrieved message.) // void nntp_article(const char *cmd) { if (CtdlAccessCheck(ac_logged_in_or_guest)) return; citnntp *nntpstate = (citnntp *) CC->session_specific_data; char which_command[16]; int acmd = 0; char requested_article[256]; long requested_msgnum = 0; char *lb, *rb = NULL; int must_change_currently_selected_article = 0; // We're going to store one of these values in the variable 'acmd' so that // we can quickly check later which version of the output we want. enum { ARTICLE, HEAD, BODY, STAT }; extract_token(which_command, cmd, 0, ' ', sizeof which_command); if (!strcasecmp(which_command, "article")) { acmd = ARTICLE; } else if (!strcasecmp(which_command, "head")) { acmd = HEAD; } else if (!strcasecmp(which_command, "body")) { acmd = BODY; } else if (!strcasecmp(which_command, "stat")) { acmd = STAT; } else { cprintf("500 I'm afraid I can't do that.\r\n"); return; } // Which NNTP command was issued, determines whether we will fetch headers, body, or both. int headers_only = HEADERS_ALL; if (acmd == HEAD) headers_only = HEADERS_FAST; else if (acmd == BODY) headers_only = HEADERS_NONE; else if (acmd == STAT) headers_only = HEADERS_FAST; // now figure out what the client is asking for. extract_token(requested_article, cmd, 1, ' ', sizeof requested_article); lb = strchr(requested_article, '<'); rb = strchr(requested_article, '>'); requested_msgnum = atol(requested_article); // If no article number or message-id is specified, the client wants the "currently selected article" if (IsEmptyStr(requested_article)) { if (nntpstate->current_article_number < 1) { cprintf("420 No current article selected\r\n"); return; } requested_msgnum = nntpstate->current_article_number; must_change_currently_selected_article = 1; // got it -- now fall through and keep going } // If the requested article is numeric, it maps directly to a message number. Good. else if (requested_msgnum > 0) { must_change_currently_selected_article = 1; // good -- fall through and keep going } // If the requested article has angle brackets, the client wants a specific message-id. // We don't know how to do that yet. else if ( (lb != NULL) && (rb != NULL) && (lb < rb) ) { must_change_currently_selected_article = 0; cprintf("500 I don't know how to fetch by message-id yet.\r\n"); // FIXME return; } // Anything else is noncompliant gobbledygook and should die in a car fire. else { must_change_currently_selected_article = 0; cprintf("500 syntax error\r\n"); return; } // At this point we know the message number of the "article" being requested. // We have an awesome API call that does all the heavy lifting for us. char *fetched_message_id = NULL; CC->redirect_buffer = NewStrBufPlain(NULL, SIZ); int fetch = CtdlOutputMsg(requested_msgnum, MT_RFC822, // output in RFC822 format ... sort of headers_only, // headers, body, or both? 0, // don't do Citadel protocol responses 1, // CRLF newlines NULL, // teh whole thing, not just a section 0, // no flags yet ... maybe new ones for Path: etc ? NULL, NULL, &fetched_message_id // extract the message ID from the message as we go... ); StrBuf *msgtext = CC->redirect_buffer; CC->redirect_buffer = NULL; if (fetch != om_ok) { cprintf("423 no article with that number\r\n"); FreeStrBuf(&msgtext); return; } // RFC3977 6.2.1.2 specifes conditions under which the "currently selected article" // MUST or MUST NOT be set to the message we just referenced. if (must_change_currently_selected_article) { nntpstate->current_article_number = requested_msgnum; } // Now give the client what it asked for. if (acmd == ARTICLE) { cprintf("220 %ld <%s>\r\n", requested_msgnum, fetched_message_id); } if (acmd == HEAD) { cprintf("221 %ld <%s>\r\n", requested_msgnum, fetched_message_id); } if (acmd == BODY) { cprintf("222 %ld <%s>\r\n", requested_msgnum, fetched_message_id); } if (acmd == STAT) { cprintf("223 %ld <%s>\r\n", requested_msgnum, fetched_message_id); FreeStrBuf(&msgtext); return; } client_write(SKEY(msgtext)); cprintf(".\r\n"); // this protocol uses a dot terminator FreeStrBuf(&msgtext); if (fetched_message_id) free(fetched_message_id); } // // Utility function for nntp_last_next() that turns a msgnum into a message ID. // The memory for the returned string is pwnz0red by the caller. // char *message_id_from_msgnum(long msgnum) { char *fetched_message_id = NULL; CC->redirect_buffer = NewStrBufPlain(NULL, SIZ); CtdlOutputMsg(msgnum, MT_RFC822, // output in RFC822 format ... sort of HEADERS_FAST, // headers, body, or both? 0, // don't do Citadel protocol responses 1, // CRLF newlines NULL, // teh whole thing, not just a section 0, // no flags yet ... maybe new ones for Path: etc ? NULL, NULL, &fetched_message_id // extract the message ID from the message as we go... ); StrBuf *msgtext = CC->redirect_buffer; CC->redirect_buffer = NULL; FreeStrBuf(&msgtext); return(fetched_message_id); } // // The LAST and NEXT commands are so similar that they are handled by a single function. // void nntp_last_next(const char *cmd) { if (CtdlAccessCheck(ac_logged_in_or_guest)) return; citnntp *nntpstate = (citnntp *) CC->session_specific_data; char which_command[16]; int acmd = 0; // We're going to store one of these values in the variable 'acmd' so that // we can quickly check later which version of the output we want. enum { NNTP_LAST, NNTP_NEXT }; extract_token(which_command, cmd, 0, ' ', sizeof which_command); if (!strcasecmp(which_command, "last")) { acmd = NNTP_LAST; } else if (!strcasecmp(which_command, "next")) { acmd = NNTP_NEXT; } else { cprintf("500 I'm afraid I can't do that.\r\n"); return; } // ok, here we go ... fetch the msglist so we can figure out our place in the universe struct nntp_msglist nm; int i = 0; long selected_msgnum = 0; char *message_id = NULL; nm = nntp_fetch_msglist(&CC->room); if ((nm.num_msgs < 0) || (nm.msgnums == NULL)) { cprintf("500 something bad happened\r\n"); return; } if ( (acmd == NNTP_LAST) && (nm.num_msgs == 0) ) { cprintf("422 no previous article in this group\r\n"); // nothing here } else if ( (acmd == NNTP_LAST) && (nntpstate->current_article_number <= nm.msgnums[0]) ) { cprintf("422 no previous article in this group\r\n"); // already at the beginning } else if (acmd == NNTP_LAST) { for (i=0; ((i= nntpstate->current_article_number) && (i > 0) ) { selected_msgnum = nm.msgnums[i-1]; } } if (selected_msgnum > 0) { nntpstate->current_article_number = selected_msgnum; message_id = message_id_from_msgnum(nntpstate->current_article_number); cprintf("223 %ld <%s>\r\n", nntpstate->current_article_number, message_id); if (message_id) free(message_id); } else { cprintf("422 no previous article in this group\r\n"); } } else if ( (acmd == NNTP_NEXT) && (nm.num_msgs == 0) ) { cprintf("421 no next article in this group\r\n"); // nothing here } else if ( (acmd == NNTP_NEXT) && (nntpstate->current_article_number >= nm.msgnums[nm.num_msgs-1]) ) { cprintf("421 no next article in this group\r\n"); // already at the end } else if (acmd == NNTP_NEXT) { for (i=0; ((i nntpstate->current_article_number) { selected_msgnum = nm.msgnums[i]; } } if (selected_msgnum > 0) { nntpstate->current_article_number = selected_msgnum; message_id = message_id_from_msgnum(nntpstate->current_article_number); cprintf("223 %ld <%s>\r\n", nntpstate->current_article_number, message_id); if (message_id) free(message_id); } else { cprintf("421 no next article in this group\r\n"); } } // should never get here else { cprintf("500 internal error\r\n"); } if (nm.msgnums != NULL) { free(nm.msgnums); } } // // back end for the XOVER command , called for each message number // void nntp_xover_backend(long msgnum, void *userdata) { struct listgroup_range *lr = (struct listgroup_range *)userdata; // check range if supplied if (msgnum < lr->lo) return; if ((lr->hi != 0) && (msgnum > lr->hi)) return; struct CtdlMessage *msg = CtdlFetchMessage(msgnum, 0); if (msg == NULL) { return; } // Teh RFC says we need: // ------------------------- // Subject header content // From header content // Date header content // Message-ID header content // References header content // :bytes metadata item // :lines metadata item time_t msgtime = atol(msg->cm_fields[eTimestamp]); char strtimebuf[26]; ctime_r(&msgtime, strtimebuf); // here we go -- print the line o'data cprintf("%ld\t%s\t%s <%s>\t%s\t%s\t%s\t100\t10\r\n", msgnum, msg->cm_fields[eMsgSubject], msg->cm_fields[eAuthor], msg->cm_fields[erFc822Addr], strtimebuf, msg->cm_fields[emessageId], msg->cm_fields[eWeferences] ); CM_Free(msg); } // // // XOVER is used by some clients, even if we don't offer it // void nntp_xover(const char *cmd) { if (CtdlAccessCheck(ac_logged_in_or_guest)) return; citnntp *nntpstate = (citnntp *) CC->session_specific_data; char range[256]; struct listgroup_range lr; extract_token(range, cmd, 1, ' ', sizeof range); lr.lo = atol(range); if (lr.lo <= 0) { lr.lo = nntpstate->current_article_number; lr.hi = nntpstate->current_article_number; } else { char *dash = strchr(range, '-'); if (dash != NULL) { ++dash; lr.hi = atol(dash); if (lr.hi == 0) { lr.hi = LONG_MAX; } if (lr.hi < lr.lo) { lr.hi = lr.lo; } } else { lr.hi = lr.lo; } } cprintf("224 Overview information follows\r\n"); CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, nntp_xover_backend, &lr); cprintf(".\r\n"); } // // Main command loop for NNTP server sessions. // void nntp_command_loop(void) { StrBuf *Cmd = NewStrBuf(); char cmdname[16]; time(&CC->lastcmd); if (CtdlClientGetLine(Cmd) < 1) { syslog(LOG_CRIT, "NNTP: client disconnected: ending session.\n"); CC->kill_me = KILLME_CLIENT_DISCONNECTED; FreeStrBuf(&Cmd); return; } syslog(LOG_DEBUG, "NNTP: %s\n", ((!strncasecmp(ChrPtr(Cmd), "AUTHINFO", 8)) ? "AUTHINFO ..." : ChrPtr(Cmd))); extract_token(cmdname, ChrPtr(Cmd), 0, ' ', sizeof cmdname); // Rumpelstiltskin lookups are *awesome* if (!strcasecmp(cmdname, "quit")) { nntp_quit(); } else if (!strcasecmp(cmdname, "help")) { nntp_help(); } else if (!strcasecmp(cmdname, "date")) { nntp_date(); } else if (!strcasecmp(cmdname, "capabilities")) { nntp_capabilities(); } else if (!strcasecmp(cmdname, "starttls")) { nntp_starttls(); } else if (!strcasecmp(cmdname, "authinfo")) { nntp_authinfo(ChrPtr(Cmd)); } else if (!strcasecmp(cmdname, "newgroups")) { nntp_newgroups(ChrPtr(Cmd)); } else if (!strcasecmp(cmdname, "list")) { nntp_list(ChrPtr(Cmd)); } else if (!strcasecmp(cmdname, "group")) { nntp_group(ChrPtr(Cmd)); } else if (!strcasecmp(cmdname, "listgroup")) { nntp_group(ChrPtr(Cmd)); } else if (!strcasecmp(cmdname, "mode")) { nntp_mode(ChrPtr(Cmd)); } else if ( (!strcasecmp(cmdname, "article")) || (!strcasecmp(cmdname, "head")) || (!strcasecmp(cmdname, "body")) || (!strcasecmp(cmdname, "stat")) ) { nntp_article(ChrPtr(Cmd)); } else if ( (!strcasecmp(cmdname, "last")) || (!strcasecmp(cmdname, "next")) ) { nntp_last_next(ChrPtr(Cmd)); } else if ( (!strcasecmp(cmdname, "xover")) || (!strcasecmp(cmdname, "over")) ) { nntp_xover(ChrPtr(Cmd)); } else { cprintf("500 I'm afraid I can't do that.\r\n"); } FreeStrBuf(&Cmd); } // **************************************************************************** // MODULE INITIALIZATION STUFF // **************************************************************************** // // This cleanup function blows away the temporary memory used by // the NNTP server. // void nntp_cleanup_function(void) { /* Don't do this stuff if this is not an NNTP session! */ if (CC->h_command_function != nntp_command_loop) return; syslog(LOG_DEBUG, "Performing NNTP cleanup hook\n"); citnntp *nntpstate = (citnntp *) CC->session_specific_data; if (nntpstate != NULL) { free(nntpstate); nntpstate = NULL; } } const char *CitadelServiceNNTP="NNTP"; const char *CitadelServiceNNTPS="NNTPS"; CTDL_MODULE_INIT(nntp) { if (!threading) { CtdlRegisterServiceHook(config.c_nntp_port, NULL, nntp_greeting, nntp_command_loop, NULL, CitadelServiceNNTP); #ifdef HAVE_OPENSSL CtdlRegisterServiceHook(config.c_nntps_port, NULL, nntps_greeting, nntp_command_loop, NULL, CitadelServiceNNTPS); #endif CtdlRegisterCleanupHook(nntp_cleanup); CtdlRegisterSessionHook(nntp_cleanup_function, EVT_STOP, PRIO_STOP + 250); } /* return our module name for the log */ return "nntp"; } citadel-9.01/modules/imap/0000755000000000000000000000000012507024051014140 5ustar rootrootcitadel-9.01/modules/imap/imap_search.c0000644000000000000000000003446612507024051016574 0ustar rootroot/* * Implements IMAP's gratuitously complex SEARCH command. * * Copyright (c) 2001-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * */ #include "ctdl_module.h" #include "sysdep.h" #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include "citadel.h" #include "server.h" #include "sysdep_decls.h" #include "citserver.h" #include "support.h" #include "config.h" #include "user_ops.h" #include "database.h" #include "msgbase.h" #include "internet_addressing.h" #include "serv_imap.h" #include "imap_tools.h" #include "imap_fetch.h" #include "imap_search.h" #include "genstamp.h" /* * imap_do_search() calls imap_do_search_msg() to search an individual * message after it has been fetched from the disk. This function returns * nonzero if there is a match. * * supplied_msg MAY be used to pass a pointer to the message in memory, * if for some reason it's already been loaded. If not, the message will * be loaded only if one or more search criteria require it. */ int imap_do_search_msg(int seq, struct CtdlMessage *supplied_msg, int num_items, ConstStr *itemlist, int is_uid) { citimap *Imap = IMAP; int match = 0; int is_not = 0; int is_or = 0; int pos = 0; int i; char *fieldptr; struct CtdlMessage *msg = NULL; int need_to_free_msg = 0; if (num_items == 0) { return(0); } msg = supplied_msg; /* Initially we start at the beginning. */ pos = 0; /* Check for the dreaded NOT criterion. */ if (!strcasecmp(itemlist[0].Key, "NOT")) { is_not = 1; pos = 1; } /* Check for the dreaded OR criterion. */ if (!strcasecmp(itemlist[0].Key, "OR")) { is_or = 1; pos = 1; } /* Now look for criteria. */ if (!strcasecmp(itemlist[pos].Key, "ALL")) { match = 1; ++pos; } else if (!strcasecmp(itemlist[pos].Key, "ANSWERED")) { if (Imap->flags[seq-1] & IMAP_ANSWERED) { match = 1; } ++pos; } else if (!strcasecmp(itemlist[pos].Key, "BCC")) { if (msg == NULL) { msg = CtdlFetchMessage(Imap->msgids[seq-1], 1); need_to_free_msg = 1; } if (msg != NULL) { fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "Bcc"); if (fieldptr != NULL) { if (bmstrcasestr(fieldptr, itemlist[pos+1].Key)) { match = 1; } free(fieldptr); } } pos += 2; } else if (!strcasecmp(itemlist[pos].Key, "BEFORE")) { if (msg == NULL) { msg = CtdlFetchMessage(Imap->msgids[seq-1], 1); need_to_free_msg = 1; } if (msg != NULL) { if (!CM_IsEmpty(msg, eTimestamp)) { if (imap_datecmp(itemlist[pos+1].Key, atol(msg->cm_fields[eTimestamp])) < 0) { match = 1; } } } pos += 2; } else if (!strcasecmp(itemlist[pos].Key, "BODY")) { /* If fulltext indexing is active, on this server, * all messages have already been qualified. */ if (config.c_enable_fulltext) { match = 1; } /* Otherwise, we have to do a slow search. */ else { if (msg == NULL) { msg = CtdlFetchMessage(Imap->msgids[seq-1], 1); need_to_free_msg = 1; } if (msg != NULL) { if (bmstrcasestr(msg->cm_fields[eMesageText], itemlist[pos+1].Key)) { match = 1; } } } pos += 2; } else if (!strcasecmp(itemlist[pos].Key, "CC")) { if (msg == NULL) { msg = CtdlFetchMessage(Imap->msgids[seq-1], 1); need_to_free_msg = 1; } if (msg != NULL) { fieldptr = msg->cm_fields[eCarbonCopY]; if (fieldptr != NULL) { if (bmstrcasestr(fieldptr, itemlist[pos+1].Key)) { match = 1; } } else { fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "Cc"); if (fieldptr != NULL) { if (bmstrcasestr(fieldptr, itemlist[pos+1].Key)) { match = 1; } free(fieldptr); } } } pos += 2; } else if (!strcasecmp(itemlist[pos].Key, "DELETED")) { if (Imap->flags[seq-1] & IMAP_DELETED) { match = 1; } ++pos; } else if (!strcasecmp(itemlist[pos].Key, "DRAFT")) { if (Imap->flags[seq-1] & IMAP_DRAFT) { match = 1; } ++pos; } else if (!strcasecmp(itemlist[pos].Key, "FLAGGED")) { if (Imap->flags[seq-1] & IMAP_FLAGGED) { match = 1; } ++pos; } else if (!strcasecmp(itemlist[pos].Key, "FROM")) { if (msg == NULL) { msg = CtdlFetchMessage(Imap->msgids[seq-1], 1); need_to_free_msg = 1; } if (msg != NULL) { if (bmstrcasestr(msg->cm_fields[eAuthor], itemlist[pos+1].Key)) { match = 1; } if (bmstrcasestr(msg->cm_fields[erFc822Addr], itemlist[pos+1].Key)) { match = 1; } } pos += 2; } else if (!strcasecmp(itemlist[pos].Key, "HEADER")) { /* We've got to do a slow search for this because the client * might be asking for an RFC822 header field that has not been * converted into a Citadel header field. That requires * examining the message body. */ if (msg == NULL) { msg = CtdlFetchMessage(Imap->msgids[seq-1], 1); need_to_free_msg = 1; } if (msg != NULL) { CC->redirect_buffer = NewStrBufPlain(NULL, SIZ); CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_FAST, 0, 1, 0); fieldptr = rfc822_fetch_field(ChrPtr(CC->redirect_buffer), itemlist[pos+1].Key); if (fieldptr != NULL) { if (bmstrcasestr(fieldptr, itemlist[pos+2].Key)) { match = 1; } free(fieldptr); } FreeStrBuf(&CC->redirect_buffer); } pos += 3; /* Yes, three */ } else if (!strcasecmp(itemlist[pos].Key, "KEYWORD")) { /* not implemented */ pos += 2; } else if (!strcasecmp(itemlist[pos].Key, "LARGER")) { if (msg == NULL) { msg = CtdlFetchMessage(Imap->msgids[seq-1], 1); need_to_free_msg = 1; } if (msg != NULL) { if (msg->cm_lengths[eMesageText] > atoi(itemlist[pos+1].Key)) { match = 1; } } pos += 2; } else if (!strcasecmp(itemlist[pos].Key, "NEW")) { if ( (Imap->flags[seq-1] & IMAP_RECENT) && (!(Imap->flags[seq-1] & IMAP_SEEN))) { match = 1; } ++pos; } else if (!strcasecmp(itemlist[pos].Key, "OLD")) { if (!(Imap->flags[seq-1] & IMAP_RECENT)) { match = 1; } ++pos; } else if (!strcasecmp(itemlist[pos].Key, "ON")) { if (msg == NULL) { msg = CtdlFetchMessage(Imap->msgids[seq-1], 1); need_to_free_msg = 1; } if (msg != NULL) { if (!CM_IsEmpty(msg, eTimestamp)) { if (imap_datecmp(itemlist[pos+1].Key, atol(msg->cm_fields[eTimestamp])) == 0) { match = 1; } } } pos += 2; } else if (!strcasecmp(itemlist[pos].Key, "RECENT")) { if (Imap->flags[seq-1] & IMAP_RECENT) { match = 1; } ++pos; } else if (!strcasecmp(itemlist[pos].Key, "SEEN")) { if (Imap->flags[seq-1] & IMAP_SEEN) { match = 1; } ++pos; } else if (!strcasecmp(itemlist[pos].Key, "SENTBEFORE")) { if (msg == NULL) { msg = CtdlFetchMessage(Imap->msgids[seq-1], 1); need_to_free_msg = 1; } if (msg != NULL) { if (!CM_IsEmpty(msg, eTimestamp)) { if (imap_datecmp(itemlist[pos+1].Key, atol(msg->cm_fields[eTimestamp])) < 0) { match = 1; } } } pos += 2; } else if (!strcasecmp(itemlist[pos].Key, "SENTON")) { if (msg == NULL) { msg = CtdlFetchMessage(Imap->msgids[seq-1], 1); need_to_free_msg = 1; } if (msg != NULL) { if (!CM_IsEmpty(msg, eTimestamp)) { if (imap_datecmp(itemlist[pos+1].Key, atol(msg->cm_fields[eTimestamp])) == 0) { match = 1; } } } pos += 2; } else if (!strcasecmp(itemlist[pos].Key, "SENTSINCE")) { if (msg == NULL) { msg = CtdlFetchMessage(Imap->msgids[seq-1], 1); need_to_free_msg = 1; } if (msg != NULL) { if (!CM_IsEmpty(msg, eTimestamp)) { if (imap_datecmp(itemlist[pos+1].Key, atol(msg->cm_fields[eTimestamp])) >= 0) { match = 1; } } } pos += 2; } else if (!strcasecmp(itemlist[pos].Key, "SINCE")) { if (msg == NULL) { msg = CtdlFetchMessage(Imap->msgids[seq-1], 1); need_to_free_msg = 1; } if (msg != NULL) { if (!CM_IsEmpty(msg, eTimestamp)) { if (imap_datecmp(itemlist[pos+1].Key, atol(msg->cm_fields[eTimestamp])) >= 0) { match = 1; } } } pos += 2; } else if (!strcasecmp(itemlist[pos].Key, "SMALLER")) { if (msg == NULL) { msg = CtdlFetchMessage(Imap->msgids[seq-1], 1); need_to_free_msg = 1; } if (msg != NULL) { if (msg->cm_lengths[eMesageText] < atoi(itemlist[pos+1].Key)) { match = 1; } } pos += 2; } else if (!strcasecmp(itemlist[pos].Key, "SUBJECT")) { if (msg == NULL) { msg = CtdlFetchMessage(Imap->msgids[seq-1], 1); need_to_free_msg = 1; } if (msg != NULL) { if (bmstrcasestr(msg->cm_fields[eMsgSubject], itemlist[pos+1].Key)) { match = 1; } } pos += 2; } else if (!strcasecmp(itemlist[pos].Key, "TEXT")) { if (msg == NULL) { msg = CtdlFetchMessage(Imap->msgids[seq-1], 1); need_to_free_msg = 1; } if (msg != NULL) { for (i='A'; i<='Z'; ++i) { if (bmstrcasestr(msg->cm_fields[i], itemlist[pos+1].Key)) { match = 1; } } } pos += 2; } else if (!strcasecmp(itemlist[pos].Key, "TO")) { if (msg == NULL) { msg = CtdlFetchMessage(Imap->msgids[seq-1], 1); need_to_free_msg = 1; } if (msg != NULL) { if (bmstrcasestr(msg->cm_fields[eRecipient], itemlist[pos+1].Key)) { match = 1; } } pos += 2; } /* FIXME this is b0rken. fix it. */ else if (imap_is_message_set(itemlist[pos].Key)) { if (is_msg_in_sequence_set(itemlist[pos].Key, seq)) { match = 1; } pos += 1; } /* FIXME this is b0rken. fix it. */ else if (!strcasecmp(itemlist[pos].Key, "UID")) { if (is_msg_in_sequence_set(itemlist[pos+1].Key, Imap->msgids[seq-1])) { match = 1; } pos += 2; } /* Now here come the 'UN' criteria. Why oh why do we have to * implement *both* the 'UN' criteria *and* the 'NOT' keyword? Why * can't there be *one* way to do things? More gratuitous complexity. */ else if (!strcasecmp(itemlist[pos].Key, "UNANSWERED")) { if ((Imap->flags[seq-1] & IMAP_ANSWERED) == 0) { match = 1; } ++pos; } else if (!strcasecmp(itemlist[pos].Key, "UNDELETED")) { if ((Imap->flags[seq-1] & IMAP_DELETED) == 0) { match = 1; } ++pos; } else if (!strcasecmp(itemlist[pos].Key, "UNDRAFT")) { if ((Imap->flags[seq-1] & IMAP_DRAFT) == 0) { match = 1; } ++pos; } else if (!strcasecmp(itemlist[pos].Key, "UNFLAGGED")) { if ((Imap->flags[seq-1] & IMAP_FLAGGED) == 0) { match = 1; } ++pos; } else if (!strcasecmp(itemlist[pos].Key, "UNKEYWORD")) { /* FIXME */ pos += 2; } else if (!strcasecmp(itemlist[pos].Key, "UNSEEN")) { if ((Imap->flags[seq-1] & IMAP_SEEN) == 0) { match = 1; } ++pos; } /* Remember to negate if we were told to */ if (is_not) { match = !match; } /* Keep going if there are more criteria! */ if (pos < num_items) { if (is_or) { match = (match || imap_do_search_msg(seq, msg, num_items - pos, &itemlist[pos], is_uid)); } else { match = (match && imap_do_search_msg(seq, msg, num_items - pos, &itemlist[pos], is_uid)); } } if (need_to_free_msg) { CM_Free(msg); } return(match); } /* * imap_search() calls imap_do_search() to do its actual work, once it's * validated and boiled down the request a bit. */ void imap_do_search(int num_items, ConstStr *itemlist, int is_uid) { citimap *Imap = IMAP; int i, j, k; int fts_num_msgs = 0; long *fts_msgs = NULL; int is_in_list = 0; int num_results = 0; /* Strip parentheses. We realize that this method will not work * in all cases, but it seems to work with all currently available * client software. Revisit later... */ for (i=0; iCmd, &itemlist[i], 1); } if (itemlist[i].Key[itemlist[i].len-1] == ')') { TokenCutRight(&Imap->Cmd, &itemlist[i], 1); } } /* If there is a BODY search criterion in the query, use our full * text index to disqualify messages that don't have any chance of * matching. (Only do this if the index is enabled!!) */ if (config.c_enable_fulltext) for (i=0; i<(num_items-1); ++i) { if (!strcasecmp(itemlist[i].Key, "BODY")) { CtdlModuleDoSearch(&fts_num_msgs, &fts_msgs, itemlist[i+1].Key, "fulltext"); if (fts_num_msgs > 0) { for (j=0; j < Imap->num_msgs; ++j) { if (Imap->flags[j] & IMAP_SELECTED) { is_in_list = 0; for (k=0; kmsgids[j] == fts_msgs[k]) { ++is_in_list; } } } if (!is_in_list) { Imap->flags[j] = Imap->flags[j] & ~IMAP_SELECTED; } } } else { /* no hits on the index; disqualify every message */ for (j=0; j < Imap->num_msgs; ++j) { Imap->flags[j] = Imap->flags[j] & ~IMAP_SELECTED; } } if (fts_msgs) { free(fts_msgs); } } } /* Now go through the messages and apply all search criteria. */ buffer_output(); IAPuts("* SEARCH "); if (Imap->num_msgs > 0) for (i = 0; i < Imap->num_msgs; ++i) if (Imap->flags[i] & IMAP_SELECTED) { if (imap_do_search_msg(i+1, NULL, num_items, itemlist, is_uid)) { if (num_results != 0) { IAPuts(" "); } if (is_uid) { IAPrintf("%ld", Imap->msgids[i]); } else { IAPrintf("%d", i+1); } ++num_results; } } IAPuts("\r\n"); unbuffer_output(); } /* * This function is called by the main command loop. */ void imap_search(int num_parms, ConstStr *Params) { int i; if (num_parms < 3) { IReply("BAD invalid parameters"); return; } for (i = 0; i < IMAP->num_msgs; ++i) { IMAP->flags[i] |= IMAP_SELECTED; } imap_do_search(num_parms-2, &Params[2], 0); IReply("OK SEARCH completed"); } /* * This function is called by the main command loop. */ void imap_uidsearch(int num_parms, ConstStr *Params) { int i; if (num_parms < 4) { IReply("BAD invalid parameters"); return; } for (i = 0; i < IMAP->num_msgs; ++i) { IMAP->flags[i] |= IMAP_SELECTED; } imap_do_search(num_parms-3, &Params[3], 1); IReply("OK UID SEARCH completed"); } citadel-9.01/modules/imap/imap_misc.c0000644000000000000000000002516612507024051016257 0ustar rootroot/* * Copyright (c) 1987-2011 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "sysdep.h" #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include "citadel.h" #include "server.h" #include "sysdep_decls.h" #include "citserver.h" #include "support.h" #include "config.h" #include "user_ops.h" #include "database.h" #include "msgbase.h" #include "room_ops.h" #include "internet_addressing.h" #include "serv_imap.h" #include "imap_tools.h" #include "imap_fetch.h" #include "imap_misc.h" #include "genstamp.h" #include "ctdl_module.h" /* * imap_copy() calls imap_do_copy() to do its actual work, once it's * validated and boiled down the request a bit. (returns 0 on success) */ int imap_do_copy(const char *destination_folder) { citimap *Imap = IMAP; int i; char roomname[ROOMNAMELEN]; struct ctdlroom qrbuf; long *selected_msgs = NULL; int num_selected = 0; if (Imap->num_msgs < 1) { return(0); } i = imap_grabroom(roomname, destination_folder, 1); if (i != 0) return(i); /* * Copy all the message pointers in one shot. */ selected_msgs = malloc(sizeof(long) * Imap->num_msgs); if (selected_msgs == NULL) return(-1); for (i = 0; i < Imap->num_msgs; ++i) { if (Imap->flags[i] & IMAP_SELECTED) { selected_msgs[num_selected++] = Imap->msgids[i]; } } if (num_selected > 0) { CtdlSaveMsgPointersInRoom(roomname, selected_msgs, num_selected, 1, NULL, 0); } free(selected_msgs); /* Don't bother wasting any more time if there were no messages. */ if (num_selected == 0) { return(0); } /* Enumerate lists of messages for which flags are toggled */ long *seen_yes = NULL; int num_seen_yes = 0; long *seen_no = NULL; int num_seen_no = 0; long *answ_yes = NULL; int num_answ_yes = 0; long *answ_no = NULL; int num_answ_no = 0; seen_yes = malloc(num_selected * sizeof(long)); seen_no = malloc(num_selected * sizeof(long)); answ_yes = malloc(num_selected * sizeof(long)); answ_no = malloc(num_selected * sizeof(long)); for (i = 0; i < Imap->num_msgs; ++i) { if (Imap->flags[i] & IMAP_SELECTED) { if (Imap->flags[i] & IMAP_SEEN) { seen_yes[num_seen_yes++] = Imap->msgids[i]; } if ((Imap->flags[i] & IMAP_SEEN) == 0) { seen_no[num_seen_no++] = Imap->msgids[i]; } if (Imap->flags[i] & IMAP_ANSWERED) { answ_yes[num_answ_yes++] = Imap->msgids[i]; } if ((Imap->flags[i] & IMAP_ANSWERED) == 0) { answ_no[num_answ_no++] = Imap->msgids[i]; } } } /* Set the flags... */ i = CtdlGetRoom(&qrbuf, roomname); if (i == 0) { CtdlSetSeen(seen_yes, num_seen_yes, 1, ctdlsetseen_seen, NULL, &qrbuf); CtdlSetSeen(seen_no, num_seen_no, 0, ctdlsetseen_seen, NULL, &qrbuf); CtdlSetSeen(answ_yes, num_answ_yes, 1, ctdlsetseen_answered, NULL, &qrbuf); CtdlSetSeen(answ_no, num_answ_no, 0, ctdlsetseen_answered, NULL, &qrbuf); } free(seen_yes); free(seen_no); free(answ_yes); free(answ_no); return(0); } /* * Output the [COPYUID xxx yyy] response code required by RFC2359 * to tell the client the UID's of the messages that were copied (if any). * We are assuming that the IMAP_SELECTED flag is still set on any relevant * messages in our source room. Since the Citadel system uses UID's that * are both globally unique and persistent across a room-to-room copy, we * can get this done quite easily. */ void imap_output_copyuid_response(void) { citimap *Imap = IMAP; int i; int num_output = 0; for (i = 0; i < Imap->num_msgs; ++i) { if (Imap->flags[i] & IMAP_SELECTED) { ++num_output; if (num_output == 1) { IAPuts("[COPYUID "); } else if (num_output > 1) { IAPuts(","); } IAPrintf("%ld", Imap->msgids[i]); } } if (num_output > 0) { IAPuts("] "); } } /* * This function is called by the main command loop. */ void imap_copy(int num_parms, ConstStr *Params) { int ret; if (num_parms != 4) { IReply("BAD invalid parameters"); return; } if (imap_is_message_set(Params[2].Key)) { imap_pick_range(Params[2].Key, 0); } else { IReply("BAD invalid parameters"); return; } ret = imap_do_copy(Params[3].Key); if (!ret) { IAPrintf("%s OK ", Params[0].Key); imap_output_copyuid_response(); IAPuts("COPY completed\r\n"); } else { IReplyPrintf("NO COPY failed (error %d)", ret); } } /* * This function is called by the main command loop. */ void imap_uidcopy(int num_parms, ConstStr *Params) { if (num_parms != 5) { IReply("BAD invalid parameters"); return; } if (imap_is_message_set(Params[3].Key)) { imap_pick_range(Params[3].Key, 1); } else { IReply("BAD invalid parameters"); return; } if (imap_do_copy(Params[4].Key) == 0) { IAPrintf("%s OK ", Params[0].Key); imap_output_copyuid_response(); IAPuts("UID COPY completed\r\n"); } else { IReply("NO UID COPY failed"); } } /* * imap_do_append_flags() is called by imap_append() to set any flags that * the client specified at append time. * * FIXME find a way to do these in bulk so we don't max out our db journal */ void imap_do_append_flags(long new_msgnum, char *new_message_flags) { char flags[32]; char this_flag[sizeof flags]; int i; if (new_message_flags == NULL) return; if (IsEmptyStr(new_message_flags)) return; safestrncpy(flags, new_message_flags, sizeof flags); for (i=0; i= 5) { for (i=3; i= 6) { * new_message_internaldate = parms[4]; * } */ literal_length = atol(&Params[num_parms-1].Key[1]); if (literal_length < 1) { IReply("BAD Message length must be at least 1."); return; } Imap = IMAP; imap_free_transmitted_message(); /* just in case. */ Imap->TransmittedMessage = NewStrBufPlain(NULL, literal_length); if (Imap->TransmittedMessage == NULL) { IReply("NO Cannot allocate memory."); return; } IAPrintf("+ Transmit message now.\r\n"); IUnbuffer (); client_read_blob(Imap->TransmittedMessage, literal_length, config.c_sleeping); if ((ret < 0) || (StrLength(Imap->TransmittedMessage) < literal_length)) { IReply("NO Read failed."); return; } /* Client will transmit a trailing CRLF after the literal (the message * text) is received. This call to client_getln() absorbs it. */ flush_output(); client_getln(dummy, sizeof dummy); /* Convert RFC822 newlines (CRLF) to Unix newlines (LF) */ IMAPM_syslog(LOG_DEBUG, "Converting CRLF to LF"); StrBufToUnixLF(Imap->TransmittedMessage); IMAPM_syslog(LOG_DEBUG, "Converting message format"); msg = convert_internet_message_buf(&Imap->TransmittedMessage); ret = imap_grabroom(roomname, Params[2].Key, 1); if (ret != 0) { IReply("NO Invalid mailbox name or access denied"); return; } /* * CtdlUserGoto() formally takes us to the desired room. (If another * folder is selected, save its name so we can return there!!!!!) */ if (Imap->selected) { strcpy(savedroom, CCC->room.QRname); } CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL); /* If the user is locally authenticated, FORCE the From: header to * show up as the real sender. FIXME do we really want to do this? * Probably should make it site-definable or even room-definable. * * For now, we allow "forgeries" if the room is one of the user's * private mailboxes. */ if (CCC->logged_in) { if ( ((CCC->room.QRflags & QR_MAILBOX) == 0) && (config.c_imap_keep_from == 0)) { CM_SetField(msg, eAuthor, CCC->user.fullname, strlen(CCC->user.fullname)); CM_SetField(msg, eNodeName, CFG_KEY(c_nodename)); CM_SetField(msg, eHumanNode, CFG_KEY(c_humannode)); } } /* * Can we post here? */ ret = CtdlDoIHavePermissionToPostInThisRoom(errbuf, sizeof errbuf, NULL, POST_LOGGED_IN, 0); if (ret) { /* Nope ... print an error message */ IReplyPrintf("NO %s", errbuf); } else { /* Yes ... go ahead and post! */ if (msg != NULL) { new_msgnum = CtdlSubmitMsg(msg, NULL, "", 0); } if (new_msgnum >= 0L) { IReplyPrintf("OK [APPENDUID %ld %ld] APPEND completed", GLOBAL_UIDVALIDITY_VALUE, new_msgnum); } else { IReplyPrintf("BAD Error %ld saving message to disk.", new_msgnum); } } /* * IMAP protocol response to client has already been sent by now. * * If another folder is selected, go back to that room so we can resume * our happy day without violent explosions. */ if (Imap->selected) { CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL); } /* We don't need this buffer anymore */ CM_Free(msg); if (IsEmptyStr(new_message_flags)) { imap_do_append_flags(new_msgnum, new_message_flags); } } citadel-9.01/modules/imap/serv_imap.c0000644000000000000000000012730412507024051016300 0ustar rootroot/* * IMAP server for the Citadel system * * Copyright (C) 2000-2011 by Art Cancro and others. * This code is released under the terms of the GNU General Public License. * * WARNING: the IMAP protocol is badly designed. No implementation of it * is perfect. Indeed, with so much gratuitous complexity, *all* IMAP * implementations have bugs. * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "sysdep.h" #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "user_ops.h" #include "database.h" #include "msgbase.h" #include "internet_addressing.h" #include "serv_imap.h" #include "imap_tools.h" #include "imap_list.h" #include "imap_fetch.h" #include "imap_search.h" #include "imap_store.h" #include "imap_acl.h" #include "imap_metadata.h" #include "imap_misc.h" #include "ctdl_module.h" int IMAPDebugEnabled = 0; HashList *ImapCmds = NULL; void registerImapCMD(const char *First, long FLen, const char *Second, long SLen, imap_handler H, int Flags) { imap_handler_hook *h; h = (imap_handler_hook*) malloc(sizeof(imap_handler_hook)); memset(h, 0, sizeof(imap_handler_hook)); h->Flags = Flags; h->h = H; if (SLen == 0) { Put(ImapCmds, First, FLen, h, NULL); } else { char CMD[SIZ]; memcpy(CMD, First, FLen); memcpy(CMD+FLen, Second, SLen); CMD[FLen+SLen] = '\0'; Put(ImapCmds, CMD, FLen + SLen, h, NULL); } } void imap_cleanup(void) { DeleteHash(&ImapCmds); } const imap_handler_hook *imap_lookup(int num_parms, ConstStr *Params) { struct CitContext *CCC = CC; void *v; citimap *Imap = CCCIMAP; if (num_parms < 1) return NULL; /* we abuse the Reply-buffer for uppercasing... */ StrBufPlain(Imap->Reply, CKEY(Params[1])); StrBufUpCase(Imap->Reply); IMAP_syslog(LOG_DEBUG, "---- Looking up [%s] -----", ChrPtr(Imap->Reply)); if (GetHash(ImapCmds, SKEY(Imap->Reply), &v)) { IMAPM_syslog(LOG_DEBUG, "Found."); FlushStrBuf(Imap->Reply); return (imap_handler_hook *) v; } if (num_parms == 1) { IMAPM_syslog(LOG_DEBUG, "NOT Found."); FlushStrBuf(Imap->Reply); return NULL; } IMAP_syslog(LOG_DEBUG, "---- Looking up [%s] -----", ChrPtr(Imap->Reply)); StrBufAppendBufPlain(Imap->Reply, CKEY(Params[2]), 0); StrBufUpCase(Imap->Reply); if (GetHash(ImapCmds, SKEY(Imap->Reply), &v)) { IMAPM_syslog(LOG_DEBUG, "Found."); FlushStrBuf(Imap->Reply); return (imap_handler_hook *) v; } IMAPM_syslog(LOG_DEBUG, "NOT Found."); FlushStrBuf(Imap->Reply); return NULL; } /* imap_rename() uses this struct containing list of rooms to rename */ struct irl { struct irl *next; char irl_oldroom[ROOMNAMELEN]; char irl_newroom[ROOMNAMELEN]; int irl_newfloor; }; /* Data which is passed between imap_rename() and imap_rename_backend() */ typedef struct __irlparms { const char *oldname; long oldnamelen; const char *newname; long newnamelen; struct irl **irl; }irlparms; /* * If there is a message ID map in memory, free it */ void imap_free_msgids(void) { citimap *Imap = IMAP; if (Imap->msgids != NULL) { free(Imap->msgids); Imap->msgids = NULL; Imap->num_msgs = 0; Imap->num_alloc = 0; } if (Imap->flags != NULL) { free(Imap->flags); Imap->flags = NULL; } Imap->last_mtime = (-1); } /* * If there is a transmitted message in memory, free it */ void imap_free_transmitted_message(void) { FreeStrBuf(&IMAP->TransmittedMessage); } /* * Set the \Seen, \Recent. and \Answered flags, based on the sequence * sets stored in the visit record for this user/room. Note that we have * to parse each sequence set manually here, because calling the utility * function is_msg_in_sequence_set() over and over again is too expensive. * * first_msg should be set to 0 to rescan the flags for every message in the * room, or some other value if we're only interested in an incremental * update. */ void imap_set_seen_flags(int first_msg) { citimap *Imap = IMAP; visit vbuf; int i; int num_sets; int s; char setstr[64], lostr[64], histr[64]; long lo, hi; if (Imap->num_msgs < 1) return; CtdlGetRelationship(&vbuf, &CC->user, &CC->room); for (i = first_msg; i < Imap->num_msgs; ++i) { Imap->flags[i] = Imap->flags[i] & ~IMAP_SEEN; Imap->flags[i] |= IMAP_RECENT; Imap->flags[i] = Imap->flags[i] & ~IMAP_ANSWERED; } /* * Do the "\Seen" flag. * (Any message not "\Seen" is considered "\Recent".) */ num_sets = num_tokens(vbuf.v_seen, ','); for (s=0; s= 2) { extract_token(histr, setstr, 1, ':', sizeof histr); if (!strcmp(histr, "*")) { snprintf(histr, sizeof histr, "%ld", LONG_MAX); } } else { strcpy(histr, lostr); } lo = atol(lostr); hi = atol(histr); for (i = first_msg; i < Imap->num_msgs; ++i) { if ((Imap->msgids[i] >= lo) && (Imap->msgids[i] <= hi)){ Imap->flags[i] |= IMAP_SEEN; Imap->flags[i] = Imap->flags[i] & ~IMAP_RECENT; } } } /* Do the ANSWERED flag */ num_sets = num_tokens(vbuf.v_answered, ','); for (s=0; s= 2) { extract_token(histr, setstr, 1, ':', sizeof histr); if (!strcmp(histr, "*")) { snprintf(histr, sizeof histr, "%ld", LONG_MAX); } } else { strcpy(histr, lostr); } lo = atol(lostr); hi = atol(histr); for (i = first_msg; i < Imap->num_msgs; ++i) { if ((Imap->msgids[i] >= lo) && (Imap->msgids[i] <= hi)){ Imap->flags[i] |= IMAP_ANSWERED; } } } } /* * Back end for imap_load_msgids() * * Optimization: instead of calling realloc() to add each message, we * allocate space in the list for REALLOC_INCREMENT messages at a time. This * allows the mapping to proceed much faster. */ void imap_add_single_msgid(long msgnum, void *userdata) { citimap *Imap = IMAP; ++Imap->num_msgs; if (Imap->num_msgs > Imap->num_alloc) { Imap->num_alloc += REALLOC_INCREMENT; Imap->msgids = realloc(Imap->msgids, (Imap->num_alloc * sizeof(long)) ); Imap->flags = realloc(Imap->flags, (Imap->num_alloc * sizeof(unsigned int)) ); } Imap->msgids[Imap->num_msgs - 1] = msgnum; Imap->flags[Imap->num_msgs - 1] = 0; } /* * Set up a message ID map for the current room (folder) */ void imap_load_msgids(void) { struct CitContext *CCC = CC; struct cdbdata *cdbfr; citimap *Imap = CCCIMAP; if (Imap->selected == 0) { IMAPM_syslog(LOG_ERR, "imap_load_msgids() can't run; no room selected"); return; } imap_free_msgids(); /* If there was already a map, free it */ /* Load the message list */ cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long)); if (cdbfr != NULL) { Imap->msgids = (long*)cdbfr->ptr; Imap->num_msgs = cdbfr->len / sizeof(long); Imap->num_alloc = cdbfr->len / sizeof(long); cdbfr->ptr = NULL; cdbfr->len = 0; cdb_free(cdbfr); } if (Imap->num_msgs) { Imap->flags = malloc(Imap->num_alloc * sizeof(unsigned int)); memset(Imap->flags, 0, (Imap->num_alloc * sizeof(unsigned int)) ); } imap_set_seen_flags(0); } /* * Re-scan the selected room (folder) and see if it's been changed at all */ void imap_rescan_msgids(void) { struct CitContext *CCC = CC; citimap *Imap = CCCIMAP; int original_num_msgs = 0; long original_highest = 0L; int i, j, jstart; int message_still_exists; struct cdbdata *cdbfr; long *msglist = NULL; int num_msgs = 0; int num_recent = 0; if (Imap->selected == 0) { IMAPM_syslog(LOG_ERR, "imap_load_msgids() can't run; no room selected"); return; } /* * Check to see if the room's contents have changed. * If not, we can avoid this rescan. */ CtdlGetRoom(&CC->room, CC->room.QRname); if (Imap->last_mtime == CC->room.QRmtime) { /* No changes! */ return; } /* Load the *current* message list from disk, so we can compare it * to what we have in memory. */ cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long)); if (cdbfr != NULL) { msglist = (long*)cdbfr->ptr; cdbfr->ptr = NULL; num_msgs = cdbfr->len / sizeof(long); cdbfr->len = 0; cdb_free(cdbfr); } else { num_msgs = 0; } /* * Check to see if any of the messages we know about have been expunged */ if (Imap->num_msgs > 0) { jstart = 0; for (i = 0; i < Imap->num_msgs; ++i) { message_still_exists = 0; if (num_msgs > 0) { for (j = jstart; j < num_msgs; ++j) { if (msglist[j] == Imap->msgids[i]) { message_still_exists = 1; jstart = j; break; } } } if (message_still_exists == 0) { IAPrintf("* %d EXPUNGE\r\n", i + 1); /* Here's some nice stupid nonsense. When a * message is expunged, we have to slide all * the existing messages up in the message * array. */ --Imap->num_msgs; memmove(&Imap->msgids[i], &Imap->msgids[i + 1], (sizeof(long) * (Imap->num_msgs - i))); memmove(&Imap->flags[i], &Imap->flags[i + 1], (sizeof(unsigned int) * (Imap->num_msgs - i))); --i; } } } /* * Remember how many messages were here before we re-scanned. */ original_num_msgs = Imap->num_msgs; if (Imap->num_msgs > 0) { original_highest = Imap->msgids[Imap->num_msgs - 1]; } else { original_highest = 0L; } /* * Now peruse the room for *new* messages only. * This logic is probably the cause of Bug # 368 * [ http://bugzilla.citadel.org/show_bug.cgi?id=368 ] */ if (num_msgs > 0) { for (j = 0; j < num_msgs; ++j) { if (msglist[j] > original_highest) { imap_add_single_msgid(msglist[j], NULL); } } } imap_set_seen_flags(original_num_msgs); /* * If new messages have arrived, tell the client about them. */ if (Imap->num_msgs > original_num_msgs) { for (j = 0; j < num_msgs; ++j) { if (Imap->flags[j] & IMAP_RECENT) { ++num_recent; } } IAPrintf("* %d EXISTS\r\n", Imap->num_msgs); IAPrintf("* %d RECENT\r\n", num_recent); } if (msglist != NULL) { free(msglist); } Imap->last_mtime = CC->room.QRmtime; } /* * This cleanup function blows away the temporary memory and files used by * the IMAP server. */ void imap_cleanup_function(void) { struct CitContext *CCC = CC; citimap *Imap = CCCIMAP; /* Don't do this stuff if this is not a Imap session! */ if (CC->h_command_function != imap_command_loop) return; /* If there is a mailbox selected, auto-expunge it. */ if (Imap->selected) { imap_do_expunge(); } IMAPM_syslog(LOG_DEBUG, "Performing IMAP cleanup hook"); imap_free_msgids(); imap_free_transmitted_message(); if (Imap->cached_rfc822 != NULL) { FreeStrBuf(&Imap->cached_rfc822); Imap->cached_rfc822_msgnum = (-1); Imap->cached_rfc822_withbody = 0; } if (Imap->cached_body != NULL) { free(Imap->cached_body); Imap->cached_body = NULL; Imap->cached_body_len = 0; Imap->cached_bodymsgnum = (-1); } FreeStrBuf(&Imap->Cmd.CmdBuf); FreeStrBuf(&Imap->Reply); if (Imap->Cmd.Params != NULL) free(Imap->Cmd.Params); free(Imap); IMAPM_syslog(LOG_DEBUG, "Finished IMAP cleanup hook"); } /* * Does the actual work of the CAPABILITY command (because we need to * output this stuff in other places as well) */ void imap_output_capability_string(void) { IAPuts("CAPABILITY IMAP4REV1 NAMESPACE ID AUTH=PLAIN AUTH=LOGIN UIDPLUS"); #ifdef HAVE_OPENSSL if (!CC->redirect_ssl) IAPuts(" STARTTLS"); #endif #ifndef DISABLE_IMAP_ACL IAPuts(" ACL"); #endif /* We are building a partial implementation of METADATA for the sole purpose * of interoperating with the ical/vcard version of the Bynari Insight Connector. * It is not a full RFC5464 implementation, but it should refuse non-Bynari * metadata in a compatible and graceful way. */ IAPuts(" METADATA"); /* * LIST-EXTENDED was originally going to be required by the METADATA extension. * It was mercifully removed prior to the finalization of RFC5464. We started * implementing this but stopped when we learned that it would not be needed. * If you uncomment this declaration you are responsible for writing a lot of new * code. * * IAPuts(" LIST-EXTENDED") */ } /* * implements the CAPABILITY command */ void imap_capability(int num_parms, ConstStr *Params) { IAPuts("* "); imap_output_capability_string(); IAPuts("\r\n"); IReply("OK CAPABILITY completed"); } /* * Implements the ID command (specified by RFC2971) * * We ignore the client-supplied information, and output a NIL response. * Although this is technically a valid implementation of the extension, it * is quite useless. It exists only so that we may see which clients are * making use of this extension. * */ void imap_id(int num_parms, ConstStr *Params) { IAPuts("* ID NIL\r\n"); IReply("OK ID completed"); } /* * Here's where our IMAP session begins its happy day. */ void imap_greeting(void) { citimap *Imap; CitContext *CCC = CC; strcpy(CCC->cs_clientname, "IMAP session"); CCC->session_specific_data = malloc(sizeof(citimap)); Imap = (citimap *)CCC->session_specific_data; memset(Imap, 0, sizeof(citimap)); Imap->authstate = imap_as_normal; Imap->cached_rfc822_msgnum = (-1); Imap->cached_rfc822_withbody = 0; Imap->Reply = NewStrBufPlain(NULL, SIZ * 10); /* 40k */ if (CCC->nologin) { IAPuts("* BYE; Server busy, try later\r\n"); CCC->kill_me = KILLME_NOLOGIN; IUnbuffer(); return; } IAPuts("* OK ["); imap_output_capability_string(); IAPrintf("] %s IMAP4rev1 %s ready\r\n", config.c_fqdn, CITADEL); IUnbuffer(); } /* * IMAPS is just like IMAP, except it goes crypto right away. */ void imaps_greeting(void) { CtdlModuleStartCryptoMsgs(NULL, NULL, NULL); #ifdef HAVE_OPENSSL if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO; /* kill session if no crypto */ #endif imap_greeting(); } /* * implements the LOGIN command (ordinary username/password login) */ void imap_login(int num_parms, ConstStr *Params) { switch (num_parms) { case 3: if (Params[2].Key[0] == '{') { IAPuts("+ go ahead\r\n"); IMAP->authstate = imap_as_expecting_multilineusername; strcpy(IMAP->authseq, Params[0].Key); return; } else { IReply("BAD incorrect number of parameters"); return; } case 4: if (CtdlLoginExistingUser(NULL, Params[2].Key) == login_ok) { if (CtdlTryPassword(Params[3].Key, Params[3].len) == pass_ok) { /* hm, thats not doable by IReply :-( */ IAPrintf("%s OK [", Params[0].Key); imap_output_capability_string(); IAPrintf("] Hello, %s\r\n", CC->user.fullname); return; } else { IReplyPrintf("NO AUTHENTICATE %s failed", Params[3].Key); } } IReply("BAD Login incorrect"); default: IReply("BAD incorrect number of parameters"); return; } } /* * Implements the AUTHENTICATE command */ void imap_authenticate(int num_parms, ConstStr *Params) { char UsrBuf[SIZ]; if (num_parms != 3) { IReply("BAD incorrect number of parameters"); return; } if (CC->logged_in) { IReply("BAD Already logged in."); return; } if (!strcasecmp(Params[2].Key, "LOGIN")) { size_t len = CtdlEncodeBase64(UsrBuf, "Username:", 9, 0); if (UsrBuf[len - 1] == '\n') { UsrBuf[len - 1] = '\0'; } IAPrintf("+ %s\r\n", UsrBuf); IMAP->authstate = imap_as_expecting_username; strcpy(IMAP->authseq, Params[0].Key); return; } if (!strcasecmp(Params[2].Key, "PLAIN")) { // size_t len = CtdlEncodeBase64(UsrBuf, "Username:", 9, 0); // if (UsrBuf[len - 1] == '\n') { // UsrBuf[len - 1] = '\0'; // } // IAPuts("+ %s\r\n", UsrBuf); IAPuts("+ \r\n"); IMAP->authstate = imap_as_expecting_plainauth; strcpy(IMAP->authseq, Params[0].Key); return; } else { IReplyPrintf("NO AUTHENTICATE %s failed", Params[1].Key); } } void imap_auth_plain(void) { citimap *Imap = IMAP; const char *decoded_authstring; char ident[256] = ""; char user[256] = ""; char pass[256] = ""; int result; long decoded_len; long len = 0; long plen = 0; memset(pass, 0, sizeof(pass)); decoded_len = StrBufDecodeBase64(Imap->Cmd.CmdBuf); if (decoded_len > 0) { decoded_authstring = ChrPtr(Imap->Cmd.CmdBuf); len = safestrncpy(ident, decoded_authstring, sizeof ident); decoded_len -= len - 1; decoded_authstring += len + 1; if (decoded_len > 0) { len = safestrncpy(user, decoded_authstring, sizeof user); decoded_authstring += len + 1; decoded_len -= len - 1; } if (decoded_len > 0) { plen = safestrncpy(pass, decoded_authstring, sizeof pass); if (plen < 0) plen = sizeof(pass) - 1; } } Imap->authstate = imap_as_normal; if (!IsEmptyStr(ident)) { result = CtdlLoginExistingUser(user, ident); } else { result = CtdlLoginExistingUser(NULL, user); } if (result == login_ok) { if (CtdlTryPassword(pass, plen) == pass_ok) { IAPrintf("%s OK authentication succeeded\r\n", Imap->authseq); return; } } IAPrintf("%s NO authentication failed\r\n", Imap->authseq); } void imap_auth_login_user(long state) { char PWBuf[SIZ]; citimap *Imap = IMAP; switch (state){ case imap_as_expecting_username: StrBufDecodeBase64(Imap->Cmd.CmdBuf); CtdlLoginExistingUser(NULL, ChrPtr(Imap->Cmd.CmdBuf)); size_t len = CtdlEncodeBase64(PWBuf, "Password:", 9, 0); if (PWBuf[len - 1] == '\n') { PWBuf[len - 1] = '\0'; } IAPrintf("+ %s\r\n", PWBuf); Imap->authstate = imap_as_expecting_password; return; case imap_as_expecting_multilineusername: extract_token(PWBuf, ChrPtr(Imap->Cmd.CmdBuf), 1, ' ', sizeof(PWBuf)); CtdlLoginExistingUser(NULL, ChrPtr(Imap->Cmd.CmdBuf)); IAPuts("+ go ahead\r\n"); Imap->authstate = imap_as_expecting_multilinepassword; return; } } void imap_auth_login_pass(long state) { citimap *Imap = IMAP; const char *pass = NULL; long len = 0; switch (state) { default: case imap_as_expecting_password: StrBufDecodeBase64(Imap->Cmd.CmdBuf); pass = ChrPtr(Imap->Cmd.CmdBuf); len = StrLength(Imap->Cmd.CmdBuf); break; case imap_as_expecting_multilinepassword: pass = ChrPtr(Imap->Cmd.CmdBuf); len = StrLength(Imap->Cmd.CmdBuf); break; } if (len > USERNAME_SIZE) StrBufCutAt(Imap->Cmd.CmdBuf, USERNAME_SIZE, NULL); if (CtdlTryPassword(pass, len) == pass_ok) { IAPrintf("%s OK authentication succeeded\r\n", Imap->authseq); } else { IAPrintf("%s NO authentication failed\r\n", Imap->authseq); } Imap->authstate = imap_as_normal; return; } /* * implements the STARTTLS command (Citadel API version) */ void imap_starttls(int num_parms, ConstStr *Params) { char ok_response[SIZ]; char nosup_response[SIZ]; char error_response[SIZ]; snprintf(ok_response, SIZ, "%s OK begin TLS negotiation now\r\n", Params[0].Key); snprintf(nosup_response, SIZ, "%s NO TLS not supported here\r\n", Params[0].Key); snprintf(error_response, SIZ, "%s BAD Internal error\r\n", Params[0].Key); CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response); } /* * implements the SELECT command */ void imap_select(int num_parms, ConstStr *Params) { citimap *Imap = IMAP; char towhere[ROOMNAMELEN]; char augmented_roomname[ROOMNAMELEN]; int c = 0; int ok = 0; int ra = 0; struct ctdlroom QRscratch; int msgs, new; int i; /* Convert the supplied folder name to a roomname */ i = imap_roomname(towhere, sizeof towhere, Params[2].Key); if (i < 0) { IReply("NO Invalid mailbox name."); Imap->selected = 0; return; } /* First try a regular match */ c = CtdlGetRoom(&QRscratch, towhere); /* Then try a mailbox name match */ if (c != 0) { CtdlMailboxName(augmented_roomname, sizeof augmented_roomname, &CC->user, towhere); c = CtdlGetRoom(&QRscratch, augmented_roomname); if (c == 0) { safestrncpy(towhere, augmented_roomname, sizeof(towhere)); } } /* If the room exists, check security/access */ if (c == 0) { /* See if there is an existing user/room relationship */ CtdlRoomAccess(&QRscratch, &CC->user, &ra, NULL); /* normal clients have to pass through security */ if (ra & UA_KNOWN) { ok = 1; } } /* Fail here if no such room */ if (!ok) { IReply("NO ... no such room, or access denied"); return; } /* If we already had some other folder selected, auto-expunge it */ imap_do_expunge(); /* * CtdlUserGoto() formally takes us to the desired room, happily returning * the number of messages and number of new messages. */ memcpy(&CC->room, &QRscratch, sizeof(struct ctdlroom)); CtdlUserGoto(NULL, 0, 0, &msgs, &new, NULL, NULL); Imap->selected = 1; if (!strcasecmp(Params[1].Key, "EXAMINE")) { Imap->readonly = 1; } else { Imap->readonly = 0; } imap_load_msgids(); Imap->last_mtime = CC->room.QRmtime; IAPrintf("* %d EXISTS\r\n", msgs); IAPrintf("* %d RECENT\r\n", new); IAPrintf("* OK [UIDVALIDITY %ld] UID validity status\r\n", GLOBAL_UIDVALIDITY_VALUE); IAPrintf("* OK [UIDNEXT %ld] Predicted next UID\r\n", CitControl.MMhighest + 1); /* Technically, \Deleted is a valid flag, but not a permanent flag, * because we don't maintain its state across sessions. Citadel * automatically expunges mailboxes when they are de-selected. * * Unfortunately, omitting \Deleted as a PERMANENTFLAGS flag causes * some clients (particularly Thunderbird) to misbehave -- they simply * elect not to transmit the flag at all. So we have to advertise * \Deleted as a PERMANENTFLAGS flag, even though it technically isn't. */ IAPuts("* FLAGS (\\Deleted \\Seen \\Answered)\r\n"); IAPuts("* OK [PERMANENTFLAGS (\\Deleted \\Seen \\Answered)] permanent flags\r\n"); IReplyPrintf("OK [%s] %s completed", (Imap->readonly ? "READ-ONLY" : "READ-WRITE"), Params[1].Key ); } /* * Does the real work for expunge. */ int imap_do_expunge(void) { struct CitContext *CCC = CC; citimap *Imap = CCCIMAP; int i; int num_expunged = 0; long *delmsgs = NULL; int num_delmsgs = 0; IMAPM_syslog(LOG_DEBUG, "imap_do_expunge() called"); if (Imap->selected == 0) { return (0); } if (Imap->num_msgs > 0) { delmsgs = malloc(Imap->num_msgs * sizeof(long)); for (i = 0; i < Imap->num_msgs; ++i) { if (Imap->flags[i] & IMAP_DELETED) { delmsgs[num_delmsgs++] = Imap->msgids[i]; } } if (num_delmsgs > 0) { CtdlDeleteMessages(CC->room.QRname, delmsgs, num_delmsgs, ""); } num_expunged += num_delmsgs; free(delmsgs); } if (num_expunged > 0) { imap_rescan_msgids(); } IMAP_syslog(LOG_DEBUG, "Expunged %d messages from <%s>", num_expunged, CC->room.QRname); return (num_expunged); } /* * implements the EXPUNGE command syntax */ void imap_expunge(int num_parms, ConstStr *Params) { int num_expunged = 0; num_expunged = imap_do_expunge(); IReplyPrintf("OK expunged %d messages.", num_expunged); } /* * implements the CLOSE command */ void imap_close(int num_parms, ConstStr *Params) { /* Yes, we always expunge on close. */ if (IMAP->selected) { imap_do_expunge(); } IMAP->selected = 0; IMAP->readonly = 0; imap_free_msgids(); IReply("OK CLOSE completed"); } /* * Implements the NAMESPACE command. */ void imap_namespace(int num_parms, ConstStr *Params) { long len; int i; struct floor *fl; int floors = 0; char Namespace[SIZ]; IAPuts("* NAMESPACE "); /* All personal folders are subordinate to INBOX. */ IAPuts("((\"INBOX/\" \"/\")) "); /* Other users' folders ... coming soon! FIXME */ IAPuts("NIL "); /* Show all floors as shared namespaces. Neato! */ IAPuts("("); for (i = 0; i < MAXFLOORS; ++i) { fl = CtdlGetCachedFloor(i); if (fl->f_flags & F_INUSE) { /* if (floors > 0) IAPuts(" "); samjam says this confuses javamail */ IAPuts("("); len = snprintf(Namespace, sizeof(Namespace), "%s/", fl->f_name); IPutStr(Namespace, len); IAPuts(" \"/\")"); ++floors; } } IAPuts(")"); /* Wind it up with a newline and a completion message. */ IAPuts("\r\n"); IReply("OK NAMESPACE completed"); } /* * Implements the CREATE command * */ void imap_create(int num_parms, ConstStr *Params) { struct CitContext *CCC = CC; int ret; char roomname[ROOMNAMELEN]; int floornum; int flags; int newroomtype = 0; int newroomview = 0; char *notification_message = NULL; if (num_parms < 3) { IReply("NO A foder name must be specified"); return; } if (strchr(Params[2].Key, '\\') != NULL) { IReply("NO Invalid character in folder name"); IMAPM_syslog(LOG_ERR, "invalid character in folder name"); return; } ret = imap_roomname(roomname, sizeof roomname, Params[2].Key); if (ret < 0) { IReply("NO Invalid mailbox name or location"); IMAPM_syslog(LOG_ERR, "invalid mailbox name or location"); return; } floornum = (ret & 0x00ff); /* lower 8 bits = floor number */ flags = (ret & 0xff00); /* upper 8 bits = flags */ if (flags & IR_MAILBOX) { if (strncasecmp(Params[2].Key, "INBOX/", 6)) { IReply("NO Personal folders must be created under INBOX"); IMAPM_syslog(LOG_ERR, "not subordinate to inbox"); return; } } if (flags & IR_MAILBOX) { newroomtype = 4; /* private mailbox */ newroomview = VIEW_MAILBOX; } else { newroomtype = 0; /* public folder */ newroomview = VIEW_BBS; } IMAP_syslog(LOG_INFO, "Create new room <%s> on floor <%d> with type <%d>", roomname, floornum, newroomtype); ret = CtdlCreateRoom(roomname, newroomtype, "", floornum, 1, 0, newroomview); if (ret == 0) { /*** DO NOT CHANGE THIS ERROR MESSAGE IN ANY WAY! BYNARI CONNECTOR DEPENDS ON IT! ***/ IReply("NO Mailbox already exists, or create failed"); } else { IReply("OK CREATE completed"); /* post a message in Aide> describing the new room */ notification_message = malloc(1024); snprintf(notification_message, 1024, "A new room called \"%s\" has been created by %s%s%s%s\n", roomname, CC->user.fullname, ((ret & QR_MAILBOX) ? " [personal]" : ""), ((ret & QR_PRIVATE) ? " [private]" : ""), ((ret & QR_GUESSNAME) ? " [hidden]" : "") ); CtdlAideMessage(notification_message, "Room Creation Message"); free(notification_message); } IMAPM_syslog(LOG_DEBUG, "imap_create() completed"); } /* * Locate a room by its IMAP folder name, and check access to it. * If zapped_ok is nonzero, we can also look for the room in the zapped list. */ int imap_grabroom(char *returned_roomname, const char *foldername, int zapped_ok) { int ret; char augmented_roomname[ROOMNAMELEN]; char roomname[ROOMNAMELEN]; int c; struct ctdlroom QRscratch; int ra; int ok = 0; ret = imap_roomname(roomname, sizeof roomname, foldername); if (ret < 0) { return (1); } /* First try a regular match */ c = CtdlGetRoom(&QRscratch, roomname); /* Then try a mailbox name match */ if (c != 0) { CtdlMailboxName(augmented_roomname, sizeof augmented_roomname, &CC->user, roomname); c = CtdlGetRoom(&QRscratch, augmented_roomname); if (c == 0) safestrncpy(roomname, augmented_roomname, sizeof(roomname)); } /* If the room exists, check security/access */ if (c == 0) { /* See if there is an existing user/room relationship */ CtdlRoomAccess(&QRscratch, &CC->user, &ra, NULL); /* normal clients have to pass through security */ if (ra & UA_KNOWN) { ok = 1; } if ((zapped_ok) && (ra & UA_ZAPPED)) { ok = 1; } } /* Fail here if no such room */ if (!ok) { strcpy(returned_roomname, ""); return (2); } else { safestrncpy(returned_roomname, QRscratch.QRname, ROOMNAMELEN); return (0); } } /* * Implements the STATUS command (sort of) * */ void imap_status(int num_parms, ConstStr *Params) { long len; int ret; char roomname[ROOMNAMELEN]; char imaproomname[SIZ]; char savedroom[ROOMNAMELEN]; int msgs, new; ret = imap_grabroom(roomname, Params[2].Key, 1); if (ret != 0) { IReply("NO Invalid mailbox name or location, or access denied"); return; } /* * CtdlUserGoto() formally takes us to the desired room, happily returning * the number of messages and number of new messages. (If another * folder is selected, save its name so we can return there!!!!!) */ if (IMAP->selected) { strcpy(savedroom, CC->room.QRname); } CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL); /* * Tell the client what it wants to know. In fact, tell it *more* than * it wants to know. We happily IGnore the supplied status data item * names and simply spew all possible data items. It's far easier to * code and probably saves us some processing time too. */ len = imap_mailboxname(imaproomname, sizeof imaproomname, &CC->room); IAPuts("* STATUS "); IPutStr(imaproomname, len); IAPrintf(" (MESSAGES %d ", msgs); IAPrintf("RECENT %d ", new); /* Initially, new==recent */ IAPrintf("UIDNEXT %ld ", CitControl.MMhighest + 1); IAPrintf("UNSEEN %d)\r\n", new); /* * If another folder is selected, go back to that room so we can resume * our happy day without violent explosions. */ if (IMAP->selected) { CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL); } /* * Oooh, look, we're done! */ IReply("OK STATUS completed"); } /* * Implements the SUBSCRIBE command * */ void imap_subscribe(int num_parms, ConstStr *Params) { int ret; char roomname[ROOMNAMELEN]; char savedroom[ROOMNAMELEN]; int msgs, new; ret = imap_grabroom(roomname, Params[2].Key, 1); if (ret != 0) { IReplyPrintf( "NO Error %d: invalid mailbox name or location, or access denied", ret ); return; } /* * CtdlUserGoto() formally takes us to the desired room, which has the side * effect of marking the room as not-zapped ... exactly the effect * we're looking for. */ if (IMAP->selected) { strcpy(savedroom, CC->room.QRname); } CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL); /* * If another folder is selected, go back to that room so we can resume * our happy day without violent explosions. */ if (IMAP->selected) { CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL); } IReply("OK SUBSCRIBE completed"); } /* * Implements the UNSUBSCRIBE command * */ void imap_unsubscribe(int num_parms, ConstStr *Params) { int ret; char roomname[ROOMNAMELEN]; char savedroom[ROOMNAMELEN]; int msgs, new; ret = imap_grabroom(roomname, Params[2].Key, 1); if (ret != 0) { IReply("NO Invalid mailbox name or location, or access denied"); return; } /* * CtdlUserGoto() formally takes us to the desired room. */ if (IMAP->selected) { strcpy(savedroom, CC->room.QRname); } CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL); /* * Now make the API call to zap the room */ if (CtdlForgetThisRoom() == 0) { IReply("OK UNSUBSCRIBE completed"); } else { IReply("NO You may not unsubscribe from this folder."); } /* * If another folder is selected, go back to that room so we can resume * our happy day without violent explosions. */ if (IMAP->selected) { CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL); } } /* * Implements the DELETE command * */ void imap_delete(int num_parms, ConstStr *Params) { int ret; char roomname[ROOMNAMELEN]; char savedroom[ROOMNAMELEN]; int msgs, new; ret = imap_grabroom(roomname, Params[2].Key, 1); if (ret != 0) { IReply("NO Invalid mailbox name, or access denied"); return; } /* * CtdlUserGoto() formally takes us to the desired room, happily returning * the number of messages and number of new messages. (If another * folder is selected, save its name so we can return there!!!!!) */ if (IMAP->selected) { strcpy(savedroom, CC->room.QRname); } CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL); /* * Now delete the room. */ if (CtdlDoIHavePermissionToDeleteThisRoom(&CC->room)) { CtdlScheduleRoomForDeletion(&CC->room); IReply("OK DELETE completed"); } else { IReply("NO Can't delete this folder."); } /* * If another folder is selected, go back to that room so we can resume * our happy day without violent explosions. */ if (IMAP->selected) { CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL); } } /* * Back end function for imap_rename() */ void imap_rename_backend(struct ctdlroom *qrbuf, void *data) { char foldername[SIZ]; char newfoldername[SIZ]; char newroomname[ROOMNAMELEN]; int newfloor = 0; struct irl *irlp = NULL; /* scratch pointer */ irlparms *myirlparms; myirlparms = (irlparms *) data; imap_mailboxname(foldername, sizeof foldername, qrbuf); /* Rename subfolders */ if ((!strncasecmp(foldername, myirlparms->oldname, myirlparms->oldnamelen) && (foldername[myirlparms->oldnamelen] == '/'))) { sprintf(newfoldername, "%s/%s", myirlparms->newname, &foldername[myirlparms->oldnamelen + 1] ); newfloor = imap_roomname(newroomname, sizeof newroomname, newfoldername) & 0xFF; irlp = (struct irl *) malloc(sizeof(struct irl)); strcpy(irlp->irl_newroom, newroomname); strcpy(irlp->irl_oldroom, qrbuf->QRname); irlp->irl_newfloor = newfloor; irlp->next = *(myirlparms->irl); *(myirlparms->irl) = irlp; } } /* * Implements the RENAME command * */ void imap_rename(int num_parms, ConstStr *Params) { char old_room[ROOMNAMELEN]; char new_room[ROOMNAMELEN]; int newr; int new_floor; int r; struct irl *irl = NULL; /* the list */ struct irl *irlp = NULL; /* scratch pointer */ irlparms irlparms; char aidemsg[1024]; if (strchr(Params[3].Key, '\\') != NULL) { IReply("NO Invalid character in folder name"); return; } imap_roomname(old_room, sizeof old_room, Params[2].Key); newr = imap_roomname(new_room, sizeof new_room, Params[3].Key); new_floor = (newr & 0xFF); r = CtdlRenameRoom(old_room, new_room, new_floor); if (r == crr_room_not_found) { IReply("NO Could not locate this folder"); return; } if (r == crr_already_exists) { IReplyPrintf("NO '%s' already exists."); return; } if (r == crr_noneditable) { IReply("NO This folder is not editable."); return; } if (r == crr_invalid_floor) { IReply("NO Folder root does not exist."); return; } if (r == crr_access_denied) { IReply("NO You do not have permission to edit this folder."); return; } if (r != crr_ok) { IReplyPrintf("NO Rename failed - undefined error %d", r); return; } /* If this is the INBOX, then RFC2060 says we have to just move the * contents. In a Citadel environment it's easier to rename the room * (already did that) and create a new inbox. */ if (!strcasecmp(Params[2].Key, "INBOX")) { CtdlCreateRoom(MAILROOM, 4, "", 0, 1, 0, VIEW_MAILBOX); } /* Otherwise, do the subfolders. Build a list of rooms to rename... */ else { irlparms.oldname = Params[2].Key; irlparms.oldnamelen = Params[2].len; irlparms.newname = Params[3].Key; irlparms.newnamelen = Params[3].len; irlparms.irl = &irl; CtdlForEachRoom(imap_rename_backend, (void *) &irlparms); /* ... and now rename them. */ while (irl != NULL) { r = CtdlRenameRoom(irl->irl_oldroom, irl->irl_newroom, irl->irl_newfloor); if (r != crr_ok) { struct CitContext *CCC = CC; /* FIXME handle error returns better */ IMAP_syslog(LOG_ERR, "CtdlRenameRoom() error %d", r); } irlp = irl; irl = irl->next; free(irlp); } } snprintf(aidemsg, sizeof aidemsg, "IMAP folder \"%s\" renamed to \"%s\" by %s\n", Params[2].Key, Params[3].Key, CC->curr_user ); CtdlAideMessage(aidemsg, "IMAP folder rename"); IReply("OK RENAME completed"); } /* * Main command loop for IMAP sessions. */ void imap_command_loop(void) { struct CitContext *CCC = CC; struct timeval tv1, tv2; suseconds_t total_time = 0; citimap *Imap; const char *pchs, *pche; const imap_handler_hook *h; gettimeofday(&tv1, NULL); CCC->lastcmd = time(NULL); Imap = CCCIMAP; flush_output(); if (Imap->Cmd.CmdBuf == NULL) Imap->Cmd.CmdBuf = NewStrBufPlain(NULL, SIZ); else FlushStrBuf(Imap->Cmd.CmdBuf); if (CtdlClientGetLine(Imap->Cmd.CmdBuf) < 1) { IMAPM_syslog(LOG_ERR, "client disconnected: ending session."); CC->kill_me = KILLME_CLIENT_DISCONNECTED; return; } if (Imap->authstate == imap_as_expecting_password) { IMAPM_syslog(LOG_INFO, ""); } else if (Imap->authstate == imap_as_expecting_plainauth) { IMAPM_syslog(LOG_INFO, ""); } else if ((Imap->authstate == imap_as_expecting_multilineusername) || cbmstrcasestr(ChrPtr(Imap->Cmd.CmdBuf), " LOGIN ")) { IMAPM_syslog(LOG_INFO, "LOGIN..."); } else { IMAP_syslog(LOG_DEBUG, "%s", ChrPtr(Imap->Cmd.CmdBuf)); } pchs = ChrPtr(Imap->Cmd.CmdBuf); pche = pchs + StrLength(Imap->Cmd.CmdBuf); while ((pche > pchs) && ((*pche == '\n') || (*pche == '\r'))) { pche --; StrBufCutRight(Imap->Cmd.CmdBuf, 1); } StrBufTrim(Imap->Cmd.CmdBuf); /* If we're in the middle of a multi-line command, handle that */ switch (Imap->authstate){ case imap_as_expecting_username: imap_auth_login_user(imap_as_expecting_username); IUnbuffer(); return; case imap_as_expecting_multilineusername: imap_auth_login_user(imap_as_expecting_multilineusername); IUnbuffer(); return; case imap_as_expecting_plainauth: imap_auth_plain(); IUnbuffer(); return; case imap_as_expecting_password: imap_auth_login_pass(imap_as_expecting_password); IUnbuffer(); return; case imap_as_expecting_multilinepassword: imap_auth_login_pass(imap_as_expecting_multilinepassword); IUnbuffer(); return; default: break; } /* Ok, at this point we're in normal command mode. * If the command just submitted does not contain a literal, we * might think about delivering some untagged stuff... */ /* Grab the tag, command, and parameters. */ imap_parameterize(&Imap->Cmd); #if 0 /* debug output the parsed vector */ { int i; IMAP_syslog(LOG_DEBUG, "----- %ld params", Imap->Cmd.num_parms); for (i=0; i < Imap->Cmd.num_parms; i++) { if (Imap->Cmd.Params[i].len != strlen(Imap->Cmd.Params[i].Key)) IMAP_syslog(LOG_DEBUG, "*********** %ld != %ld : %s", Imap->Cmd.Params[i].len, strlen(Imap->Cmd.Params[i].Key), Imap->Cmd.Params[i].Key); else IMAP_syslog(LOG_DEBUG, "%ld : %s", Imap->Cmd.Params[i].len, Imap->Cmd.Params[i].Key); }} #endif /* Now for the command set. */ h = imap_lookup(Imap->Cmd.num_parms, Imap->Cmd.Params); if (h == NULL) { IReply("BAD command unrecognized"); goto BAIL; } /* RFC3501 says that we cannot output untagged data during these commands */ if ((h->Flags & I_FLAG_UNTAGGED) == 0) { /* we can put any additional untagged stuff right here in the future */ /* * Before processing the command that was just entered... if we happen * to have a folder selected, we'd like to rescan that folder for new * messages, and for deletions/changes of existing messages. This * could probably be optimized better with some deep thought... */ if (Imap->selected) { imap_rescan_msgids(); } } /* does our command require a logged-in state */ if ((!CC->logged_in) && ((h->Flags & I_FLAG_LOGGED_IN) != 0)) { IReply("BAD Not logged in."); goto BAIL; } /* does our command require the SELECT state on a mailbox */ if ((Imap->selected == 0) && ((h->Flags & I_FLAG_SELECT) != 0)){ IReply("BAD no folder selected"); goto BAIL; } h->h(Imap->Cmd.num_parms, Imap->Cmd.Params); /* If the client transmitted a message we can free it now */ BAIL: IUnbuffer(); imap_free_transmitted_message(); gettimeofday(&tv2, NULL); total_time = (tv2.tv_usec + (tv2.tv_sec * 1000000)) - (tv1.tv_usec + (tv1.tv_sec * 1000000)); IMAP_syslog(LOG_DEBUG, "IMAP command completed in %ld.%ld seconds", (total_time / 1000000), (total_time % 1000000) ); } void imap_noop (int num_parms, ConstStr *Params) { IReply("OK No operation"); } void imap_logout(int num_parms, ConstStr *Params) { if (IMAP->selected) { imap_do_expunge(); /* yes, we auto-expunge at logout */ } IAPrintf("* BYE %s logging out\r\n", config.c_fqdn); IReply("OK Citadel IMAP session ended."); CC->kill_me = KILLME_CLIENT_LOGGED_OUT; return; } const char *CitadelServiceIMAP="IMAP"; const char *CitadelServiceIMAPS="IMAPS"; void SetIMAPDebugEnabled(const int n) { IMAPDebugEnabled = n; } /* * This function is called to register the IMAP extension with Citadel. */ CTDL_MODULE_INIT(imap) { if (ImapCmds == NULL) ImapCmds = NewHash(1, NULL); RegisterImapCMD("NOOP", "", imap_noop, I_FLAG_NONE); RegisterImapCMD("CHECK", "", imap_noop, I_FLAG_NONE); RegisterImapCMD("ID", "", imap_id, I_FLAG_NONE); RegisterImapCMD("LOGOUT", "", imap_logout, I_FLAG_NONE); RegisterImapCMD("LOGIN", "", imap_login, I_FLAG_NONE); RegisterImapCMD("AUTHENTICATE", "", imap_authenticate, I_FLAG_NONE); RegisterImapCMD("CAPABILITY", "", imap_capability, I_FLAG_NONE); #ifdef HAVE_OPENSSL RegisterImapCMD("STARTTLS", "", imap_starttls, I_FLAG_NONE); #endif /* The commans below require a logged-in state */ RegisterImapCMD("SELECT", "", imap_select, I_FLAG_LOGGED_IN); RegisterImapCMD("EXAMINE", "", imap_select, I_FLAG_LOGGED_IN); RegisterImapCMD("LSUB", "", imap_list, I_FLAG_LOGGED_IN); RegisterImapCMD("LIST", "", imap_list, I_FLAG_LOGGED_IN); RegisterImapCMD("CREATE", "", imap_create, I_FLAG_LOGGED_IN); RegisterImapCMD("DELETE", "", imap_delete, I_FLAG_LOGGED_IN); RegisterImapCMD("RENAME", "", imap_rename, I_FLAG_LOGGED_IN); RegisterImapCMD("STATUS", "", imap_status, I_FLAG_LOGGED_IN); RegisterImapCMD("SUBSCRIBE", "", imap_subscribe, I_FLAG_LOGGED_IN); RegisterImapCMD("UNSUBSCRIBE", "", imap_unsubscribe, I_FLAG_LOGGED_IN); RegisterImapCMD("APPEND", "", imap_append, I_FLAG_LOGGED_IN); RegisterImapCMD("NAMESPACE", "", imap_namespace, I_FLAG_LOGGED_IN); RegisterImapCMD("SETACL", "", imap_setacl, I_FLAG_LOGGED_IN); RegisterImapCMD("DELETEACL", "", imap_deleteacl, I_FLAG_LOGGED_IN); RegisterImapCMD("GETACL", "", imap_getacl, I_FLAG_LOGGED_IN); RegisterImapCMD("LISTRIGHTS", "", imap_listrights, I_FLAG_LOGGED_IN); RegisterImapCMD("MYRIGHTS", "", imap_myrights, I_FLAG_LOGGED_IN); RegisterImapCMD("GETMETADATA", "", imap_getmetadata, I_FLAG_LOGGED_IN); RegisterImapCMD("SETMETADATA", "", imap_setmetadata, I_FLAG_LOGGED_IN); /* The commands below require the SELECT state on a mailbox */ RegisterImapCMD("FETCH", "", imap_fetch, I_FLAG_LOGGED_IN | I_FLAG_SELECT | I_FLAG_UNTAGGED); RegisterImapCMD("UID", "FETCH", imap_uidfetch, I_FLAG_LOGGED_IN | I_FLAG_SELECT); RegisterImapCMD("SEARCH", "", imap_search, I_FLAG_LOGGED_IN | I_FLAG_SELECT | I_FLAG_UNTAGGED); RegisterImapCMD("UID", "SEARCH", imap_uidsearch, I_FLAG_LOGGED_IN | I_FLAG_SELECT); RegisterImapCMD("STORE", "", imap_store, I_FLAG_LOGGED_IN | I_FLAG_SELECT | I_FLAG_UNTAGGED); RegisterImapCMD("UID", "STORE", imap_uidstore, I_FLAG_LOGGED_IN | I_FLAG_SELECT); RegisterImapCMD("COPY", "", imap_copy, I_FLAG_LOGGED_IN | I_FLAG_SELECT); RegisterImapCMD("UID", "COPY", imap_uidcopy, I_FLAG_LOGGED_IN | I_FLAG_SELECT); RegisterImapCMD("EXPUNGE", "", imap_expunge, I_FLAG_LOGGED_IN | I_FLAG_SELECT); RegisterImapCMD("UID", "EXPUNGE", imap_expunge, I_FLAG_LOGGED_IN | I_FLAG_SELECT); RegisterImapCMD("CLOSE", "", imap_close, I_FLAG_LOGGED_IN | I_FLAG_SELECT); if (!threading) { CtdlRegisterDebugFlagHook(HKEY("imapsrv"), SetIMAPDebugEnabled, &IMAPDebugEnabled); CtdlRegisterServiceHook(config.c_imap_port, NULL, imap_greeting, imap_command_loop, NULL, CitadelServiceIMAP); #ifdef HAVE_OPENSSL CtdlRegisterServiceHook(config.c_imaps_port, NULL, imaps_greeting, imap_command_loop, NULL, CitadelServiceIMAPS); #endif CtdlRegisterSessionHook(imap_cleanup_function, EVT_STOP, PRIO_STOP + 30); CtdlRegisterCleanupHook(imap_cleanup); } /* return our module name for the log */ return "imap"; } citadel-9.01/modules/imap/imap_fetch.c0000644000000000000000000011344412507024051016412 0ustar rootroot/* * Implements the FETCH command in IMAP. * This is a good example of the protocol's gratuitous complexity. * * Copyright (c) 2001-2011 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "sysdep.h" #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include "citadel.h" #include "server.h" #include "sysdep_decls.h" #include "citserver.h" #include "support.h" #include "config.h" #include "user_ops.h" #include "database.h" #include "msgbase.h" #include "internet_addressing.h" #include "serv_imap.h" #include "imap_tools.h" #include "imap_fetch.h" #include "genstamp.h" #include "ctdl_module.h" /* * Individual field functions for imap_do_fetch_msg() ... */ void imap_fetch_uid(int seq) { IAPrintf("UID %ld", IMAP->msgids[seq-1]); } void imap_fetch_flags(int seq) { citimap *Imap = IMAP; int num_flags_printed = 0; IAPuts("FLAGS ("); if (Imap->flags[seq] & IMAP_DELETED) { if (num_flags_printed > 0) IAPuts(" "); IAPuts("\\Deleted"); ++num_flags_printed; } if (Imap->flags[seq] & IMAP_SEEN) { if (num_flags_printed > 0) IAPuts(" "); IAPuts("\\Seen"); ++num_flags_printed; } if (Imap->flags[seq] & IMAP_ANSWERED) { if (num_flags_printed > 0) IAPuts(" "); IAPuts("\\Answered"); ++num_flags_printed; } if (Imap->flags[seq] & IMAP_RECENT) { if (num_flags_printed > 0) IAPuts(" "); IAPuts("\\Recent"); ++num_flags_printed; } IAPuts(")"); } void imap_fetch_internaldate(struct CtdlMessage *msg) { char datebuf[64]; time_t msgdate; if (!msg) return; if (!CM_IsEmpty(msg, eTimestamp)) { msgdate = atol(msg->cm_fields[eTimestamp]); } else { msgdate = time(NULL); } datestring(datebuf, sizeof datebuf, msgdate, DATESTRING_IMAP); IAPrintf( "INTERNALDATE \"%s\"", datebuf); } /* * Fetch RFC822-formatted messages. * * 'whichfmt' should be set to one of: * "RFC822" entire message * "RFC822.HEADER" headers only (with trailing blank line) * "RFC822.SIZE" size of translated message * "RFC822.TEXT" body only (without leading blank line) */ void imap_fetch_rfc822(long msgnum, const char *whichfmt) { CitContext *CCC = CC; citimap *Imap = CCCIMAP; const char *ptr = NULL; size_t headers_size, text_size, total_size; size_t bytes_to_send = 0; struct MetaData smi; int need_to_rewrite_metadata = 0; int need_body = 0; /* Determine whether this particular fetch operation requires * us to fetch the message body from disk. If not, we can save * on some disk operations... */ if ( (!strcasecmp(whichfmt, "RFC822")) || (!strcasecmp(whichfmt, "RFC822.TEXT")) ) { need_body = 1; } /* If this is an RFC822.SIZE fetch, first look in the message's * metadata record to see if we've saved that information. */ if (!strcasecmp(whichfmt, "RFC822.SIZE")) { GetMetaData(&smi, msgnum); if (smi.meta_rfc822_length > 0L) { IAPrintf("RFC822.SIZE %ld", smi.meta_rfc822_length); return; } need_to_rewrite_metadata = 1; need_body = 1; } /* Cache the most recent RFC822 FETCH because some clients like to * fetch in pieces, and we don't want to have to go back to the * message store for each piece. We also burn the cache if the * client requests something that involves reading the message * body, but we haven't fetched the body yet. */ if ((Imap->cached_rfc822 != NULL) && (Imap->cached_rfc822_msgnum == msgnum) && (Imap->cached_rfc822_withbody || (!need_body)) ) { /* Good to go! */ } else if (Imap->cached_rfc822 != NULL) { /* Some other message is cached -- free it */ FreeStrBuf(&Imap->cached_rfc822); Imap->cached_rfc822_msgnum = (-1); } /* At this point, we now can fetch and convert the message iff it's not * the one we had cached. */ if (Imap->cached_rfc822 == NULL) { /* * Load the message into memory for translation & measurement */ CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ); CtdlOutputMsg(msgnum, MT_RFC822, (need_body ? HEADERS_ALL : HEADERS_FAST), 0, 1, NULL, SUPPRESS_ENV_TO, NULL, NULL, NULL ); if (!need_body) IAPuts("\r\n"); /* extra trailing newline */ Imap->cached_rfc822 = CCC->redirect_buffer; CCC->redirect_buffer = NULL; Imap->cached_rfc822_msgnum = msgnum; Imap->cached_rfc822_withbody = need_body; if ( (need_to_rewrite_metadata) && (StrLength(Imap->cached_rfc822) > 0) ) { smi.meta_rfc822_length = StrLength(Imap->cached_rfc822); PutMetaData(&smi); } } /* * Now figure out where the headers/text break is. IMAP considers the * intervening blank line to be part of the headers, not the text. */ headers_size = 0; if (need_body) { StrBuf *Line = NewStrBuf(); ptr = NULL; do { StrBufSipLine(Line, Imap->cached_rfc822, &ptr); if ((StrLength(Line) != 0) && (ptr != StrBufNOTNULL)) { StrBufTrim(Line); if ((StrLength(Line) != 0) && (ptr != StrBufNOTNULL) ) { headers_size = ptr - ChrPtr(Imap->cached_rfc822); } } } while ( (headers_size == 0) && (ptr != StrBufNOTNULL) ); total_size = StrLength(Imap->cached_rfc822); text_size = total_size - headers_size; FreeStrBuf(&Line); } else { headers_size = total_size = StrLength(Imap->cached_rfc822); text_size = 0; } IMAP_syslog(LOG_DEBUG, "RFC822: headers=" SIZE_T_FMT ", text=" SIZE_T_FMT ", total=" SIZE_T_FMT, headers_size, text_size, total_size); if (!strcasecmp(whichfmt, "RFC822.SIZE")) { IAPrintf("RFC822.SIZE " SIZE_T_FMT, total_size); return; } else if (!strcasecmp(whichfmt, "RFC822")) { ptr = ChrPtr(Imap->cached_rfc822); bytes_to_send = total_size; } else if (!strcasecmp(whichfmt, "RFC822.HEADER")) { ptr = ChrPtr(Imap->cached_rfc822); bytes_to_send = headers_size; } else if (!strcasecmp(whichfmt, "RFC822.TEXT")) { ptr = &ChrPtr(Imap->cached_rfc822)[headers_size]; bytes_to_send = text_size; } IAPrintf("%s {" SIZE_T_FMT "}\r\n", whichfmt, bytes_to_send); iaputs(ptr, bytes_to_send); } /* * Load a specific part of a message into the temp file to be output to a * client. FIXME we can handle parts like "2" and "2.1" and even "2.MIME" * but we still can't handle "2.HEADER" (which might not be a problem). * * Note: mime_parser() was called with dont_decode set to 1, so we have the * luxury of simply spewing without having to re-encode. */ void imap_load_part(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata) { struct CitContext *CCC = CC; char mimebuf2[SIZ]; StrBuf *desired_section; desired_section = (StrBuf *)cbuserdata; IMAP_syslog(LOG_DEBUG, "imap_load_part() looking for %s, found %s", ChrPtr(desired_section), partnum ); if (!strcasecmp(partnum, ChrPtr(desired_section))) { client_write(content, length); } snprintf(mimebuf2, sizeof mimebuf2, "%s.MIME", partnum); if (!strcasecmp(ChrPtr(desired_section), mimebuf2)) { client_write(HKEY("Content-type: ")); client_write(cbtype, strlen(cbtype)); if (!IsEmptyStr(cbcharset)) { client_write(HKEY("; charset=\"")); client_write(cbcharset, strlen(cbcharset)); client_write(HKEY("\"")); } if (!IsEmptyStr(name)) { client_write(HKEY("; name=\"")); client_write(name, strlen(name)); client_write(HKEY("\"")); } client_write(HKEY("\r\n")); if (!IsEmptyStr(encoding)) { client_write(HKEY("Content-Transfer-Encoding: ")); client_write(encoding, strlen(encoding)); client_write(HKEY("\r\n")); } if (!IsEmptyStr(encoding)) { client_write(HKEY("Content-Disposition: ")); client_write(disp, strlen(disp)); if (!IsEmptyStr(filename)) { client_write(HKEY("; filename=\"")); client_write(filename, strlen(filename)); client_write(HKEY("\"")); } client_write(HKEY("\r\n")); } cprintf("Content-Length: %ld\r\n\r\n", (long)length); } } /* * Called by imap_fetch_envelope() to output the "From" field. * This is in its own function because its logic is kind of complex. We * really need to make this suck less. */ void imap_output_envelope_from(struct CtdlMessage *msg) { char user[SIZ], node[SIZ], name[SIZ]; if (!msg) return; /* For anonymous messages, it's so easy! */ if (!is_room_aide() && (msg->cm_anon_type == MES_ANONONLY)) { IAPuts("((\"----\" NIL \"x\" \"x.org\")) "); return; } if (!is_room_aide() && (msg->cm_anon_type == MES_ANONOPT)) { IAPuts("((\"anonymous\" NIL \"x\" \"x.org\")) "); return; } /* For everything else, we do stuff. */ IAPuts("(("); /* open double-parens */ IPutMsgField(eAuthor); /* personal name */ IAPuts(" NIL "); /* source route (not used) */ if (!CM_IsEmpty(msg, erFc822Addr)) { process_rfc822_addr(msg->cm_fields[erFc822Addr], user, node, name); IPutStr(user, strlen(user)); /* mailbox name (user id) */ IAPuts(" "); if (!strcasecmp(node, config.c_nodename)) { IPutStr(CFG_KEY(c_fqdn)); } else { IPutStr(node, strlen(node)); /* host name */ } } else { IPutMsgField(eAuthor); /* mailbox name (user id) */ IAPuts(" "); IPutMsgField(eNodeName); /* host name */ } IAPuts(")) "); /* close double-parens */ } /* * Output an envelope address (or set of addresses) in the official, * convoluted, braindead format. (Note that we can't use this for * the "From" address because its data may come from a number of different * fields. But we can use it for "To" and possibly others. */ void imap_output_envelope_addr(char *addr) { char individual_addr[256]; int num_addrs; int i; char user[256]; char node[256]; char name[256]; if (addr == NULL) { IAPuts("NIL "); return; } if (IsEmptyStr(addr)) { IAPuts("NIL "); return; } IAPuts("("); /* How many addresses are listed here? */ num_addrs = num_tokens(addr, ','); /* Output them one by one. */ for (i=0; icm_fields[eTimestamp]); } else { msgdate = time(NULL); } len = datestring(datestringbuf, sizeof datestringbuf, msgdate, DATESTRING_IMAP); /* Now start spewing data fields. The order is important, as it is * defined by the protocol specification. Nonexistent fields must * be output as NIL, existent fields must be quoted or literalled. * The imap_strout() function conveniently does all this for us. */ IAPuts("ENVELOPE ("); /* Date */ IPutStr(datestringbuf, len); IAPuts(" "); /* Subject */ IPutMsgField(eMsgSubject); IAPuts(" "); /* From */ imap_output_envelope_from(msg); /* Sender (default to same as 'From' if not present) */ fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "Sender"); if (fieldptr != NULL) { imap_output_envelope_addr(fieldptr); free(fieldptr); } else { imap_output_envelope_from(msg); } /* Reply-to */ fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "Reply-to"); if (fieldptr != NULL) { imap_output_envelope_addr(fieldptr); free(fieldptr); } else { imap_output_envelope_from(msg); } /* To */ imap_output_envelope_addr(msg->cm_fields[eRecipient]); /* Cc (we do it this way because there might be a legacy non-Citadel Cc: field present) */ fieldptr = msg->cm_fields[eCarbonCopY]; if (fieldptr != NULL) { imap_output_envelope_addr(fieldptr); } else { fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "Cc"); imap_output_envelope_addr(fieldptr); if (fieldptr != NULL) free(fieldptr); } /* Bcc */ fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "Bcc"); imap_output_envelope_addr(fieldptr); if (fieldptr != NULL) free(fieldptr); /* In-reply-to */ fieldptr = rfc822_fetch_field(msg->cm_fields[eMesageText], "In-reply-to"); IPutStr(fieldptr, (fieldptr)?strlen(fieldptr):0); IAPuts(" "); if (fieldptr != NULL) free(fieldptr); /* message ID */ len = msg->cm_lengths[emessageId]; if ((len == 0) || ( (msg->cm_fields[emessageId][0] == '<') && (msg->cm_fields[emessageId][len - 1] == '>')) ) { IPutMsgField(emessageId); } else { char *Buf = malloc(len + 3); long pos = 0; if (msg->cm_fields[emessageId][0] != '<') { Buf[pos] = '<'; pos ++; } memcpy(&Buf[pos], msg->cm_fields[emessageId], len); pos += len; if (msg->cm_fields[emessageId][len] != '>') { Buf[pos] = '>'; pos++; } Buf[pos] = '\0'; IPutStr(Buf, pos); free(Buf); } IAPuts(")"); } /* * This function is called only when CC->redirect_buffer contains a set of * RFC822 headers with no body attached. Its job is to strip that set of * headers down to *only* the ones we're interested in. */ void imap_strip_headers(StrBuf *section) { citimap_command Cmd; StrBuf *which_fields = NULL; int doing_headers = 0; int headers_not = 0; int num_parms = 0; int i; StrBuf *boiled_headers = NULL; StrBuf *Line; int ok = 0; int done_headers = 0; const char *Ptr = NULL; CitContext *CCC = CC; if (CCC->redirect_buffer == NULL) return; which_fields = NewStrBufDup(section); if (!strncasecmp(ChrPtr(which_fields), "HEADER.FIELDS", 13)) doing_headers = 1; if (doing_headers && !strncasecmp(ChrPtr(which_fields), "HEADER.FIELDS.NOT", 17)) headers_not = 1; for (i=0; i < StrLength(which_fields); ++i) { if (ChrPtr(which_fields)[i]=='(') StrBufReplaceToken(which_fields, i, 1, HKEY("")); } for (i=0; i < StrLength(which_fields); ++i) { if (ChrPtr(which_fields)[i]==')') { StrBufCutAt(which_fields, i, NULL); break; } } memset(&Cmd, 0, sizeof(citimap_command)); Cmd.CmdBuf = which_fields; num_parms = imap_parameterize(&Cmd); boiled_headers = NewStrBufPlain(NULL, StrLength(CCC->redirect_buffer)); Line = NewStrBufPlain(NULL, SIZ); Ptr = NULL; ok = 0; do { StrBufSipLine(Line, CCC->redirect_buffer, &Ptr); if (!isspace(ChrPtr(Line)[0])) { if (doing_headers == 0) ok = 1; else { /* we're supposed to print all headers that are not matching the filter list */ if (headers_not) for (i=0, ok = 1; (i < num_parms) && (ok == 1); ++i) { if ( (!strncasecmp(ChrPtr(Line), Cmd.Params[i].Key, Cmd.Params[i].len)) && (ChrPtr(Line)[Cmd.Params[i].len]==':') ) { ok = 0; } } /* we're supposed to print all headers matching the filterlist */ else for (i=0, ok = 0; ((i < num_parms) && (ok == 0)); ++i) { if ( (!strncasecmp(ChrPtr(Line), Cmd.Params[i].Key, Cmd.Params[i].len)) && (ChrPtr(Line)[Cmd.Params[i].len]==':') ) { ok = 1; } } } } if (ok) { StrBufAppendBuf(boiled_headers, Line, 0); StrBufAppendBufPlain(boiled_headers, HKEY("\r\n"), 0); } if ((Ptr == StrBufNOTNULL) || (StrLength(Line) == 0) || (ChrPtr(Line)[0]=='\r') || (ChrPtr(Line)[0]=='\n') ) done_headers = 1; } while (!done_headers); StrBufAppendBufPlain(boiled_headers, HKEY("\r\n"), 0); /* Now save it back (it'll always be smaller) */ FreeStrBuf(&CCC->redirect_buffer); CCC->redirect_buffer = boiled_headers; free(Cmd.Params); FreeStrBuf(&which_fields); FreeStrBuf(&Line); } /* * Implements the BODY and BODY.PEEK fetch items */ void imap_fetch_body(long msgnum, ConstStr item, int is_peek) { struct CtdlMessage *msg = NULL; StrBuf *section; StrBuf *partial; int is_partial = 0; size_t pstart, pbytes; int loading_body_now = 0; int need_body = 1; int burn_the_cache = 0; CitContext *CCC = CC; citimap *Imap = CCCIMAP; /* extract section */ section = NewStrBufPlain(CKEY(item)); if (strchr(ChrPtr(section), '[') != NULL) { StrBufStripAllBut(section, '[', ']'); } IMAP_syslog(LOG_DEBUG, "Section is: [%s]", (StrLength(section) == 0) ? "(empty)" : ChrPtr(section) ); /* Burn the cache if we don't have the same section of the * same message again. */ if (Imap->cached_body != NULL) { if (Imap->cached_bodymsgnum != msgnum) { burn_the_cache = 1; } else if ( (!Imap->cached_body_withbody) && (need_body) ) { burn_the_cache = 1; } else if (strcasecmp(Imap->cached_bodypart, ChrPtr(section))) { burn_the_cache = 1; } if (burn_the_cache) { /* Yup, go ahead and burn the cache. */ free(Imap->cached_body); Imap->cached_body_len = 0; Imap->cached_body = NULL; Imap->cached_bodymsgnum = (-1); strcpy(Imap->cached_bodypart, ""); } } /* extract partial */ partial = NewStrBufPlain(CKEY(item)); if (strchr(ChrPtr(partial), '<') != NULL) { StrBufStripAllBut(partial, '<', '>'); is_partial = 1; } if ( (is_partial == 1) && (StrLength(partial) > 0) ) { IMAP_syslog(LOG_DEBUG, "Partial is <%s>", ChrPtr(partial)); } if (Imap->cached_body == NULL) { CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ); loading_body_now = 1; msg = CtdlFetchMessage(msgnum, (need_body ? 1 : 0)); } /* Now figure out what the client wants, and get it */ if (!loading_body_now) { /* What we want is already in memory */ } else if ( (!strcmp(ChrPtr(section), "1")) && (msg->cm_format_type != 4) ) { CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_NONE, 0, 1, SUPPRESS_ENV_TO); } else if (StrLength(section) == 0) { CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, SUPPRESS_ENV_TO); } /* * If the client asked for just headers, or just particular header * fields, strip it down. */ else if (!strncasecmp(ChrPtr(section), "HEADER", 6)) { /* This used to work with HEADERS_FAST, but then Apple got stupid with their * IMAP library and this broke Mail.App and iPhone Mail, so we had to change it * to HEADERS_ONLY so the trendy hipsters with their iPhones can read mail. */ CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ONLY, 0, 1, SUPPRESS_ENV_TO); imap_strip_headers(section); } /* * Strip it down if the client asked for everything _except_ headers. */ else if (!strncasecmp(ChrPtr(section), "TEXT", 4)) { CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_NONE, 0, 1, SUPPRESS_ENV_TO); } /* * Anything else must be a part specifier. * (Note value of 1 passed as 'dont_decode' so client gets it encoded) */ else { mime_parser(CM_RANGE(msg, eMesageText), *imap_load_part, NULL, NULL, section, 1 ); } if (loading_body_now) { Imap->cached_body_len = StrLength(CCC->redirect_buffer); Imap->cached_body = SmashStrBuf(&CCC->redirect_buffer); Imap->cached_bodymsgnum = msgnum; Imap->cached_body_withbody = need_body; strcpy(Imap->cached_bodypart, ChrPtr(section)); } if (is_partial == 0) { IAPuts("BODY["); iaputs(SKEY(section)); IAPrintf("] {" SIZE_T_FMT "}\r\n", Imap->cached_body_len); pstart = 0; pbytes = Imap->cached_body_len; } else { sscanf(ChrPtr(partial), SIZE_T_FMT "." SIZE_T_FMT, &pstart, &pbytes); if (pbytes > (Imap->cached_body_len - pstart)) { pbytes = Imap->cached_body_len - pstart; } IAPuts("BODY["); iaputs(SKEY(section)); IAPrintf("]<" SIZE_T_FMT "> {" SIZE_T_FMT "}\r\n", pstart, pbytes); } FreeStrBuf(&partial); /* Here we go -- output it */ iaputs(&Imap->cached_body[pstart], pbytes); if (msg != NULL) { CM_Free(msg); } /* Mark this message as "seen" *unless* this is a "peek" operation */ if (is_peek == 0) { CtdlSetSeen(&msgnum, 1, 1, ctdlsetseen_seen, NULL, NULL); } FreeStrBuf(§ion); } /* * Called immediately before outputting a multipart bodystructure */ void imap_fetch_bodystructure_pre( char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata ) { IAPuts("("); } /* * Called immediately after outputting a multipart bodystructure */ void imap_fetch_bodystructure_post( char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata ) { long len; char subtype[128]; IAPuts(" "); /* disposition */ len = extract_token(subtype, cbtype, 1, '/', sizeof subtype); IPutStr(subtype, len); /* body language */ /* IAPuts(" NIL"); We thought we needed this at one point, but maybe we don't... */ IAPuts(")"); } /* * Output the info for a MIME part in the format required by BODYSTRUCTURE. * */ void imap_fetch_bodystructure_part( char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata ) { int have_cbtype = 0; int have_encoding = 0; int lines = 0; size_t i; char cbmaintype[128]; char cbsubtype[128]; long cbmaintype_len; long cbsubtype_len; if (cbtype != NULL) if (!IsEmptyStr(cbtype)) have_cbtype = 1; if (have_cbtype) { cbmaintype_len = extract_token(cbmaintype, cbtype, 0, '/', sizeof cbmaintype); cbsubtype_len = extract_token(cbsubtype, cbtype, 1, '/', sizeof cbsubtype); } else { strcpy(cbmaintype, "TEXT"); cbmaintype_len = 4; strcpy(cbsubtype, "PLAIN"); cbsubtype_len = 5; } IAPuts("("); IPutStr(cbmaintype, cbmaintype_len); /* body type */ IAPuts(" "); IPutStr(cbsubtype, cbsubtype_len); /* body subtype */ IAPuts(" "); IAPuts("("); /* begin body parameter list */ /* "NAME" must appear as the first parameter. This is not required by IMAP, * but the Asterisk voicemail application blindly assumes that NAME will be in * the first position. If it isn't, it rejects the message. */ if ((name != NULL) && (!IsEmptyStr(name))) { IAPuts("\"NAME\" "); IPutStr(name, strlen(name)); IAPuts(" "); } IAPuts("\"CHARSET\" "); if ((cbcharset == NULL) || (cbcharset[0] == 0)){ IPutStr(HKEY("US-ASCII")); } else { IPutStr(cbcharset, strlen(cbcharset)); } IAPuts(") "); /* end body parameter list */ IAPuts("NIL "); /* Body ID */ IAPuts("NIL "); /* Body description */ if ((encoding != NULL) && (encoding[0] != 0)) have_encoding = 1; if (have_encoding) { IPutStr(encoding, strlen(encoding)); } else { IPutStr(HKEY("7BIT")); } IAPuts(" "); /* The next field is the size of the part in bytes. */ IAPrintf("%ld ", (long)length); /* bytes */ /* The next field is the number of lines in the part, if and only * if the part is TEXT. More gratuitous complexity. */ if (!strcasecmp(cbmaintype, "TEXT")) { if (length) for (i=0; icm_format_type != FMT_RFC822) { /* *sigh* We have to RFC822-format the message just to be able * to measure it. FIXME use smi cached fields if possible */ CC->redirect_buffer = NewStrBufPlain(NULL, SIZ); CtdlOutputPreLoadedMsg(msg, MT_RFC822, 0, 0, 1, SUPPRESS_ENV_TO); rfc822_len = StrLength(CC->redirect_buffer); rfc822 = pch = SmashStrBuf(&CC->redirect_buffer); ptr = rfc822; do { ptr = cmemreadline(ptr, buf, sizeof buf); ++lines; if ((IsEmptyStr(buf)) && (rfc822_body == NULL)) { rfc822_body = ptr; } } while (*ptr != 0); rfc822_headers_len = rfc822_body - rfc822; rfc822_body_len = rfc822_len - rfc822_headers_len; free(pch); IAPuts("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" " "(\"CHARSET\" \"US-ASCII\") NIL NIL " "\"7BIT\" "); IAPrintf(SIZE_T_FMT " %d)", rfc822_body_len, lines); return; } /* For messages already stored in RFC822 format, we have to parse. */ IAPuts("BODYSTRUCTURE "); mime_parser(CM_RANGE(msg, eMesageText), *imap_fetch_bodystructure_part, /* part */ *imap_fetch_bodystructure_pre, /* pre-multi */ *imap_fetch_bodystructure_post, /* post-multi */ NULL, 1); /* don't decode -- we want it as-is */ } /* * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an * individual message, once it has been selected for output. */ void imap_do_fetch_msg(int seq, citimap_command *Cmd) { int i; citimap *Imap = IMAP; struct CtdlMessage *msg = NULL; int body_loaded = 0; /* Don't attempt to fetch bogus messages or UID's */ if (seq < 1) return; if (Imap->msgids[seq-1] < 1L) return; buffer_output(); IAPrintf("* %d FETCH (", seq); for (i=0; inum_parms; ++i) { /* Fetchable without going to the message store at all */ if (!strcasecmp(Cmd->Params[i].Key, "UID")) { imap_fetch_uid(seq); } else if (!strcasecmp(Cmd->Params[i].Key, "FLAGS")) { imap_fetch_flags(seq-1); } /* Potentially fetchable from cache, if the client requests * stuff from the same message several times in a row. */ else if (!strcasecmp(Cmd->Params[i].Key, "RFC822")) { imap_fetch_rfc822(Imap->msgids[seq-1], Cmd->Params[i].Key); } else if (!strcasecmp(Cmd->Params[i].Key, "RFC822.HEADER")) { imap_fetch_rfc822(Imap->msgids[seq-1], Cmd->Params[i].Key); } else if (!strcasecmp(Cmd->Params[i].Key, "RFC822.SIZE")) { imap_fetch_rfc822(Imap->msgids[seq-1], Cmd->Params[i].Key); } else if (!strcasecmp(Cmd->Params[i].Key, "RFC822.TEXT")) { imap_fetch_rfc822(Imap->msgids[seq-1], Cmd->Params[i].Key); } /* BODY fetches do their own fetching and caching too. */ else if (!strncasecmp(Cmd->Params[i].Key, "BODY[", 5)) { imap_fetch_body(Imap->msgids[seq-1], Cmd->Params[i], 0); } else if (!strncasecmp(Cmd->Params[i].Key, "BODY.PEEK[", 10)) { imap_fetch_body(Imap->msgids[seq-1], Cmd->Params[i], 1); } /* Otherwise, load the message into memory. */ else if (!strcasecmp(Cmd->Params[i].Key, "BODYSTRUCTURE")) { if ((msg != NULL) && (!body_loaded)) { CM_Free(msg); /* need the whole thing */ msg = NULL; } if (msg == NULL) { msg = CtdlFetchMessage(Imap->msgids[seq-1], 1); body_loaded = 1; } imap_fetch_bodystructure(Imap->msgids[seq-1], Cmd->Params[i].Key, msg); } else if (!strcasecmp(Cmd->Params[i].Key, "ENVELOPE")) { if (msg == NULL) { msg = CtdlFetchMessage(Imap->msgids[seq-1], 0); body_loaded = 0; } imap_fetch_envelope(msg); } else if (!strcasecmp(Cmd->Params[i].Key, "INTERNALDATE")) { if (msg == NULL) { msg = CtdlFetchMessage(Imap->msgids[seq-1], 0); body_loaded = 0; } imap_fetch_internaldate(msg); } if (i != Cmd->num_parms-1) IAPuts(" "); } IAPuts(")\r\n"); unbuffer_output(); if (msg != NULL) { CM_Free(msg); } } /* * imap_fetch() calls imap_do_fetch() to do its actual work, once it's * validated and boiled down the request a bit. */ void imap_do_fetch(citimap_command *Cmd) { citimap *Imap = IMAP; int i; #if 0 /* debug output the parsed vector */ { int i; IMAP_syslog(LOG_DEBUG, "----- %ld params", Cmd->num_parms); for (i=0; i < Cmd->num_parms; i++) { if (Cmd->Params[i].len != strlen(Cmd->Params[i].Key)) IMAP_syslog(LOG_DEBUG, "*********** %ld != %ld : %s", Cmd->Params[i].len, strlen(Cmd->Params[i].Key), Cmd->Params[i].Key); else IMAP_syslog(LOG_DEBUG, "%ld : %s", Cmd->Params[i].len, Cmd->Params[i].Key); }} #endif if (Imap->num_msgs > 0) { for (i = 0; i < Imap->num_msgs; ++i) { /* Abort the fetch loop if the session breaks. * This is important for users who keep mailboxes * that are too big *and* are too impatient to * let them finish loading. :) */ if (CC->kill_me) return; /* Get any message marked for fetch. */ if (Imap->flags[i] & IMAP_SELECTED) { imap_do_fetch_msg(i+1, Cmd); } } } } /* * Back end for imap_handle_macros() * Note that this function *only* looks at the beginning of the string. It * is not a generic search-and-replace function. */ void imap_macro_replace(StrBuf *Buf, long where, StrBuf *TmpBuf, char *find, long findlen, char *replace, long replacelen) { if (StrLength(Buf) - where > findlen) return; if (!strncasecmp(ChrPtr(Buf) + where, find, findlen)) { if (ChrPtr(Buf)[where + findlen] == ' ') { StrBufPlain(TmpBuf, replace, replacelen); StrBufAppendBufPlain(TmpBuf, HKEY(" "), 0); StrBufReplaceToken(Buf, where, findlen, SKEY(TmpBuf)); } if (where + findlen == StrLength(Buf)) { StrBufReplaceToken(Buf, where, findlen, replace, replacelen); } } } /* * Handle macros embedded in FETCH data items. * (What the heck are macros doing in a wire protocol? Are we trying to save * the computer at the other end the trouble of typing a lot of characters?) */ void imap_handle_macros(citimap_command *Cmd) { long i; int nest = 0; StrBuf *Tmp = NewStrBuf(); for (i=0; i < StrLength(Cmd->CmdBuf); ++i) { char ch = ChrPtr(Cmd->CmdBuf)[i]; if ((ch=='(') || (ch=='[') || (ch=='<') || (ch=='{')) ++nest; else if ((ch==')') || (ch==']') || (ch=='>') || (ch=='}')) --nest; if (nest <= 0) { imap_macro_replace(Cmd->CmdBuf, i, Tmp, HKEY("ALL"), HKEY("FLAGS INTERNALDATE RFC822.SIZE ENVELOPE") ); imap_macro_replace(Cmd->CmdBuf, i, Tmp, HKEY("BODY"), HKEY("BODYSTRUCTURE") ); imap_macro_replace(Cmd->CmdBuf, i, Tmp, HKEY("FAST"), HKEY("FLAGS INTERNALDATE RFC822.SIZE") ); imap_macro_replace(Cmd->CmdBuf, i, Tmp, HKEY("FULL"), HKEY("FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY") ); } } FreeStrBuf(&Tmp); } /* * Break out the data items requested, possibly a parenthesized list. * Returns the number of data items, or -1 if the list is invalid. * NOTE: this function alters the string it is fed, and uses it as a buffer * to hold the data for the pointers it returns. */ int imap_extract_data_items(citimap_command *Cmd) { int nArgs; int nest = 0; const char *pch, *end; /* Convert all whitespace to ordinary space characters. */ pch = ChrPtr(Cmd->CmdBuf); end = pch + StrLength(Cmd->CmdBuf); while (pch < end) { if (isspace(*pch)) StrBufPeek(Cmd->CmdBuf, pch, 0, ' '); pch++; } /* Strip leading and trailing whitespace, then strip leading and * trailing parentheses if it's a list */ StrBufTrim(Cmd->CmdBuf); pch = ChrPtr(Cmd->CmdBuf); if ( (pch[0]=='(') && (pch[StrLength(Cmd->CmdBuf)-1]==')') ) { StrBufCutRight(Cmd->CmdBuf, 1); StrBufCutLeft(Cmd->CmdBuf, 1); StrBufTrim(Cmd->CmdBuf); } /* Parse any macro data items */ imap_handle_macros(Cmd); /* * Now break out the data items. We throw in one trailing space in * order to avoid having to break out the last one manually. */ nArgs = StrLength(Cmd->CmdBuf) / 10 + 10; nArgs = CmdAdjust(Cmd, nArgs, 0); Cmd->num_parms = 0; Cmd->Params[Cmd->num_parms].Key = pch = ChrPtr(Cmd->CmdBuf); end = Cmd->Params[Cmd->num_parms].Key + StrLength(Cmd->CmdBuf); while (pch < end) { if ((*pch=='(') || (*pch=='[') || (*pch=='<') || (*pch=='{')) ++nest; else if ((*pch==')') || (*pch==']') || (*pch=='>') || (*pch=='}')) --nest; if ((nest <= 0) && (*pch==' ')) { StrBufPeek(Cmd->CmdBuf, pch, 0, '\0'); Cmd->Params[Cmd->num_parms].len = pch - Cmd->Params[Cmd->num_parms].Key; if (Cmd->num_parms + 1 >= Cmd->avail_parms) { nArgs = CmdAdjust(Cmd, nArgs * 2, 1); } Cmd->num_parms++; Cmd->Params[Cmd->num_parms].Key = ++pch; } else if (pch + 1 == end) { Cmd->Params[Cmd->num_parms].len = pch - Cmd->Params[Cmd->num_parms].Key + 1; Cmd->num_parms++; } pch ++; } return Cmd->num_parms; } /* * One particularly hideous aspect of IMAP is that we have to allow the client * to specify arbitrary ranges and/or sets of messages to fetch. Citadel IMAP * handles this by setting the IMAP_SELECTED flag for each message specified in * the ranges/sets, then looping through the message array, outputting messages * with the flag set. We don't bother returning an error if an out-of-range * number is specified (we just return quietly) because any client braindead * enough to request a bogus message number isn't going to notice the * difference anyway. * * This function clears out the IMAP_SELECTED bits, then sets that bit for each * message included in the specified range. * * Set is_uid to 1 to fetch by UID instead of sequence number. */ void imap_pick_range(const char *supplied_range, int is_uid) { citimap *Imap = IMAP; int i; int num_sets; int s; char setstr[SIZ], lostr[SIZ], histr[SIZ]; long lo, hi; char actual_range[SIZ]; /* * Handle the "ALL" macro */ if (!strcasecmp(supplied_range, "ALL")) { safestrncpy(actual_range, "1:*", sizeof actual_range); } else { safestrncpy(actual_range, supplied_range, sizeof actual_range); } /* * Clear out the IMAP_SELECTED flags for all messages. */ for (i = 0; i < Imap->num_msgs; ++i) { Imap->flags[i] = Imap->flags[i] & ~IMAP_SELECTED; } /* * Now set it for all specified messages. */ num_sets = num_tokens(actual_range, ','); for (s=0; s= 2) { extract_token(histr, setstr, 1, ':', sizeof histr); if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%ld", LONG_MAX); } else { safestrncpy(histr, lostr, sizeof histr); } lo = atol(lostr); hi = atol(histr); /* Loop through the array, flipping bits where appropriate */ for (i = 1; i <= Imap->num_msgs; ++i) { if (is_uid) { /* fetch by sequence number */ if ( (Imap->msgids[i-1]>=lo) && (Imap->msgids[i-1]<=hi)) { Imap->flags[i-1] |= IMAP_SELECTED; } } else { /* fetch by uid */ if ( (i>=lo) && (i<=hi)) { Imap->flags[i-1] |= IMAP_SELECTED; } } } } } /* * This function is called by the main command loop. */ void imap_fetch(int num_parms, ConstStr *Params) { citimap_command Cmd; int num_items; if (num_parms < 4) { IReply("BAD invalid parameters"); return; } imap_pick_range(Params[2].Key, 0); memset(&Cmd, 0, sizeof(citimap_command)); Cmd.CmdBuf = NewStrBufPlain(NULL, StrLength(IMAP->Cmd.CmdBuf)); MakeStringOf(Cmd.CmdBuf, 3); num_items = imap_extract_data_items(&Cmd); if (num_items < 1) { IReply("BAD invalid data item list"); FreeStrBuf(&Cmd.CmdBuf); free(Cmd.Params); return; } imap_do_fetch(&Cmd); IReply("OK FETCH completed"); FreeStrBuf(&Cmd.CmdBuf); free(Cmd.Params); } /* * This function is called by the main command loop. */ void imap_uidfetch(int num_parms, ConstStr *Params) { citimap_command Cmd; int num_items; int i; int have_uid_item = 0; if (num_parms < 5) { IReply("BAD invalid parameters"); return; } imap_pick_range(Params[3].Key, 1); memset(&Cmd, 0, sizeof(citimap_command)); Cmd.CmdBuf = NewStrBufPlain(NULL, StrLength(IMAP->Cmd.CmdBuf)); MakeStringOf(Cmd.CmdBuf, 4); #if 0 IMAP_syslog(LOG_DEBUG, "-------%s--------", ChrPtr(Cmd.CmdBuf)); #endif num_items = imap_extract_data_items(&Cmd); if (num_items < 1) { IReply("BAD invalid data item list"); FreeStrBuf(&Cmd.CmdBuf); free(Cmd.Params); return; } /* If the "UID" item was not included, we include it implicitly * (at the beginning) because this is a UID FETCH command */ for (i=0; i= Cmd.avail_parms) CmdAdjust(&Cmd, Cmd.avail_parms + 1, 1); memmove(&Cmd.Params[1], &Cmd.Params[0], sizeof(ConstStr) * Cmd.num_parms); Cmd.num_parms++; Cmd.Params[0] = (ConstStr){HKEY("UID")}; } imap_do_fetch(&Cmd); IReply("OK UID FETCH completed"); FreeStrBuf(&Cmd.CmdBuf); free(Cmd.Params); } citadel-9.01/modules/imap/serv_imap.h0000644000000000000000000001063212507024051016300 0ustar rootroot#define GLOBAL_UIDVALIDITY_VALUE 1L void imap_cleanup_function(void); void imap_greeting(void); void imap_command_loop(void); int imap_grabroom(char *returned_roomname, const char *foldername, int zapped_ok); void imap_free_transmitted_message(void); int imap_do_expunge(void); void imap_rescan_msgids(void); /* * FDELIM defines which character we want to use as a folder delimiter * in room names. Originally we used a forward slash, but that caused * rooms with names like "Sent/Received Pages" to get delimited, so we * changed it to a backslash. This is completely irrelevant to how Citadel * speaks to IMAP clients -- the delimiter used in the IMAP protocol is * a vertical bar, which is illegal in Citadel room names anyway. */ typedef void (*imap_handler)(int num_parms, ConstStr *Params); typedef struct _imap_handler_hook { imap_handler h; int Flags; } imap_handler_hook; typedef struct __citimap_command { StrBuf *CmdBuf; /* our current commandline; gets chopped into: */ ConstStr *Params; /* Commandline tokens */ int num_parms; /* Number of Commandline tokens available */ int avail_parms; /* Number of ConstStr args is big */ const imap_handler_hook *hh; } citimap_command; typedef struct __citimap { StrBuf *Reply; int authstate; char authseq[SIZ]; int selected; /* set to 1 if in the SELECTED state */ int readonly; /* mailbox is open read only */ int num_msgs; /* Number of messages being mapped */ int num_alloc; /* Number of messages for which we've allocated space */ time_t last_mtime; /* For checking whether the room was modified... */ long *msgids; unsigned int *flags; StrBuf *TransmittedMessage; /* for APPEND command... */ citimap_command Cmd; /* our current commandline */ /* Cache most recent RFC822 FETCH because client might load in pieces */ StrBuf *cached_rfc822; long cached_rfc822_msgnum; char cached_rfc822_withbody; /* 1 = body cached; 0 = only headers cached */ /* Cache most recent BODY FETCH because client might load in pieces */ char *cached_body; size_t cached_body_len; char cached_bodypart[SIZ]; long cached_bodymsgnum; char cached_body_withbody; /* 1 = body cached; 0 = only headers cached */ } citimap; /* * values of 'authstate' */ enum { imap_as_normal, imap_as_expecting_username, imap_as_expecting_password, imap_as_expecting_plainauth, imap_as_expecting_multilineusername, imap_as_expecting_multilinepassword }; /* Flags for the above struct. Note that some of these are for internal use, * and are not to be reported to IMAP clients. */ #define IMAP_ANSWERED 1 /* reportable and setable */ #define IMAP_FLAGGED 2 /* reportable and setable */ #define IMAP_DELETED 4 /* reportable and setable */ #define IMAP_DRAFT 8 /* reportable and setable */ #define IMAP_SEEN 16 /* reportable and setable */ #define IMAP_MASK_SETABLE 0x1f #define IMAP_MASK_SYSTEM 0xe0 #define IMAP_SELECTED 32 /* neither reportable nor setable */ #define IMAP_RECENT 64 /* reportable but not setable */ /* * Flags that may be returned by imap_roomname() * (the lower eight bits will be the floor number) */ #define IR_MAILBOX 0x0100 /* Mailbox */ #define IR_EXISTS 0x0200 /* Room exists (not implemented) */ #define IR_BABOON 0x0000 /* Just had to put this here :) */ #define FDELIM '\\' extern int IMAPDebugEnabled; #define IMAP ((citimap *)CC->session_specific_data) #define CCCIMAP ((citimap *)CCC->session_specific_data) #define IMAPDBGLOG(LEVEL) if ((LEVEL != LOG_DEBUG) || (IMAPDebugEnabled != 0)) #define CCCID CCC->cs_pid #define IMAP_syslog(LEVEL, FORMAT, ...) \ IMAPDBGLOG(LEVEL) syslog(LEVEL, \ "IMAP %s CC[%d] " FORMAT, \ IOSTR, CCCID, __VA_ARGS__) #define IMAPM_syslog(LEVEL, FORMAT) \ IMAPDBGLOG(LEVEL) syslog(LEVEL, \ "IMAP %s CC[%d] " FORMAT, \ IOSTR, CCCID) #define I_FLAG_NONE (0) #define I_FLAG_LOGGED_IN (1<<0) #define I_FLAG_SELECT (1<<1) /* RFC3501 says that we cannot output untagged data during these commands */ #define I_FLAG_UNTAGGED (1<<2) /* * When loading arrays of message ID's into memory, increase the buffer to * hold this many additional messages instead of calling realloc() each time. */ #define REALLOC_INCREMENT 100 void registerImapCMD(const char *First, long FLen, const char *Second, long SLen, imap_handler H, int Flags); #define RegisterImapCMD(First, Second, H, Flags) \ registerImapCMD(HKEY(First), HKEY(Second), H, Flags) citadel-9.01/modules/imap/imap_tools.c0000644000000000000000000005220612507024051016457 0ustar rootroot/* * Utility functions for the IMAP module. * * Copyright (c) 2001-2009 by the citadel.org team and others, except for * most of the UTF7 and UTF8 handling code which was lifted from Evolution. * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define SHOW_ME_VAPPEND_PRINTF #include #include #include #include #include #include #include #include "citadel.h" #include "sysdep_decls.h" #include "internet_addressing.h" #include "serv_imap.h" #include "imap_tools.h" #include "ctdl_module.h" /* String handling helpers */ /* This code uses some pretty nasty string manipulation. To make everything * manageable, we use this semi-high-level string manipulation API. Strings are * always \0-terminated, despite the fact that we keep track of the size. */ struct string { char* buffer; int maxsize; int size; }; static void string_init(struct string* s, char* buf, int bufsize) { s->buffer = buf; s->maxsize = bufsize-1; s->size = strlen(buf); } static char* string_end(struct string* s) { return s->buffer + s->size; } /* Append a UTF8 string of a particular length (in bytes). -1 to autocalculate. */ static void string_append_sn(struct string* s, char* p, int len) { if (len == -1) len = strlen(p); if ((s->size+len) > s->maxsize) len = s->maxsize - s->size; memcpy(s->buffer + s->size, p, len); s->size += len; s->buffer[s->size] = '\0'; } /* As above, always autocalculate. */ #define string_append_s(s, p) string_append_sn((s), (p), -1) /* Appends a UTF8 character --- which may make the size change by more than 1! * If the string overflows, the last character may become invalid. */ static void string_append_c(struct string* s, int c) { char UmlChar[5]; int len = 0; /* Don't do anything if there's no room. */ if (s->size == s->maxsize) return; if (c <= 0x7F) { /* This is the most common case, so we optimise it. */ s->buffer[s->size++] = c; s->buffer[s->size] = 0; return; } else if (c <= 0x7FF) { UmlChar[0] = 0xC0 | (c >> 6); UmlChar[1] = 0x80 | (c & 0x3F); len = 2; } else if (c <= 0xFFFF) { UmlChar[0] = 0xE0 | (c >> 12); UmlChar[1] = 0x80 | ((c >> 6) & 0x3f); UmlChar[2] = 0x80 | (c & 0x3f); len = 3; } else { UmlChar[0] = 0xf0 | c >> 18; UmlChar[1] = 0x80 | ((c >> 12) & 0x3f); UmlChar[2] = 0x80 | ((c >> 6) & 0x3f); UmlChar[3] = 0x80 | (c & 0x3f); len = 4; } string_append_sn(s, UmlChar, len); } /* Reads a UTF8 character from a char*, advancing the pointer. */ int utf8_getc(char** ptr) { unsigned char* p = (unsigned char*) *ptr; unsigned char c, r; int v, m; for (;;) { r = *p++; loop: if (r < 0x80) { *ptr = (char*) p; v = r; break; } else if (r < 0xf8) { /* valid start char? (max 4 octets) */ v = r; m = 0x7f80; /* used to mask out the length bits */ do { c = *p++; if ((c & 0xc0) != 0x80) { r = c; goto loop; } v = (v<<6) | (c & 0x3f); r<<=1; m<<=5; } while (r & 0x40); *ptr = (char*)p; v &= ~m; break; } } return v; } /* IMAP name safety */ /* IMAP has certain special requirements in its character set, which means we * have to do a fair bit of work to convert Citadel's UTF8 strings to IMAP * strings. The next two routines (and their data tables) do that. */ static char *utf7_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,"; static unsigned char utf7_rank[256] = { 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x3E,0x3F,0xFF,0xFF,0xFF, 0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E, 0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28, 0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32,0x33,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, }; /* Base64 helpers. */ static void utf7_closeb64(struct string* out, int v, int i) { int x; if (i > 0) { x = (v << (6-i)) & 0x3F; string_append_c(out, utf7_alphabet[x]); } string_append_c(out, '-'); } /* Convert from a Citadel name to an IMAP-safe name. Returns the end * of the destination. */ static char* toimap(char* destp, char* destend, char* src) { struct string dest; int state = 0; int v = 0; int i = 0; *destp = 0; string_init(&dest, destp, destend-destp); /* IMAP_syslog(LOG_DEBUG, "toimap %s", src); */ for (;;) { int c = utf8_getc(&src); if (c == '\0') break; if (c >= 0x20 && c <= 0x7e) { if (state == 1) { utf7_closeb64(&dest, v, i); state = 0; i = 0; } switch (c) { case '&': string_append_sn(&dest, "&-", 2); break; case '/': /* Citadel extension: / becomes |, because / * isn't valid as part of an IMAP name. */ c = '|'; goto defaultcase; case '\\': /* Citadel extension: backslashes mark folder * seperators in the IMAP subfolder emulation * hack. We turn them into / characters, * *except* if it's the last character in the * string. */ if (*src != '\0') c = '/'; /* fall through */ default: defaultcase: string_append_c(&dest, c); } } else { if (state == 0) { string_append_c(&dest, '&'); state = 1; } v = (v << 16) | c; i += 16; while (i >= 6) { int x = (v >> (i-6)) & 0x3f; string_append_c(&dest, utf7_alphabet[x]); i -= 6; } } } if (state == 1) utf7_closeb64(&dest, v, i); /* IMAP_syslog(LOG_DEBUG, " -> %s", destp); */ return string_end(&dest); } /* Convert from an IMAP-safe name back into a Citadel name. Returns the end of the destination. */ static int cfrommap(int c); static char* fromimap(char* destp, char* destend, const char* src) { struct string dest; unsigned const char *p = (unsigned const char*) src; int v = 0; int i = 0; int state = 0; int c; *destp = 0; string_init(&dest, destp, destend-destp); /* IMAP_syslog(LOG_DEBUG, "fromimap %s", src); */ do { c = *p++; switch (state) { case 0: /* US-ASCII characters. */ if (c == '&') state = 1; else string_append_c(&dest, cfrommap(c)); break; case 1: if (c == '-') { string_append_c(&dest, '&'); state = 0; } else if (utf7_rank[c] != 0xff) { v = utf7_rank[c]; i = 6; state = 2; } else { /* invalid char */ string_append_sn(&dest, "&-", 2); state = 0; } break; case 2: if (c == '-') state = 0; else if (utf7_rank[c] != 0xFF) { v = (v<<6) | utf7_rank[c]; i += 6; if (i >= 16) { int x = (v >> (i-16)) & 0xFFFF; string_append_c(&dest, cfrommap(x)); i -= 16; } } else { string_append_c(&dest, cfrommap(c)); state = 0; } break; } } while (c != '\0'); /* IMAP_syslog(LOG_DEBUG, " -> %s", destp); */ return string_end(&dest); } /* Undoes the special character conversion. */ static int cfrommap(int c) { switch (c) { case '|': return '/'; case '/': return '\\'; } return c; } /* Break a command down into tokens, unquoting any escaped characters. */ void MakeStringOf(StrBuf *Buf, int skip) { int i; citimap_command *Cmd = &IMAP->Cmd; for (i=skip; inum_parms; ++i) { StrBufAppendBufPlain(Buf, Cmd->Params[i].Key, Cmd->Params[i].len, 0); if (i < (Cmd->num_parms-1)) StrBufAppendBufPlain(Buf, HKEY(" "), 0); } } void TokenCutRight(citimap_command *Cmd, ConstStr *CutMe, int n) { const char *CutAt; if (CutMe->len < n) { CutAt = CutMe->Key; CutMe->len = 0; } else { CutAt = CutMe->Key + CutMe->len - n; CutMe->len -= n; } StrBufPeek(Cmd->CmdBuf, CutAt, -1, '\0'); } void TokenCutLeft(citimap_command *Cmd, ConstStr *CutMe, int n) { if (CutMe->len < n) { CutMe->Key += CutMe->len; CutMe->len = 0; } else { CutMe->Key += n; CutMe->len -= n; } } int CmdAdjust(citimap_command *Cmd, int nArgs, int Realloc) { ConstStr *Params; if (nArgs > Cmd->avail_parms) { Params = (ConstStr*) malloc(sizeof(ConstStr) * nArgs); if (Realloc) { memcpy(Params, Cmd->Params, sizeof(ConstStr) * Cmd->avail_parms); memset(Cmd->Params + sizeof(ConstStr) * Cmd->avail_parms, 0, sizeof(ConstStr) * nArgs - sizeof(ConstStr) * Cmd->avail_parms ); } else { Cmd->num_parms = 0; memset(Params, 0, sizeof(ConstStr) * nArgs); } Cmd->avail_parms = nArgs; if (Cmd->Params != NULL) free (Cmd->Params); Cmd->Params = Params; } else { if (!Realloc) { memset(Cmd->Params, 0, sizeof(ConstStr) * Cmd->avail_parms); Cmd->num_parms = 0; } } return Cmd->avail_parms; } int imap_parameterize(citimap_command *Cmd) { int nArgs; const char *In, *End; In = ChrPtr(Cmd->CmdBuf); End = In + StrLength(Cmd->CmdBuf); /* we start with 10 chars per arg, maybe we need to realloc later. */ nArgs = StrLength(Cmd->CmdBuf) / 10 + 10; nArgs = CmdAdjust(Cmd, nArgs, 0); while (In < End) { /* Skip whitespace. */ while (isspace(*In)) In++; if (*In == '\0') break; /* Found the start of a token. */ Cmd->Params[Cmd->num_parms].Key = In; /* Read in the token. */ for (;;) { if (isspace(*In)) break; if (*In == '\"') { /* Found a quoted section. */ Cmd->Params[Cmd->num_parms].Key++; //In++; for (;;) { In++; if (*In == '\"') { StrBufPeek(Cmd->CmdBuf, In, -1, '\0'); break; } else if (*In == '\\') In++; if (*In == '\0') { Cmd->Params[Cmd->num_parms].len = In - Cmd->Params[Cmd->num_parms].Key; Cmd->num_parms++; return Cmd->num_parms; } } break; } else if (*In == '\\') { In++; } if (*In == '\0') { Cmd->Params[Cmd->num_parms].len = In - Cmd->Params[Cmd->num_parms].Key; Cmd->num_parms++; return Cmd->num_parms; } In++; } StrBufPeek(Cmd->CmdBuf, In, -1, '\0'); Cmd->Params[Cmd->num_parms].len = In - Cmd->Params[Cmd->num_parms].Key; if (Cmd->num_parms + 1 >= Cmd->avail_parms) { nArgs = CmdAdjust(Cmd, nArgs * 2, 1); } Cmd->num_parms ++; In++; } return Cmd->num_parms; } /* Convert a struct ctdlroom to an IMAP-compatible mailbox name. */ long imap_mailboxname(char *buf, int bufsize, struct ctdlroom *qrbuf) { char* bufend = buf+bufsize; struct floor *fl; char* p = buf; const char *pend; /* For mailboxes, just do it straight. * Do the Cyrus-compatible thing: all private folders are * subfolders of INBOX. */ if (qrbuf->QRflags & QR_MAILBOX) { if (strcasecmp(qrbuf->QRname+11, MAILROOM) == 0) { pend = toimap(p, bufend, "INBOX"); return pend - buf; } else { p = toimap(p, bufend, "INBOX"); if (p < bufend) *p++ = '/'; pend = toimap(p, bufend, qrbuf->QRname+11); return pend - buf; } } else { /* Otherwise, prefix the floor name as a "public folders" moniker. */ fl = CtdlGetCachedFloor(qrbuf->QRfloor); p = toimap(p, bufend, fl->f_name); if (p < bufend) *p++ = '/'; pend = toimap(p, bufend, qrbuf->QRname); return pend - buf; } } /* * Convert an inputted folder name to our best guess as to what an equivalent * room name should be. * * If an error occurs, it returns -1. Otherwise... * * The lower eight bits of the return value are the floor number on which the * room most likely resides. The upper eight bits may contain flags, * including IR_MAILBOX if we're dealing with a personal room. * */ int imap_roomname(char *rbuf, int bufsize, const char *foldername) { struct CitContext *CCC = CC; int levels; char floorname[ROOMNAMELEN*2]; char roomname[ROOMNAMELEN]; int i; struct floor *fl; int ret = (-1); if (foldername == NULL) return(-1); /* Unmunge the entire string into the output buffer. */ fromimap(rbuf, rbuf+bufsize, foldername); /* Is this an IMAP inbox? */ if (strncasecmp(rbuf, "INBOX", 5) == 0) { if (rbuf[5] == 0) { /* It's the system inbox. */ safestrncpy(rbuf, MAILROOM, bufsize); ret = (0 | IR_MAILBOX); goto exit; } else if (rbuf[5] == FDELIM) { /* It's another personal mail folder. */ safestrncpy(rbuf, rbuf+6, bufsize); ret = (0 | IR_MAILBOX); goto exit; } /* If we get here, the folder just happens to start with INBOX * --- fall through. */ } /* Is this a multi-level room name? */ levels = num_tokens(rbuf, FDELIM); if (levels > 1) { long len; /* Extract the main room name. */ len = extract_token(floorname, rbuf, 0, FDELIM, sizeof floorname); if (len < 0) len = 0; safestrncpy(roomname, &rbuf[len + 1], sizeof(roomname)); /* Try and find it on any floor. */ for (i = 0; i < MAXFLOORS; ++i) { fl = CtdlGetCachedFloor(i); if (fl->f_flags & F_INUSE) { if (strcasecmp(floorname, fl->f_name) == 0) { /* Got it! */ safestrncpy(rbuf, roomname, bufsize); ret = i; goto exit; } } } } /* Meh. It's either not a multi-level room name, or else we * couldn't find it. */ ret = (0 | IR_MAILBOX); exit: IMAP_syslog(LOG_DEBUG, "(That translates to \"%s\")", rbuf); return(ret); } /* * Determine whether the supplied string is a valid message set. * If the string contains only numbers, colons, commas, and asterisks, * return 1 for a valid message set. If any other character is found, * return 0. */ int imap_is_message_set(const char *buf) { int i; if (buf == NULL) return (0); /* stupidity checks */ if (IsEmptyStr(buf)) return (0); if (!strcasecmp(buf, "ALL")) return (1); /* macro? why? */ for (i = 0; buf[i]; ++i) { /* now start the scan */ if ( (!isdigit(buf[i])) && (buf[i] != ':') && (buf[i] != ',') && (buf[i] != '*') ) return (0); } return (1); /* looks like we're good */ } /* * imap_match.c, based on wildmat.c from INN * hacked for Citadel/IMAP by Daniel Malament */ /* note: not all return statements use these; don't change them */ #define WILDMAT_TRUE 1 #define WILDMAT_FALSE 0 #define WILDMAT_ABORT -1 #define WILDMAT_DELIM '/' /* * Match text and p, return TRUE, FALSE, or ABORT. */ static int do_imap_match(const char *supplied_text, const char *supplied_p) { int matched, i; char lcase_text[SIZ], lcase_p[SIZ]; char *text; char *p; /* Copy both strings and lowercase them, in order to * make this entire operation case-insensitive. */ for (i=0; ((supplied_text[i] != '\0') && (i < sizeof(lcase_text))); ++i) lcase_text[i] = tolower(supplied_text[i]); lcase_text[i] = '\0'; for (i=0; ((supplied_p[i] != '\0') && (i < sizeof(lcase_p))); ++i) lcase_p[i] = tolower(supplied_p[i]); lcase_p[i] = '\0'; /* Start matching */ for (p = lcase_p, text = lcase_text; !IsEmptyStr(p) && !IsEmptyStr(text); text++, p++) { if ((*text == '\0') && (*p != '*') && (*p != '%')) { return WILDMAT_ABORT; } switch (*p) { default: if (*text != *p) { return WILDMAT_FALSE; } continue; case '*': star: while (++p, ((*p == '*') || (*p == '%'))) { /* Consecutive stars or %'s act * just like one star. */ continue; } if (*p == '\0') { /* Trailing star matches everything. */ return WILDMAT_TRUE; } while (*text) { if ((matched = do_imap_match(text++, p)) != WILDMAT_FALSE) { return matched; } } return WILDMAT_ABORT; case '%': while (++p, (!IsEmptyStr(p) && ((*p == '*') || (*p == '%')))) { /* Consecutive %'s act just like one, but even * a single star makes the sequence act like * one star, instead. */ if (*p == '*') { goto star; } continue; } if (*p == '\0') { /* * Trailing % matches everything * without a delimiter. */ while (!IsEmptyStr(text)) { if (*text == WILDMAT_DELIM) { return WILDMAT_FALSE; } text++; } return WILDMAT_TRUE; } while (!IsEmptyStr(text) && /* make shure texst - 1 isn't before lcase_p */ ((text == lcase_text) || (*(text - 1) != WILDMAT_DELIM))) { if ((matched = do_imap_match(text++, p)) != WILDMAT_FALSE) { return matched; } } return WILDMAT_ABORT; } } if ((*text == '\0') && (*p == '\0')) return WILDMAT_TRUE; else return WILDMAT_FALSE; } /* * Support function for mailbox pattern name matching in LIST and LSUB * Returns nonzero if the supplied mailbox name matches the supplied pattern. */ int imap_mailbox_matches_pattern(const char *pattern, char *mailboxname) { /* handle just-star case quickly */ if ((pattern[0] == '*') && (pattern[1] == '\0')) { return WILDMAT_TRUE; } return (do_imap_match(mailboxname, pattern) == WILDMAT_TRUE); } /* * Compare an IMAP date string (date only, no time) to the date found in * a Unix timestamp. */ int imap_datecmp(const char *datestr, time_t msgtime) { char daystr[256]; char monthstr[256]; char yearstr[256]; int i; int day, month, year; int msgday, msgmonth, msgyear; struct tm msgtm; char *imap_datecmp_ascmonths[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; if (datestr == NULL) return(0); /* Expecting a date in the form dd-Mmm-yyyy */ extract_token(daystr, datestr, 0, '-', sizeof daystr); extract_token(monthstr, datestr, 1, '-', sizeof monthstr); extract_token(yearstr, datestr, 2, '-', sizeof yearstr); day = atoi(daystr); year = atoi(yearstr); month = 0; for (i=0; i<12; ++i) { if (!strcasecmp(monthstr, imap_datecmp_ascmonths[i])) { month = i; } } /* Extract day/month/year from message timestamp */ localtime_r(&msgtime, &msgtm); msgday = msgtm.tm_mday; msgmonth = msgtm.tm_mon; msgyear = msgtm.tm_year + 1900; /* Now start comparing */ if (year < msgyear) return(+1); if (year > msgyear) return(-1); if (month < msgmonth) return(+1); if (month > msgmonth) return(-1); if (day < msgday) return(+1); if (day > msgday) return(-1); return(0); } void IAPrintf(const char *Format, ...) { va_list arg_ptr; va_start(arg_ptr, Format); StrBufVAppendPrintf(IMAP->Reply, Format, arg_ptr); va_end(arg_ptr); } void iaputs(const char *Str, long Len) { StrBufAppendBufPlain(IMAP->Reply, Str, Len, 0); } void ireply(const char *Msg, long len) { citimap *Imap = IMAP; StrBufAppendBufPlain(Imap->Reply, CKEY(Imap->Cmd.Params[0]), 0); StrBufAppendBufPlain(Imap->Reply, HKEY(" "), 0); StrBufAppendBufPlain(Imap->Reply, Msg, len, 0); StrBufAppendBufPlain(Imap->Reply, HKEY("\r\n"), 0); } void IReplyPrintf(const char *Format, ...) { citimap *Imap = IMAP; va_list arg_ptr; StrBufAppendBufPlain(Imap->Reply, CKEY(Imap->Cmd.Params[0]), 0); StrBufAppendBufPlain(Imap->Reply, HKEY(" "), 0); va_start(arg_ptr, Format); StrBufVAppendPrintf(IMAP->Reply, Format, arg_ptr); va_end(arg_ptr); StrBufAppendBufPlain(Imap->Reply, HKEY("\r\n"), 0); } /* Output a string to the IMAP client, either as a literal or quoted. * (We do a literal if it has any double-quotes or backslashes.) */ void IPutStr(const char *Msg, long Len) { int i; int is_literal = 0; citimap *Imap = IMAP; if ((Msg == NULL) || (Len == 0)) { /* yeah, we handle this */ StrBufAppendBufPlain(Imap->Reply, HKEY("NIL"), 0); return; } for (i = 0; i < Len; ++i) { if ((Msg[i] == '\"') || (Msg[i] == '\\')) is_literal = 1; } if (is_literal) { StrBufAppendPrintf(Imap->Reply, "{%ld}\r\n", Len); StrBufAppendBufPlain(Imap->Reply, Msg, Len, 0); } else { StrBufAppendBufPlain(Imap->Reply, HKEY("\""), 0); StrBufAppendBufPlain(Imap->Reply, Msg, Len, 0); StrBufAppendBufPlain(Imap->Reply, HKEY("\""), 0); } } void IUnbuffer (void) { citimap *Imap = IMAP; cputbuf(Imap->Reply); FlushStrBuf(Imap->Reply); } citadel-9.01/modules/imap/imap_acl.h0000644000000000000000000000135112507024051016056 0ustar rootroot/* * Copyright (c) 2007-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * */ void imap_setacl(int num_parms, ConstStr *Params); void imap_deleteacl(int num_parms, ConstStr *Params); void imap_getacl(int num_parms, ConstStr *Params); void imap_listrights(int num_parms, ConstStr *Params); void imap_myrights(int num_parms, ConstStr *Params); citadel-9.01/modules/imap/imap_store.h0000644000000000000000000000112212507024051016447 0ustar rootroot/* * Copyright (c) 2001-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * */ void imap_store(int num_parms, ConstStr *Params); void imap_uidstore(int num_parms, ConstStr *Params); citadel-9.01/modules/imap/imap_list.c0000644000000000000000000002644412507024051016277 0ustar rootroot/* * Implements the LIST and LSUB commands. * * Copyright (c) 2000-2009 by Art Cancro and others. * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "sysdep.h" #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include "citadel.h" #include "server.h" #include "sysdep_decls.h" #include "citserver.h" #include "support.h" #include "config.h" #include "user_ops.h" #include "database.h" #include "msgbase.h" #include "internet_addressing.h" #include "serv_imap.h" #include "imap_tools.h" #include "imap_fetch.h" #include "imap_search.h" #include "imap_store.h" #include "imap_acl.h" #include "imap_misc.h" #include "imap_list.h" #include "ctdl_module.h" typedef struct __ImapRoomListFilter { char verb[16]; int subscribed_rooms_only; int return_subscribed; int return_children; int num_patterns; int num_patterns_avail; StrBuf **patterns; }ImapRoomListFilter; /* * Used by LIST and LSUB to show the floors in the listing */ void imap_list_floors(char *verb, int num_patterns, StrBuf **patterns) { int i; struct floor *fl; int j = 0; int match = 0; for (i = 0; i < MAXFLOORS; ++i) { fl = CtdlGetCachedFloor(i); if (fl->f_flags & F_INUSE) { match = 0; for (j=0; jf_name)) { match = 1; } } if (match) { IAPrintf("* %s (\\NoSelect \\HasChildren) \"/\" ", verb); IPutStr(fl->f_name, (fl->f_name)?strlen(fl->f_name):0); IAPuts("\r\n"); } } } } /* * Back end for imap_list() * * Implementation note: IMAP "subscribed folder" is equivalent to Citadel "known room" * * The "user data" field is actually an array of pointers; see below for the breakdown * */ void imap_listroom(struct ctdlroom *qrbuf, void *data) { #define SUBSCRIBED_STR "\\Subscribed" #define HASCHILD_STR "\\HasChildren" char MailboxName[SIZ]; char return_options[256]; int ra; int yes_output_this_room; ImapRoomListFilter *ImapFilter; int i = 0; int match = 0; int ROLen; /* Here's how we break down the array of pointers passed to us */ ImapFilter = (ImapRoomListFilter*)data; /* Only list rooms to which the user has access!! */ yes_output_this_room = 0; *return_options = '\0'; ROLen = 0; CtdlRoomAccess(qrbuf, &CC->user, &ra, NULL); if (ImapFilter->return_subscribed) { if (ra & UA_KNOWN) { memcpy(return_options, HKEY(SUBSCRIBED_STR) + 1); ROLen += sizeof(SUBSCRIBED_STR) - 1; } } /* Warning: ugly hack. * We don't have any way to determine the presence of child mailboxes * without refactoring this entire module. So we're just going to return * the \HasChildren attribute for every room. * We'll fix this later when we have time. */ if (ImapFilter->return_children) { if (!IsEmptyStr(return_options)) { memcpy(return_options + ROLen, HKEY(" ")); ROLen ++; } memcpy(return_options + ROLen, HKEY(SUBSCRIBED_STR) + 1); } if (ImapFilter->subscribed_rooms_only) { if (ra & UA_KNOWN) { yes_output_this_room = 1; } } else { if ((ra & UA_KNOWN) || ((ra & UA_GOTOALLOWED) && (ra & UA_ZAPPED))) { yes_output_this_room = 1; } } if (yes_output_this_room) { long len; len = imap_mailboxname(MailboxName, sizeof MailboxName, qrbuf); match = 0; for (i=0; inum_patterns; ++i) { if (imap_mailbox_matches_pattern(ChrPtr(ImapFilter->patterns[i]), MailboxName)) { match = 1; } } if (match) { IAPrintf("* %s (%s) \"/\" ", ImapFilter->verb, return_options); IPutStr(MailboxName, len); IAPuts("\r\n"); } } } /* * Implements the LIST and LSUB commands */ void imap_list(int num_parms, ConstStr *Params) { struct CitContext *CCC = CC; citimap *Imap = CCCIMAP; int i, j, paren_nest; ImapRoomListFilter ImapFilter; int selection_left = (-1); int selection_right = (-1); int return_left = (-1); int root_pos = 2; int patterns_left = 3; int patterns_right = 3; int extended_list_in_use = 0; if (num_parms < 4) { IReply("BAD arguments invalid"); return; } ImapFilter.num_patterns = 1; ImapFilter.return_subscribed = 0; ImapFilter.return_children = 0; ImapFilter.subscribed_rooms_only = 0; /* parms[1] is the IMAP verb being used (e.g. LIST or LSUB) * This tells us how to behave, and what verb to return back to the caller */ safestrncpy(ImapFilter.verb, Params[1].Key, sizeof ImapFilter.verb); j = Params[1].len; for (i=0; i 0) && (selection_right >= selection_left)) { /* Strip off the outer parentheses */ if (Params[selection_left].Key[0] == '(') { TokenCutLeft(&Imap->Cmd, &Params[selection_left], 1); } if (Params[selection_right].Key[Params[selection_right].len-1] == ')') { TokenCutRight(&Imap->Cmd, &Params[selection_right], 1); } for (i=selection_left; i<=selection_right; ++i) { if (!strcasecmp(Params[i].Key, "SUBSCRIBED")) { ImapFilter.subscribed_rooms_only = 1; } else if (!strcasecmp(Params[i].Key, "RECURSIVEMATCH")) { /* FIXME - do this! */ } } } /* The folder root appears immediately after the selection options, * or in position 2 if no selection options were specified. */ ImapFilter.num_patterns_avail = num_parms + 1; ImapFilter.patterns = malloc(ImapFilter.num_patterns_avail * sizeof(StrBuf*)); memset(ImapFilter.patterns, 0, ImapFilter.num_patterns_avail * sizeof(StrBuf*)); patterns_left = root_pos + 1; patterns_right = root_pos + 1; if (Params[patterns_left].Key[0] == '(') { extended_list_in_use = 1; paren_nest = 0; for (i=patterns_left; i 1) StrBufAppendBufPlain(ImapFilter.patterns[i], 1 + CKEY(Params[root_pos]) - 1, 0); } else StrBufAppendBufPlain(ImapFilter.patterns[i], CKEY(Params[root_pos]), 0); if (i == ImapFilter.num_patterns-1) { if (Params[patterns_left+i].len > 1) StrBufAppendBufPlain(ImapFilter.patterns[i], CKEY(Params[patterns_left+i]) - 1, 0); } else StrBufAppendBufPlain(ImapFilter.patterns[i], CKEY(Params[patterns_left+i]), 0); } } } else { ImapFilter.num_patterns = 1; ImapFilter.patterns[0] = NewStrBufPlain(NULL, Params[root_pos].len + Params[patterns_left].len); StrBufAppendBufPlain(ImapFilter.patterns[0], CKEY(Params[root_pos]), 0); StrBufAppendBufPlain(ImapFilter.patterns[0], CKEY(Params[patterns_left]), 0); } /* If the word "RETURN" appears after the folder pattern list, then the client * is specifying return options. */ if (num_parms - patterns_right > 2) if (!strcasecmp(Params[patterns_right+1].Key, "RETURN")) { return_left = patterns_right + 2; extended_list_in_use = 1; paren_nest = 0; for (i=return_left; iCmd, &Params[i], 1); if (Params[i].Key[Params[i].len-1] == ')') TokenCutRight(&Imap->Cmd, &Params[i], 1); IMAP_syslog(LOG_DEBUG, "evaluating <%s>", Params[i].Key); if (!strcasecmp(Params[i].Key, "SUBSCRIBED")) { ImapFilter.return_subscribed = 1; } else if (!strcasecmp(Params[i].Key, "CHILDREN")) { ImapFilter.return_children = 1; } if (paren_nest == 0) { i = num_parms + 1; /* break out of the loop */ } } } /* Now start setting up the data we're going to send to the CtdlForEachRoom() callback. */ /* The non-extended LIST command is required to treat an empty * ("" string) mailbox name argument as a special request to return the * hierarchy delimiter and the root name of the name given in the * reference parameter. */ if ( (StrLength(ImapFilter.patterns[0]) == 0) && (extended_list_in_use == 0) ) { IAPrintf("* %s (\\Noselect) \"/\" \"\"\r\n", ImapFilter.verb); } /* Non-empty mailbox names, and any form of the extended LIST command, * is handled by this loop. */ else { imap_list_floors(ImapFilter.verb, ImapFilter.num_patterns, ImapFilter.patterns); CtdlForEachRoom(imap_listroom, (char**)&ImapFilter); } /* * Free the pattern buffers we allocated above. */ for (i=0; i #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include "citadel.h" #include "server.h" #include "sysdep_decls.h" #include "citserver.h" #include "support.h" #include "config.h" #include "user_ops.h" #include "database.h" #include "room_ops.h" #include "msgbase.h" #include "internet_addressing.h" #include "serv_imap.h" #include "imap_tools.h" #include "imap_fetch.h" #include "imap_store.h" #include "genstamp.h" /* * imap_do_store() calls imap_do_store_msg() to tweak the settings of * an individual message. * * We also implement the ".SILENT" protocol option here. :( */ void imap_do_store_msg(int seq, const char *oper, unsigned int bits_to_twiddle) { citimap *Imap = IMAP; if (!strncasecmp(oper, "FLAGS", 5)) { Imap->flags[seq] &= IMAP_MASK_SYSTEM; Imap->flags[seq] |= bits_to_twiddle; } else if (!strncasecmp(oper, "+FLAGS", 6)) { Imap->flags[seq] |= bits_to_twiddle; } else if (!strncasecmp(oper, "-FLAGS", 6)) { Imap->flags[seq] &= (~bits_to_twiddle); } } /* * imap_store() calls imap_do_store() to perform the actual bit twiddling * on the flags. */ void imap_do_store(citimap_command *Cmd) { int i, j; unsigned int bits_to_twiddle = 0; const char *oper; char flag[32]; char whichflags[256]; char num_flags; int silent = 0; long *ss_msglist; int num_ss = 0; int last_item_twiddled = (-1); citimap *Imap = IMAP; if (Cmd->num_parms < 2) return; oper = Cmd->Params[0].Key; if (cbmstrcasestr(oper, ".SILENT")) { silent = 1; } /* * ss_msglist is an array of message numbers to manipulate. We * are going to supply this array to CtdlSetSeen() later. */ ss_msglist = malloc(Imap->num_msgs * sizeof(long)); if (ss_msglist == NULL) return; /* * Ok, go ahead and parse the flags. */ for (i=1; inum_parms; ++i) {///TODO: why strcpy? strcpy(whichflags, Cmd->Params[i].Key); if (whichflags[0]=='(') { safestrncpy(whichflags, &whichflags[1], sizeof whichflags); } if (whichflags[strlen(whichflags)-1]==')') { whichflags[strlen(whichflags)-1]=0; } striplt(whichflags); /* A client might twiddle more than one bit at a time. * Note that we check for the flag names without the leading * backslash because imap_parameterize() strips them out. */ num_flags = num_tokens(whichflags, ' '); for (j=0; jnum_msgs > 0) { for (i = 0; i < Imap->num_msgs; ++i) { if (Imap->flags[i] & IMAP_SELECTED) { last_item_twiddled = i; ss_msglist[num_ss++] = Imap->msgids[i]; imap_do_store_msg(i, oper, bits_to_twiddle); if (!silent) { IAPrintf("* %d FETCH (", i+1); imap_fetch_flags(i); IAPuts(")\r\n"); } } } } /* * Now manipulate the database -- all in one shot. */ if ( (last_item_twiddled >= 0) && (num_ss > 0) ) { if (bits_to_twiddle & IMAP_SEEN) { CtdlSetSeen(ss_msglist, num_ss, ((Imap->flags[last_item_twiddled] & IMAP_SEEN) ? 1 : 0), ctdlsetseen_seen, NULL, NULL ); } if (bits_to_twiddle & IMAP_ANSWERED) { CtdlSetSeen(ss_msglist, num_ss, ((Imap->flags[last_item_twiddled] & IMAP_ANSWERED) ? 1 : 0), ctdlsetseen_answered, NULL, NULL ); } } free(ss_msglist); imap_do_expunge(); // Citadel always expunges immediately. imap_rescan_msgids(); } /* * This function is called by the main command loop. */ void imap_store(int num_parms, ConstStr *Params) { citimap_command Cmd; int num_items; if (num_parms < 3) { IReply("BAD invalid parameters"); return; } if (imap_is_message_set(Params[2].Key)) { imap_pick_range(Params[2].Key, 0); } else { IReply("BAD invalid parameters"); return; } memset(&Cmd, 0, sizeof(citimap_command)); Cmd.CmdBuf = NewStrBufPlain(NULL, StrLength(IMAP->Cmd.CmdBuf)); MakeStringOf(Cmd.CmdBuf, 3); num_items = imap_extract_data_items(&Cmd); if (num_items < 1) { IReply("BAD invalid data item list"); FreeStrBuf(&Cmd.CmdBuf); free(Cmd.Params); return; } imap_do_store(&Cmd); IReply("OK STORE completed"); FreeStrBuf(&Cmd.CmdBuf); free(Cmd.Params); } /* * This function is called by the main command loop. */ void imap_uidstore(int num_parms, ConstStr *Params) { citimap_command Cmd; int num_items; if (num_parms < 4) { IReply("BAD invalid parameters"); return; } if (imap_is_message_set(Params[3].Key)) { imap_pick_range(Params[3].Key, 1); } else { IReply("BAD invalid parameters"); return; } memset(&Cmd, 0, sizeof(citimap_command)); Cmd.CmdBuf = NewStrBufPlain(NULL, StrLength(IMAP->Cmd.CmdBuf)); MakeStringOf(Cmd.CmdBuf, 4); num_items = imap_extract_data_items(&Cmd); if (num_items < 1) { IReply("BAD invalid data item list"); FreeStrBuf(&Cmd.CmdBuf); free(Cmd.Params); return; } imap_do_store(&Cmd); IReply("OK UID STORE completed"); FreeStrBuf(&Cmd.CmdBuf); free(Cmd.Params); } citadel-9.01/modules/imap/imap_acl.c0000644000000000000000000001774412507024051016066 0ustar rootroot/* * Functions which implement RFC2086 (and maybe RFC4314) (IMAP ACL extension) * * * Copyright (c) 2007-2011 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "sysdep.h" #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include "citadel.h" #include "server.h" #include "sysdep_decls.h" #include "citserver.h" #include "support.h" #include "config.h" #include "user_ops.h" #include "database.h" #include "msgbase.h" #include "internet_addressing.h" #include "serv_imap.h" #include "imap_tools.h" #include "imap_fetch.h" #include "imap_misc.h" #include "genstamp.h" #include "ctdl_module.h" /* * Implements the SETACL command. */ void imap_setacl(int num_parms, ConstStr *Params) { IReply("BAD not yet implemented FIXME"); return; } /* * Implements the DELETEACL command. */ void imap_deleteacl(int num_parms, ConstStr *Params) { IReply("BAD not yet implemented FIXME"); return; } /* * Given the bits returned by CtdlRoomAccess(), populate a string buffer * with IMAP ACL format flags. This code is common to GETACL and MYRIGHTS. */ void imap_acl_flags(StrBuf *rights, int ra) { FlushStrBuf(rights); /* l - lookup (mailbox is visible to LIST/LSUB commands) * r - read (SELECT the mailbox, perform STATUS et al) * s - keep seen/unseen information across sessions (STORE SEEN flag) */ if ( (ra & UA_KNOWN) /* known rooms */ || ((ra & UA_GOTOALLOWED) && (ra & UA_ZAPPED)) /* zapped rooms */ ) { StrBufAppendBufPlain(rights, HKEY("l"), 0); StrBufAppendBufPlain(rights, HKEY("r"), 0); StrBufAppendBufPlain(rights, HKEY("s"), 0); /* Only output the remaining flags if the room is known */ /* w - write (set or clear flags other than SEEN or DELETED, not supported in Citadel */ /* i - insert (perform APPEND, COPY into mailbox) */ /* p - post (send mail to submission address for mailbox - not enforced) */ /* c - create (CREATE new sub-mailboxes) */ if (ra & UA_POSTALLOWED) { StrBufAppendBufPlain(rights, HKEY("i"), 0); StrBufAppendBufPlain(rights, HKEY("p"), 0); StrBufAppendBufPlain(rights, HKEY("c"), 0); } /* d - delete messages (STORE DELETED flag, perform EXPUNGE) */ if (ra & UA_DELETEALLOWED) { StrBufAppendBufPlain(rights, HKEY("d"), 0); } /* a - administer (perform SETACL/DELETEACL/GETACL/LISTRIGHTS) */ if (ra & UA_ADMINALLOWED) { /* * This is the correct place to put the "a" flag. We are leaving * it commented out for now, because it implies that we could * perform any of SETACL/DELETEACL/GETACL/LISTRIGHTS. Since these * commands are not yet implemented, omitting the flag should * theoretically prevent compliant clients from attempting to * perform them. * * StrBufAppendBufPlain(rights, HKEY("a"), 0); */ } } } /* * Implements the GETACL command. */ void imap_getacl(int num_parms, ConstStr *Params) { char roomname[ROOMNAMELEN]; char savedroom[ROOMNAMELEN]; int msgs, new; int ret; struct ctdluser temp; struct cdbdata *cdbus; int ra; StrBuf *rights; if (num_parms != 3) { IReply("BAD usage error"); return; } /* * Search for the specified room or folder */ ret = imap_grabroom(roomname, Params[2].Key, 1); if (ret != 0) { IReply("NO Invalid mailbox name or access denied"); return; } /* * CtdlUserGoto() formally takes us to the desired room. (If another * folder is selected, save its name so we can return there!!!!!) */ if (IMAP->selected) { strcpy(savedroom, CC->room.QRname); } CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL); IAPuts("* ACL "); IPutCParamStr(2); /* * Traverse the userlist */ rights = NewStrBuf(); cdb_rewind(CDB_USERS); while (cdbus = cdb_next_item(CDB_USERS), cdbus != NULL) { memset(&temp, 0, sizeof temp); memcpy(&temp, cdbus->ptr, sizeof temp); cdb_free(cdbus); CtdlRoomAccess(&CC->room, &temp, &ra, NULL); if (!IsEmptyStr(temp.fullname)) { imap_acl_flags(rights, ra); if (StrLength(rights) > 0) { IAPuts(" "); IPutStr(temp.fullname, strlen(temp.fullname)); IAPuts(" "); iaputs(SKEY( rights)); } } } FreeStrBuf(&rights); IAPuts("\r\n"); /* * If another folder is selected, go back to that room so we can resume * our happy day without violent explosions. */ if (IMAP->selected) { CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL); } IReply("OK GETACL completed"); } /* * Implements the LISTRIGHTS command. */ void imap_listrights(int num_parms, ConstStr *Params) { char roomname[ROOMNAMELEN]; char savedroom[ROOMNAMELEN]; int msgs, new; int ret; recptypes *valid; struct ctdluser temp; if (num_parms != 4) { IReply("BAD usage error"); return; } /* * Search for the specified room/folder */ ret = imap_grabroom(roomname, Params[2].Key, 1); if (ret != 0) { IReply("NO Invalid mailbox name or access denied"); return; } /* * Search for the specified user */ ret = (-1); valid = validate_recipients(Params[3].Key, NULL, 0); if (valid != NULL) { if (valid->num_local == 1) { ret = CtdlGetUser(&temp, valid->recp_local); } free_recipients(valid); } if (ret != 0) { IReply("NO Invalid user name or access denied"); return; } /* * CtdlUserGoto() formally takes us to the desired room. (If another * folder is selected, save its name so we can return there!!!!!) */ if (IMAP->selected) { strcpy(savedroom, CC->room.QRname); } CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL); /* * Now output the list of rights */ IAPuts("* LISTRIGHTS "); IPutCParamStr(2); IAPuts(" "); IPutCParamStr(3); IAPuts(" "); IPutStr(HKEY("")); /* FIXME ... do something here */ IAPuts("\r\n"); /* * If another folder is selected, go back to that room so we can resume * our happy day without violent explosions. */ if (IMAP->selected) { CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL); } IReply("OK LISTRIGHTS completed"); return; } /* * Implements the MYRIGHTS command. */ void imap_myrights(int num_parms, ConstStr *Params) { char roomname[ROOMNAMELEN]; char savedroom[ROOMNAMELEN]; int msgs, new; int ret; int ra; StrBuf *rights; if (num_parms != 3) { IReply("BAD usage error"); return; } ret = imap_grabroom(roomname, Params[2].Key, 1); if (ret != 0) { IReply("NO Invalid mailbox name or access denied"); return; } /* * CtdlUserGoto() formally takes us to the desired room. (If another * folder is selected, save its name so we can return there!!!!!) */ if (IMAP->selected) { strcpy(savedroom, CC->room.QRname); } CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL); CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL); rights = NewStrBuf(); imap_acl_flags(rights, ra); IAPuts("* MYRIGHTS "); IPutCParamStr(2); IAPuts(" "); IPutStr(SKEY(rights)); IAPuts("\r\n"); FreeStrBuf(&rights); /* * If a different folder was previously selected, return there now. */ if ( (IMAP->selected) && (strcasecmp(roomname, savedroom)) ) { CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL); } IReply("OK MYRIGHTS completed"); return; } citadel-9.01/modules/imap/imap_metadata.h0000644000000000000000000000113312507024051017075 0ustar rootroot/* * Copyright (c) 2007-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * */ void imap_getmetadata(int num_parms, ConstStr *Params); void imap_setmetadata(int num_parms, ConstStr *Params); citadel-9.01/modules/imap/imap_fetch.h0000644000000000000000000000133212507024051016407 0ustar rootroot/* * Copyright (c) 2001-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * */ void imap_pick_range(const char *range, int is_uid); void imap_fetch(int num_parms, ConstStr *Params); void imap_uidfetch(int num_parms, ConstStr *Params); void imap_fetch_flags(int seq); int imap_extract_data_items(citimap_command *Cmd); citadel-9.01/modules/imap/imap_list.h0000644000000000000000000000031012507024051016264 0ustar rootroot /* * In the extended form of LIST the client is allowed to specify * multiple match patterns. How many will we allow? */ #define MAX_PATTERNS 20 void imap_list(int num_parms, ConstStr *Params); citadel-9.01/modules/imap/imap_tools.h0000644000000000000000000000357612507024051016472 0ustar rootroot /* * since we work with shifted pointers to ConstStrs in some places, * we can't just say we want to cut the n'th of Cmd, we have to pass it in * and rely on that CutMe references Cmd->CmdBuf; else peek won't work out * and len will differ. */ void TokenCutRight(citimap_command *Cmd, ConstStr *CutMe, int n); /* * since we just move Key around here, Cmd is just here so the syntax is identical. */ void TokenCutLeft(citimap_command *Cmd, ConstStr *CutMe, int n); void MakeStringOf(StrBuf *Buf, int skip); int CmdAdjust(citimap_command *Cmd, int nArgs, int Realloc); void imap_strout(ConstStr *args); void imap_strbuffer(StrBuf *Reply, ConstStr *args); void plain_imap_strbuffer(StrBuf *Reply, char *buf); int imap_parameterize(citimap_command *Cmd); long imap_mailboxname(char *buf, int bufsize, struct ctdlroom *qrbuf); int imap_roomname(char *buf, int bufsize, const char *foldername); int imap_is_message_set(const char *); int imap_mailbox_matches_pattern(const char *pattern, char *mailboxname); int imap_datecmp(const char *datestr, time_t msgtime); /* Imap Append Printf, send to the outbuffer */ void IAPrintf(const char *Format, ...) __attribute__((__format__(__printf__,1,2))); void iaputs(const char *Str, long Len); #define IAPuts(Msg) iaputs(HKEY(Msg)) /* give it a naughty name since its ugly. */ #define _iaputs(Msg) iaputs(Msg, strlen(Msg)) /* outputs a static message prepended by the sequence no */ void ireply(const char *Msg, long len); #define IReply(msg) ireply(HKEY(msg)) /* outputs a dynamic message prepended by the sequence no */ void IReplyPrintf(const char *Format, ...); /* output a string like that {%ld}%s */ void IPutStr(const char *Msg, long Len); #define IPutCStr(_ConstStr) IPutStr(CKEY(_ConstStr)) #define IPutCParamStr(n) IPutStr(CKEY(Params[n])) #define IPutMsgField(Which) IPutStr(CM_KEY(msg, Which)) void IUnbuffer (void); citadel-9.01/modules/imap/imap_misc.h0000644000000000000000000000120312507024051016246 0ustar rootroot/* * Copyright (c) 2001-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * */ void imap_copy(int num_parms, ConstStr *Params); void imap_uidcopy(int num_parms, ConstStr *Params); void imap_append(int num_parms, ConstStr *Params); citadel-9.01/modules/imap/imap_metadata.c0000644000000000000000000002003512507024051017072 0ustar rootroot/* * IMAP METADATA extension * * This is an implementation of the Bynari variant of the METADATA extension. * * Copyright (c) 2007-2009 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "sysdep.h" #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include "citadel.h" #include "server.h" #include "sysdep_decls.h" #include "citserver.h" #include "support.h" #include "config.h" #include "user_ops.h" #include "database.h" #include "msgbase.h" #include "internet_addressing.h" #include "serv_imap.h" #include "imap_tools.h" #include "imap_fetch.h" #include "imap_misc.h" #include "genstamp.h" #include "ctdl_module.h" /* * Implements the SETMETADATA command. * * Again, the only thing we're interested in setting here is the folder type. * * Attempting to set anything else calls a stub which fools the client into * thinking that there is no remaining space available to store annotations. */ void imap_setmetadata(int num_parms, ConstStr *Params) { char roomname[ROOMNAMELEN]; char savedroom[ROOMNAMELEN]; int msgs, new; int ret; int setting_user_value = 0; char set_value[32]; int set_view = VIEW_BBS; visit vbuf; if (num_parms != 6) { IReply("BAD usage error"); return; } /* * Don't allow other types of metadata to be set */ if (strcasecmp(Params[3].Key, "/vendor/kolab/folder-type")) { IReply("NO [METADATA TOOMANY] SETMETADATA failed"); return; } if (!strcasecmp(Params[4].Key, "(value.shared")) { setting_user_value = 0; /* global view */ } else if (!strcasecmp(Params[4].Key, "(value.priv")) { setting_user_value = 1; /* per-user view */ } else { IReply("NO [METADATA TOOMANY] SETMETADATA failed"); return; } /* * Extract the folder type without any parentheses. Then learn * the Citadel view type based on the supplied folder type. */ extract_token(set_value, Params[5].Key, 0, ')', sizeof set_value); if (!strncasecmp(set_value, "mail", 4)) { set_view = VIEW_MAILBOX; } else if (!strncasecmp(set_value, "event", 5)) { set_view = VIEW_CALENDAR; } else if (!strncasecmp(set_value, "contact", 7)) { set_view = VIEW_ADDRESSBOOK; } else if (!strncasecmp(set_value, "journal", 7)) { set_view = VIEW_JOURNAL; } else if (!strncasecmp(set_value, "note", 4)) { set_view = VIEW_NOTES; } else if (!strncasecmp(set_value, "task", 4)) { set_view = VIEW_TASKS; } else { set_view = VIEW_MAILBOX; } ret = imap_grabroom(roomname, Params[2].Key, 1); if (ret != 0) { IReply("NO Invalid mailbox name or access denied"); return; } /* * CtdlUserGoto() formally takes us to the desired room. (If another * folder is selected, save its name so we can return there!!!!!) */ if (IMAP->selected) { strcpy(savedroom, CC->room.QRname); } CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL); /* * Always set the per-user view to the requested one. */ CtdlGetRelationship(&vbuf, &CC->user, &CC->room); vbuf.v_view = set_view; CtdlSetRelationship(&vbuf, &CC->user, &CC->room); /* If this is a "value.priv" set operation, we're done. */ if (setting_user_value) { IReply("OK SETANNOTATION complete"); } /* If this is a "value.shared" set operation, we are allowed to perform it * under certain conditions. */ else if ( (is_room_aide()) /* aide or room aide */ || ( (CC->room.QRflags & QR_MAILBOX) && (CC->user.usernum == atol(CC->room.QRname)) /* mailbox owner */ ) || (msgs == 0) /* hack: if room is empty, assume we just created it */ ) { CtdlGetRoomLock(&CC->room, CC->room.QRname); CC->room.QRdefaultview = set_view; CtdlPutRoomLock(&CC->room); IReply("OK SETANNOTATION complete"); } /* If we got to this point, we don't have permission to set the default view. */ else { IReply("NO [METADATA TOOMANY] SETMETADATA failed"); } /* * If a different folder was previously selected, return there now. */ if ( (IMAP->selected) && (strcasecmp(roomname, savedroom)) ) { CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL); } return; } /* * Implements the GETMETADATA command. * * Regardless of what the client asked for, we are going to supply them with * the folder type. It's the only metadata we have anyway. */ void imap_getmetadata(int num_parms, ConstStr *Params) { char roomname[ROOMNAMELEN]; char savedroom[ROOMNAMELEN]; int msgs, new; int ret; int found = 0; /* this doesn't work if you have rooms/floors with spaces. we need this for the bynari connector. if (num_parms > 5) { IReply("BAD usage error"); return; } */ ret = imap_grabroom(roomname, Params[2].Key, 1); if (ret != 0) { IReply("NO Invalid mailbox name or access denied"); return; } /* * CtdlUserGoto() formally takes us to the desired room. (If another * folder is selected, save its name so we can return there!!!!!) */ if (IMAP->selected) { strcpy(savedroom, CC->room.QRname); } CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL); IAPuts("* METADATA "); IPutCParamStr(2); IAPuts(" \"/vendor/kolab/folder-type\" (\"value.shared\" \""); /* If it's one of our hard-coded default rooms, we know what to do... */ if (CC->room.QRname[10] == '.') { if (!strcasecmp(&CC->room.QRname[11], MAILROOM)) { found = 1; IAPuts("mail.inbox"); } else if (!strcasecmp(&CC->room.QRname[11], SENTITEMS)) { found = 1; IAPuts("mail.sentitems"); } else if (!strcasecmp(&CC->room.QRname[11], USERDRAFTROOM)) { found = 1; IAPuts("mail.drafts"); } else if (!strcasecmp(&CC->room.QRname[11], USERCALENDARROOM)) { found = 1; IAPuts("event.default"); } else if (!strcasecmp(&CC->room.QRname[11], USERCONTACTSROOM)) { found = 1; IAPuts("contact.default"); } else if (!strcasecmp(&CC->room.QRname[11], USERNOTESROOM)) { found = 1; IAPuts("note.default"); } else if (!strcasecmp(&CC->room.QRname[11], USERTASKSROOM)) { found = 1; IAPuts("task.default"); } } /* Otherwise, use the view for this room to determine the type of data. * We are going with the default view rather than the user's view, because * the default view almost always defines the actual contents, while the * user's view might only make changes to presentation. It also saves us * an extra database access because we don't need to load the visit record. */ if (!found) { if (CC->room.QRdefaultview == VIEW_CALENDAR) { IAPuts("event"); } else if (CC->room.QRdefaultview == VIEW_ADDRESSBOOK) { IAPuts("contact"); } else if (CC->room.QRdefaultview == VIEW_TASKS) { IAPuts("task"); } else if (CC->room.QRdefaultview == VIEW_NOTES) { IAPuts("note"); } else if (CC->room.QRdefaultview == VIEW_JOURNAL) { IAPuts("journal"); } } /* If none of the above conditions were met, consider it an ordinary mailbox. */ if (!found) { IAPuts("mail"); } /* "mail.outbox" and "junkemail" are not implemented. */ IAPuts("\")\r\n"); /* * If a different folder was previously selected, return there now. */ if ( (IMAP->selected) && (strcasecmp(roomname, savedroom)) ) { CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL); } IReply("OK GETMETADATA complete"); return; } citadel-9.01/modules/listsub/0000755000000000000000000000000012507024051014677 5ustar rootrootcitadel-9.01/modules/listsub/serv_listsub.c0000644000000000000000000004344012507024051017574 0ustar rootroot/* * This module handles self-service subscription/unsubscription to mail lists. * * Copyright (c) 2002-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "sysdep.h" #include #include #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "user_ops.h" #include "database.h" #include "msgbase.h" #include "internet_addressing.h" #include "clientsocket.h" #include "ctdl_module.h" /* * Generate a randomizationalisticized token to use for authentication of * a subscribe or unsubscribe request. */ void listsub_generate_token(char *buf) { char sourcebuf[SIZ]; static int seq = 0; size_t len; /* Theo, please sit down and shut up. This key doesn't have to be * tinfoil-hat secure, it just needs to be reasonably unguessable * and unique. */ len = sprintf(sourcebuf, "%lx", (long) (++seq + getpid() + time(NULL)) ); /* Convert it to base64 so it looks cool */ len = CtdlEncodeBase64(buf, sourcebuf, len, 0); if (buf[len - 1] == '\n') { buf[len - 1] = '\0'; } } const RoomNetCfg ActiveSubscribers[] = {listrecp, digestrecp}; int CountThisSubscriber(OneRoomNetCfg *OneRNCfg, StrBuf *email) { RoomNetCfgLine *Line; int found_sub = 0; int i; for (i = 0; i < 2; i++) { Line = OneRNCfg->NetConfigs[ActiveSubscribers[i]]; while (Line != NULL) { if (!strcmp(ChrPtr(email), ChrPtr(Line->Value[0]))) { ++found_sub; break; } Line = Line->next; } } return found_sub; } /* * Enter a subscription request */ void do_subscribe(StrBuf **room, StrBuf **email, StrBuf **subtype, StrBuf **webpage) { struct ctdlroom qrbuf; char token[256]; char *pcf_req; StrBuf *cf_req; StrBuf *UrlRoom; int found_sub = 0; const char *RoomMailAddress; OneRoomNetCfg *OneRNCfg; RoomNetCfgLine *Line; const char *EmailSender = NULL; long RoomMailAddressLen; if (CtdlGetRoom(&qrbuf, ChrPtr(*room)) != 0) { cprintf("%d There is no list called '%s'\n", ERROR + ROOM_NOT_FOUND, ChrPtr(*room)); return; } if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) { cprintf("%d '%s' " "does not accept subscribe/unsubscribe requests.\n", ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname); return; } /* * Make sure the requested address isn't already subscribed */ begin_critical_section(S_NETCONFIGS); RoomMailAddress = qrbuf.QRname; OneRNCfg = CtdlGetNetCfgForRoom(qrbuf.QRnumber); if (OneRNCfg!=NULL) { found_sub = CountThisSubscriber(OneRNCfg, *email); if (StrLength(OneRNCfg->Sender) > 0) { EmailSender = RoomMailAddress = ChrPtr(OneRNCfg->Sender); } } if (found_sub != 0) { cprintf("%d '%s' is already subscribed to '%s'.\n", ERROR + ALREADY_EXISTS, ChrPtr(*email), RoomMailAddress); end_critical_section(S_NETCONFIGS); return; } /* * Now add it to the config */ RoomMailAddressLen = strlen(RoomMailAddress); listsub_generate_token(token); Line = (RoomNetCfgLine*)malloc(sizeof(RoomNetCfgLine)); memset(Line, 0, sizeof(RoomNetCfgLine)); Line->Value = (StrBuf**) malloc(sizeof(StrBuf*) * 5); Line->Value[0] = NewStrBufDup(*email); Line->Value[1] = *subtype; *subtype = NULL; Line->Value[2] = NewStrBufPlain(token, -1); Line->Value[3] = NewStrBufPlain(NULL, 10); StrBufPrintf(Line->Value[3], "%ld", time(NULL)); Line->Value[4] = *webpage; *webpage = NULL; Line->nValues = 5; AddRoomCfgLine(OneRNCfg, &qrbuf, subpending, Line); /* Generate and send the confirmation request */ UrlRoom = NewStrBuf(); StrBufUrlescAppend(UrlRoom, NULL, qrbuf.QRname); cf_req = NewStrBufPlain(NULL, 2048); StrBufAppendBufPlain( cf_req, HKEY("MIME-Version: 1.0\n" "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n" "\n" "This is a multipart message in MIME format.\n" "\n" "--__ctdlmultipart__\n" "Content-type: text/plain\n" "\n" "Someone (probably you) has submitted a request to subscribe\n" "<"), 0); StrBufAppendBuf(cf_req, Line->Value[0], 0); StrBufAppendBufPlain(cf_req, HKEY("> to the '"), 0); StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0); StrBufAppendBufPlain( cf_req, HKEY("' mailing list.\n" "\n" "Please go here to confirm this request:\n" " "), 0); StrBufAppendBuf(cf_req, Line->Value[4], 0); StrBufAppendBufPlain(cf_req, HKEY("?room="), 0); StrBufAppendBuf(cf_req, UrlRoom, 0); StrBufAppendBufPlain(cf_req, HKEY("&token="), 0); StrBufAppendBuf(cf_req, Line->Value[2], 0); StrBufAppendBufPlain( cf_req, HKEY("&cmd=confirm \n" "\n" "If this request has been submitted in error and you do not\n" "wish to receive the '"), 0); StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0); StrBufAppendBufPlain( cf_req, HKEY("' mailing list, simply do nothing,\n" "and you will not receive any further mailings.\n" "\n" "--__ctdlmultipart__\n" "Content-type: text/html\n" "\n" "\n" "Someone (probably you) has submitted a request to subscribe\n" "<"), 0); StrBufAppendBuf(cf_req, Line->Value[0], 0); StrBufAppendBufPlain(cf_req, HKEY( "> to the "), 0); StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0); StrBufAppendBufPlain( cf_req, HKEY("' mailing list.

    \n" "Please click here to confirm this request:
    \n" "Value[4], 0); StrBufAppendBufPlain(cf_req, HKEY("?room="), 0); StrBufAppendBuf(cf_req, UrlRoom, 0); StrBufAppendBufPlain(cf_req, HKEY("&token="), 0); StrBufAppendBuf(cf_req, Line->Value[2], 0); StrBufAppendBufPlain(cf_req, HKEY("&cmd=confirm\">"), 0); StrBufAppendBuf(cf_req, Line->Value[4], 0); StrBufAppendBufPlain(cf_req, HKEY("?room="), 0); StrBufAppendBuf(cf_req, UrlRoom, 0); StrBufAppendBufPlain(cf_req, HKEY("&token="), 0); StrBufAppendBuf(cf_req, Line->Value[2], 0); StrBufAppendBufPlain( cf_req, HKEY("&cmd=confirm

    \n" "If this request has been submitted in error and you do not\n" "wish to receive the '"), 0); StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0); StrBufAppendBufPlain( cf_req, HKEY("' mailing list, simply do nothing,\n" "and you will not receive any further mailings.\n" "\n" "\n" "--__ctdlmultipart__--\n"), 0); end_critical_section(S_NETCONFIGS); pcf_req = SmashStrBuf(&cf_req); quickie_message( /* This delivers the message */ "Citadel", EmailSender, ChrPtr(*email), NULL, pcf_req, FMT_RFC822, "Please confirm your list subscription" ); free(pcf_req); cprintf("%d Subscription entered; confirmation request sent\n", CIT_OK); FreeStrBuf(&UrlRoom); } /* * Enter an unsubscription request */ void do_unsubscribe(StrBuf **room, StrBuf **email, StrBuf **webpage) { struct ctdlroom qrbuf; const char *EmailSender = NULL; char token[256]; char *pcf_req; StrBuf *cf_req; StrBuf *UrlRoom; int found_sub = 0; const char *RoomMailAddress; OneRoomNetCfg *OneRNCfg; RoomNetCfgLine *Line; long RoomMailAddressLen; if (CtdlGetRoom(&qrbuf, ChrPtr(*room)) != 0) { cprintf("%d There is no list called '%s'\n", ERROR + ROOM_NOT_FOUND, ChrPtr(*room)); return; } if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) { cprintf("%d '%s' " "does not accept subscribe/unsubscribe requests.\n", ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname); return; } listsub_generate_token(token); /* * Make sure there's actually a subscription there to remove */ begin_critical_section(S_NETCONFIGS); RoomMailAddress = qrbuf.QRname; OneRNCfg = CtdlGetNetCfgForRoom(qrbuf.QRnumber); if (OneRNCfg!=NULL) { found_sub = CountThisSubscriber(OneRNCfg, *email); if (StrLength(OneRNCfg->Sender) > 0) EmailSender = RoomMailAddress = ChrPtr(OneRNCfg->Sender); } if (found_sub == 0) { cprintf("%d '%s' is not subscribed to '%s'.\n", ERROR + NO_SUCH_USER, ChrPtr(*email), qrbuf.QRname); end_critical_section(S_NETCONFIGS); return; } /* * Ok, now enter the unsubscribe-pending entry. */ RoomMailAddressLen = strlen(RoomMailAddress); listsub_generate_token(token); Line = (RoomNetCfgLine*)malloc(sizeof(RoomNetCfgLine)); memset(Line, 0, sizeof(RoomNetCfgLine)); Line->Value = (StrBuf**) malloc(sizeof(StrBuf*) * 4); Line->Value[0] = NewStrBufDup(*email); Line->Value[1] = NewStrBufPlain(token, -1); Line->Value[2] = NewStrBufPlain(NULL, 10); StrBufPrintf(Line->Value[2], "%ld", time(NULL)); Line->Value[3] = *webpage; *webpage = NULL; Line->nValues = 4; AddRoomCfgLine(OneRNCfg, &qrbuf, unsubpending, Line); /* Generate and send the confirmation request */ UrlRoom = NewStrBuf(); StrBufUrlescAppend(UrlRoom, NULL, qrbuf.QRname); cf_req = NewStrBufPlain(NULL, 2048); StrBufAppendBufPlain( cf_req, HKEY("MIME-Version: 1.0\n" "Content-Type: multipart/alternative; boundary=\"__ctdlmultipart__\"\n" "\n" "This is a multipart message in MIME format.\n" "\n" "--__ctdlmultipart__\n" "Content-type: text/plain\n" "\n" "Someone (probably you) has submitted a request to unsubscribe\n" "<"), 0); StrBufAppendBuf(cf_req, Line->Value[0], 0); StrBufAppendBufPlain( cf_req, HKEY("> from the '"), 0); StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0); StrBufAppendBufPlain( cf_req, HKEY("' mailing list.\n" "\n" "Please go here to confirm this request:\n "), 0); StrBufAppendBuf(cf_req, Line->Value[3], 0); StrBufAppendBufPlain(cf_req, HKEY("?room="), 0); StrBufAppendBuf(cf_req, UrlRoom, 0); StrBufAppendBufPlain(cf_req, HKEY("&token="), 0); StrBufAppendBuf(cf_req, Line->Value[1], 0); StrBufAppendBufPlain( cf_req, HKEY("&cmd=confirm \n" "\n" "If this request has been submitted in error and you do not\n" "wish to unsubscribe from the '"), 0); StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0); StrBufAppendBufPlain( cf_req, HKEY("' mailing list, simply do nothing,\n" "and the request will not be processed.\n" "\n" "--__ctdlmultipart__\n" "Content-type: text/html\n" "\n" "\n" "Someone (probably you) has submitted a request to unsubscribe\n" "<"), 0); StrBufAppendBuf(cf_req, Line->Value[0], 0); StrBufAppendBufPlain(cf_req, HKEY("> from the "), 0); StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0); StrBufAppendBufPlain( cf_req, HKEY(" mailing list.

    \n" "Please click here to confirm this request:
    \n" "Value[3], 0); StrBufAppendBufPlain(cf_req, HKEY("?room="), 0); StrBufAppendBuf(cf_req, UrlRoom, 0); StrBufAppendBufPlain(cf_req, HKEY("&token="), 0); StrBufAppendBuf(cf_req, Line->Value[1], 0); StrBufAppendBufPlain(cf_req, HKEY("&cmd=confirm\">"), 0); StrBufAppendBuf(cf_req, Line->Value[3], 0); StrBufAppendBufPlain(cf_req, HKEY("?room="), 0); StrBufAppendBuf(cf_req, UrlRoom, 0); StrBufAppendBufPlain(cf_req, HKEY("&token="), 0); StrBufAppendBuf(cf_req, Line->Value[1], 0); StrBufAppendBufPlain( cf_req, HKEY("&cmd=confirm

    \n" "If this request has been submitted in error and you do not\n" "wish to unsubscribe from the '"), 0); StrBufAppendBufPlain(cf_req, RoomMailAddress, RoomMailAddressLen, 0); StrBufAppendBufPlain( cf_req, HKEY("' mailing list, simply do nothing,\n" "and the request will not be processed.\n" "\n" "\n" "--__ctdlmultipart__--\n"), 0); end_critical_section(S_NETCONFIGS); pcf_req = SmashStrBuf(&cf_req); quickie_message( /* This delivers the message */ "Citadel", EmailSender, ChrPtr(*email), NULL, pcf_req, FMT_RFC822, "Please confirm your unsubscribe request" ); free(pcf_req); FreeStrBuf(&UrlRoom); cprintf("%d Unubscription noted; confirmation request sent\n", CIT_OK); } const RoomNetCfg ConfirmSubscribers[] = {subpending, unsubpending}; /* * Confirm a subscribe/unsubscribe request. */ void do_confirm(StrBuf **room, StrBuf **token) { struct ctdlroom qrbuf; OneRoomNetCfg *OneRNCfg; RoomNetCfgLine *Line; RoomNetCfgLine *ConfirmLine = NULL; RoomNetCfgLine *RemoveLine = NULL; RoomNetCfgLine **PrevLine; int success = 0; RoomNetCfg ConfirmType; const char *errmsg = ""; int i; if (CtdlGetRoom(&qrbuf, ChrPtr(*room)) != 0) { cprintf("%d There is no list called '%s'\n", ERROR + ROOM_NOT_FOUND, ChrPtr(*room)); return; } if ((qrbuf.QRflags2 & QR2_SELFLIST) == 0) { cprintf("%d '%s' " "does not accept subscribe/unsubscribe requests.\n", ERROR + HIGHER_ACCESS_REQUIRED, qrbuf.QRname); return; } if (StrLength(*token) == 0) { cprintf("%d empty token.\n", ERROR + ILLEGAL_VALUE); return; } /* * Now start scanning this room's netconfig file for the * specified token. */ begin_critical_section(S_NETCONFIGS); OneRNCfg = CtdlGetNetCfgForRoom(qrbuf.QRnumber); ConfirmType = maxRoomNetCfg; if (OneRNCfg==NULL) { errmsg = "no networking config found"; } else for (i = 0; i < 2; i++) { int offset; if (ConfirmSubscribers[i] == subpending) offset = 2; else offset = 1; PrevLine = &OneRNCfg->NetConfigs[ConfirmSubscribers[i]]; Line = *PrevLine; while (Line != NULL) { if (!strcasecmp(ChrPtr(*token), ChrPtr(Line->Value[offset]))) { ConfirmLine = Line; *PrevLine = Line->next; /* Remove it from the list */ ConfirmType = ConfirmSubscribers[i]; ConfirmLine->next = NULL; i += 100; break; } PrevLine = &(*PrevLine)->next; Line = Line->next; } if (ConfirmType == maxRoomNetCfg) { errmsg = "No active un/subscribe request found"; } } if (ConfirmType == subpending) { if (CountThisSubscriber(OneRNCfg, ConfirmLine->Value[0]) == 0) { if (!strcasecmp(ChrPtr(ConfirmLine->Value[2]), ("digest"))) { ConfirmType = digestrecp; } else /* "list" */ { ConfirmType = listrecp; } syslog(LOG_NOTICE, "Mailing list: %s subscribed to %s with token %s\n", ChrPtr(ConfirmLine->Value[0]), qrbuf.QRname, ChrPtr(*token)); FreeStrBuf(&ConfirmLine->Value[1]); FreeStrBuf(&ConfirmLine->Value[2]); FreeStrBuf(&ConfirmLine->Value[3]); FreeStrBuf(&ConfirmLine->Value[4]); ConfirmLine->nValues = 1; AddRoomCfgLine(OneRNCfg, &qrbuf, ConfirmType, ConfirmLine); success = 1; } else { /* whipe duplicate subscribe entry... */ OneRNCfg->changed = 1; SaveChangedConfigs(); errmsg = "already subscribed"; } } else if (ConfirmType == unsubpending) { for (i = 0; i < 2; i++) { PrevLine = &OneRNCfg->NetConfigs[ActiveSubscribers[i]]; Line = *PrevLine; while (Line != NULL) { if (!strcasecmp(ChrPtr(ConfirmLine->Value[0]), ChrPtr(Line->Value[0]))) { success = 1; RemoveLine = Line; *PrevLine = Line->next; /* Remove it from the list */ RemoveLine->next = NULL; if (RemoveLine != NULL) DeleteGenericCfgLine(NULL/*TODO*/, &RemoveLine); Line = *PrevLine; continue; } PrevLine = &(*PrevLine)->next; Line = Line->next; } } if (success) { syslog(LOG_NOTICE, "Mailing list: %s unsubscribed to %s with token %s\n", ChrPtr(ConfirmLine->Value[0]), qrbuf.QRname, ChrPtr(*token)); } else { errmsg = "no subscriber found for this unsubscription request"; } DeleteGenericCfgLine(NULL/*TODO*/, &ConfirmLine); OneRNCfg->changed = 1; SaveChangedConfigs(); } end_critical_section(S_NETCONFIGS); /* * Did we do anything useful today? */ if (success) { cprintf("%d %d operation(s) confirmed.\n", CIT_OK, success); } else { syslog(LOG_NOTICE, "failed processing (un)subscribe request: %s", errmsg); cprintf("%d Invalid token.\n", ERROR + ILLEGAL_VALUE); } } /* * process subscribe/unsubscribe requests and confirmations */ void cmd_subs(char *cmdbuf) { const char *Pos = NULL; StrBuf *Segments[20]; int i=1; memset(Segments, 0, sizeof(StrBuf*) * 20); Segments[0] = NewStrBufPlain(cmdbuf, -1); while ((Pos != StrBufNOTNULL) && (i < 20)) { Segments[i] = NewStrBufPlain(NULL, StrLength(Segments[0])); StrBufExtract_NextToken(Segments[i], Segments[0], &Pos, '|'); i++; } if (!strcasecmp(ChrPtr(Segments[1]), "subscribe")) { if ( (strcasecmp(ChrPtr(Segments[4]), "list")) && (strcasecmp(ChrPtr(Segments[4]), "digest")) ) { cprintf("%d Invalid subscription type '%s'\n", ERROR + ILLEGAL_VALUE, ChrPtr(Segments[4])); } else { do_subscribe(&Segments[2], &Segments[3], &Segments[4], &Segments[5]); } } else if (!strcasecmp(ChrPtr(Segments[1]), "unsubscribe")) { do_unsubscribe(&Segments[2], &Segments[3], &Segments[4]); } else if (!strcasecmp(ChrPtr(Segments[1]), "confirm")) { do_confirm(&Segments[2], &Segments[3]); } else { cprintf("%d Invalid command\n", ERROR + ILLEGAL_VALUE); } for (; i>=0; i--) { FreeStrBuf(&Segments[i]); } } /* * Module entry point */ CTDL_MODULE_INIT(listsub) { if (!threading) { CtdlRegisterProtoHook(cmd_subs, "SUBS", "List subscribe/unsubscribe"); } /* return our module name for the log */ return "listsub"; } citadel-9.01/modules/smtp/0000755000000000000000000000000012507024051014175 5ustar rootrootcitadel-9.01/modules/smtp/smtp_util.c0000644000000000000000000002063412507024051016366 0ustar rootroot/* * This module is an SMTP and ESMTP implementation for the Citadel system. * It is compliant with all of the following: * * RFC 821 - Simple Mail Transfer Protocol * RFC 876 - Survey of SMTP Implementations * RFC 1047 - Duplicate messages and SMTP * RFC 1652 - 8 bit MIME * RFC 1869 - Extended Simple Mail Transfer Protocol * RFC 1870 - SMTP Service Extension for Message Size Declaration * RFC 2033 - Local Mail Transfer Protocol * RFC 2197 - SMTP Service Extension for Command Pipelining * RFC 2476 - Message Submission * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS * RFC 2554 - SMTP Service Extension for Authentication * RFC 2821 - Simple Mail Transfer Protocol * RFC 2822 - Internet Message Format * RFC 2920 - SMTP Service Extension for Command Pipelining * * The VRFY and EXPN commands have been removed from this implementation * because nobody uses these commands anymore, except for spammers. * * Copyright (c) 1998-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * */ #include "sysdep.h" #include #include #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "control.h" #include "user_ops.h" #include "database.h" #include "msgbase.h" #include "internet_addressing.h" #include "genstamp.h" #include "domain.h" #include "clientsocket.h" #include "locate_host.h" #include "citadel_dirs.h" #include "ctdl_module.h" #include "smtp_util.h" const char *smtp_get_Recipients(void) { citsmtp *sSMTP = SMTP; if (sSMTP == NULL) return NULL; else return ChrPtr(sSMTP->from); } /* * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery * instructions for "5" codes (permanent fatal errors) and produce/deliver * a "bounce" message (delivery status notification). */ void smtp_do_bounce(char *instr, StrBuf *OMsgTxt) { int i; int lines; int status; char buf[1024]; char key[1024]; char addr[1024]; char dsn[1024]; char bounceto[1024]; StrBuf *boundary; int num_bounces = 0; int bounce_this = 0; time_t submitted = 0L; struct CtdlMessage *bmsg = NULL; int give_up = 0; recptypes *valid; int successful_bounce = 0; static int seq = 0; StrBuf *BounceMB; long omsgid = (-1); syslog(LOG_DEBUG, "smtp_do_bounce() called\n"); strcpy(bounceto, ""); boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_")); StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq); lines = num_tokens(instr, '\n'); /* See if it's time to give up on delivery of this message */ for (i=0; i SMTP_GIVE_UP ) { give_up = 1; } /* Start building our bounce message */ bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage)); if (bmsg == NULL) return; memset(bmsg, 0, sizeof(struct CtdlMessage)); BounceMB = NewStrBufPlain(NULL, 1024); bmsg->cm_magic = CTDLMESSAGE_MAGIC; bmsg->cm_anon_type = MES_NORMAL; bmsg->cm_format_type = FMT_RFC822; CM_SetField(bmsg, eAuthor, HKEY("Citadel")); CM_SetField(bmsg, eOriginalRoom, HKEY(MAILROOM)); CM_SetField(bmsg, eNodeName, CFG_KEY(c_nodename)); CM_SetField(bmsg, eMsgSubject, HKEY("Delivery Status Notification (Failure)")); StrBufAppendBufPlain( BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0); StrBufAppendBuf(BounceMB, boundary, 0); StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0); StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0); StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0); StrBufAppendBufPlain( BounceMB, HKEY("\r\nThis is a multipart message in MIME format." "\r\n\r\n"), 0); StrBufAppendBufPlain(BounceMB, HKEY("--"), 0); StrBufAppendBuf(BounceMB, boundary, 0); StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0); StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0); if (give_up) { StrBufAppendBufPlain( BounceMB, HKEY("A message you sent could not be delivered " "to some or all of its recipients\ndue to " "prolonged unavailability of its destination(s).\n" "Giving up on the following addresses:\n\n"), 0); } else { StrBufAppendBufPlain( BounceMB, HKEY("A message you sent could not be delivered " "to some or all of its recipients.\n" "The following addresses were undeliverable:\n\n" ), 0); } /* * Now go through the instructions checking for stuff. */ for (i=0; i addr=<%s> status=%d dsn=<%s>\n", key, addr, status, dsn); if (!strcasecmp(key, "bounceto")) { strcpy(bounceto, addr); } if (!strcasecmp(key, "msgid")) { omsgid = atol(addr); } if (!strcasecmp(key, "remote")) { if (status == 5) bounce_this = 1; if (give_up) bounce_this = 1; } if (bounce_this) { ++num_bounces; StrBufAppendBufPlain(BounceMB, addr, addrlen, 0); StrBufAppendBufPlain(BounceMB, HKEY(": "), 0); StrBufAppendBufPlain(BounceMB, dsn, dsnlen, 0); StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0); remove_token(instr, i, '\n'); --i; --lines; } } /* Attach the original message */ if (omsgid >= 0) { StrBufAppendBufPlain(BounceMB, HKEY("--"), 0); StrBufAppendBuf(BounceMB, boundary, 0); StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0); StrBufAppendBufPlain( BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0); StrBufAppendBufPlain( BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0); StrBufAppendBufPlain( BounceMB, HKEY("Content-Disposition: inline\r\n"), 0); StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0); if (OMsgTxt == NULL) { CC->redirect_buffer = NewStrBufPlain(NULL, SIZ); CtdlOutputMsg(omsgid, MT_RFC822, HEADERS_ALL, 0, 1, NULL, 0, NULL, NULL, NULL); StrBufAppendBuf(BounceMB, CC->redirect_buffer, 0); FreeStrBuf(&CC->redirect_buffer); } else { StrBufAppendBuf(BounceMB, OMsgTxt, 0); } } /* Close the multipart MIME scope */ StrBufAppendBufPlain(BounceMB, HKEY("--"), 0); StrBufAppendBuf(BounceMB, boundary, 0); StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0); CM_SetAsFieldSB(bmsg, eMesageText, &BounceMB); /* Deliver the bounce if there's anything worth mentioning */ syslog(LOG_DEBUG, "num_bounces = %d\n", num_bounces); if (num_bounces > 0) { /* First try the user who sent the message */ if (IsEmptyStr(bounceto)) syslog(LOG_ERR, "No bounce address specified\n"); else syslog(LOG_DEBUG, "bounce to user <%s>\n", bounceto); /* Can we deliver the bounce to the original sender? */ valid = validate_recipients(bounceto, smtp_get_Recipients (), 0); if (valid != NULL) { if (valid->num_error == 0) { CtdlSubmitMsg(bmsg, valid, "", QP_EADDR); successful_bounce = 1; } } /* If not, post it in the Aide> room */ if (successful_bounce == 0) { CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR); } /* Free up the memory we used */ if (valid != NULL) { free_recipients(valid); } } FreeStrBuf(&boundary); CM_Free(bmsg); syslog(LOG_DEBUG, "Done processing bounces\n"); } citadel-9.01/modules/smtp/smtp_clienthandlers.h0000644000000000000000000000735612507024051020423 0ustar rootroot/* * * Copyright (c) 1998-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ typedef enum _eSMTP_C_States { eConnectMX, eEHLO, eHELO, eSMTPAuth, eSMTPAuthPlain1, eSMTPAuthPlain2, eFROM, eRCPT, eDATA, eDATABody, eDATATerminateBody, eQUIT, eMaxSMTPC } eSMTP_C_States; typedef struct _stmp_out_msg { MailQEntry *MyQEntry; OneQueItem *MyQItem; long n; AsyncIO IO; long CXFlags; int IDestructQueItem; int nRemain; eSMTP_C_States State; struct ares_mx_reply *AllMX; struct ares_mx_reply *CurrMX; const char *mx_port; const char *mx_host; const char *LookupHostname; int iMX, nMX; int LookupWhich; DNSQueryParts MxLookup; DNSQueryParts HostLookup; struct hostent *OneMX; char **pIP; ParsedURL *Relay; ParsedURL *pCurrRelay; StrBuf *msgtext; StrBuf *QMsgData; StrBuf *MultiLineBuf; const char *envelope_from; char user[1024]; char node[1024]; char name[1024]; char mailfrom[1024]; long SendLogin; long Flags; long IsRelay; } SmtpOutMsg; typedef eNextState (*SMTPReadHandler)(SmtpOutMsg *Msg); typedef eNextState (*SMTPSendHandler)(SmtpOutMsg *Msg); SMTPReadHandler ReadHandlers[eMaxSMTPC]; SMTPSendHandler SendHandlers[eMaxSMTPC]; const ConstStr ReadErrors[eMaxSMTPC+1]; const double SMTP_C_ReadTimeouts[eMaxSMTPC]; const double SMTP_C_SendTimeouts[eMaxSMTPC]; const double SMTP_C_ConnTimeout; #define F_RELAY (1<<0) /* we have a Relay host configuration */ #define F_HAVE_FALLBACK (1<<1) /* we have a fallback host configuration */ #define F_FALLBACK (1<<2) #define F_HAVE_MX (1<<3) /* we have a list of mx records to go through.*/ #define F_DIRECT (1<<4) /* no mx record found, trying direct connect. */ extern int SMTPClientDebugEnabled; int smtp_resolve_recipients(SmtpOutMsg *SendMsg); #define QID ((SmtpOutMsg*)IO->Data)->MyQItem->MessageID #define N ((SmtpOutMsg*)IO->Data)->n #define DBGLOG(LEVEL) if ((LEVEL != LOG_DEBUG) || (SMTPClientDebugEnabled != 0)) #define EVS_syslog(LEVEL, FORMAT, ...) \ DBGLOG(LEVEL) syslog(LEVEL, \ "SMTPC:%s[%ld]CC[%d]S[%ld][%ld] " FORMAT, \ IOSTR, IO->ID, CCID, QID, N, __VA_ARGS__) #define EVSM_syslog(LEVEL, FORMAT) \ DBGLOG(LEVEL) syslog(LEVEL, \ "SMTPC:%s[%ld]CC[%d]S[%ld][%ld] " FORMAT, \ IOSTR, IO->ID, CCID, QID, N) #define EVNCS_syslog(LEVEL, FORMAT, ...) \ DBGLOG(LEVEL) syslog(LEVEL, "SMTPC:%s[%ld]S[%ld][%ld] " FORMAT, \ IOSTR, IO->ID, QID, N, __VA_ARGS__) #define EVNCSM_syslog(LEVEL, FORMAT) \ DBGLOG(LEVEL) syslog(LEVEL, "SMTPC:%s[%ld]S[%ld][%ld] " FORMAT, \ IOSTR, IO->ID, QID, N) #define SMTPC_syslog(LEVEL, FORMAT, ...) \ DBGLOG(LEVEL) syslog(LEVEL, \ "SMTPCQ: " FORMAT, \ __VA_ARGS__) #define SMTPCM_syslog(LEVEL, FORMAT) \ DBGLOG(LEVEL) syslog(LEVEL, \ "SMTPCQ: " FORMAT) typedef enum __smtpstate { eSTMPmxlookup, eSTMPevaluatenext, eSTMPalookup, eSTMPaaaalookup, eSTMPconnecting, eSTMPsmtp, eSTMPsmtpdata, eSTMPsmtpdone, eSTMPfinished, eSTMPfailOne, eSMTPFailTemporary, eSMTPFailTotal } smtpstate; void SetSMTPState(AsyncIO *IO, smtpstate State); citadel-9.01/modules/smtp/smtp_util.h0000644000000000000000000000361412507024051016372 0ustar rootroot/* * This module is an SMTP and ESMTP implementation for the Citadel system. * It is compliant with all of the following: * * RFC 821 - Simple Mail Transfer Protocol * RFC 876 - Survey of SMTP Implementations * RFC 1047 - Duplicate messages and SMTP * RFC 1652 - 8 bit MIME * RFC 1869 - Extended Simple Mail Transfer Protocol * RFC 1870 - SMTP Service Extension for Message Size Declaration * RFC 2033 - Local Mail Transfer Protocol * RFC 2197 - SMTP Service Extension for Command Pipelining * RFC 2476 - Message Submission * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS * RFC 2554 - SMTP Service Extension for Authentication * RFC 2821 - Simple Mail Transfer Protocol * RFC 2822 - Internet Message Format * RFC 2920 - SMTP Service Extension for Command Pipelining * * The VRFY and EXPN commands have been removed from this implementation * because nobody uses these commands anymore, except for spammers. * * Copyright (c) 1998-2013 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ const char *smtp_get_Recipients(void); typedef struct _citsmtp { /* Information about the current session */ int command_state; StrBuf *Cmd; StrBuf *helo_node; StrBuf *from; StrBuf *recipients; StrBuf *OneRcpt; int number_of_recipients; int delivery_mode; int message_originated_locally; int is_lmtp; int is_unfiltered; int is_msa; StrBuf *preferred_sender_email; StrBuf *preferred_sender_name; } citsmtp; #define SMTP ((citsmtp *)CC->session_specific_data) void smtp_do_bounce(char *instr, StrBuf *OMsgTxt); citadel-9.01/modules/smtp/serv_smtpqueue.c0000644000000000000000000010413212507024051017431 0ustar rootroot/* * This module is an SMTP and ESMTP implementation for the Citadel system. * It is compliant with all of the following: * * RFC 821 - Simple Mail Transfer Protocol * RFC 876 - Survey of SMTP Implementations * RFC 1047 - Duplicate messages and SMTP * RFC 1652 - 8 bit MIME * RFC 1869 - Extended Simple Mail Transfer Protocol * RFC 1870 - SMTP Service Extension for Message Size Declaration * RFC 2033 - Local Mail Transfer Protocol * RFC 2197 - SMTP Service Extension for Command Pipelining * RFC 2476 - Message Submission * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS * RFC 2554 - SMTP Service Extension for Authentication * RFC 2821 - Simple Mail Transfer Protocol * RFC 2822 - Internet Message Format * RFC 2920 - SMTP Service Extension for Command Pipelining * * The VRFY and EXPN commands have been removed from this implementation * because nobody uses these commands anymore, except for spammers. * * Copyright (c) 1998-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * */ #include "sysdep.h" #include #include #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "control.h" #include "user_ops.h" #include "database.h" #include "msgbase.h" #include "internet_addressing.h" #include "genstamp.h" #include "domain.h" #include "clientsocket.h" #include "locate_host.h" #include "citadel_dirs.h" #include "ctdl_module.h" #include "smtpqueue.h" #include "smtp_clienthandlers.h" #include "event_client.h" struct CitContext smtp_queue_CC; pthread_mutex_t ActiveQItemsLock; HashList *ActiveQItems = NULL; HashList *QItemHandlers = NULL; const unsigned short DefaultMXPort = 25; int max_sessions_for_outbound_smtp = 500; /* how many sessions might be active till we stop adding more smtp jobs */ int ndelay_count = 50; /* every n queued messages we will sleep... */ int delay_msec = 5000; /* this many seconds. */ static const long MaxRetry = SMTP_RETRY_INTERVAL * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2; int MsgCount = 0; int run_queue_now = 0; /* Set to 1 to ignore SMTP send retry times */ void RegisterQItemHandler(const char *Key, long Len, QItemHandler H) { QItemHandlerStruct *HS = (QItemHandlerStruct*)malloc(sizeof(QItemHandlerStruct)); HS->H = H; Put(QItemHandlers, Key, Len, HS, NULL); } void smtp_try_one_queue_entry(OneQueItem *MyQItem, MailQEntry *MyQEntry, StrBuf *MsgText, /* KeepMsgText allows us to use MsgText as ours. */ int KeepMsgText, int MsgCount, ParsedURL *RelayUrls); void smtp_evq_cleanup(void) { pthread_mutex_lock(&ActiveQItemsLock); DeleteHash(&QItemHandlers); DeleteHash(&ActiveQItems); pthread_mutex_unlock(&ActiveQItemsLock); pthread_setspecific(MyConKey, (void *)&smtp_queue_CC); /* citthread_mutex_destroy(&ActiveQItemsLock); TODO */ } int DecreaseQReference(OneQueItem *MyQItem) { int IDestructQueItem; pthread_mutex_lock(&ActiveQItemsLock); MyQItem->ActiveDeliveries--; IDestructQueItem = MyQItem->ActiveDeliveries == 0; pthread_mutex_unlock(&ActiveQItemsLock); return IDestructQueItem; } void DecreaseShutdownDeliveries(OneQueItem *MyQItem) { pthread_mutex_lock(&ActiveQItemsLock); MyQItem->NotYetShutdownDeliveries--; pthread_mutex_unlock(&ActiveQItemsLock); } int GetShutdownDeliveries(OneQueItem *MyQItem) { int DestructNow; pthread_mutex_lock(&ActiveQItemsLock); DestructNow = MyQItem->ActiveDeliveries == 0; pthread_mutex_unlock(&ActiveQItemsLock); return DestructNow; } void RemoveQItem(OneQueItem *MyQItem) { long len; const char* Key; void *VData; HashPos *It; pthread_mutex_lock(&ActiveQItemsLock); It = GetNewHashPos(ActiveQItems, 0); if (GetHashPosFromKey(ActiveQItems, LKEY(MyQItem->MessageID), It)) DeleteEntryFromHash(ActiveQItems, It); else { SMTPC_syslog(LOG_WARNING, "unable to find QItem with ID[%ld]", MyQItem->MessageID); while (GetNextHashPos(ActiveQItems, It, &len, &Key, &VData)) SMTPC_syslog(LOG_WARNING, "have_: ID[%ld]", ((OneQueItem *)VData)->MessageID); } pthread_mutex_unlock(&ActiveQItemsLock); DeleteHashPos(&It); } void FreeMailQEntry(void *qv) { MailQEntry *Q = qv; /* SMTPC_syslog(LOG_DEBUG, "---------------%s--------------", __FUNCTION__); cit_backtrace(); */ FreeStrBuf(&Q->Recipient); FreeStrBuf(&Q->StatusMessage); FreeStrBuf(&Q->AllStatusMessages); memset(Q, 0, sizeof(MailQEntry)); free(Q); } void FreeQueItem(OneQueItem **Item) { /* SMTPC_syslog(LOG_DEBUG, "---------------%s--------------", __FUNCTION__); cit_backtrace(); */ DeleteHash(&(*Item)->MailQEntries); FreeStrBuf(&(*Item)->EnvelopeFrom); FreeStrBuf(&(*Item)->BounceTo); FreeStrBuf(&(*Item)->SenderRoom); FreeURL(&(*Item)->URL); memset(*Item, 0, sizeof(OneQueItem)); free(*Item); Item = NULL; } void HFreeQueItem(void *Item) { FreeQueItem((OneQueItem**)&Item); } /* inspect recipients with a status of: * - 0 (no delivery yet attempted) * - 3/4 (transient errors * were experienced and it's time to try again) */ int CheckQEntryActive(MailQEntry *ThisItem) { if ((ThisItem->Status == 0) || (ThisItem->Status == 3) || (ThisItem->Status == 4)) { return 1; } else return 0; } int CheckQEntryIsBounce(MailQEntry *ThisItem) { if ((ThisItem->Status == 3) || (ThisItem->Status == 4) || (ThisItem->Status == 5)) { return 1; } else return 0; } int CountActiveQueueEntries(OneQueItem *MyQItem, int before) { HashPos *It; long len; long ActiveDeliveries; const char *Key; void *vQE; ActiveDeliveries = 0; It = GetNewHashPos(MyQItem->MailQEntries, 0); while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)) { int Active; MailQEntry *ThisItem = vQE; if (CheckQEntryActive(ThisItem)) { ActiveDeliveries++; Active = 1; } else Active = 0; if (before) ThisItem->Active = Active; else ThisItem->StillActive = Active; } DeleteHashPos(&It); return ActiveDeliveries; } OneQueItem *DeserializeQueueItem(StrBuf *RawQItem, long QueMsgID) { OneQueItem *Item; const char *pLine = NULL; StrBuf *Line; StrBuf *Token; void *v; Item = (OneQueItem*)malloc(sizeof(OneQueItem)); memset(Item, 0, sizeof(OneQueItem)); Item->Retry = SMTP_RETRY_INTERVAL; Item->MessageID = -1; Item->QueMsgID = QueMsgID; Token = NewStrBuf(); Line = NewStrBufPlain(NULL, 128); while (pLine != StrBufNOTNULL) { const char *pItemPart = NULL; void *vHandler; StrBufExtract_NextToken(Line, RawQItem, &pLine, '\n'); if (StrLength(Line) == 0) continue; StrBufExtract_NextToken(Token, Line, &pItemPart, '|'); if (GetHash(QItemHandlers, SKEY(Token), &vHandler)) { QItemHandlerStruct *HS; HS = (QItemHandlerStruct*) vHandler; HS->H(Item, Line, &pItemPart); } } FreeStrBuf(&Line); FreeStrBuf(&Token); if (Item->Retry >= MaxRetry) Item->FailNow = 1; pthread_mutex_lock(&ActiveQItemsLock); if (GetHash(ActiveQItems, LKEY(Item->MessageID), &v)) { /* WHOOPS. somebody else is already working on this. */ pthread_mutex_unlock(&ActiveQItemsLock); FreeQueItem(&Item); return NULL; } else { /* mark our claim on this. */ Put(ActiveQItems, LKEY(Item->MessageID), Item, HFreeQueItem); pthread_mutex_unlock(&ActiveQItemsLock); } return Item; } StrBuf *SerializeQueueItem(OneQueItem *MyQItem) { StrBuf *QMessage; HashPos *It; const char *Key; long len; void *vQE; QMessage = NewStrBufPlain(NULL, SIZ); StrBufPrintf(QMessage, "Content-type: %s\n", SPOOLMIME); // "attempted|%ld\n" "retry|%ld\n",, (long)time(NULL), (long)retry ); StrBufAppendBufPlain(QMessage, HKEY("\nmsgid|"), 0); StrBufAppendPrintf(QMessage, "%ld", MyQItem->MessageID); StrBufAppendBufPlain(QMessage, HKEY("\nsubmitted|"), 0); StrBufAppendPrintf(QMessage, "%ld", MyQItem->Submitted); if (StrLength(MyQItem->BounceTo) > 0) { StrBufAppendBufPlain(QMessage, HKEY("\nbounceto|"), 0); StrBufAppendBuf(QMessage, MyQItem->BounceTo, 0); } if (StrLength(MyQItem->EnvelopeFrom) > 0) { StrBufAppendBufPlain(QMessage, HKEY("\nenvelope_from|"), 0); StrBufAppendBuf(QMessage, MyQItem->EnvelopeFrom, 0); } if (StrLength(MyQItem->SenderRoom) > 0) { StrBufAppendBufPlain(QMessage, HKEY("\nsource_room|"), 0); StrBufAppendBuf(QMessage, MyQItem->SenderRoom, 0); } StrBufAppendBufPlain(QMessage, HKEY("\nretry|"), 0); StrBufAppendPrintf(QMessage, "%ld", MyQItem->Retry); StrBufAppendBufPlain(QMessage, HKEY("\nattempted|"), 0); StrBufAppendPrintf(QMessage, "%ld", time(NULL) /*ctdl_ev_now()*/ + MyQItem->Retry); It = GetNewHashPos(MyQItem->MailQEntries, 0); while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)) { MailQEntry *ThisItem = vQE; StrBufAppendBufPlain(QMessage, HKEY("\nremote|"), 0); StrBufAppendBuf(QMessage, ThisItem->Recipient, 0); StrBufAppendBufPlain(QMessage, HKEY("|"), 0); StrBufAppendPrintf(QMessage, "%d", ThisItem->Status); StrBufAppendBufPlain(QMessage, HKEY("|"), 0); if (ThisItem->AllStatusMessages != NULL) StrBufAppendBuf(QMessage, ThisItem->AllStatusMessages, 0); else StrBufAppendBuf(QMessage, ThisItem->StatusMessage, 0); } DeleteHashPos(&It); StrBufAppendBufPlain(QMessage, HKEY("\n"), 0); return QMessage; } void NewMailQEntry(OneQueItem *Item) { Item->Current = (MailQEntry*) malloc(sizeof(MailQEntry)); memset(Item->Current, 0, sizeof(MailQEntry)); if (Item->MailQEntries == NULL) Item->MailQEntries = NewHash(1, Flathash); /* alocate big buffer so we won't get problems reallocating later. */ Item->Current->StatusMessage = NewStrBufPlain(NULL, SIZ); Item->Current->n = GetCount(Item->MailQEntries); Put(Item->MailQEntries, IKEY(Item->Current->n), Item->Current, FreeMailQEntry); } void QItem_Handle_MsgID(OneQueItem *Item, StrBuf *Line, const char **Pos) { Item->MessageID = StrBufExtractNext_long(Line, Pos, '|'); } void QItem_Handle_EnvelopeFrom(OneQueItem *Item, StrBuf *Line, const char **Pos) { if (Item->EnvelopeFrom == NULL) Item->EnvelopeFrom = NewStrBufPlain(NULL, StrLength(Line)); StrBufExtract_NextToken(Item->EnvelopeFrom, Line, Pos, '|'); } void QItem_Handle_BounceTo(OneQueItem *Item, StrBuf *Line, const char **Pos) { if (Item->BounceTo == NULL) Item->BounceTo = NewStrBufPlain(NULL, StrLength(Line)); StrBufExtract_NextToken(Item->BounceTo, Line, Pos, '|'); } void QItem_Handle_SenderRoom(OneQueItem *Item, StrBuf *Line, const char **Pos) { if (Item->SenderRoom == NULL) Item->SenderRoom = NewStrBufPlain(NULL, StrLength(Line)); StrBufExtract_NextToken(Item->SenderRoom, Line, Pos, '|'); } void QItem_Handle_Recipient(OneQueItem *Item, StrBuf *Line, const char **Pos) { if (Item->Current == NULL) NewMailQEntry(Item); if (Item->Current->Recipient == NULL) Item->Current->Recipient=NewStrBufPlain(NULL, StrLength(Line)); StrBufExtract_NextToken(Item->Current->Recipient, Line, Pos, '|'); Item->Current->Status = StrBufExtractNext_int(Line, Pos, '|'); StrBufExtract_NextToken(Item->Current->StatusMessage, Line, Pos, '|'); Item->Current = NULL; // TODO: is this always right? } void QItem_Handle_retry(OneQueItem *Item, StrBuf *Line, const char **Pos) { Item->Retry = StrBufExtractNext_int(Line, Pos, '|'); if (Item->Retry == 0) Item->Retry = SMTP_RETRY_INTERVAL; else Item->Retry *= 2; } void QItem_Handle_Submitted(OneQueItem *Item, StrBuf *Line, const char **Pos) { Item->Submitted = atol(*Pos); } void QItem_Handle_Attempted(OneQueItem *Item, StrBuf *Line, const char **Pos) { Item->ReattemptWhen = StrBufExtractNext_int(Line, Pos, '|'); } /** * this one has to have the context for loading the message via the redirect buffer... */ StrBuf *smtp_load_msg(OneQueItem *MyQItem, int n, char **Author, char **Address) { CitContext *CCC=CC; StrBuf *SendMsg; CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ); CtdlOutputMsg(MyQItem->MessageID, MT_RFC822, HEADERS_ALL, 0, 1, NULL, (ESC_DOT|SUPPRESS_ENV_TO), Author, Address, NULL); SendMsg = CCC->redirect_buffer; CCC->redirect_buffer = NULL; if ((StrLength(SendMsg) > 0) && ChrPtr(SendMsg)[StrLength(SendMsg) - 1] != '\n') { SMTPC_syslog(LOG_WARNING, "[%d] Possible problem: message did not " "correctly terminate. (expecting 0x10, got 0x%02x)\n", MsgCount, //yes uncool, but best choice here... ChrPtr(SendMsg)[StrLength(SendMsg) - 1] ); StrBufAppendBufPlain(SendMsg, HKEY("\r\n"), 0); } return SendMsg; } /* * smtp_do_bounce() is caled by smtp_do_procmsg() to scan a set of delivery * instructions for "5" codes (permanent fatal errors) and produce/deliver * a "bounce" message (delivery status notification). */ void smtpq_do_bounce(OneQueItem *MyQItem, StrBuf *OMsgTxt, ParsedURL *Relay) { static int seq = 0; struct CtdlMessage *bmsg = NULL; StrBuf *boundary; StrBuf *Msg = NULL; StrBuf *BounceMB; recptypes *valid; time_t now; HashPos *It; void *vQE; long len; const char *Key; int first_attempt = 0; int successful_bounce = 0; int num_bounces = 0; int give_up = 0; SMTPCM_syslog(LOG_DEBUG, "smtp_do_bounce() called\n"); if (MyQItem->SendBounceMail == 0) return; now = time (NULL); //ev_time(); if ( (now - MyQItem->Submitted) > SMTP_GIVE_UP ) { give_up = 1; } if (MyQItem->Retry == SMTP_RETRY_INTERVAL) { first_attempt = 1; } /* * Now go through the instructions checking for stuff. */ Msg = NewStrBufPlain(NULL, 1024); It = GetNewHashPos(MyQItem->MailQEntries, 0); while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)) { MailQEntry *ThisItem = vQE; if ((ThisItem->Active && (ThisItem->Status == 5)) || /* failed now? */ ((give_up == 1) && (ThisItem->Status != 2)) || ((first_attempt == 1) && (ThisItem->Status != 2))) /* giving up after failed attempts... */ { ++num_bounces; StrBufAppendBufPlain(Msg, HKEY(" "), 0); StrBufAppendBuf(Msg, ThisItem->Recipient, 0); StrBufAppendBufPlain(Msg, HKEY(": "), 0); if (ThisItem->AllStatusMessages != NULL) StrBufAppendBuf(Msg, ThisItem->AllStatusMessages, 0); else StrBufAppendBuf(Msg, ThisItem->StatusMessage, 0); StrBufAppendBufPlain(Msg, HKEY("\r\n"), 0); } } DeleteHashPos(&It); /* Deliver the bounce if there's anything worth mentioning */ SMTPC_syslog(LOG_DEBUG, "num_bounces = %d\n", num_bounces); if (num_bounces == 0) { FreeStrBuf(&Msg); return; } if ((StrLength(MyQItem->SenderRoom) == 0) && MyQItem->HaveRelay) { const char *RelayUrlStr = "[not found]"; /* one message that relaying is broken is enough; no extra room error message. */ StrBuf *RelayDetails = NewStrBuf(); if (Relay != NULL) RelayUrlStr = ChrPtr(Relay->URL); StrBufPrintf(RelayDetails, "Relaying via %s failed permanently. \n Reason:\n%s\n Revalidate your relay configuration.", RelayUrlStr, ChrPtr(Msg)); CtdlAideMessage(ChrPtr(RelayDetails), "Relaying Failed"); FreeStrBuf(&RelayDetails); } boundary = NewStrBufPlain(HKEY("=_Citadel_Multipart_")); StrBufAppendPrintf(boundary, "%s_%04x%04x", config.c_fqdn, getpid(), ++seq); /* Start building our bounce message; go shopping for memory first. */ BounceMB = NewStrBufPlain( NULL, 1024 + /* mime stuff.... */ StrLength(Msg) + /* the bounce information... */ StrLength(OMsgTxt)); /* the original message */ if (BounceMB == NULL) { FreeStrBuf(&boundary); SMTPCM_syslog(LOG_ERR, "Failed to alloc() bounce message.\n"); return; } bmsg = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage)); if (bmsg == NULL) { FreeStrBuf(&boundary); FreeStrBuf(&BounceMB); SMTPCM_syslog(LOG_ERR, "Failed to alloc() bounce message.\n"); return; } memset(bmsg, 0, sizeof(struct CtdlMessage)); StrBufAppendBufPlain(BounceMB, HKEY("Content-type: multipart/mixed; boundary=\""), 0); StrBufAppendBuf(BounceMB, boundary, 0); StrBufAppendBufPlain(BounceMB, HKEY("\"\r\n"), 0); StrBufAppendBufPlain(BounceMB, HKEY("MIME-Version: 1.0\r\n"), 0); StrBufAppendBufPlain(BounceMB, HKEY("X-Mailer: " CITADEL "\r\n"), 0); StrBufAppendBufPlain(BounceMB, HKEY("\r\nThis is a multipart message in MIME format.\r\n\r\n"), 0); StrBufAppendBufPlain(BounceMB, HKEY("--"), 0); StrBufAppendBuf(BounceMB, boundary, 0); StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0); StrBufAppendBufPlain(BounceMB, HKEY("Content-type: text/plain\r\n\r\n"), 0); if (give_up) StrBufAppendBufPlain( BounceMB, HKEY( "A message you sent could not be delivered " "to some or all of its recipients\n" "due to prolonged unavailability " "of its destination(s).\n" "Giving up on the following addresses:\n\n" ), 0); else StrBufAppendBufPlain( BounceMB, HKEY( "A message you sent could not be delivered " "to some or all of its recipients.\n" "The following addresses " "were undeliverable:\n\n" ), 0); StrBufAppendBuf(BounceMB, Msg, 0); FreeStrBuf(&Msg); if (StrLength(MyQItem->SenderRoom) > 0) { StrBufAppendBufPlain( BounceMB, HKEY("The message was originaly posted in: "), 0); StrBufAppendBuf(BounceMB, MyQItem->SenderRoom, 0); StrBufAppendBufPlain( BounceMB, HKEY("\n"), 0); } /* Attach the original message */ StrBufAppendBufPlain(BounceMB, HKEY("\r\n--"), 0); StrBufAppendBuf(BounceMB, boundary, 0); StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0); StrBufAppendBufPlain(BounceMB, HKEY("Content-type: message/rfc822\r\n"), 0); StrBufAppendBufPlain(BounceMB, HKEY("Content-Transfer-Encoding: 7bit\r\n"), 0); StrBufAppendBufPlain(BounceMB, HKEY("Content-Disposition: inline\r\n"), 0); StrBufAppendBufPlain(BounceMB, HKEY("\r\n"), 0); StrBufAppendBuf(BounceMB, OMsgTxt, 0); /* Close the multipart MIME scope */ StrBufAppendBufPlain(BounceMB, HKEY("--"), 0); StrBufAppendBuf(BounceMB, boundary, 0); StrBufAppendBufPlain(BounceMB, HKEY("--\r\n"), 0); bmsg->cm_magic = CTDLMESSAGE_MAGIC; bmsg->cm_anon_type = MES_NORMAL; bmsg->cm_format_type = FMT_RFC822; CM_SetField(bmsg, eOriginalRoom, HKEY(MAILROOM)); CM_SetField(bmsg, eAuthor, HKEY("Citadel")); CM_SetField(bmsg, eNodeName, CFG_KEY(c_nodename)); CM_SetField(bmsg, eMsgSubject, HKEY("Delivery Status Notification (Failure)")); CM_SetAsFieldSB(bmsg, eMesageText, &BounceMB); /* First try the user who sent the message */ if (StrLength(MyQItem->BounceTo) == 0) { SMTPCM_syslog(LOG_ERR, "No bounce address specified\n"); } else { SMTPC_syslog(LOG_DEBUG, "bounce to user? <%s>\n", ChrPtr(MyQItem->BounceTo)); } /* Can we deliver the bounce to the original sender? */ valid = validate_recipients(ChrPtr(MyQItem->BounceTo), NULL, 0); if ((valid != NULL) && (valid->num_error == 0)) { CtdlSubmitMsg(bmsg, valid, "", QP_EADDR); successful_bounce = 1; } /* If not, post it in the Aide> room */ if (successful_bounce == 0) { CtdlSubmitMsg(bmsg, NULL, config.c_aideroom, QP_EADDR); } /* Free up the memory we used */ free_recipients(valid); FreeStrBuf(&boundary); CM_Free(bmsg); SMTPCM_syslog(LOG_DEBUG, "Done processing bounces\n"); } ParsedURL *LoadRelayUrls(OneQueItem *MyQItem, char *Author, char *Address) { int nRelays = 0; ParsedURL *RelayUrls = NULL; char mxbuf[SIZ]; ParsedURL **Url = &MyQItem->URL; nRelays = get_hosts(mxbuf, "fallbackhost"); if (nRelays > 0) { StrBuf *All; StrBuf *One; const char *Pos = NULL; All = NewStrBufPlain(mxbuf, -1); One = NewStrBufPlain(NULL, StrLength(All) + 1); while ((Pos != StrBufNOTNULL) && ((Pos == NULL) || !IsEmptyStr(Pos))) { StrBufExtract_NextToken(One, All, &Pos, '|'); if (!ParseURL(Url, One, DefaultMXPort)) { SMTPC_syslog(LOG_DEBUG, "Failed to parse: %s\n", ChrPtr(One)); } else { (*Url)->IsRelay = 1; MyQItem->HaveRelay = 1; } } FreeStrBuf(&All); FreeStrBuf(&One); } nRelays = get_hosts(mxbuf, "smarthost"); if (nRelays > 0) { char *User; StrBuf *All; StrBuf *One; const char *Pos = NULL; All = NewStrBufPlain(mxbuf, -1); One = NewStrBufPlain(NULL, StrLength(All) + 1); while ((Pos != StrBufNOTNULL) && ((Pos == NULL) || !IsEmptyStr(Pos))) { StrBufExtract_NextToken(One, All, &Pos, '|'); User = strchr(ChrPtr(One), ' '); if (User != NULL) { if (!strcmp(User + 1, Author) || !strcmp(User + 1, Address)) StrBufCutAt(One, 0, User); else { MyQItem->HaveRelay = 1; continue; } } if (!ParseURL(Url, One, DefaultMXPort)) { SMTPC_syslog(LOG_DEBUG, "Failed to parse: %s\n", ChrPtr(One)); } else { ///if (!Url->IsIP)) // todo dupe me fork ipv6 (*Url)->IsRelay = 1; MyQItem->HaveRelay = 1; } } FreeStrBuf(&All); FreeStrBuf(&One); } return RelayUrls; } /* * smtp_do_procmsg() * * Called by smtp_do_queue() to handle an individual message. */ void smtp_do_procmsg(long msgnum, void *userdata) { time_t now; int mynumsessions = num_sessions; struct CtdlMessage *msg = NULL; char *Author = NULL; char *Address = NULL; char *instr = NULL; StrBuf *PlainQItem; OneQueItem *MyQItem; char *pch; HashPos *It; void *vQE; long len; const char *Key; int HaveBuffers = 0; StrBuf *Msg =NULL; if (mynumsessions > max_sessions_for_outbound_smtp) { SMTPC_syslog(LOG_INFO, "skipping because of num jobs %d > %d max_sessions_for_outbound_smtp", mynumsessions, max_sessions_for_outbound_smtp); } SMTPC_syslog(LOG_DEBUG, "smtp_do_procmsg(%ld)\n", msgnum); ///strcpy(envelope_from, ""); msg = CtdlFetchMessage(msgnum, 1); if (msg == NULL) { SMTPC_syslog(LOG_ERR, "tried %ld but no such message!\n", msgnum); return; } pch = instr = msg->cm_fields[eMesageText]; /* Strip out the headers (no not amd any other non-instruction) line */ while (pch != NULL) { pch = strchr(pch, '\n'); if ((pch != NULL) && ((*(pch + 1) == '\n') || (*(pch + 1) == '\r'))) { instr = pch + 2; pch = NULL; } } PlainQItem = NewStrBufPlain(instr, -1); CM_Free(msg); MyQItem = DeserializeQueueItem(PlainQItem, msgnum); FreeStrBuf(&PlainQItem); if (MyQItem == NULL) { SMTPC_syslog(LOG_ERR, "Msg No %ld: already in progress!\n", msgnum); return; /* s.b. else is already processing... */ } /* * Postpone delivery if we've already tried recently. */ now = time(NULL); if ((MyQItem->ReattemptWhen != 0) && (now < MyQItem->ReattemptWhen) && (run_queue_now == 0)) { SMTPC_syslog(LOG_DEBUG, "Retry time not yet reached. %ld seconds left.", MyQItem->ReattemptWhen - now); It = GetNewHashPos(MyQItem->MailQEntries, 0); pthread_mutex_lock(&ActiveQItemsLock); { if (GetHashPosFromKey(ActiveQItems, LKEY(MyQItem->MessageID), It)) { DeleteEntryFromHash(ActiveQItems, It); } } pthread_mutex_unlock(&ActiveQItemsLock); ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this? DeleteHashPos(&It); return; } /* * Bail out if there's no actual message associated with this */ if (MyQItem->MessageID < 0L) { SMTPCM_syslog(LOG_ERR, "no 'msgid' directive found!\n"); It = GetNewHashPos(MyQItem->MailQEntries, 0); pthread_mutex_lock(&ActiveQItemsLock); { if (GetHashPosFromKey(ActiveQItems, LKEY(MyQItem->MessageID), It)) { DeleteEntryFromHash(ActiveQItems, It); } } pthread_mutex_unlock(&ActiveQItemsLock); DeleteHashPos(&It); ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this? return; } It = GetNewHashPos(MyQItem->MailQEntries, 0); while (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE)) { MailQEntry *ThisItem = vQE; SMTPC_syslog(LOG_DEBUG, "SMTP Queue: Task: <%s> %d\n", ChrPtr(ThisItem->Recipient), ThisItem->Active); } DeleteHashPos(&It); MyQItem->NotYetShutdownDeliveries = MyQItem->ActiveDeliveries = CountActiveQueueEntries(MyQItem, 1); /* failsafe against overload: * will we exceed the limit set? */ if ((MyQItem->ActiveDeliveries + mynumsessions > max_sessions_for_outbound_smtp) && /* if yes, did we reach more than half of the quota? */ ((mynumsessions * 2) > max_sessions_for_outbound_smtp) && /* if... would we ever fit into half of the quota?? */ (((MyQItem->ActiveDeliveries * 2) < max_sessions_for_outbound_smtp))) { /* abort delivery for another time. */ SMTPC_syslog(LOG_INFO, "SMTP Queue: skipping because of num jobs %d + %ld > %d max_sessions_for_outbound_smtp", mynumsessions, MyQItem->ActiveDeliveries, max_sessions_for_outbound_smtp); It = GetNewHashPos(MyQItem->MailQEntries, 0); pthread_mutex_lock(&ActiveQItemsLock); { if (GetHashPosFromKey(ActiveQItems, LKEY(MyQItem->MessageID), It)) { DeleteEntryFromHash(ActiveQItems, It); } } pthread_mutex_unlock(&ActiveQItemsLock); return; } if (MyQItem->ActiveDeliveries > 0) { ParsedURL *RelayUrls = NULL; int nActivated = 0; int n = MsgCount++; int m = MyQItem->ActiveDeliveries; int i = 1; It = GetNewHashPos(MyQItem->MailQEntries, 0); Msg = smtp_load_msg(MyQItem, n, &Author, &Address); RelayUrls = LoadRelayUrls(MyQItem, Author, Address); if ((RelayUrls == NULL) && MyQItem->HaveRelay) { while ((i <= m) && (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))) { int KeepBuffers = (i == m); MailQEntry *ThisItem = vQE; StrBufPrintf(ThisItem->StatusMessage, "No relay configured matching %s / %s", (Author != NULL)? Author : "", (Address != NULL)? Address : ""); ThisItem->Status = 5; nActivated++; if (i > 1) n = MsgCount++; syslog(LOG_INFO, "SMTPC: giving up on <%ld> <%s> %d / %d \n", MyQItem->MessageID, ChrPtr(ThisItem->Recipient), i, m); (*((int*) userdata)) ++; smtp_try_one_queue_entry(MyQItem, ThisItem, Msg, KeepBuffers, n, RelayUrls); if (KeepBuffers) HaveBuffers++; i++; } if (Author != NULL) free (Author); if (Address != NULL) free (Address); DeleteHashPos(&It); return; } if (Author != NULL) free (Author); if (Address != NULL) free (Address); while ((i <= m) && (GetNextHashPos(MyQItem->MailQEntries, It, &len, &Key, &vQE))) { MailQEntry *ThisItem = vQE; if (ThisItem->Active == 1) { int KeepBuffers = (i == m); nActivated++; if (nActivated % ndelay_count == 0) usleep(delay_msec); if (i > 1) n = MsgCount++; syslog(LOG_DEBUG, "SMTPC: Trying <%ld> <%s> %d / %d \n", MyQItem->MessageID, ChrPtr(ThisItem->Recipient), i, m); (*((int*) userdata)) ++; smtp_try_one_queue_entry(MyQItem, ThisItem, Msg, KeepBuffers, n, RelayUrls); if (KeepBuffers) HaveBuffers++; i++; } } DeleteHashPos(&It); } else { It = GetNewHashPos(MyQItem->MailQEntries, 0); pthread_mutex_lock(&ActiveQItemsLock); { if (GetHashPosFromKey(ActiveQItems, LKEY(MyQItem->MessageID), It)) { DeleteEntryFromHash(ActiveQItems, It); } else { long len; const char* Key; void *VData; SMTPC_syslog(LOG_WARNING, "unable to find QItem with ID[%ld]", MyQItem->MessageID); while (GetNextHashPos(ActiveQItems, It, &len, &Key, &VData)) { SMTPC_syslog(LOG_WARNING, "have: ID[%ld]", ((OneQueItem *)VData)->MessageID); } } } pthread_mutex_unlock(&ActiveQItemsLock); DeleteHashPos(&It); ////FreeQueItem(&MyQItem); TODO: DeleteEntryFromHash frees this? // TODO: bounce & delete? } if (!HaveBuffers) { FreeStrBuf (&Msg); // TODO : free RelayUrls } } /* * smtp_queue_thread() * * Run through the queue sending out messages. */ void smtp_do_queue(void) { int num_processed = 0; int num_activated = 0; pthread_setspecific(MyConKey, (void *)&smtp_queue_CC); SMTPCM_syslog(LOG_INFO, "processing outbound queue"); if (CtdlGetRoom(&CC->room, SMTP_SPOOLOUT_ROOM) != 0) { SMTPC_syslog(LOG_ERR, "Cannot find room <%s>", SMTP_SPOOLOUT_ROOM); } else { num_processed = CtdlForEachMessage(MSGS_ALL, 0L, NULL, SPOOLMIME, NULL, smtp_do_procmsg, &num_activated); } SMTPC_syslog(LOG_INFO, "queue run completed; %d messages processed %d activated", num_processed, num_activated); } /* * Initialize the SMTP outbound queue */ void smtp_init_spoolout(void) { struct ctdlroom qrbuf; /* * Create the room. This will silently fail if the room already * exists, and that's perfectly ok, because we want it to exist. */ CtdlCreateRoom(SMTP_SPOOLOUT_ROOM, 3, "", 0, 1, 0, VIEW_QUEUE); /* * Make sure it's set to be a "system room" so it doesn't show up * in the nown rooms list for Aides. */ if (CtdlGetRoomLock(&qrbuf, SMTP_SPOOLOUT_ROOM) == 0) { qrbuf.QRflags2 |= QR2_SYSTEM; CtdlPutRoomLock(&qrbuf); } } /*****************************************************************************/ /* SMTP UTILITY COMMANDS */ /*****************************************************************************/ void cmd_smtp(char *argbuf) { char cmd[64]; char node[256]; char buf[1024]; int i; int num_mxhosts; if (CtdlAccessCheck(ac_aide)) return; extract_token(cmd, argbuf, 0, '|', sizeof cmd); if (!strcasecmp(cmd, "mx")) { extract_token(node, argbuf, 1, '|', sizeof node); num_mxhosts = getmx(buf, node); cprintf("%d %d MX hosts listed for %s\n", LISTING_FOLLOWS, num_mxhosts, node); for (i=0; inum_internet > 0)) { struct CtdlMessage *imsg = NULL; char recipient[SIZ]; CitContext *CCC = MyContext(); StrBuf *SpoolMsg = NewStrBuf(); long nTokens; int i; MSGM_syslog(LOG_DEBUG, "Generating delivery instructions\n"); StrBufPrintf(SpoolMsg, "Content-type: "SPOOLMIME"\n" "\n" "msgid|%s\n" "submitted|%ld\n" "bounceto|%s\n", msg->cm_fields[eVltMsgNum], (long)time(NULL), recps->bounce_to); if (recps->envelope_from != NULL) { StrBufAppendBufPlain(SpoolMsg, HKEY("envelope_from|"), 0); StrBufAppendBufPlain(SpoolMsg, recps->envelope_from, -1, 0); StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0); } if (recps->sending_room != NULL) { StrBufAppendBufPlain(SpoolMsg, HKEY("source_room|"), 0); StrBufAppendBufPlain(SpoolMsg, recps->sending_room, -1, 0); StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0); } nTokens = num_tokens(recps->recp_internet, '|'); for (i = 0; i < nTokens; i++) { long len; len = extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient); if (len > 0) { StrBufAppendBufPlain(SpoolMsg, HKEY("remote|"), 0); StrBufAppendBufPlain(SpoolMsg, recipient, len, 0); StrBufAppendBufPlain(SpoolMsg, HKEY("|0||\n"), 0); } } imsg = malloc(sizeof(struct CtdlMessage)); memset(imsg, 0, sizeof(struct CtdlMessage)); imsg->cm_magic = CTDLMESSAGE_MAGIC; imsg->cm_anon_type = MES_NORMAL; imsg->cm_format_type = FMT_RFC822; CM_SetField(imsg, eMsgSubject, HKEY("QMSG")); CM_SetField(imsg, eAuthor, HKEY("Citadel")); CM_SetField(imsg, eJournal, HKEY("do not journal")); CM_SetAsFieldSB(imsg, eMesageText, &SpoolMsg); CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR); CM_Free(imsg); } return 0; } CTDL_MODULE_INIT(smtp_queu) { char *pstr; if (!threading) { pstr = getenv("CITSERVER_n_session_max"); if ((pstr != NULL) && (*pstr != '\0')) max_sessions_for_outbound_smtp = atol(pstr); /* how many sessions might be active till we stop adding more smtp jobs */ pstr = getenv("CITSERVER_smtp_n_delay_count"); if ((pstr != NULL) && (*pstr != '\0')) ndelay_count = atol(pstr); /* every n queued messages we will sleep... */ pstr = getenv("CITSERVER_smtp_delay"); if ((pstr != NULL) && (*pstr != '\0')) delay_msec = atol(pstr) * 1000; /* this many seconds. */ CtdlRegisterMessageHook(smtp_aftersave, EVT_AFTERSAVE); CtdlFillSystemContext(&smtp_queue_CC, "SMTP_Send"); ActiveQItems = NewHash(1, lFlathash); pthread_mutex_init(&ActiveQItemsLock, NULL); QItemHandlers = NewHash(0, NULL); RegisterQItemHandler(HKEY("msgid"), QItem_Handle_MsgID); RegisterQItemHandler(HKEY("envelope_from"), QItem_Handle_EnvelopeFrom); RegisterQItemHandler(HKEY("retry"), QItem_Handle_retry); RegisterQItemHandler(HKEY("attempted"), QItem_Handle_Attempted); RegisterQItemHandler(HKEY("remote"), QItem_Handle_Recipient); RegisterQItemHandler(HKEY("bounceto"), QItem_Handle_BounceTo); RegisterQItemHandler(HKEY("source_room"), QItem_Handle_SenderRoom); RegisterQItemHandler(HKEY("submitted"), QItem_Handle_Submitted); smtp_init_spoolout(); CtdlRegisterEVCleanupHook(smtp_evq_cleanup); CtdlRegisterProtoHook(cmd_smtp, "SMTP", "SMTP utility commands"); CtdlRegisterSessionHook(smtp_do_queue, EVT_TIMER, PRIO_SEND + 10); } /* return our Subversion id for the Log */ return "smtpeventclient"; } citadel-9.01/modules/smtp/serv_smtp.c0000644000000000000000000007250712507024051016376 0ustar rootroot/* * This module is an SMTP and ESMTP server for the Citadel system. * It is compliant with all of the following: * * RFC 821 - Simple Mail Transfer Protocol * RFC 876 - Survey of SMTP Implementations * RFC 1047 - Duplicate messages and SMTP * RFC 1652 - 8 bit MIME * RFC 1869 - Extended Simple Mail Transfer Protocol * RFC 1870 - SMTP Service Extension for Message Size Declaration * RFC 2033 - Local Mail Transfer Protocol * RFC 2197 - SMTP Service Extension for Command Pipelining * RFC 2476 - Message Submission * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS * RFC 2554 - SMTP Service Extension for Authentication * RFC 2821 - Simple Mail Transfer Protocol * RFC 2822 - Internet Message Format * RFC 2920 - SMTP Service Extension for Command Pipelining * * The VRFY and EXPN commands have been removed from this implementation * because nobody uses these commands anymore, except for spammers. * * Copyright (c) 1998-2013 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "sysdep.h" #include #include #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "control.h" #include "user_ops.h" #include "room_ops.h" #include "database.h" #include "msgbase.h" #include "internet_addressing.h" #include "genstamp.h" #include "domain.h" #include "clientsocket.h" #include "locate_host.h" #include "citadel_dirs.h" #include "ctdl_module.h" #include "smtp_util.h" enum { /* Command states for login authentication */ smtp_command, smtp_user, smtp_password, smtp_plain }; enum SMTP_FLAGS { HELO, EHLO, LHLO }; typedef void (*smtp_handler)(long offest, long Flags); typedef struct _smtp_handler_hook { smtp_handler h; int Flags; } smtp_handler_hook; HashList *SMTPCmds = NULL; #define MaxSMTPCmdLen 10 #define RegisterSmtpCMD(First, H, Flags) \ registerSmtpCMD(HKEY(First), H, Flags) void registerSmtpCMD(const char *First, long FLen, smtp_handler H, int Flags) { smtp_handler_hook *h; if (FLen >= MaxSMTPCmdLen) cit_panic_backtrace (0); h = (smtp_handler_hook*) malloc(sizeof(smtp_handler_hook)); memset(h, 0, sizeof(smtp_handler_hook)); h->Flags = Flags; h->h = H; Put(SMTPCmds, First, FLen, h, NULL); } void smtp_cleanup(void) { DeleteHash(&SMTPCmds); } /* * Here's where our SMTP session begins its happy day. */ void smtp_greeting(int is_msa) { citsmtp *sSMTP; char message_to_spammer[1024]; strcpy(CC->cs_clientname, "SMTP session"); CC->internal_pgm = 1; CC->cs_flags |= CS_STEALTH; CC->session_specific_data = malloc(sizeof(citsmtp)); memset(SMTP, 0, sizeof(citsmtp)); sSMTP = SMTP; sSMTP->is_msa = is_msa; sSMTP->Cmd = NewStrBufPlain(NULL, SIZ); sSMTP->helo_node = NewStrBuf(); sSMTP->from = NewStrBufPlain(NULL, SIZ); sSMTP->recipients = NewStrBufPlain(NULL, SIZ); sSMTP->OneRcpt = NewStrBufPlain(NULL, SIZ); sSMTP->preferred_sender_email = NULL; sSMTP->preferred_sender_name = NULL; /* If this config option is set, reject connections from problem * addresses immediately instead of after they execute a RCPT */ if ( (config.c_rbl_at_greeting) && (sSMTP->is_msa == 0) ) { if (rbl_check(message_to_spammer)) { if (server_shutting_down) cprintf("421 %s\r\n", message_to_spammer); else cprintf("550 %s\r\n", message_to_spammer); CC->kill_me = KILLME_SPAMMER; /* no need to free_recipients(valid), it's not allocated yet */ return; } } /* Otherwise we're either clean or we check later. */ if (CC->nologin==1) { cprintf("451 Too many connections are already open; please try again later.\r\n"); CC->kill_me = KILLME_MAX_SESSIONS_EXCEEDED; /* no need to free_recipients(valid), it's not allocated yet */ return; } /* Note: the FQDN *must* appear as the first thing after the 220 code. * Some clients (including citmail.c) depend on it being there. */ cprintf("220 %s ESMTP Citadel server ready.\r\n", config.c_fqdn); } /* * SMTPS is just like SMTP, except it goes crypto right away. */ void smtps_greeting(void) { CtdlModuleStartCryptoMsgs(NULL, NULL, NULL); #ifdef HAVE_OPENSSL if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO; /* kill session if no crypto */ #endif smtp_greeting(0); } /* * SMTP MSA port requires authentication. */ void smtp_msa_greeting(void) { smtp_greeting(1); } /* * LMTP is like SMTP but with some extra bonus footage added. */ void lmtp_greeting(void) { smtp_greeting(0); SMTP->is_lmtp = 1; } /* * Generic SMTP MTA greeting */ void smtp_mta_greeting(void) { smtp_greeting(0); } /* * We also have an unfiltered LMTP socket that bypasses spam filters. */ void lmtp_unfiltered_greeting(void) { citsmtp *sSMTP; smtp_greeting(0); sSMTP = SMTP; sSMTP->is_lmtp = 1; sSMTP->is_unfiltered = 1; } /* * Login greeting common to all auth methods */ void smtp_auth_greeting(long offset, long Flags) { cprintf("235 Hello, %s\r\n", CC->user.fullname); syslog(LOG_NOTICE, "SMTP authenticated %s\n", CC->user.fullname); CC->internal_pgm = 0; CC->cs_flags &= ~CS_STEALTH; } /* * Implement HELO and EHLO commands. * * which_command: 0=HELO, 1=EHLO, 2=LHLO */ void smtp_hello(long offset, long which_command) { citsmtp *sSMTP = SMTP; StrBufAppendBuf (sSMTP->helo_node, sSMTP->Cmd, offset); if ( (which_command != LHLO) && (sSMTP->is_lmtp) ) { cprintf("500 Only LHLO is allowed when running LMTP\r\n"); return; } if ( (which_command == LHLO) && (sSMTP->is_lmtp == 0) ) { cprintf("500 LHLO is only allowed when running LMTP\r\n"); return; } if (which_command == HELO) { cprintf("250 Hello %s (%s [%s])\r\n", ChrPtr(sSMTP->helo_node), CC->cs_host, CC->cs_addr ); } else { if (which_command == EHLO) { cprintf("250-Hello %s (%s [%s])\r\n", ChrPtr(sSMTP->helo_node), CC->cs_host, CC->cs_addr ); } else { cprintf("250-Greetings and joyous salutations.\r\n"); } cprintf("250-HELP\r\n"); cprintf("250-SIZE %ld\r\n", config.c_maxmsglen); #ifdef HAVE_OPENSSL /* * Offer TLS, but only if TLS is not already active. * Furthermore, only offer TLS when running on * the SMTP-MSA port, not on the SMTP-MTA port, due to * questionable reliability of TLS in certain sending MTA's. */ if ( (!CC->redirect_ssl) && (sSMTP->is_msa) ) { cprintf("250-STARTTLS\r\n"); } #endif /* HAVE_OPENSSL */ cprintf("250-AUTH LOGIN PLAIN\r\n" "250-AUTH=LOGIN PLAIN\r\n" "250 8BITMIME\r\n" ); } } /* * Backend function for smtp_webcit_preferences_hack(). * Look at a message and determine if it's the preferences file. */ void smtp_webcit_preferences_hack_backend(long msgnum, void *userdata) { struct CtdlMessage *msg; char **webcit_conf = (char **) userdata; if (*webcit_conf) { return; // already got it } msg = CtdlFetchMessage(msgnum, 1); if (msg == NULL) { return; } if ( !CM_IsEmpty(msg, eMsgSubject) && (!strcasecmp(msg->cm_fields[eMsgSubject], "__ WebCit Preferences __"))) { /* This is it! Change ownership of the message text so it doesn't get freed. */ *webcit_conf = (char *)msg->cm_fields[eMesageText]; msg->cm_fields[eMesageText] = NULL; } CM_Free(msg); } /* * The configuration item for the user's preferred display name for outgoing email is, unfortunately, * stored in the account's WebCit configuration. We have to fetch it now. */ void smtp_webcit_preferences_hack(void) { char config_roomname[ROOMNAMELEN]; char *webcit_conf = NULL; citsmtp *sSMTP = SMTP; snprintf(config_roomname, sizeof config_roomname, "%010ld.%s", CC->user.usernum, USERCONFIGROOM); if (CtdlGetRoom(&CC->room, config_roomname) != 0) { return; } /* * Find the WebCit configuration message */ CtdlForEachMessage(MSGS_ALL, 1, NULL, NULL, NULL, smtp_webcit_preferences_hack_backend, (void *)&webcit_conf); if (!webcit_conf) { return; } /* Parse the webcit configuration and attempt to do something useful with it */ char *str = webcit_conf; char *saveptr = str; char *this_line = NULL; while (this_line = strtok_r(str, "\n", &saveptr), this_line != NULL) { str = NULL; if (!strncasecmp(this_line, "defaultfrom|", 12)) { sSMTP->preferred_sender_email = NewStrBufPlain(&this_line[12], -1); } if (!strncasecmp(this_line, "defaultname|", 12)) { sSMTP->preferred_sender_name = NewStrBufPlain(&this_line[12], -1); } if ((!strncasecmp(this_line, "defaultname|", 12)) && (sSMTP->preferred_sender_name == NULL)) { sSMTP->preferred_sender_name = NewStrBufPlain(&this_line[12], -1); } } free(webcit_conf); } /* * Implement HELP command. */ void smtp_help(long offset, long Flags) { cprintf("214 RTFM http://www.ietf.org/rfc/rfc2821.txt\r\n"); } /* * */ void smtp_get_user(long offset) { char buf[SIZ]; citsmtp *sSMTP = SMTP; StrBufDecodeBase64(sSMTP->Cmd); /* syslog(LOG_DEBUG, "Trying <%s>\n", username); */ if (CtdlLoginExistingUser(NULL, ChrPtr(sSMTP->Cmd)) == login_ok) { size_t len = CtdlEncodeBase64(buf, "Password:", 9, 0); if (buf[len - 1] == '\n') { buf[len - 1] = '\0'; } cprintf("334 %s\r\n", buf); sSMTP->command_state = smtp_password; } else { cprintf("500 No such user.\r\n"); sSMTP->command_state = smtp_command; } } /* * */ void smtp_get_pass(long offset, long Flags) { citsmtp *sSMTP = SMTP; char password[SIZ]; memset(password, 0, sizeof(password)); StrBufDecodeBase64(sSMTP->Cmd); /* syslog(LOG_DEBUG, "Trying <%s>\n", password); */ if (CtdlTryPassword(SKEY(sSMTP->Cmd)) == pass_ok) { smtp_auth_greeting(offset, Flags); } else { cprintf("535 Authentication failed.\r\n"); } sSMTP->command_state = smtp_command; } /* * Back end for PLAIN auth method (either inline or multistate) */ void smtp_try_plain(long offset, long Flags) { citsmtp *sSMTP = SMTP; const char*decoded_authstring; char ident[256] = ""; char user[256] = ""; char pass[256] = ""; int result; long decoded_len; long len = 0; long plen = 0; memset(pass, 0, sizeof(pass)); decoded_len = StrBufDecodeBase64(sSMTP->Cmd); if (decoded_len > 0) { decoded_authstring = ChrPtr(sSMTP->Cmd); len = safestrncpy(ident, decoded_authstring, sizeof ident); decoded_len -= len - 1; decoded_authstring += len + 1; if (decoded_len > 0) { len = safestrncpy(user, decoded_authstring, sizeof user); decoded_authstring += len + 1; decoded_len -= len - 1; } if (decoded_len > 0) { plen = safestrncpy(pass, decoded_authstring, sizeof pass); if (plen < 0) plen = sizeof(pass) - 1; } } sSMTP->command_state = smtp_command; if (!IsEmptyStr(ident)) { result = CtdlLoginExistingUser(user, ident); } else { result = CtdlLoginExistingUser(NULL, user); } if (result == login_ok) { if (CtdlTryPassword(pass, plen) == pass_ok) { smtp_webcit_preferences_hack(); smtp_auth_greeting(offset, Flags); return; } } cprintf("504 Authentication failed.\r\n"); } /* * Attempt to perform authenticated SMTP */ void smtp_auth(long offset, long Flags) { citsmtp *sSMTP = SMTP; char username_prompt[64]; char method[64]; char encoded_authstring[1024]; if (CC->logged_in) { cprintf("504 Already logged in.\r\n"); return; } extract_token(method, ChrPtr(sSMTP->Cmd) + offset, 0, ' ', sizeof method); if (!strncasecmp(method, "login", 5) ) { if (StrLength(sSMTP->Cmd) - offset >= 7) { smtp_get_user(6); } else { size_t len = CtdlEncodeBase64(username_prompt, "Username:", 9, 0); if (username_prompt[len - 1] == '\n') { username_prompt[len - 1] = '\0'; } cprintf("334 %s\r\n", username_prompt); sSMTP->command_state = smtp_user; } return; } if (!strncasecmp(method, "plain", 5) ) { long len; if (num_tokens(ChrPtr(sSMTP->Cmd) + offset, ' ') < 2) { cprintf("334 \r\n"); SMTP->command_state = smtp_plain; return; } len = extract_token(encoded_authstring, ChrPtr(sSMTP->Cmd) + offset, 1, ' ', sizeof encoded_authstring); StrBufPlain(sSMTP->Cmd, encoded_authstring, len); smtp_try_plain(0, Flags); return; } if (strncasecmp(method, "login", 5) ) { cprintf("504 Unknown authentication method.\r\n"); return; } } /* * Implements the RSET (reset state) command. * Currently this just zeroes out the state buffer. If pointers to data * allocated with malloc() are ever placed in the state buffer, we have to * be sure to free() them first! * * Set do_response to nonzero to output the SMTP RSET response code. */ void smtp_rset(long offset, long do_response) { citsmtp *sSMTP = SMTP; /* * Our entire SMTP state is discarded when a RSET command is issued, * but we need to preserve this one little piece of information, so * we save it for later. */ FlushStrBuf(sSMTP->Cmd); FlushStrBuf(sSMTP->helo_node); FlushStrBuf(sSMTP->from); FlushStrBuf(sSMTP->recipients); FlushStrBuf(sSMTP->OneRcpt); sSMTP->command_state = 0; sSMTP->number_of_recipients = 0; sSMTP->delivery_mode = 0; sSMTP->message_originated_locally = 0; sSMTP->is_msa = 0; /* * we must remember is_lmtp & is_unfiltered. */ /* * It is somewhat ambiguous whether we want to log out when a RSET * command is issued. Here's the code to do it. It is commented out * because some clients (such as Pine) issue RSET commands before * each message, but still expect to be logged in. * * if (CC->logged_in) { * logout(CC); * } */ if (do_response) { cprintf("250 Zap!\r\n"); } } /* * Clear out the portions of the state buffer that need to be cleared out * after the DATA command finishes. */ void smtp_data_clear(long offset, long flags) { citsmtp *sSMTP = SMTP; FlushStrBuf(sSMTP->from); FlushStrBuf(sSMTP->recipients); FlushStrBuf(sSMTP->OneRcpt); sSMTP->number_of_recipients = 0; sSMTP->delivery_mode = 0; sSMTP->message_originated_locally = 0; } /* * Implements the "MAIL FROM:" command */ void smtp_mail(long offset, long flags) { char user[SIZ]; char node[SIZ]; char name[SIZ]; citsmtp *sSMTP = SMTP; if (StrLength(sSMTP->from) > 0) { cprintf("503 Only one sender permitted\r\n"); return; } if (strncasecmp(ChrPtr(sSMTP->Cmd) + offset, "From:", 5)) { cprintf("501 Syntax error\r\n"); return; } StrBufAppendBuf(sSMTP->from, sSMTP->Cmd, offset); StrBufTrim(sSMTP->from); if (strchr(ChrPtr(sSMTP->from), '<') != NULL) { StrBufStripAllBut(sSMTP->from, '<', '>'); } /* We used to reject empty sender names, until it was brought to our * attention that RFC1123 5.2.9 requires that this be allowed. So now * we allow it, but replace the empty string with a fake * address so we don't have to contend with the empty string causing * other code to fail when it's expecting something there. */ if (StrLength(sSMTP->from) == 0) { StrBufPlain(sSMTP->from, HKEY("someone@example.com")); } /* If this SMTP connection is from a logged-in user, force the 'from' * to be the user's Internet e-mail address as Citadel knows it. */ if (CC->logged_in) { StrBufPlain(sSMTP->from, CC->cs_inet_email, -1); cprintf("250 Sender ok <%s>\r\n", ChrPtr(sSMTP->from)); sSMTP->message_originated_locally = 1; return; } else if (sSMTP->is_lmtp) { /* Bypass forgery checking for LMTP */ } /* Otherwise, make sure outsiders aren't trying to forge mail from * this system (unless, of course, c_allow_spoofing is enabled) */ else if (config.c_allow_spoofing == 0) { process_rfc822_addr(ChrPtr(sSMTP->from), user, node, name); syslog(LOG_DEBUG, "Claimed envelope sender is '%s' == '%s' @ '%s' ('%s')", ChrPtr(sSMTP->from), user, node, name ); if (CtdlHostAlias(node) != hostalias_nomatch) { cprintf("550 You must log in to send mail from %s\r\n", node); FlushStrBuf(sSMTP->from); syslog(LOG_DEBUG, "Rejecting unauthenticated mail from %s", node); return; } } cprintf("250 Sender ok\r\n"); } /* * Implements the "RCPT To:" command */ void smtp_rcpt(long offset, long flags) { struct CitContext *CCC = CC; char message_to_spammer[SIZ]; recptypes *valid = NULL; citsmtp *sSMTP = SMTP; if (StrLength(sSMTP->from) == 0) { cprintf("503 Need MAIL before RCPT\r\n"); return; } if (strncasecmp(ChrPtr(sSMTP->Cmd) + offset, "To:", 3)) { cprintf("501 Syntax error\r\n"); return; } if ( (sSMTP->is_msa) && (!CCC->logged_in) ) { cprintf("550 You must log in to send mail on this port.\r\n"); FlushStrBuf(sSMTP->from); return; } FlushStrBuf(sSMTP->OneRcpt); StrBufAppendBuf(sSMTP->OneRcpt, sSMTP->Cmd, offset + 3); StrBufTrim(sSMTP->OneRcpt); StrBufStripAllBut(sSMTP->OneRcpt, '<', '>'); if ( (StrLength(sSMTP->OneRcpt) + StrLength(sSMTP->recipients)) >= SIZ) { cprintf("452 Too many recipients\r\n"); return; } /* RBL check */ if ( (!CCC->logged_in) /* Don't RBL authenticated users */ && (!sSMTP->is_lmtp) ) { /* Don't RBL LMTP clients */ if (config.c_rbl_at_greeting == 0) { /* Don't RBL again if we already did it */ if (rbl_check(message_to_spammer)) { if (server_shutting_down) cprintf("421 %s\r\n", message_to_spammer); else cprintf("550 %s\r\n", message_to_spammer); /* no need to free_recipients(valid), it's not allocated yet */ return; } } } valid = validate_recipients( ChrPtr(sSMTP->OneRcpt), smtp_get_Recipients(), (sSMTP->is_lmtp)? POST_LMTP: (CCC->logged_in)? POST_LOGGED_IN: POST_EXTERNAL ); if (valid->num_error != 0) { cprintf("550 %s\r\n", valid->errormsg); free_recipients(valid); return; } if (valid->num_internet > 0) { if (CCC->logged_in) { if (CtdlCheckInternetMailPermission(&CCC->user)==0) { cprintf("551 <%s> - you do not have permission to send Internet mail\r\n", ChrPtr(sSMTP->OneRcpt)); free_recipients(valid); return; } } } if (valid->num_internet > 0) { if ( (sSMTP->message_originated_locally == 0) && (sSMTP->is_lmtp == 0) ) { cprintf("551 <%s> - relaying denied\r\n", ChrPtr(sSMTP->OneRcpt)); free_recipients(valid); return; } } cprintf("250 RCPT ok <%s>\r\n", ChrPtr(sSMTP->OneRcpt)); if (StrLength(sSMTP->recipients) > 0) { StrBufAppendBufPlain(sSMTP->recipients, HKEY(","), 0); } StrBufAppendBuf(sSMTP->recipients, sSMTP->OneRcpt, 0); sSMTP->number_of_recipients ++; if (valid != NULL) { free_recipients(valid); } } /* * Implements the DATA command */ void smtp_data(long offset, long flags) { struct CitContext *CCC = CC; StrBuf *body; StrBuf *defbody; struct CtdlMessage *msg = NULL; long msgnum = (-1L); char nowstamp[SIZ]; recptypes *valid; int scan_errors; int i; citsmtp *sSMTP = SMTP; if (StrLength(sSMTP->from) == 0) { cprintf("503 Need MAIL command first.\r\n"); return; } if (sSMTP->number_of_recipients < 1) { cprintf("503 Need RCPT command first.\r\n"); return; } cprintf("354 Transmit message now - terminate with '.' by itself\r\n"); datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822); defbody = NewStrBufPlain(NULL, SIZ); if (defbody != NULL) { if (sSMTP->is_lmtp && (CCC->cs_UDSclientUID != -1)) { StrBufPrintf( defbody, "Received: from %s (Citadel from userid %ld)\n" " by %s; %s\n", ChrPtr(sSMTP->helo_node), (long int) CCC->cs_UDSclientUID, config.c_fqdn, nowstamp); } else { StrBufPrintf( defbody, "Received: from %s (%s [%s])\n" " by %s; %s\n", ChrPtr(sSMTP->helo_node), CCC->cs_host, CCC->cs_addr, config.c_fqdn, nowstamp); } } body = CtdlReadMessageBodyBuf(HKEY("."), config.c_maxmsglen, defbody, 1, NULL); FreeStrBuf(&defbody); if (body == NULL) { cprintf("550 Unable to save message: internal error.\r\n"); return; } syslog(LOG_DEBUG, "Converting message...\n"); msg = convert_internet_message_buf(&body); /* If the user is locally authenticated, FORCE the From: header to * show up as the real sender. Yes, this violates the RFC standard, * but IT MAKES SENSE. If you prefer strict RFC adherence over * common sense, you can disable this in the configuration. * * We also set the "message room name" ('O' field) to MAILROOM * (which is Mail> on most systems) to prevent it from getting set * to something ugly like "0000058008.Sent Items>" when the message * is read with a Citadel client. */ if ( (CCC->logged_in) && (config.c_rfc822_strict_from != CFG_SMTP_FROM_NOFILTER) ) { int validemail = 0; if (!CM_IsEmpty(msg, erFc822Addr) && ((config.c_rfc822_strict_from == CFG_SMTP_FROM_CORRECT) || (config.c_rfc822_strict_from == CFG_SMTP_FROM_REJECT) ) ) { if (!IsEmptyStr(CCC->cs_inet_email)) validemail = strcmp(CCC->cs_inet_email, msg->cm_fields[erFc822Addr]) == 0; if ((!validemail) && (!IsEmptyStr(CCC->cs_inet_other_emails))) { int num_secondary_emails = 0; int i; num_secondary_emails = num_tokens(CCC->cs_inet_other_emails, '|'); for (i=0; i < num_secondary_emails && !validemail; ++i) { char buf[256]; extract_token(buf, CCC->cs_inet_other_emails,i,'|',sizeof CCC->cs_inet_other_emails); validemail = strcmp(buf, msg->cm_fields[erFc822Addr]) == 0; } } } if (!validemail && (config.c_rfc822_strict_from == CFG_SMTP_FROM_REJECT)) { syslog(LOG_ERR, "invalid sender '%s' - rejecting this message", msg->cm_fields[erFc822Addr]); cprintf("550 Invalid sender '%s' - rejecting this message.\r\n", msg->cm_fields[erFc822Addr]); return; } CM_SetField(msg, eNodeName, CFG_KEY(c_nodename)); CM_SetField(msg, eHumanNode, CFG_KEY(c_humannode)); CM_SetField(msg, eOriginalRoom, HKEY(MAILROOM)); if (sSMTP->preferred_sender_name != NULL) CM_SetField(msg, eAuthor, SKEY(sSMTP->preferred_sender_name)); else CM_SetField(msg, eAuthor, CCC->user.fullname, strlen(CCC->user.fullname)); if (!validemail) { if (sSMTP->preferred_sender_email != NULL) CM_SetField(msg, erFc822Addr, SKEY(sSMTP->preferred_sender_email)); else CM_SetField(msg, erFc822Addr, CCC->cs_inet_email, strlen(CCC->cs_inet_email)); } } /* Set the "envelope from" address */ CM_SetField(msg, eMessagePath, SKEY(sSMTP->from)); /* Set the "envelope to" address */ CM_SetField(msg, eenVelopeTo, SKEY(sSMTP->recipients)); /* Submit the message into the Citadel system. */ valid = validate_recipients( ChrPtr(sSMTP->recipients), smtp_get_Recipients(), (sSMTP->is_lmtp)? POST_LMTP: (CCC->logged_in)? POST_LOGGED_IN: POST_EXTERNAL ); /* If there are modules that want to scan this message before final * submission (such as virus checkers or spam filters), call them now * and give them an opportunity to reject the message. */ if (sSMTP->is_unfiltered) { scan_errors = 0; } else { scan_errors = PerformMessageHooks(msg, valid, EVT_SMTPSCAN); } if (scan_errors > 0) { /* We don't want this message! */ if (CM_IsEmpty(msg, eErrorMsg)) { CM_SetField(msg, eErrorMsg, HKEY("Message rejected by filter")); } StrBufPrintf(sSMTP->OneRcpt, "550 %s\r\n", msg->cm_fields[eErrorMsg]); } else { /* Ok, we'll accept this message. */ msgnum = CtdlSubmitMsg(msg, valid, "", 0); if (msgnum > 0L) { StrBufPrintf(sSMTP->OneRcpt, "250 Message accepted.\r\n"); } else { StrBufPrintf(sSMTP->OneRcpt, "550 Internal delivery error\r\n"); } } /* For SMTP and ESMTP, just print the result message. For LMTP, we * have to print one result message for each recipient. Since there * is nothing in Citadel which would cause different recipients to * have different results, we can get away with just spitting out the * same message once for each recipient. */ if (sSMTP->is_lmtp) { for (i=0; inumber_of_recipients; ++i) { cputbuf(sSMTP->OneRcpt); } } else { cputbuf(sSMTP->OneRcpt); } /* Write something to the syslog(which may or may not be where the * rest of the Citadel logs are going; some sysadmins want LOG_MAIL). */ syslog((LOG_MAIL | LOG_INFO), "%ld: from=<%s>, nrcpts=%d, relay=%s [%s], stat=%s", msgnum, ChrPtr(sSMTP->from), sSMTP->number_of_recipients, CCC->cs_host, CCC->cs_addr, ChrPtr(sSMTP->OneRcpt) ); /* Clean up */ CM_Free(msg); free_recipients(valid); smtp_data_clear(0, 0); /* clear out the buffers now */ } /* * implements the STARTTLS command */ void smtp_starttls(long offset, long flags) { char ok_response[SIZ]; char nosup_response[SIZ]; char error_response[SIZ]; sprintf(ok_response, "220 Begin TLS negotiation now\r\n"); sprintf(nosup_response, "554 TLS not supported here\r\n"); sprintf(error_response, "554 Internal error\r\n"); CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response); smtp_rset(0, 0); } /* * Main command loop for SMTP server sessions. */ void smtp_command_loop(void) { static const ConstStr AuthPlainStr = {HKEY("AUTH PLAIN")}; struct CitContext *CCC = CC; citsmtp *sSMTP = SMTP; const char *pch, *pchs; long i; char CMD[MaxSMTPCmdLen + 1]; if (sSMTP == NULL) { syslog(LOG_EMERG, "Session SMTP data is null. WTF? We will crash now.\n"); return cit_panic_backtrace (0); } time(&CCC->lastcmd); if (CtdlClientGetLine(sSMTP->Cmd) < 1) { syslog(LOG_CRIT, "SMTP: client disconnected: ending session.\n"); CC->kill_me = KILLME_CLIENT_DISCONNECTED; return; } syslog(LOG_DEBUG, "SMTP server: %s\n", ChrPtr(sSMTP->Cmd)); if (sSMTP->command_state == smtp_user) { if (!strncmp(ChrPtr(sSMTP->Cmd), AuthPlainStr.Key, AuthPlainStr.len)) smtp_try_plain(0, 0); else smtp_get_user(0); return; } else if (sSMTP->command_state == smtp_password) { smtp_get_pass(0, 0); return; } else if (sSMTP->command_state == smtp_plain) { smtp_try_plain(0, 0); return; } pchs = pch = ChrPtr(sSMTP->Cmd); i = 0; while ((*pch != '\0') && (!isblank(*pch)) && (pch - pchs <= MaxSMTPCmdLen)) { CMD[i] = toupper(*pch); pch ++; i++; } CMD[i] = '\0'; if ((*pch == '\0') || (isblank(*pch))) { void *v; if (GetHash(SMTPCmds, CMD, i, &v) && (v != NULL)) { smtp_handler_hook *h = (smtp_handler_hook*) v; if (isblank(pchs[i])) i++; h->h(i, h->Flags); return; } } cprintf("502 I'm afraid I can't do that.\r\n"); } void smtp_noop(long offest, long Flags) { cprintf("250 NOOP\r\n"); } void smtp_quit(long offest, long Flags) { cprintf("221 Goodbye...\r\n"); CC->kill_me = KILLME_CLIENT_LOGGED_OUT; } /*****************************************************************************/ /* MODULE INITIALIZATION STUFF */ /*****************************************************************************/ /* * This cleanup function blows away the temporary memory used by * the SMTP server. */ void smtp_cleanup_function(void) { citsmtp *sSMTP = SMTP; /* Don't do this stuff if this is not an SMTP session! */ if (CC->h_command_function != smtp_command_loop) return; syslog(LOG_DEBUG, "Performing SMTP cleanup hook\n"); FreeStrBuf(&sSMTP->Cmd); FreeStrBuf(&sSMTP->helo_node); FreeStrBuf(&sSMTP->from); FreeStrBuf(&sSMTP->recipients); FreeStrBuf(&sSMTP->OneRcpt); FreeStrBuf(&sSMTP->preferred_sender_email); FreeStrBuf(&sSMTP->preferred_sender_name); free(sSMTP); } const char *CitadelServiceSMTP_MTA="SMTP-MTA"; const char *CitadelServiceSMTPS_MTA="SMTPs-MTA"; const char *CitadelServiceSMTP_MSA="SMTP-MSA"; const char *CitadelServiceSMTP_LMTP="LMTP"; const char *CitadelServiceSMTP_LMTP_UNF="LMTP-UnF"; CTDL_MODULE_INIT(smtp) { if (!threading) { SMTPCmds = NewHash(1, NULL); RegisterSmtpCMD("AUTH", smtp_auth, 0); RegisterSmtpCMD("DATA", smtp_data, 0); RegisterSmtpCMD("HELO", smtp_hello, HELO); RegisterSmtpCMD("EHLO", smtp_hello, EHLO); RegisterSmtpCMD("LHLO", smtp_hello, LHLO); RegisterSmtpCMD("HELP", smtp_help, 0); RegisterSmtpCMD("MAIL", smtp_mail, 0); RegisterSmtpCMD("NOOP", smtp_noop, 0); RegisterSmtpCMD("QUIT", smtp_quit, 0); RegisterSmtpCMD("RCPT", smtp_rcpt, 0); RegisterSmtpCMD("RSET", smtp_rset, 1); #ifdef HAVE_OPENSSL RegisterSmtpCMD("STARTTLS", smtp_starttls, 0); #endif CtdlRegisterServiceHook(config.c_smtp_port, /* SMTP MTA */ NULL, smtp_mta_greeting, smtp_command_loop, NULL, CitadelServiceSMTP_MTA); #ifdef HAVE_OPENSSL CtdlRegisterServiceHook(config.c_smtps_port, NULL, smtps_greeting, smtp_command_loop, NULL, CitadelServiceSMTPS_MTA); #endif CtdlRegisterServiceHook(config.c_msa_port, /* SMTP MSA */ NULL, smtp_msa_greeting, smtp_command_loop, NULL, CitadelServiceSMTP_MSA); CtdlRegisterServiceHook(0, /* local LMTP */ file_lmtp_socket, lmtp_greeting, smtp_command_loop, NULL, CitadelServiceSMTP_LMTP); CtdlRegisterServiceHook(0, /* local LMTP */ file_lmtp_unfiltered_socket, lmtp_unfiltered_greeting, smtp_command_loop, NULL, CitadelServiceSMTP_LMTP_UNF); CtdlRegisterCleanupHook(smtp_cleanup); CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP, PRIO_STOP + 250); } /* return our module name for the log */ return "smtp"; } citadel-9.01/modules/smtp/smtpqueue.h0000644000000000000000000000531412507024051016401 0ustar rootroot/* * * Copyright (c) 1998-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /*****************************************************************************/ /* SMTP CLIENT (Queue Management) STUFF */ /*****************************************************************************/ #define MaxAttempts 15 extern const unsigned short DefaultMXPort; typedef struct _mailq_entry { StrBuf *Recipient; StrBuf *StatusMessage; StrBuf *AllStatusMessages; int Status; /**< * 0 = No delivery has yet been attempted * 2 = Delivery was successful * 3 = Transient error like connection problem. Try next remote if available. * 4 = A transient error was experienced ... try again later * 5 = Delivery to this address failed permanently. The error message * should be placed in the fourth field so that a bounce message may * be generated. */ int n; int Active; int StillActive; int nAttempt; }MailQEntry; typedef struct queueitem { long SendBounceMail; long MessageID; long QueMsgID; long Submitted; int FailNow; HashList *MailQEntries; /* copy of the currently parsed item in the MailQEntries list; * if null add a new one. */ MailQEntry *Current; time_t ReattemptWhen; time_t Retry; long ActiveDeliveries; long NotYetShutdownDeliveries; StrBuf *EnvelopeFrom; StrBuf *BounceTo; StrBuf *SenderRoom; ParsedURL *URL; ParsedURL *FallBackHost; int HaveRelay; } OneQueItem; typedef void (*QItemHandler)(OneQueItem *Item, StrBuf *Line, const char **Pos); typedef struct __QItemHandlerStruct { QItemHandler H; } QItemHandlerStruct; int DecreaseQReference(OneQueItem *MyQItem); void DecreaseShutdownDeliveries(OneQueItem *MyQItem); int GetShutdownDeliveries(OneQueItem *MyQItem); void RemoveQItem(OneQueItem *MyQItem); int CountActiveQueueEntries(OneQueItem *MyQItem, int before); StrBuf *SerializeQueueItem(OneQueItem *MyQItem); void smtpq_do_bounce(OneQueItem *MyQItem, StrBuf *OMsgTxt, ParsedURL *Relay); int CheckQEntryIsBounce(MailQEntry *ThisItem); citadel-9.01/modules/smtp/serv_smtpeventclient.c0000644000000000000000000005512312507024051020632 0ustar rootroot/* * This module is an SMTP and ESMTP implementation for the Citadel system. * It is compliant with all of the following: * * RFC 821 - Simple Mail Transfer Protocol * RFC 876 - Survey of SMTP Implementations * RFC 1047 - Duplicate messages and SMTP * RFC 1652 - 8 bit MIME * RFC 1869 - Extended Simple Mail Transfer Protocol * RFC 1870 - SMTP Service Extension for Message Size Declaration * RFC 2033 - Local Mail Transfer Protocol * RFC 2197 - SMTP Service Extension for Command Pipelining * RFC 2476 - Message Submission * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS * RFC 2554 - SMTP Service Extension for Authentication * RFC 2821 - Simple Mail Transfer Protocol * RFC 2822 - Internet Message Format * RFC 2920 - SMTP Service Extension for Command Pipelining * * The VRFY and EXPN commands have been removed from this implementation * because nobody uses these commands anymore, except for spammers. * * Copyright (c) 1998-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * */ #include "sysdep.h" #include #include #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "control.h" #include "user_ops.h" #include "database.h" #include "msgbase.h" #include "internet_addressing.h" #include "genstamp.h" #include "domain.h" #include "clientsocket.h" #include "locate_host.h" #include "citadel_dirs.h" #include "ctdl_module.h" #include "smtp_util.h" #include "event_client.h" #include "smtpqueue.h" #include "smtp_clienthandlers.h" ConstStr SMTPStates[] = { {HKEY("looking up mx - record")}, {HKEY("evaluating what to do next")}, {HKEY("looking up a - record")}, {HKEY("looking up aaaa - record")}, {HKEY("connecting remote")}, {HKEY("smtp conversation ongoing")}, {HKEY("smtp sending maildata")}, {HKEY("smtp sending done")}, {HKEY("smtp successfully finished")}, {HKEY("failed one attempt")}, {HKEY("failed temporarily")}, {HKEY("failed permanently")} }; void SetSMTPState(AsyncIO *IO, smtpstate State) { CitContext* CCC = IO->CitContext; if (CCC != NULL) memcpy(CCC->cs_clientname, SMTPStates[State].Key, SMTPStates[State].len + 1); } int SMTPClientDebugEnabled = 0; void DeleteSmtpOutMsg(void *v) { SmtpOutMsg *Msg = v; AsyncIO *IO = &Msg->IO; EV_syslog(LOG_DEBUG, "%s Exit\n", __FUNCTION__); /* these are kept in our own space and free'd below */ Msg->IO.ConnectMe = NULL; ares_free_data(Msg->AllMX); if (Msg->HostLookup.VParsedDNSReply != NULL) Msg->HostLookup.DNSReplyFree(Msg->HostLookup.VParsedDNSReply); FreeURL(&Msg->Relay); FreeStrBuf(&Msg->msgtext); FreeStrBuf(&Msg->MultiLineBuf); FreeAsyncIOContents(&Msg->IO); memset (Msg, 0, sizeof(SmtpOutMsg)); /* just to be shure... */ free(Msg); } eNextState SMTP_C_Shutdown(AsyncIO *IO); eNextState SMTP_C_Timeout(AsyncIO *IO); eNextState SMTP_C_ConnFail(AsyncIO *IO); eNextState SMTP_C_DispatchReadDone(AsyncIO *IO); eNextState SMTP_C_DispatchWriteDone(AsyncIO *IO); eNextState SMTP_C_DNSFail(AsyncIO *IO); eNextState SMTP_C_Terminate(AsyncIO *IO); eNextState SMTP_C_TerminateDB(AsyncIO *IO); eReadState SMTP_C_ReadServerStatus(AsyncIO *IO); eNextState mx_connect_ip(AsyncIO *IO); eNextState get_one_mx_host_ip(AsyncIO *IO); /****************************************************************************** * So, we're finished with sending (regardless of success or failure) * * This Message might be referenced by several Queue-Items, if we're the last,* * we need to free the memory and send bounce messages (on terminal failure) * * else we just free our SMTP-Message struct. * ******************************************************************************/ eNextState FinalizeMessageSend_DB(AsyncIO *IO) { const char *Status; SmtpOutMsg *Msg = IO->Data; StrBuf *StatusMessage; if (Msg->MyQEntry->AllStatusMessages != NULL) StatusMessage = Msg->MyQEntry->AllStatusMessages; else StatusMessage = Msg->MyQEntry->StatusMessage; if (Msg->MyQEntry->Status == 2) { SetSMTPState(IO, eSTMPfinished); Status = "Delivery successful."; } else if (Msg->MyQEntry->Status == 5) { SetSMTPState(IO, eSMTPFailTotal); Status = "Delivery failed permanently; giving up."; } else { SetSMTPState(IO, eSMTPFailTemporary); Status = "Delivery failed temporarily; will retry later."; } EVS_syslog(LOG_INFO, "%s Time[%fs] Recipient <%s> @ <%s> (%s) Status message: %s\n", Status, Msg->IO.Now - Msg->IO.StartIO, Msg->user, Msg->node, Msg->name, ChrPtr(StatusMessage)); Msg->IDestructQueItem = DecreaseQReference(Msg->MyQItem); Msg->nRemain = CountActiveQueueEntries(Msg->MyQItem, 0); if (Msg->MyQEntry->Active && !Msg->MyQEntry->StillActive && CheckQEntryIsBounce(Msg->MyQEntry)) { /* are we casue for a bounce mail? */ Msg->MyQItem->SendBounceMail |= (1<MyQEntry->Status); } if ((Msg->nRemain > 0) || Msg->IDestructQueItem) Msg->QMsgData = SerializeQueueItem(Msg->MyQItem); else Msg->QMsgData = NULL; /* * Uncompleted delivery instructions remain, so delete the old * instructions and replace with the updated ones. */ EVS_syslog(LOG_DEBUG, "%ld", Msg->MyQItem->QueMsgID); CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &Msg->MyQItem->QueMsgID, 1, ""); Msg->MyQItem->QueMsgID = -1; if (Msg->IDestructQueItem) smtpq_do_bounce(Msg->MyQItem, Msg->msgtext, Msg->pCurrRelay); if (Msg->nRemain > 0) { struct CtdlMessage *msg; msg = malloc(sizeof(struct CtdlMessage)); memset(msg, 0, sizeof(struct CtdlMessage)); msg->cm_magic = CTDLMESSAGE_MAGIC; msg->cm_anon_type = MES_NORMAL; msg->cm_format_type = FMT_RFC822; CM_SetAsFieldSB(msg, eMesageText, &Msg->QMsgData); CM_SetField(msg, eMsgSubject, HKEY("QMSG")); Msg->MyQItem->QueMsgID = CtdlSubmitMsg(msg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR); EVS_syslog(LOG_DEBUG, "%ld", Msg->MyQItem->QueMsgID); CM_Free(msg); } else { CtdlDeleteMessages(SMTP_SPOOLOUT_ROOM, &Msg->MyQItem->MessageID, 1, ""); FreeStrBuf(&Msg->QMsgData); } RemoveContext(Msg->IO.CitContext); return eAbort; } eNextState Terminate(AsyncIO *IO) { SmtpOutMsg *Msg = IO->Data; if (Msg->IDestructQueItem) RemoveQItem(Msg->MyQItem); DeleteSmtpOutMsg(Msg); return eAbort; } eNextState FinalizeMessageSend(SmtpOutMsg *Msg) { /* hand over to DB Queue */ return EventQueueDBOperation(&Msg->IO, FinalizeMessageSend_DB, 0); } eNextState FailOneAttempt(AsyncIO *IO) { SmtpOutMsg *Msg = IO->Data; SetSMTPState(IO, eSTMPfailOne); if (Msg->MyQEntry->Status == 2) return eAbort; /* * possible ways here: * - connection timeout * - dns lookup failed */ StopClientWatchers(IO, 1); Msg->MyQEntry->nAttempt ++; if (Msg->MyQEntry->AllStatusMessages == NULL) Msg->MyQEntry->AllStatusMessages = NewStrBuf(); StrBufAppendPrintf(Msg->MyQEntry->AllStatusMessages, "%ld) ", Msg->MyQEntry->nAttempt); StrBufAppendBuf(Msg->MyQEntry->AllStatusMessages, Msg->MyQEntry->StatusMessage, 0); StrBufAppendBufPlain(Msg->MyQEntry->AllStatusMessages, HKEY("; "), 0); if (Msg->pCurrRelay != NULL) Msg->pCurrRelay = Msg->pCurrRelay->Next; if ((Msg->pCurrRelay != NULL) && !Msg->pCurrRelay->IsRelay && Msg->MyQItem->HaveRelay) { EVS_syslog(LOG_DEBUG, "%s Aborting; last relay failed.\n", __FUNCTION__); return FinalizeMessageSend(Msg); } if (Msg->pCurrRelay == NULL) { EVS_syslog(LOG_DEBUG, "%s Aborting\n", __FUNCTION__); return FinalizeMessageSend(Msg); } if (Msg->pCurrRelay->IsIP) { EVS_syslog(LOG_DEBUG, "%s connecting IP\n", __FUNCTION__); return mx_connect_ip(IO); } else { EVS_syslog(LOG_DEBUG, "%s resolving next MX Record\n", __FUNCTION__); return get_one_mx_host_ip(IO); } } void SetConnectStatus(AsyncIO *IO) { SmtpOutMsg *Msg = IO->Data; char buf[256]; void *src; buf[0] = '\0'; if (IO->ConnectMe->IPv6) { src = &IO->ConnectMe->Addr.sin6_addr; } else { struct sockaddr_in *addr; addr = (struct sockaddr_in *)&IO->ConnectMe->Addr; src = &addr->sin_addr.s_addr; } inet_ntop((IO->ConnectMe->IPv6)?AF_INET6:AF_INET, src, buf, sizeof(buf)); if (Msg->mx_host == NULL) Msg->mx_host = ""; EVS_syslog(LOG_INFO, "connecting to %s [%s]:%d ...\n", Msg->mx_host, buf, Msg->IO.ConnectMe->Port); Msg->MyQEntry->Status = 4; StrBufPrintf(Msg->MyQEntry->StatusMessage, "Timeout while connecting %s [%s]:%d ", Msg->mx_host, buf, Msg->IO.ConnectMe->Port); Msg->IO.NextState = eConnect; } /***************************************************************************** * So we connect our Relay IP here. * *****************************************************************************/ eNextState mx_connect_ip(AsyncIO *IO) { SmtpOutMsg *Msg = IO->Data; SetSMTPState(IO, eSTMPconnecting); EVS_syslog(LOG_DEBUG, "%s(%s)\n", __FUNCTION__, (Msg->IsRelay)? "Relay":"Remote"); IO->ConnectMe = Msg->pCurrRelay; Msg->State = eConnectMX; SetConnectStatus(IO); return EvConnectSock(IO, SMTP_C_ConnTimeout, SMTP_C_ReadTimeouts[0], 1); } eNextState get_one_mx_host_ip_done(AsyncIO *IO) { SmtpOutMsg *Msg = IO->Data; struct hostent *hostent; IO->ConnectMe = Msg->pCurrRelay; QueryCbDone(IO); EVS_syslog(LOG_DEBUG, "%s Time[%fs]\n", __FUNCTION__, IO->Now - IO->DNS.Start); hostent = Msg->HostLookup.VParsedDNSReply; if ((Msg->HostLookup.DNSStatus == ARES_SUCCESS) && (hostent != NULL) ) { memset(&Msg->pCurrRelay->Addr, 0, sizeof(struct in6_addr)); if (Msg->pCurrRelay->IPv6) { memcpy(&Msg->pCurrRelay->Addr.sin6_addr.s6_addr, &hostent->h_addr_list[0], sizeof(struct in6_addr)); Msg->pCurrRelay->Addr.sin6_family = hostent->h_addrtype; Msg->pCurrRelay->Addr.sin6_port = htons(Msg->IO.ConnectMe->Port); } else { struct sockaddr_in *addr; /* * Bypass the ns lookup result like this: * IO->Addr.sin_addr.s_addr = inet_addr("127.0.0.1"); * addr->sin_addr.s_addr = * htonl((uint32_t)&hostent->h_addr_list[0]); */ addr = (struct sockaddr_in*) &Msg->pCurrRelay->Addr; memcpy(&addr->sin_addr.s_addr, hostent->h_addr_list[0], sizeof(uint32_t)); addr->sin_family = hostent->h_addrtype; addr->sin_port = htons(Msg->IO.ConnectMe->Port); } Msg->mx_host = Msg->pCurrRelay->Host; if (Msg->HostLookup.VParsedDNSReply != NULL) { Msg->HostLookup.DNSReplyFree(Msg->HostLookup.VParsedDNSReply); Msg->HostLookup.VParsedDNSReply = NULL; } return mx_connect_ip(IO); } else { SetSMTPState(IO, eSTMPfailOne); if (Msg->HostLookup.VParsedDNSReply != NULL) { Msg->HostLookup.DNSReplyFree(Msg->HostLookup.VParsedDNSReply); Msg->HostLookup.VParsedDNSReply = NULL; } return FailOneAttempt(IO); } } eNextState get_one_mx_host_ip(AsyncIO *IO) { SmtpOutMsg * Msg = IO->Data; /* * here we start with the lookup of one host. it might be... * - the relay host *sigh* * - the direct hostname if there was no mx record * - one of the mx'es */ SetSMTPState(IO, (Msg->pCurrRelay->IPv6)?eSTMPalookup:eSTMPaaaalookup); EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); EVS_syslog(LOG_DEBUG, "looking up %s-Record %s : %d ...\n", (Msg->pCurrRelay->IPv6)? "aaaa": "a", Msg->pCurrRelay->Host, Msg->pCurrRelay->Port); if (!QueueQuery((Msg->pCurrRelay->IPv6)? ns_t_aaaa : ns_t_a, Msg->pCurrRelay->Host, &Msg->IO, &Msg->HostLookup, get_one_mx_host_ip_done)) { Msg->MyQEntry->Status = 5; StrBufPrintf(Msg->MyQEntry->StatusMessage, "No MX hosts found for <%s>", Msg->node); Msg->IO.NextState = eTerminateConnection; return IO->NextState; } IO->NextState = eReadDNSReply; return IO->NextState; } /***************************************************************************** * here we try to find out about the MX records for our recipients. * *****************************************************************************/ eNextState smtp_resolve_mx_record_done(AsyncIO *IO) { SmtpOutMsg * Msg = IO->Data; ParsedURL **pp; QueryCbDone(IO); EVS_syslog(LOG_DEBUG, "%s Time[%fs]\n", __FUNCTION__, IO->Now - IO->DNS.Start); pp = &Msg->Relay; while ((pp != NULL) && (*pp != NULL) && ((*pp)->Next != NULL)) pp = &(*pp)->Next; if ((IO->DNS.Query->DNSStatus == ARES_SUCCESS) && (IO->DNS.Query->VParsedDNSReply != NULL)) { /* ok, we found mx records. */ Msg->CurrMX = Msg->AllMX = IO->DNS.Query->VParsedDNSReply; while (Msg->CurrMX) { int i; for (i = 0; i < 2; i++) { ParsedURL *p; p = (ParsedURL*) malloc(sizeof(ParsedURL)); memset(p, 0, sizeof(ParsedURL)); p->Priority = Msg->CurrMX->priority; p->IsIP = 0; p->Port = DefaultMXPort; p->IPv6 = i == 1; p->Host = Msg->CurrMX->host; if (*pp == NULL) *pp = p; else { ParsedURL *ppp = *pp; while ((ppp->Next != NULL) && (ppp->Next->Priority <= p->Priority)) ppp = ppp->Next; if ((ppp == *pp) && (ppp->Priority > p->Priority)) { p->Next = *pp; *pp = p; } else { p->Next = ppp->Next; ppp->Next = p; } } } Msg->CurrMX = Msg->CurrMX->next; } Msg->CXFlags = Msg->CXFlags & F_HAVE_MX; } else { /* else fall back to the plain hostname */ int i; for (i = 0; i < 2; i++) { ParsedURL *p; p = (ParsedURL*) malloc(sizeof(ParsedURL)); memset(p, 0, sizeof(ParsedURL)); p->IsIP = 0; p->Port = DefaultMXPort; p->IPv6 = i == 1; p->Host = Msg->node; *pp = p; pp = &p->Next; } Msg->CXFlags = Msg->CXFlags & F_DIRECT; } if (Msg->MyQItem->FallBackHost != NULL) { Msg->MyQItem->FallBackHost->Next = *pp; *pp = Msg->MyQItem->FallBackHost; } Msg->pCurrRelay = Msg->Relay; return get_one_mx_host_ip(IO); } eNextState resolve_mx_records(AsyncIO *IO) { SmtpOutMsg * Msg = IO->Data; SetSMTPState(IO, eSTMPmxlookup); EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); /* start resolving MX records here. */ if (!QueueQuery(ns_t_mx, Msg->node, &Msg->IO, &Msg->MxLookup, smtp_resolve_mx_record_done)) { Msg->MyQEntry->Status = 5; StrBufPrintf(Msg->MyQEntry->StatusMessage, "No MX hosts found for <%s>", Msg->node); return IO->NextState; } Msg->IO.NextState = eReadDNSReply; return IO->NextState; } /****************************************************************************** * so, we're going to start a SMTP delivery. lets get it on. * ******************************************************************************/ SmtpOutMsg *new_smtp_outmsg(OneQueItem *MyQItem, MailQEntry *MyQEntry, int MsgCount) { SmtpOutMsg * Msg; Msg = (SmtpOutMsg *) malloc(sizeof(SmtpOutMsg)); if (Msg == NULL) return NULL; memset(Msg, 0, sizeof(SmtpOutMsg)); Msg->n = MsgCount; Msg->MyQEntry = MyQEntry; Msg->MyQItem = MyQItem; Msg->pCurrRelay = MyQItem->URL; InitIOStruct(&Msg->IO, Msg, eReadMessage, SMTP_C_ReadServerStatus, SMTP_C_DNSFail, SMTP_C_DispatchWriteDone, SMTP_C_DispatchReadDone, SMTP_C_Terminate, SMTP_C_TerminateDB, SMTP_C_ConnFail, SMTP_C_Timeout, SMTP_C_Shutdown); Msg->IO.ErrMsg = Msg->MyQEntry->StatusMessage; return Msg; } void smtp_try_one_queue_entry(OneQueItem *MyQItem, MailQEntry *MyQEntry, StrBuf *MsgText, /*KeepMsgText allows us to use MsgText as ours.*/ int KeepMsgText, int MsgCount) { SmtpOutMsg *Msg; SMTPC_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); Msg = new_smtp_outmsg(MyQItem, MyQEntry, MsgCount); if (Msg == NULL) { SMTPC_syslog(LOG_DEBUG, "%s Failed to alocate message context.\n", __FUNCTION__); if (KeepMsgText) FreeStrBuf (&MsgText); return; } if (KeepMsgText) Msg->msgtext = MsgText; else Msg->msgtext = NewStrBufDup(MsgText); if (smtp_resolve_recipients(Msg) && (!MyQItem->HaveRelay || (MyQItem->URL != NULL))) { safestrncpy( ((CitContext *)Msg->IO.CitContext)->cs_host, Msg->node, sizeof(((CitContext *) Msg->IO.CitContext)->cs_host)); SMTPC_syslog(LOG_DEBUG, "Starting: [%ld] <%s> CC <%d> \n", Msg->MyQItem->MessageID, ChrPtr(Msg->MyQEntry->Recipient), ((CitContext*)Msg->IO.CitContext)->cs_pid); if (Msg->pCurrRelay == NULL) { SetSMTPState(&Msg->IO, eSTMPmxlookup); QueueEventContext(&Msg->IO, resolve_mx_records); } else { /* oh... via relay host */ Msg->IsRelay = 1; if (Msg->pCurrRelay->IsIP) { SetSMTPState(&Msg->IO, eSTMPconnecting); QueueEventContext(&Msg->IO, mx_connect_ip); } else { SetSMTPState(&Msg->IO, eSTMPalookup); /* uneducated admin has chosen to add DNS to the equation... */ QueueEventContext(&Msg->IO, get_one_mx_host_ip); } } } else { SetSMTPState(&Msg->IO, eSMTPFailTotal); /* No recipients? well fail then. */ if (Msg->MyQEntry != NULL) { Msg->MyQEntry->Status = 5; if (StrLength(Msg->MyQEntry->StatusMessage) == 0) StrBufPlain(Msg->MyQEntry->StatusMessage, HKEY("Invalid Recipient!")); } FinalizeMessageSend_DB(&Msg->IO); DeleteSmtpOutMsg(Msg); } } /*****************************************************************************/ /* SMTP CLIENT DISPATCHER */ /*****************************************************************************/ void SMTPSetTimeout(eNextState NextTCPState, SmtpOutMsg *Msg) { double Timeout = 0.0; AsyncIO *IO = &Msg->IO; EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); switch (NextTCPState) { case eSendFile: case eSendReply: case eSendMore: Timeout = SMTP_C_SendTimeouts[Msg->State]; if (Msg->State == eDATABody) { /* if we're sending a huge message, * we need more time. */ Timeout += StrLength(Msg->msgtext) / 512; } break; case eReadMessage: Timeout = SMTP_C_ReadTimeouts[Msg->State]; if (Msg->State == eDATATerminateBody) { /* * some mailservers take a nap before accepting * the message content inspection and such. */ Timeout += StrLength(Msg->msgtext) / 512; } break; case eSendDNSQuery: case eReadDNSReply: case eDBQuery: case eReadFile: case eReadMore: case eReadPayload: case eConnect: case eTerminateConnection: case eAbort: return; } SetNextTimeout(&Msg->IO, Timeout); } eNextState SMTP_C_DispatchReadDone(AsyncIO *IO) { EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); SmtpOutMsg *Msg = IO->Data; eNextState rc; rc = ReadHandlers[Msg->State](Msg); if (rc != eAbort) { Msg->State++; SMTPSetTimeout(rc, Msg); } return rc; } eNextState SMTP_C_DispatchWriteDone(AsyncIO *IO) { EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); SmtpOutMsg *Msg = IO->Data; eNextState rc; rc = SendHandlers[Msg->State](Msg); SMTPSetTimeout(rc, Msg); return rc; } /*****************************************************************************/ /* SMTP CLIENT ERROR CATCHERS */ /*****************************************************************************/ eNextState SMTP_C_Terminate(AsyncIO *IO) { SmtpOutMsg *Msg = IO->Data; EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); return FinalizeMessageSend(Msg); } eNextState SMTP_C_TerminateDB(AsyncIO *IO) { EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); return Terminate(IO); } eNextState SMTP_C_Timeout(AsyncIO *IO) { SmtpOutMsg *Msg = IO->Data; Msg->MyQEntry->Status = 4; EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); StrBufPrintf(IO->ErrMsg, "Timeout: %s while talking to %s", ReadErrors[Msg->State].Key, Msg->mx_host); if (Msg->State > eRCPT) return eAbort; else return FailOneAttempt(IO); } eNextState SMTP_C_ConnFail(AsyncIO *IO) { SmtpOutMsg *Msg = IO->Data; Msg->MyQEntry->Status = 4; EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); StrBufPrintf(IO->ErrMsg, "Connection failure: %s while talking to %s", ReadErrors[Msg->State].Key, Msg->mx_host); return FailOneAttempt(IO); } eNextState SMTP_C_DNSFail(AsyncIO *IO) { SmtpOutMsg *Msg = IO->Data; Msg->MyQEntry->Status = 4; EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); return FailOneAttempt(IO); } eNextState SMTP_C_Shutdown(AsyncIO *IO) { EVS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); SmtpOutMsg *Msg = IO->Data; switch (IO->NextState) { case eSendDNSQuery: case eReadDNSReply: /* todo: abort c-ares */ case eConnect: case eSendReply: case eSendMore: case eSendFile: case eReadMessage: case eReadMore: case eReadPayload: case eReadFile: StopClientWatchers(IO, 1); break; case eDBQuery: break; case eTerminateConnection: case eAbort: break; } Msg->MyQEntry->Status = 3; StrBufPlain(Msg->MyQEntry->StatusMessage, HKEY("server shutdown during message submit.")); return FinalizeMessageSend(Msg); } /** * @brief lineread Handler; * understands when to read more SMTP lines, and when this is a one-lined reply. */ eReadState SMTP_C_ReadServerStatus(AsyncIO *IO) { eReadState Finished = eBufferNotEmpty; while (Finished == eBufferNotEmpty) { Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf); switch (Finished) { case eMustReadMore: /// read new from socket... return Finished; break; case eBufferNotEmpty: /* shouldn't happen... */ case eReadSuccess: /// done for now... if (StrLength(IO->IOBuf) < 4) continue; if (ChrPtr(IO->IOBuf)[3] == '-') { SmtpOutMsg *Msg; Msg = (SmtpOutMsg *)IO->Data; if (Msg->MultiLineBuf == NULL) Msg->MultiLineBuf = NewStrBuf (); else StrBufAppendBufPlain(Msg->MultiLineBuf, HKEY("\n"), 0); StrBufAppendBuf(Msg->MultiLineBuf, IO->IOBuf, 0); Finished = eBufferNotEmpty; } else return Finished; break; case eReadFail: /// WHUT? ///todo: shut down! break; } } return Finished; } void LogDebugEnableSMTPClient(const int n) { SMTPClientDebugEnabled = n; } CTDL_MODULE_INIT(smtp_eventclient) { if (!threading) CtdlRegisterDebugFlagHook(HKEY("smtpeventclient"), LogDebugEnableSMTPClient, &SMTPClientDebugEnabled); return "smtpeventclient"; } citadel-9.01/modules/smtp/smtp_clienthandlers.c0000644000000000000000000003741512507024051020415 0ustar rootroot/* * This module is an SMTP and ESMTP implementation for the Citadel system. * It is compliant with all of the following: * * RFC 821 - Simple Mail Transfer Protocol * RFC 876 - Survey of SMTP Implementations * RFC 1047 - Duplicate messages and SMTP * RFC 1652 - 8 bit MIME * RFC 1869 - Extended Simple Mail Transfer Protocol * RFC 1870 - SMTP Service Extension for Message Size Declaration * RFC 2033 - Local Mail Transfer Protocol * RFC 2197 - SMTP Service Extension for Command Pipelining * RFC 2476 - Message Submission * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS * RFC 2554 - SMTP Service Extension for Authentication * RFC 2821 - Simple Mail Transfer Protocol * RFC 2822 - Internet Message Format * RFC 2920 - SMTP Service Extension for Command Pipelining * * Copyright (c) 1998-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * */ #include "sysdep.h" #include #include #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "control.h" #include "user_ops.h" #include "database.h" #include "msgbase.h" #include "internet_addressing.h" #include "genstamp.h" #include "domain.h" #include "clientsocket.h" #include "locate_host.h" #include "citadel_dirs.h" #include "ctdl_module.h" #include "smtp_util.h" #include "event_client.h" #include "smtpqueue.h" #include "smtp_clienthandlers.h" #define SMTP_ERROR(WHICH_ERR, ERRSTR) do { \ Msg->MyQEntry->Status = WHICH_ERR; \ StrBufAppendBufPlain(Msg->MyQEntry->StatusMessage, \ HKEY(ERRSTR), 0); \ StrBufTrim(Msg->MyQEntry->StatusMessage); \ return eAbort; } \ while (0) #define SMTP_VERROR(WHICH_ERR) do { \ Msg->MyQEntry->Status = WHICH_ERR; \ StrBufPlain(Msg->MyQEntry->StatusMessage, \ ChrPtr(Msg->IO.IOBuf) + 4, \ StrLength(Msg->IO.IOBuf) - 4); \ StrBufTrim(Msg->MyQEntry->StatusMessage); \ return eAbort; } \ while (0) #define SMTP_IS_STATE(WHICH_STATE) (ChrPtr(Msg->IO.IOBuf)[0] == WHICH_STATE) #define SMTP_DBG_SEND() \ EVS_syslog(LOG_DEBUG, "> %s\n", ChrPtr(Msg->IO.SendBuf.Buf)) #define SMTP_DBG_READ() \ EVS_syslog(LOG_DEBUG, "< %s\n", ChrPtr(Msg->IO.IOBuf)) /* * if a Read handler wants to skip to a specific part use this macro. * the -1 is here since the auto-forward following has to be taken into account. */ #define READ_NEXT_STATE(state) Msg->State = state - 1 /*****************************************************************************/ /* SMTP CLIENT STATE CALLBACKS */ /*****************************************************************************/ eNextState SMTPC_read_greeting(SmtpOutMsg *Msg) { /* Process the SMTP greeting from the server */ AsyncIO *IO = &Msg->IO; SMTP_DBG_READ(); SetSMTPState(IO, eSTMPsmtp); if (!SMTP_IS_STATE('2')) { if (SMTP_IS_STATE('4')) SMTP_VERROR(4); else SMTP_VERROR(5); } return eSendReply; } eNextState SMTPC_send_EHLO(SmtpOutMsg *Msg) { AsyncIO *IO = &Msg->IO; /* At this point we know we are talking to a real SMTP server */ /* Do a EHLO command. If it fails, try the HELO command. */ StrBufPrintf(Msg->IO.SendBuf.Buf, "EHLO %s\r\n", config.c_fqdn); SMTP_DBG_SEND(); return eReadMessage; } eNextState SMTPC_read_EHLO_reply(SmtpOutMsg *Msg) { AsyncIO *IO = &Msg->IO; SMTP_DBG_READ(); if (SMTP_IS_STATE('2')) { READ_NEXT_STATE(eSMTPAuth); if ((Msg->pCurrRelay == NULL) || (Msg->pCurrRelay->User == NULL)) READ_NEXT_STATE(eFROM); /* Skip auth... */ if (Msg->pCurrRelay != NULL) { if (strstr(ChrPtr(Msg->IO.IOBuf), "LOGIN") != NULL) Msg->SendLogin = 1; else if ((Msg->MultiLineBuf != NULL) && strstr(ChrPtr(Msg->MultiLineBuf), "LOGIN") != NULL) { Msg->SendLogin = 1; } } } /* else we fall back to 'helo' */ return eSendReply; } eNextState STMPC_send_HELO(SmtpOutMsg *Msg) { AsyncIO *IO = &Msg->IO; StrBufPrintf(Msg->IO.SendBuf.Buf, "HELO %s\r\n", config.c_fqdn); SMTP_DBG_SEND(); return eReadMessage; } eNextState SMTPC_read_HELO_reply(SmtpOutMsg *Msg) { AsyncIO *IO = &Msg->IO; SMTP_DBG_READ(); if (!SMTP_IS_STATE('2')) { if (SMTP_IS_STATE('4')) SMTP_VERROR(4); else SMTP_VERROR(5); } if (Msg->pCurrRelay != NULL) { if (strstr(ChrPtr(Msg->IO.IOBuf), "LOGIN") != NULL) Msg->SendLogin = 1; } if ((Msg->pCurrRelay == NULL) || (Msg->pCurrRelay->User == NULL)) READ_NEXT_STATE(eFROM); /* Skip auth... */ return eSendReply; } eNextState SMTPC_send_auth(SmtpOutMsg *Msg) { AsyncIO *IO = &Msg->IO; char buf[SIZ]; char encoded[1024]; if ((Msg->pCurrRelay == NULL) || (Msg->pCurrRelay->User == NULL)) READ_NEXT_STATE(eFROM); /* Skip auth, shouldn't even come here!... */ else { /* Do an AUTH command if necessary */ if (Msg->SendLogin) { StrBufPlain(Msg->IO.SendBuf.Buf, HKEY("AUTH LOGIN\r\n")); } else { sprintf(buf, "%s%c%s%c%s", Msg->pCurrRelay->User, '\0', Msg->pCurrRelay->User, '\0', Msg->pCurrRelay->Pass); size_t len = CtdlEncodeBase64(encoded, buf, strlen(Msg->pCurrRelay->User) * 2 + strlen(Msg->pCurrRelay->Pass) + 2, 0); if (buf[len - 1] == '\n') { buf[len - 1] = '\0'; } StrBufPrintf(Msg->IO.SendBuf.Buf, "AUTH PLAIN %s\r\n", encoded); } } SMTP_DBG_SEND(); return eReadMessage; } eNextState SMTPC_read_auth_reply(SmtpOutMsg *Msg) { AsyncIO *IO = &Msg->IO; /* Do an AUTH command if necessary */ SMTP_DBG_READ(); if (Msg->SendLogin) { if (!SMTP_IS_STATE('3')) SMTP_VERROR(5); } else { if (!SMTP_IS_STATE('2')) { if (SMTP_IS_STATE('4')) SMTP_VERROR(4); else SMTP_VERROR(5); } READ_NEXT_STATE(eFROM); } return eSendReply; } eNextState SMTPC_send_authplain_1(SmtpOutMsg *Msg) { AsyncIO *IO = &Msg->IO; char buf[SIZ]; char encoded[1024]; long encodedlen; sprintf(buf, "%s", Msg->pCurrRelay->User); encodedlen = CtdlEncodeBase64( encoded, Msg->pCurrRelay->User, strlen(Msg->pCurrRelay->User), 0); if (encoded[encodedlen - 1] == '\n') { encodedlen --; encoded[encodedlen] = '\0'; } StrBufPlain(Msg->IO.SendBuf.Buf, encoded, encodedlen); StrBufAppendBufPlain(Msg->IO.SendBuf.Buf, HKEY("\r\n"), 0); SMTP_DBG_SEND(); return eReadMessage; } eNextState SMTPC_read_auth_plain_reply_1(SmtpOutMsg *Msg) { AsyncIO *IO = &Msg->IO; /* Do an AUTH command if necessary */ SMTP_DBG_READ(); if (!SMTP_IS_STATE('3')) SMTP_VERROR(5); return eSendReply; } eNextState SMTPC_send_authplain_2(SmtpOutMsg *Msg) { AsyncIO *IO = &Msg->IO; char buf[SIZ]; char encoded[1024]; long encodedlen; sprintf(buf, "%s", Msg->pCurrRelay->Pass); encodedlen = CtdlEncodeBase64( encoded, Msg->pCurrRelay->Pass, strlen(Msg->pCurrRelay->Pass), 0); if (encoded[encodedlen - 1] == '\n') { encodedlen --; encoded[encodedlen] = '\0'; } StrBufPlain(Msg->IO.SendBuf.Buf, encoded, encodedlen); StrBufAppendBufPlain(Msg->IO.SendBuf.Buf, HKEY("\r\n"), 0); SMTP_DBG_SEND(); return eReadMessage; } eNextState SMTPC_read_auth_plain_reply_2(SmtpOutMsg *Msg) { AsyncIO *IO = &Msg->IO; /* Do an AUTH command if necessary */ SMTP_DBG_READ(); if (!SMTP_IS_STATE('2')) { if (SMTP_IS_STATE('4')) SMTP_VERROR(4); else SMTP_VERROR(5); } return eSendReply; } eNextState SMTPC_send_FROM(SmtpOutMsg *Msg) { AsyncIO *IO = &Msg->IO; /* previous command succeeded, now try the MAIL FROM: command */ StrBufPrintf(Msg->IO.SendBuf.Buf, "MAIL FROM:<%s>\r\n", Msg->envelope_from); SMTP_DBG_SEND(); return eReadMessage; } eNextState SMTPC_read_FROM_reply(SmtpOutMsg *Msg) { AsyncIO *IO = &Msg->IO; SMTP_DBG_READ(); if (!SMTP_IS_STATE('2')) { if (SMTP_IS_STATE('4')) SMTP_VERROR(4); else SMTP_VERROR(5); } return eSendReply; } eNextState SMTPC_send_RCPT(SmtpOutMsg *Msg) { AsyncIO *IO = &Msg->IO; /* MAIL succeeded, now try the RCPT To: command */ StrBufPrintf(Msg->IO.SendBuf.Buf, "RCPT TO:<%s@%s>\r\n", Msg->user, Msg->node); SMTP_DBG_SEND(); return eReadMessage; } eNextState SMTPC_read_RCPT_reply(SmtpOutMsg *Msg) { AsyncIO *IO = &Msg->IO; SMTP_DBG_READ(); if (!SMTP_IS_STATE('2')) { if (SMTP_IS_STATE('4')) SMTP_VERROR(4); else SMTP_VERROR(5); } return eSendReply; } eNextState SMTPC_send_DATAcmd(SmtpOutMsg *Msg) { AsyncIO *IO = &Msg->IO; /* RCPT succeeded, now try the DATA command */ StrBufPlain(Msg->IO.SendBuf.Buf, HKEY("DATA\r\n")); SMTP_DBG_SEND(); return eReadMessage; } eNextState SMTPC_read_DATAcmd_reply(SmtpOutMsg *Msg) { AsyncIO *IO = &Msg->IO; SMTP_DBG_READ(); if (!SMTP_IS_STATE('3')) { SetSMTPState(IO, eSTMPfailOne); if (SMTP_IS_STATE('4')) SMTP_VERROR(3); else SMTP_VERROR(5); } SetSMTPState(IO, eSTMPsmtpdata); return eSendReply; } eNextState SMTPC_send_data_body(SmtpOutMsg *Msg) { StrBuf *Buf; /* If we reach this point, the server is expecting data.*/ Buf = Msg->IO.SendBuf.Buf; Msg->IO.SendBuf.Buf = Msg->msgtext; Msg->msgtext = Buf; /* * sending the message itself doesn't use this state machine. * so we have to operate it here by ourselves. */ Msg->State ++; return eSendMore; } eNextState SMTPC_send_terminate_data_body(SmtpOutMsg *Msg) { StrBuf *Buf; Buf = Msg->IO.SendBuf.Buf; Msg->IO.SendBuf.Buf = Msg->msgtext; Msg->msgtext = Buf; StrBufPlain(Msg->IO.SendBuf.Buf, HKEY(".\r\n")); return eReadMessage; } eNextState SMTPC_read_data_body_reply(SmtpOutMsg *Msg) { AsyncIO *IO = &Msg->IO; SMTP_DBG_READ(); if (!SMTP_IS_STATE('2')) { if (SMTP_IS_STATE('4')) SMTP_VERROR(4); else SMTP_VERROR(5); } SetSMTPState(IO, eSTMPsmtpdone); /* We did it! */ StrBufPlain(Msg->MyQEntry->StatusMessage, &ChrPtr(Msg->IO.RecvBuf.Buf)[4], StrLength(Msg->IO.RecvBuf.Buf) - 4); StrBufTrim(Msg->MyQEntry->StatusMessage); Msg->MyQEntry->Status = 2; return eSendReply; } eNextState SMTPC_send_QUIT(SmtpOutMsg *Msg) { AsyncIO *IO = &Msg->IO; StrBufPlain(Msg->IO.SendBuf.Buf, HKEY("QUIT\r\n")); SMTP_DBG_SEND(); return eReadMessage; } eNextState SMTPC_read_QUIT_reply(SmtpOutMsg *Msg) { AsyncIO *IO = &Msg->IO; SMTP_DBG_READ(); EVS_syslog(LOG_DEBUG, "delivery to <%s> @ <%s> (%s) succeeded\n", Msg->user, Msg->node, Msg->name); return eTerminateConnection; } eNextState SMTPC_read_dummy(SmtpOutMsg *Msg) { return eSendReply; } eNextState SMTPC_send_dummy(SmtpOutMsg *Msg) { return eReadMessage; } /*****************************************************************************/ /* SMTP CLIENT DISPATCHER */ /*****************************************************************************/ SMTPReadHandler ReadHandlers[eMaxSMTPC] = { SMTPC_read_greeting, SMTPC_read_EHLO_reply, SMTPC_read_HELO_reply, SMTPC_read_auth_reply, SMTPC_read_auth_plain_reply_1, SMTPC_read_auth_plain_reply_2, SMTPC_read_FROM_reply, SMTPC_read_RCPT_reply, SMTPC_read_DATAcmd_reply, SMTPC_read_dummy, SMTPC_read_data_body_reply, SMTPC_read_QUIT_reply }; SMTPSendHandler SendHandlers[eMaxSMTPC] = { SMTPC_send_dummy, /* we don't send a greeting, the server does... */ SMTPC_send_EHLO, STMPC_send_HELO, SMTPC_send_auth, SMTPC_send_authplain_1, SMTPC_send_authplain_2, SMTPC_send_FROM, SMTPC_send_RCPT, SMTPC_send_DATAcmd, SMTPC_send_data_body, SMTPC_send_terminate_data_body, SMTPC_send_QUIT }; const double SMTP_C_ConnTimeout = 300.; /* wail 1 minute for connections... */ const double SMTP_C_ReadTimeouts[eMaxSMTPC] = { 300., /* Greeting... */ 30., /* EHLO */ 30., /* HELO */ 30., /* Auth */ 30., /* Auth */ 30., /* Auth */ 30., /* From */ 90., /* RCPT */ 30., /* DATA */ 90., /* DATABody */ 90., /* end of body... */ 30. /* QUIT */ }; const double SMTP_C_SendTimeouts[eMaxSMTPC] = { 90., /* Greeting... */ 30., /* EHLO */ 30., /* HELO */ 30., /* Auth */ 30., /* Auth */ 30., /* Auth */ 30., /* From */ 30., /* RCPT */ 30., /* DATA */ 90., /* DATABody */ 900., /* end of body... */ 30. /* QUIT */ }; const ConstStr ReadErrors[eMaxSMTPC + 1] = { {HKEY("Connection broken during SMTP conversation")}, {HKEY("Connection broken during SMTP EHLO")}, {HKEY("Connection broken during SMTP HELO")}, {HKEY("Connection broken during SMTP AUTH")}, {HKEY("Connection broken during SMTP AUTH PLAIN I")}, {HKEY("Connection broken during SMTP AUTH PLAIN II")}, {HKEY("Connection broken during SMTP MAIL FROM")}, {HKEY("Connection broken during SMTP RCPT")}, {HKEY("Connection broken during SMTP DATA")}, {HKEY("Connection broken during SMTP message transmit")}, {HKEY("Connection broken during SMTP message transmit")},/* quit reply, don't care. */ {HKEY("Connection broken during SMTP message transmit")},/* quit reply, don't care. */ {HKEY("")}/* quit reply, don't care. */ }; int smtp_resolve_recipients(SmtpOutMsg *Msg) { AsyncIO *IO = &Msg->IO; const char *ptr; char buf[1024]; int scan_done; int lp, rp; int i; EVNCS_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); if ((Msg==NULL) || (Msg->MyQEntry == NULL) || (StrLength(Msg->MyQEntry->Recipient) == 0)) { return 0; } /* Parse out the host portion of the recipient address */ process_rfc822_addr(ChrPtr(Msg->MyQEntry->Recipient), Msg->user, Msg->node, Msg->name); EVNCS_syslog(LOG_DEBUG, "Attempting delivery to <%s> @ <%s> (%s)\n", Msg->user, Msg->node, Msg->name); /* If no envelope_from is supplied, extract one from the message */ Msg->envelope_from = ChrPtr(Msg->MyQItem->EnvelopeFrom); if ( (Msg->envelope_from == NULL) || (IsEmptyStr(Msg->envelope_from)) ) { Msg->mailfrom[0] = '\0'; scan_done = 0; ptr = ChrPtr(Msg->msgtext); do { if (ptr = cmemreadline(ptr, buf, sizeof buf), *ptr == 0) { scan_done = 1; } if (!strncasecmp(buf, "From:", 5)) { safestrncpy(Msg->mailfrom, &buf[5], sizeof Msg->mailfrom); striplt(Msg->mailfrom); for (i=0; Msg->mailfrom[i]; ++i) { if (!isprint(Msg->mailfrom[i])) { strcpy(&Msg->mailfrom[i], &Msg->mailfrom[i+1]); i=0; } } /* Strip out parenthesized names */ lp = (-1); rp = (-1); for (i=0; !IsEmptyStr(Msg->mailfrom + i); ++i) { if (Msg->mailfrom[i] == '(') lp = i; if (Msg->mailfrom[i] == ')') rp = i; } if ((lp>0)&&(rp>lp)) { strcpy(&Msg->mailfrom[lp-1], &Msg->mailfrom[rp+1]); } /* Prefer brokketized names */ lp = (-1); rp = (-1); for (i=0; !IsEmptyStr(Msg->mailfrom + i); ++i) { if (Msg->mailfrom[i] == '<') lp = i; if (Msg->mailfrom[i] == '>') rp = i; } if ( (lp>=0) && (rp>lp) ) { Msg->mailfrom[rp] = 0; memmove(Msg->mailfrom, &Msg->mailfrom[lp + 1], rp - lp); } scan_done = 1; } } while (scan_done == 0); if (IsEmptyStr(Msg->mailfrom)) strcpy(Msg->mailfrom, "someone@somewhere.org"); stripallbut(Msg->mailfrom, '<', '>'); Msg->envelope_from = Msg->mailfrom; } return 1; } citadel-9.01/modules/fulltext/0000755000000000000000000000000012507024051015061 5ustar rootrootcitadel-9.01/modules/fulltext/serv_fulltext.c0000644000000000000000000003023612507024051020137 0ustar rootroot/* * This module handles fulltext indexing of the message base. * Copyright (c) 2005-2011 by the citadel.org team * * This program is open source software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "sysdep.h" #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "database.h" #include "msgbase.h" #include "control.h" #include "serv_fulltext.h" #include "ft_wordbreaker.h" #include "threads.h" #include "context.h" #include "ctdl_module.h" long ft_newhighest = 0L; long *ft_newmsgs = NULL; int ft_num_msgs = 0; int ft_num_alloc = 0; int ftc_num_msgs[65536]; long *ftc_msgs[65536]; /* * Compare function */ int longcmp(const void *rec1, const void *rec2) { long i1, i2; i1 = *(const long *)rec1; i2 = *(const long *)rec2; if (i1 > i2) return(1); if (i1 < i2) return(-1); return(0); } /* * Flush our index cache out to disk. */ void ft_flush_cache(void) { int i; time_t last_update = 0; for (i=0; i<65536; ++i) { if ((time(NULL) - last_update) >= 10) { syslog(LOG_INFO, "Flushing index cache to disk (%d%% complete)", (i * 100 / 65536) ); last_update = time(NULL); } if (ftc_msgs[i] != NULL) { cdb_store(CDB_FULLTEXT, &i, sizeof(int), ftc_msgs[i], (ftc_num_msgs[i] * sizeof(long))); ftc_num_msgs[i] = 0; free(ftc_msgs[i]); ftc_msgs[i] = NULL; } } syslog(LOG_INFO, "Flushed index cache to disk (100%% complete)"); } /* * Index or de-index a message. (op == 1 to index, 0 to de-index) */ void ft_index_message(long msgnum, int op) { int num_tokens = 0; int *tokens = NULL; int i, j; struct cdbdata *cdb_bucket; StrBuf *msgtext; char *txt; int tok; struct CtdlMessage *msg = NULL; msg = CtdlFetchMessage(msgnum, 1); if (msg == NULL) { syslog(LOG_ERR, "ft_index_message() could not load msg %ld", msgnum); return; } if (!CM_IsEmpty(msg, eSuppressIdx)) { syslog(LOG_DEBUG, "ft_index_message() excluded msg %ld", msgnum); CM_Free(msg); return; } syslog(LOG_DEBUG, "ft_index_message() %s msg %ld", (op ? "adding" : "removing") , msgnum); /* Output the message as text before indexing it, so we don't end up * indexing a bunch of encoded base64, etc. */ CC->redirect_buffer = NewStrBufPlain(NULL, SIZ); CtdlOutputPreLoadedMsg(msg, MT_CITADEL, HEADERS_ALL, 0, 1, 0); CM_Free(msg); msgtext = CC->redirect_buffer; CC->redirect_buffer = NULL; if (msgtext != NULL) { syslog(LOG_DEBUG, "Wordbreaking message %ld (%d bytes)", msgnum, StrLength(msgtext)); } txt = SmashStrBuf(&msgtext); wordbreaker(txt, &num_tokens, &tokens); free(txt); syslog(LOG_DEBUG, "Indexing message %ld [%d tokens]", msgnum, num_tokens); if (num_tokens > 0) { for (i=0; i= 0) && (tok <= 65535) ) { /* fetch the bucket, Liza */ if (ftc_msgs[tok] == NULL) { cdb_bucket = cdb_fetch(CDB_FULLTEXT, &tok, sizeof(int)); if (cdb_bucket != NULL) { ftc_num_msgs[tok] = cdb_bucket->len / sizeof(long); ftc_msgs[tok] = (long *)cdb_bucket->ptr; cdb_bucket->ptr = NULL; cdb_free(cdb_bucket); } else { ftc_num_msgs[tok] = 0; ftc_msgs[tok] = malloc(sizeof(long)); } } if (op == 1) { /* add to index */ ++ftc_num_msgs[tok]; ftc_msgs[tok] = realloc(ftc_msgs[tok], ftc_num_msgs[tok]*sizeof(long)); ftc_msgs[tok][ftc_num_msgs[tok] - 1] = msgnum; } if (op == 0) { /* remove from index */ if (ftc_num_msgs[tok] >= 1) { for (j=0; j CitControl.MMfulltext) && (msgnum <= ft_newhighest)) { ++ft_num_msgs; if (ft_num_msgs > ft_num_alloc) { ft_num_alloc += 1024; ft_newmsgs = realloc(ft_newmsgs, (ft_num_alloc * sizeof(long))); } ft_newmsgs[ft_num_msgs - 1] = msgnum; } } /* * Scan a room for messages to index. */ void ft_index_room(struct ctdlroom *qrbuf, void *data) { if (server_shutting_down) return; CtdlGetRoom(&CC->room, qrbuf->QRname); CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, ft_index_msg, NULL); } /* * Begin the fulltext indexing process. */ void do_fulltext_indexing(void) { int i; static time_t last_index = 0L; static time_t last_progress = 0L; time_t run_time = 0L; time_t end_time = 0L; static int is_running = 0; if (is_running) return; /* Concurrency check - only one can run */ is_running = 1; /* * Don't do this if the site doesn't have it enabled. */ if (!config.c_enable_fulltext) { return; } /* * Make sure we don't run the indexer too frequently. * FIXME move the setting into config */ if ( (time(NULL) - last_index) < 300L) { return; } /* * Check to see whether the fulltext index is up to date; if there * are no messages to index, don't waste any more time trying. */ if ((CitControl.MMfulltext >= CitControl.MMhighest) && (CitControl.fulltext_wordbreaker == FT_WORDBREAKER_ID)) { return; /* nothing to do! */ } run_time = time(NULL); syslog(LOG_DEBUG, "do_fulltext_indexing() started (%ld)", run_time); /* * If we've switched wordbreaker modules, burn the index and start * over. */ begin_critical_section(S_CONTROL); if (CitControl.fulltext_wordbreaker != FT_WORDBREAKER_ID) { syslog(LOG_DEBUG, "wb ver on disk = %d, code ver = %d", CitControl.fulltext_wordbreaker, FT_WORDBREAKER_ID ); syslog(LOG_INFO, "(re)initializing full text index"); cdb_trunc(CDB_FULLTEXT); CitControl.MMfulltext = 0L; put_control(); } end_critical_section(S_CONTROL); /* * Now go through each room and find messages to index. */ ft_newhighest = CitControl.MMhighest; CtdlForEachRoom(ft_index_room, NULL); /* load all msg pointers */ if (ft_num_msgs > 0) { qsort(ft_newmsgs, ft_num_msgs, sizeof(long), longcmp); for (i=0; i<(ft_num_msgs-1); ++i) { /* purge dups */ if (ft_newmsgs[i] == ft_newmsgs[i+1]) { memmove(&ft_newmsgs[i], &ft_newmsgs[i+1], ((ft_num_msgs - i - 1)*sizeof(long))); --ft_num_msgs; --i; } } /* Here it is ... do each message! */ for (i=0; i= FT_MAX_CACHE) { syslog(LOG_DEBUG, "Time to flush."); ft_newhighest = ft_newmsgs[i]; break; } } free(ft_newmsgs); ft_num_msgs = 0; ft_num_alloc = 0; ft_newmsgs = NULL; } end_time = time(NULL); if (server_shutting_down) { is_running = 0; return; } syslog(LOG_DEBUG, "do_fulltext_indexing() duration (%ld)", end_time - run_time); /* Save our place so we don't have to do this again */ ft_flush_cache(); begin_critical_section(S_CONTROL); CitControl.MMfulltext = ft_newhighest; CitControl.fulltext_wordbreaker = FT_WORDBREAKER_ID; put_control(); end_critical_section(S_CONTROL); last_index = time(NULL); syslog(LOG_DEBUG, "do_fulltext_indexing() finished"); is_running = 0; return; } /* * API call to perform searches. * (This one does the "all of these words" search.) * Caller is responsible for freeing the message list. */ void ft_search(int *fts_num_msgs, long **fts_msgs, const char *search_string) { int num_tokens = 0; int *tokens = NULL; int i, j; struct cdbdata *cdb_bucket; int num_all_msgs = 0; long *all_msgs = NULL; int num_ret_msgs = 0; int num_ret_alloc = 0; long *ret_msgs = NULL; int tok; wordbreaker(search_string, &num_tokens, &tokens); if (num_tokens > 0) { for (i=0; ilen / sizeof(long); ftc_msgs[tok] = (long *)cdb_bucket->ptr; cdb_bucket->ptr = NULL; cdb_free(cdb_bucket); } else { ftc_num_msgs[tok] = 0; ftc_msgs[tok] = malloc(sizeof(long)); } } num_all_msgs += ftc_num_msgs[tok]; if (num_all_msgs > 0) { all_msgs = realloc(all_msgs, num_all_msgs*sizeof(long) ); memcpy(&all_msgs[num_all_msgs-ftc_num_msgs[tok]], ftc_msgs[tok], ftc_num_msgs[tok]*sizeof(long) ); } } free(tokens); if (all_msgs != NULL) { qsort(all_msgs, num_all_msgs, sizeof(long), longcmp); /* * At this point, if a message appears num_tokens times in the * list, then it contains all of the search tokens. */ if (num_all_msgs >= num_tokens) for (j=0; j<(num_all_msgs-num_tokens+1); ++j) { if (all_msgs[j] == all_msgs[j+num_tokens-1]) { ++num_ret_msgs; if (num_ret_msgs > num_ret_alloc) { num_ret_alloc += 64; ret_msgs = realloc(ret_msgs, (num_ret_alloc*sizeof(long)) ); } ret_msgs[num_ret_msgs - 1] = all_msgs[j]; } } free(all_msgs); } } *fts_num_msgs = num_ret_msgs; *fts_msgs = ret_msgs; } /* * This search command is for diagnostic purposes and may be removed or replaced. */ void cmd_srch(char *argbuf) { int num_msgs = 0; long *msgs = NULL; int i; char search_string[256]; if (CtdlAccessCheck(ac_logged_in)) return; if (!config.c_enable_fulltext) { cprintf("%d Full text index is not enabled on this server.\n", ERROR + CMD_NOT_SUPPORTED); return; } extract_token(search_string, argbuf, 0, '|', sizeof search_string); ft_search(&num_msgs, &msgs, search_string); cprintf("%d %d msgs match all search words:\n", LISTING_FOLLOWS, num_msgs); if (num_msgs > 0) { for (i=0; i> 8) ^ value) & 255) << 8; int crc = 0; int bits = 8; do { if (( crc ^ k ) & 0x8000) crc = (crc << 1) ^ 0x1021; else crc <<= 1; k <<= 1; } while (--bits); return ((crcin << 8) ^ crc); } #else const CRC16 ccitt_16Table[] = { 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0 }; #define ByteCRC16(v, crc) \ (unsigned short)((crc << 8) ^ ccitt_16Table[((crc >> 8) ^ (v)) & 255]) /* * ===== CalcCRC16Words ===== * Calculate the CRC for a buffer of 16-bit words. Supports both * Little and Big Endian formats using conditional compilation. * Note: minimum count is 1 (0 case not handled) */ CRC16 CalcCRC16Words(unsigned int count, short *buffer) { int crc = 0; do { int value = *buffer++; #ifdef _BIG_ENDIAN crc = ByteCRC16(value >> 8, crc); crc = ByteCRC16(value, crc); #else crc = ByteCRC16(value, crc); crc = ByteCRC16(value >> 8, crc); #endif } while (--count); return (CRC16) crc; } #endif /* _OPT_SIZE */ #ifdef _CRC16_BYTES /* * ===== CalcCRC16Bytes ===== * Calculate the CRC for a buffer of 8-bit words. * Note: minimum count is 1 (0 case not handled) */ CRC16 CalcCRC16Bytes(unsigned int count, char *buffer) { int crc = 0; do { int value = *buffer++; crc = ByteCRC16(value, crc); } while (--count); return crc; } #endif /* _CRC16_BYTES */ citadel-9.01/modules/fulltext/ft_wordbreaker.c0000644000000000000000000001221112507024051020222 0ustar rootroot/* * Default wordbreaker module for full text indexing. * * Copyright (c) 2005-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * */ #include "sysdep.h" #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include "citadel.h" #include "server.h" #include "sysdep_decls.h" #include "citserver.h" #include "support.h" #include "config.h" #include "database.h" #include "msgbase.h" #include "control.h" #include "ft_wordbreaker.h" #include "crc16.h" #include "ctdl_module.h" /* * Noise words are not included in search indices. * NOTE: if the noise word list is altered in any way, the FT_WORDBREAKER_ID * must also be changed, so that the index is rebuilt. */ noise_word *noise_words[26]; static char *noise_words_init[] = { "about", "after", "also", "another", "because", "been", "before", "being", "between", "both", "came", "come", "could", "each", "from", "have", "here", "himself", "into", "like", "make", "many", "might", "more", "most", "much", "must", "never", "only", "other", "over", "said", "same", "should", "since", "some", "still", "such", "take", "than", "that", "their", "them", "then", "there", "these", "they", "this", "those", "through", "under", "very", "well", "were", "what", "where", "which", "while", "with", "would", "your" }; void initialize_noise_words(void) { int i; int len; int ch; noise_word *next; memset (noise_words, 0, sizeof(noise_words)); for (i=0; i<(sizeof(noise_words_init)/sizeof(char *)); ++i) { ch = noise_words_init[i][0] - 'a'; len = strlen(noise_words_init[i]); next = malloc(sizeof(noise_word)); next->len = len; next->word = strdup(noise_words_init[i]); next->next = noise_words[ch]; noise_words[ch] = next; } } void noise_word_cleanup(void) { int i; noise_word *cur, *next; syslog(LOG_INFO, "Cleaning up fulltext noise words.\n"); for (i = 0 ; i < 26 ; i++) { cur = noise_words[i]; while (cur) { next = cur->next; free(cur->word); free(cur); cur = next; } } } /* * Compare function */ int intcmp(const void *rec1, const void *rec2) { int i1, i2; i1 = *(const int *)rec1; i2 = *(const int *)rec2; if (i1 > i2) return(1); if (i1 < i2) return(-1); return(0); } void wordbreaker(const char *text, int *num_tokens, int **tokens) { int wb_num_tokens = 0; int wb_num_alloc = 0; int *wb_tokens = NULL; const char *ptr; const char *word_start; const char *word_end; char ch; int word_len; char word[256]; int i; int word_crc; noise_word *noise; if (text == NULL) { /* no NULL text please */ *num_tokens = 0; *tokens = NULL; return; } if (text[0] == 0) { /* no empty text either */ *num_tokens = 0; *tokens = NULL; return; } ptr = text; word_start = NULL; while (*ptr) { ch = *ptr; if (isalnum(ch)) { if (!word_start) { word_start = ptr; } } ++ptr; ch = *ptr; if ( (!isalnum(ch)) && (word_start) ) { word_end = ptr; // --word_end; /* extract the word */ word_len = word_end - word_start; if (word_len >= sizeof word) { syslog(LOG_DEBUG, "Invalid word length: %d\n", word_len); safestrncpy(word, word_start, sizeof word); word[(sizeof word) - 1] = 0; } else { safestrncpy(word, word_start, word_len+1); word[word_len] = 0; } word_start = NULL; /* are we ok with the length? */ if ( (word_len >= WB_MIN) && (word_len <= WB_MAX) ) { for (i=0; ilen == word_len) { if (!strcmp(word, noise->word)) { word_len = 0; break; } } noise = noise->next; } if (word_len == 0) continue; word_crc = (int) CalcCRC16Bytes(word_len, word); ++wb_num_tokens; if (wb_num_tokens > wb_num_alloc) { wb_num_alloc += 512; wb_tokens = realloc(wb_tokens, (sizeof(int) * wb_num_alloc)); } wb_tokens[wb_num_tokens - 1] = word_crc; } } } /* sort and purge dups */ if (wb_num_tokens > 1) { qsort(wb_tokens, wb_num_tokens, sizeof(int), intcmp); for (i=0; i<(wb_num_tokens-1); ++i) { if (wb_tokens[i] == wb_tokens[i+1]) { memmove(&wb_tokens[i], &wb_tokens[i+1], ((wb_num_tokens - i - 1)*sizeof(int))); --wb_num_tokens; --i; } } } *num_tokens = wb_num_tokens; *tokens = wb_tokens; } citadel-9.01/modules/fulltext/crc16.h0000644000000000000000000000427512507024051016160 0ustar rootroot/**************************************************************************** Filename: crc16.h Description: Cyclic Redundancy Check 16 functions Created: 24-Feb-1999 Copyright (c) 2002-2003, Indigo Systems Corporation All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of the Indigo Systems Corporation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ****************************************************************************/ #define _CRC16_BYTES 1 /* ig */ #ifndef __CRC16_H__ #define __CRC16_H__ #ifdef __cplusplus extern "C" { #endif typedef unsigned short CRC16; #ifdef _OPT_SIZE int ByteCRC16(int value, int crcin); #else CRC16 CalcCRC16Words(unsigned int count, short *buffer); #endif #ifdef _CRC16_BYTES CRC16 CalcCRC16Bytes(unsigned int count, char *buffer); #endif #ifdef __cplusplus } #endif #endif /* __CRC16_H__ */ citadel-9.01/modules/fulltext/serv_fulltext.h0000644000000000000000000000120712507024051020140 0ustar rootroot/* * Copyright (c) 2005-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * */ void ft_index_message(long msgnum, int op); void ft_search(int *fts_num_msgs, long **fts_msgs, const char *search_string); void *indexer_thread(void *arg); citadel-9.01/modules/fulltext/ft_wordbreaker.h0000644000000000000000000000216212507024051020233 0ustar rootroot/* * Copyright (c) 2005-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * */ /* * This is an ID for the wordbreaker module. If we do pluggable wordbreakers * later on, or even if we update this one, we can use a different ID so the * system knows it needs to throw away the existing index and rebuild it. */ #define FT_WORDBREAKER_ID 0x0021 /* * Minimum and maximum length of words to index */ #define WB_MIN 4 // nothing with 3 or less chars #define WB_MAX 40 void wordbreaker(const char *text, int *num_tokens, int **tokens); void initialize_noise_words(void); void noise_word_cleanup(void); typedef struct noise_word noise_word; struct noise_word { unsigned int len; char *word; noise_word *next; }; citadel-9.01/modules/sieve/0000755000000000000000000000000012507024051014325 5ustar rootrootcitadel-9.01/modules/sieve/serv_sieve.c0000644000000000000000000010457612507024051016660 0ustar rootroot/* * This module glues libSieve to the Citadel server in order to implement * the Sieve mailbox filtering language (RFC 3028). * * Copyright (c) 1987-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "sysdep.h" #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "database.h" #include "msgbase.h" #include "internet_addressing.h" #include "ctdl_module.h" #include "serv_sieve.h" struct RoomProcList *sieve_list = NULL; char *msiv_extensions = NULL; int SieveDebugEnable = 0; #define DBGLOG(LEVEL) if ((LEVEL != LOG_DEBUG) || (SieveDebugEnable != 0)) #define SV_syslog(LEVEL, FORMAT, ...) \ DBGLOG(LEVEL) syslog(LEVEL, \ "Sieve: " FORMAT, __VA_ARGS__) #define SVM_syslog(LEVEL, FORMAT) \ DBGLOG(LEVEL) syslog(LEVEL, \ "Sieve: " FORMAT); /* * Callback function to send libSieve trace messages to Citadel log facility */ int ctdl_debug(sieve2_context_t *s, void *my) { SV_syslog(LOG_DEBUG, "%s", sieve2_getvalue_string(s, "message")); return SIEVE2_OK; } /* * Callback function to log script parsing errors */ int ctdl_errparse(sieve2_context_t *s, void *my) { SV_syslog(LOG_WARNING, "Error in script, line %d: %s", sieve2_getvalue_int(s, "lineno"), sieve2_getvalue_string(s, "message") ); return SIEVE2_OK; } /* * Callback function to log script execution errors */ int ctdl_errexec(sieve2_context_t *s, void *my) { SV_syslog(LOG_WARNING, "Error executing script: %s", sieve2_getvalue_string(s, "message") ); return SIEVE2_OK; } /* * Callback function to redirect a message to a different folder */ int ctdl_redirect(sieve2_context_t *s, void *my) { struct ctdl_sieve *cs = (struct ctdl_sieve *)my; struct CtdlMessage *msg = NULL; recptypes *valid = NULL; char recp[256]; safestrncpy(recp, sieve2_getvalue_string(s, "address"), sizeof recp); SV_syslog(LOG_DEBUG, "Action is REDIRECT, recipient <%s>", recp); valid = validate_recipients(recp, NULL, 0); if (valid == NULL) { SV_syslog(LOG_WARNING, "REDIRECT failed: bad recipient <%s>", recp); return SIEVE2_ERROR_BADARGS; } if (valid->num_error > 0) { SV_syslog(LOG_WARNING, "REDIRECT failed: bad recipient <%s>", recp); free_recipients(valid); return SIEVE2_ERROR_BADARGS; } msg = CtdlFetchMessage(cs->msgnum, 1); if (msg == NULL) { SV_syslog(LOG_WARNING, "REDIRECT failed: unable to fetch msg %ld", cs->msgnum); free_recipients(valid); return SIEVE2_ERROR_BADARGS; } CtdlSubmitMsg(msg, valid, NULL, 0); cs->cancel_implicit_keep = 1; free_recipients(valid); CM_Free(msg); return SIEVE2_OK; } /* * Callback function to indicate that a message *will* be kept in the inbox */ int ctdl_keep(sieve2_context_t *s, void *my) { struct ctdl_sieve *cs = (struct ctdl_sieve *)my; SVM_syslog(LOG_DEBUG, "Action is KEEP"); cs->keep = 1; cs->cancel_implicit_keep = 1; return SIEVE2_OK; } /* * Callback function to file a message into a different mailbox */ int ctdl_fileinto(sieve2_context_t *s, void *my) { struct ctdl_sieve *cs = (struct ctdl_sieve *)my; const char *dest_folder = sieve2_getvalue_string(s, "mailbox"); int c; char foldername[256]; char original_room_name[ROOMNAMELEN]; SV_syslog(LOG_DEBUG, "Action is FILEINTO, destination is <%s>", dest_folder); /* FILEINTO 'INBOX' is the same thing as KEEP */ if ( (!strcasecmp(dest_folder, "INBOX")) || (!strcasecmp(dest_folder, MAILROOM)) ) { cs->keep = 1; cs->cancel_implicit_keep = 1; return SIEVE2_OK; } /* Remember what room we came from */ safestrncpy(original_room_name, CC->room.QRname, sizeof original_room_name); /* First try a mailbox name match (check personal mail folders first) */ snprintf(foldername, sizeof foldername, "%010ld.%s", cs->usernum, dest_folder); c = CtdlGetRoom(&CC->room, foldername); /* Then a regular room name match (public and private rooms) */ if (c != 0) { safestrncpy(foldername, dest_folder, sizeof foldername); c = CtdlGetRoom(&CC->room, foldername); } if (c != 0) { SV_syslog(LOG_WARNING, "FILEINTO failed: target <%s> does not exist", dest_folder); return SIEVE2_ERROR_BADARGS; } /* Yes, we actually have to go there */ CtdlUserGoto(NULL, 0, 0, NULL, NULL, NULL, NULL); c = CtdlSaveMsgPointersInRoom(NULL, &cs->msgnum, 1, 0, NULL, 0); /* Go back to the room we came from */ if (strcasecmp(original_room_name, CC->room.QRname)) { CtdlUserGoto(original_room_name, 0, 0, NULL, NULL, NULL, NULL); } if (c == 0) { cs->cancel_implicit_keep = 1; return SIEVE2_OK; } else { return SIEVE2_ERROR_BADARGS; } } /* * Callback function to indicate that a message should be discarded. */ int ctdl_discard(sieve2_context_t *s, void *my) { struct ctdl_sieve *cs = (struct ctdl_sieve *)my; SVM_syslog(LOG_DEBUG, "Action is DISCARD"); /* Cancel the implicit keep. That's all there is to it. */ cs->cancel_implicit_keep = 1; return SIEVE2_OK; } /* * Callback function to indicate that a message should be rejected */ int ctdl_reject(sieve2_context_t *s, void *my) { struct ctdl_sieve *cs = (struct ctdl_sieve *)my; char *reject_text = NULL; SVM_syslog(LOG_DEBUG, "Action is REJECT"); /* If we don't know who sent the message, do a DISCARD instead. */ if (IsEmptyStr(cs->sender)) { SVM_syslog(LOG_INFO, "Unknown sender. Doing DISCARD instead of REJECT."); return ctdl_discard(s, my); } /* Assemble the reject message. */ reject_text = malloc(strlen(sieve2_getvalue_string(s, "message")) + 1024); if (reject_text == NULL) { return SIEVE2_ERROR_FAIL; } sprintf(reject_text, "Content-type: text/plain\n" "\n" "The message was refused by the recipient's mail filtering program.\n" "The reason given was as follows:\n" "\n" "%s\n" "\n" , sieve2_getvalue_string(s, "message") ); quickie_message( /* This delivers the message */ NULL, cs->envelope_to, cs->sender, NULL, reject_text, FMT_RFC822, "Delivery status notification" ); free(reject_text); cs->cancel_implicit_keep = 1; return SIEVE2_OK; } /* * Callback function to indicate that a vacation message should be generated */ int ctdl_vacation(sieve2_context_t *s, void *my) { struct ctdl_sieve *cs = (struct ctdl_sieve *)my; struct sdm_vacation *vptr; int days = 1; const char *message; char *vacamsg_text = NULL; char vacamsg_subject[1024]; SVM_syslog(LOG_DEBUG, "Action is VACATION"); message = sieve2_getvalue_string(s, "message"); if (message == NULL) return SIEVE2_ERROR_BADARGS; if (sieve2_getvalue_string(s, "subject") != NULL) { safestrncpy(vacamsg_subject, sieve2_getvalue_string(s, "subject"), sizeof vacamsg_subject); } else { snprintf(vacamsg_subject, sizeof vacamsg_subject, "Re: %s", cs->subject); } days = sieve2_getvalue_int(s, "days"); if (days < 1) days = 1; if (days > MAX_VACATION) days = MAX_VACATION; /* Check to see whether we've already alerted this sender that we're on vacation. */ for (vptr = cs->u->first_vacation; vptr != NULL; vptr = vptr->next) { if (!strcasecmp(vptr->fromaddr, cs->sender)) { if ( (time(NULL) - vptr->timestamp) < (days * 86400) ) { SV_syslog(LOG_DEBUG, "Already alerted <%s> recently.", cs->sender); return SIEVE2_OK; } } } /* Assemble the reject message. */ vacamsg_text = malloc(strlen(message) + 1024); if (vacamsg_text == NULL) { return SIEVE2_ERROR_FAIL; } sprintf(vacamsg_text, "Content-type: text/plain charset=utf-8\n" "\n" "%s\n" "\n" , message ); quickie_message( /* This delivers the message */ NULL, cs->envelope_to, cs->sender, NULL, vacamsg_text, FMT_RFC822, vacamsg_subject ); free(vacamsg_text); /* Now update the list to reflect the fact that we've alerted this sender. * If they're already in the list, just update the timestamp. */ for (vptr = cs->u->first_vacation; vptr != NULL; vptr = vptr->next) { if (!strcasecmp(vptr->fromaddr, cs->sender)) { vptr->timestamp = time(NULL); return SIEVE2_OK; } } /* If we get to this point, create a new record. */ vptr = malloc(sizeof(struct sdm_vacation)); memset(vptr, 0, sizeof(struct sdm_vacation)); vptr->timestamp = time(NULL); safestrncpy(vptr->fromaddr, cs->sender, sizeof vptr->fromaddr); vptr->next = cs->u->first_vacation; cs->u->first_vacation = vptr; return SIEVE2_OK; } /* * Callback function to parse addresses per local system convention * It is disabled because we don't support subaddresses. */ #if 0 int ctdl_getsubaddress(sieve2_context_t *s, void *my) { struct ctdl_sieve *cs = (struct ctdl_sieve *)my; /* libSieve does not take ownership of the memory used here. But, since we * are just pointing to locations inside a struct which we are going to free * later, we're ok. */ sieve2_setvalue_string(s, "user", cs->recp_user); sieve2_setvalue_string(s, "detail", ""); sieve2_setvalue_string(s, "localpart", cs->recp_user); sieve2_setvalue_string(s, "domain", cs->recp_node); return SIEVE2_OK; } #endif /* * Callback function to parse message envelope */ int ctdl_getenvelope(sieve2_context_t *s, void *my) { struct ctdl_sieve *cs = (struct ctdl_sieve *)my; SVM_syslog(LOG_DEBUG, "Action is GETENVELOPE"); SV_syslog(LOG_DEBUG, "EnvFrom: %s", cs->envelope_from); SV_syslog(LOG_DEBUG, "EnvTo: %s", cs->envelope_to); if (cs->envelope_from != NULL) { if ((cs->envelope_from[0] != '@')&&(cs->envelope_from[strlen(cs->envelope_from)-1] != '@')) { sieve2_setvalue_string(s, "from", cs->envelope_from); } else { sieve2_setvalue_string(s, "from", "invalid_envelope_from@example.org"); } } else { sieve2_setvalue_string(s, "from", "null_envelope_from@example.org"); } if (cs->envelope_to != NULL) { if ((cs->envelope_to[0] != '@') && (cs->envelope_to[strlen(cs->envelope_to)-1] != '@')) { sieve2_setvalue_string(s, "to", cs->envelope_to); } else { sieve2_setvalue_string(s, "to", "invalid_envelope_to@example.org"); } } else { sieve2_setvalue_string(s, "to", "null_envelope_to@example.org"); } return SIEVE2_OK; } /* * Callback function to fetch message body * (Uncomment the code if we implement this extension) * int ctdl_getbody(sieve2_context_t *s, void *my) { return SIEVE2_ERROR_UNSUPPORTED; } * */ /* * Callback function to fetch message size */ int ctdl_getsize(sieve2_context_t *s, void *my) { struct ctdl_sieve *cs = (struct ctdl_sieve *)my; struct MetaData smi; GetMetaData(&smi, cs->msgnum); if (smi.meta_rfc822_length > 0L) { sieve2_setvalue_int(s, "size", (int)smi.meta_rfc822_length); return SIEVE2_OK; } return SIEVE2_ERROR_UNSUPPORTED; } /* * Return a pointer to the active Sieve script. * (Caller does NOT own the memory and should not free the returned pointer.) */ char *get_active_script(struct sdm_userdata *u) { struct sdm_script *sptr; for (sptr=u->first_script; sptr!=NULL; sptr=sptr->next) { if (sptr->script_active > 0) { SV_syslog(LOG_DEBUG, "get_active_script() is using script '%s'", sptr->script_name); return(sptr->script_content); } } SVM_syslog(LOG_DEBUG, "get_active_script() found no active script"); return(NULL); } /* * Callback function to retrieve the sieve script */ int ctdl_getscript(sieve2_context_t *s, void *my) { struct ctdl_sieve *cs = (struct ctdl_sieve *)my; char *active_script = get_active_script(cs->u); if (active_script != NULL) { sieve2_setvalue_string(s, "script", active_script); return SIEVE2_OK; } return SIEVE2_ERROR_GETSCRIPT; } /* * Callback function to retrieve message headers */ int ctdl_getheaders(sieve2_context_t *s, void *my) { struct ctdl_sieve *cs = (struct ctdl_sieve *)my; SVM_syslog(LOG_DEBUG, "ctdl_getheaders() was called"); sieve2_setvalue_string(s, "allheaders", cs->rfc822headers); return SIEVE2_OK; } /* * Add a room to the list of those rooms which potentially require sieve processing */ void sieve_queue_room(struct ctdlroom *which_room) { struct RoomProcList *ptr; ptr = (struct RoomProcList *) malloc(sizeof (struct RoomProcList)); if (ptr == NULL) return; safestrncpy(ptr->name, which_room->QRname, sizeof ptr->name); begin_critical_section(S_SIEVELIST); ptr->next = sieve_list; sieve_list = ptr; end_critical_section(S_SIEVELIST); SV_syslog(LOG_DEBUG, "<%s> queued for Sieve processing", which_room->QRname); } /* * Perform sieve processing for one message (called by sieve_do_room() for each message) */ void sieve_do_msg(long msgnum, void *userdata) { struct sdm_userdata *u = (struct sdm_userdata *) userdata; sieve2_context_t *sieve2_context; struct ctdl_sieve my; int res; struct CtdlMessage *msg; int i; size_t headers_len = 0; int len = 0; if (u == NULL) { SV_syslog(LOG_EMERG, "Can't process message <%ld> without userdata!", msgnum); return; } sieve2_context = u->sieve2_context; SV_syslog(LOG_DEBUG, "Performing sieve processing on msg <%ld>", msgnum); /* * Make sure you include message body so you can get those second-level headers ;) */ msg = CtdlFetchMessage(msgnum, 1); if (msg == NULL) return; /* * Grab the message headers so we can feed them to libSieve. * Use HEADERS_ONLY rather than HEADERS_FAST in order to include second-level headers. */ CC->redirect_buffer = NewStrBufPlain(NULL, SIZ); CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ONLY, 0, 1, 0); headers_len = StrLength(CC->redirect_buffer); my.rfc822headers = SmashStrBuf(&CC->redirect_buffer); /* * libSieve clobbers the stack if it encounters badly formed * headers. Sanitize our headers by stripping nonprintable * characters. */ for (i=0; iroom.QRname); /* Keep track of the owner of the room's namespace */ my.msgnum = msgnum; /* Keep track of the message number in our local store */ my.u = u; /* Hand off a pointer to the rest of this info */ /* Keep track of the recipient so we can do handling based on it later */ process_rfc822_addr(msg->cm_fields[eRecipient], my.recp_user, my.recp_node, my.recp_name); /* Keep track of the sender so we can use it for REJECT and VACATION responses */ if (!CM_IsEmpty(msg, erFc822Addr)) { safestrncpy(my.sender, msg->cm_fields[erFc822Addr], sizeof my.sender); } else if ( (!CM_IsEmpty(msg, eAuthor)) && (!CM_IsEmpty(msg, eNodeName)) ) { snprintf(my.sender, sizeof my.sender, "%s@%s", msg->cm_fields[eAuthor], msg->cm_fields[eNodeName]); } else if (!CM_IsEmpty(msg, eAuthor)) { safestrncpy(my.sender, msg->cm_fields[eAuthor], sizeof my.sender); } else { strcpy(my.sender, ""); } /* Keep track of the subject so we can use it for VACATION responses */ if (!CM_IsEmpty(msg, eMsgSubject)) { safestrncpy(my.subject, msg->cm_fields[eMsgSubject], sizeof my.subject); } else { strcpy(my.subject, ""); } /* Keep track of the envelope-from address (use body-from if not found) */ if (!CM_IsEmpty(msg, eMessagePath)) { safestrncpy(my.envelope_from, msg->cm_fields[eMessagePath], sizeof my.envelope_from); stripallbut(my.envelope_from, '<', '>'); } else if (!CM_IsEmpty(msg, erFc822Addr)) { safestrncpy(my.envelope_from, msg->cm_fields[erFc822Addr], sizeof my.envelope_from); stripallbut(my.envelope_from, '<', '>'); } else { strcpy(my.envelope_from, ""); } len = strlen(my.envelope_from); for (i=0; icm_fields[eenVelopeTo], sizeof my.envelope_to); stripallbut(my.envelope_to, '<', '>'); } else if (!CM_IsEmpty(msg, eRecipient)) { safestrncpy(my.envelope_to, msg->cm_fields[eRecipient], sizeof my.envelope_to); if (!CM_IsEmpty(msg, eDestination)) { strcat(my.envelope_to, "@"); strcat(my.envelope_to, msg->cm_fields[eDestination]); } stripallbut(my.envelope_to, '<', '>'); } else { strcpy(my.envelope_to, ""); } len = strlen(my.envelope_to); for (i=0; iroom.QRname, &msgnum, 1, ""); } SV_syslog(LOG_DEBUG, "Completed sieve processing on msg <%ld>", msgnum); u->lastproc = msgnum; return; } /* * Given the on-disk representation of our Sieve config, load * it into an in-memory data structure. */ void parse_sieve_config(char *conf, struct sdm_userdata *u) { char *ptr; char *c, *vacrec; char keyword[256]; struct sdm_script *sptr; struct sdm_vacation *vptr; ptr = conf; while (c = ptr, ptr = bmstrcasestr(ptr, CTDLSIEVECONFIGSEPARATOR), ptr != NULL) { *ptr = 0; ptr += strlen(CTDLSIEVECONFIGSEPARATOR); extract_token(keyword, c, 0, '|', sizeof keyword); if (!strcasecmp(keyword, "lastproc")) { u->lastproc = extract_long(c, 1); } else if (!strcasecmp(keyword, "script")) { sptr = malloc(sizeof(struct sdm_script)); extract_token(sptr->script_name, c, 1, '|', sizeof sptr->script_name); sptr->script_active = extract_int(c, 2); remove_token(c, 0, '|'); remove_token(c, 0, '|'); remove_token(c, 0, '|'); sptr->script_content = strdup(c); sptr->next = u->first_script; u->first_script = sptr; } else if (!strcasecmp(keyword, "vacation")) { if (c != NULL) while (vacrec=c, c=strchr(c, '\n'), (c != NULL)) { *c = 0; ++c; if (strncasecmp(vacrec, "vacation|", 9)) { vptr = malloc(sizeof(struct sdm_vacation)); extract_token(vptr->fromaddr, vacrec, 0, '|', sizeof vptr->fromaddr); vptr->timestamp = extract_long(vacrec, 1); vptr->next = u->first_vacation; u->first_vacation = vptr; } } } /* ignore unknown keywords */ } } /* * We found the Sieve configuration for this user. * Now do something with it. */ void get_sieve_config_backend(long msgnum, void *userdata) { struct sdm_userdata *u = (struct sdm_userdata *) userdata; struct CtdlMessage *msg; char *conf; long conflen; u->config_msgnum = msgnum; msg = CtdlFetchMessage(msgnum, 1); if (msg == NULL) { u->config_msgnum = (-1) ; return; } CM_GetAsField(msg, eMesageText, &conf, &conflen); CM_Free(msg); if (conf != NULL) { parse_sieve_config(conf, u); free(conf); } } /* * Write our citadel sieve config back to disk * * (Set yes_write_to_disk to nonzero to make it actually write the config; * otherwise it just frees the data structures.) */ void rewrite_ctdl_sieve_config(struct sdm_userdata *u, int yes_write_to_disk) { StrBuf *text; struct sdm_script *sptr; struct sdm_vacation *vptr; text = NewStrBufPlain(NULL, SIZ); StrBufPrintf(text, "Content-type: application/x-citadel-sieve-config\n" "\n" CTDLSIEVECONFIGSEPARATOR "lastproc|%ld" CTDLSIEVECONFIGSEPARATOR , u->lastproc ); while (u->first_script != NULL) { StrBufAppendPrintf(text, "script|%s|%d|%s" CTDLSIEVECONFIGSEPARATOR, u->first_script->script_name, u->first_script->script_active, u->first_script->script_content ); sptr = u->first_script; u->first_script = u->first_script->next; free(sptr->script_content); free(sptr); } if (u->first_vacation != NULL) { StrBufAppendPrintf(text, "vacation|\n"); while (u->first_vacation != NULL) { if ( (time(NULL) - u->first_vacation->timestamp) < (MAX_VACATION * 86400)) { StrBufAppendPrintf(text, "%s|%ld\n", u->first_vacation->fromaddr, u->first_vacation->timestamp ); } vptr = u->first_vacation; u->first_vacation = u->first_vacation->next; free(vptr); } StrBufAppendPrintf(text, CTDLSIEVECONFIGSEPARATOR); } if (yes_write_to_disk) { /* Save the config */ quickie_message("Citadel", NULL, NULL, u->config_roomname, ChrPtr(text), 4, "Sieve configuration" ); /* And delete the old one */ if (u->config_msgnum > 0) { CtdlDeleteMessages(u->config_roomname, &u->config_msgnum, 1, ""); } } FreeStrBuf (&text); } /* * This is our callback registration table for libSieve. */ sieve2_callback_t ctdl_sieve_callbacks[] = { { SIEVE2_ACTION_REJECT, ctdl_reject }, { SIEVE2_ACTION_VACATION, ctdl_vacation }, { SIEVE2_ERRCALL_PARSE, ctdl_errparse }, { SIEVE2_ERRCALL_RUNTIME, ctdl_errexec }, { SIEVE2_ACTION_FILEINTO, ctdl_fileinto }, { SIEVE2_ACTION_REDIRECT, ctdl_redirect }, { SIEVE2_ACTION_DISCARD, ctdl_discard }, { SIEVE2_ACTION_KEEP, ctdl_keep }, { SIEVE2_SCRIPT_GETSCRIPT, ctdl_getscript }, { SIEVE2_DEBUG_TRACE, ctdl_debug }, { SIEVE2_MESSAGE_GETALLHEADERS, ctdl_getheaders }, { SIEVE2_MESSAGE_GETSIZE, ctdl_getsize }, { SIEVE2_MESSAGE_GETENVELOPE, ctdl_getenvelope }, /* * These actions are unsupported by Citadel so we don't declare them. * { SIEVE2_ACTION_NOTIFY, ctdl_notify }, { SIEVE2_MESSAGE_GETSUBADDRESS, ctdl_getsubaddress }, { SIEVE2_MESSAGE_GETBODY, ctdl_getbody }, * */ { 0 } }; /* * Perform sieve processing for a single room */ void sieve_do_room(char *roomname) { struct sdm_userdata u; sieve2_context_t *sieve2_context = NULL; /* Context for sieve parser */ int res; /* Return code from libsieve calls */ long orig_lastproc = 0; memset(&u, 0, sizeof u); /* See if the user who owns this 'mailbox' has any Sieve scripts that * require execution. */ snprintf(u.config_roomname, sizeof u.config_roomname, "%010ld.%s", atol(roomname), USERCONFIGROOM); if (CtdlGetRoom(&CC->room, u.config_roomname) != 0) { SV_syslog(LOG_DEBUG, "<%s> does not exist. No processing is required.", u.config_roomname); return; } /* * Find the sieve scripts and control record and do something */ u.config_msgnum = (-1); CtdlForEachMessage(MSGS_LAST, 1, NULL, SIEVECONFIG, NULL, get_sieve_config_backend, (void *)&u ); if (u.config_msgnum < 0) { SVM_syslog(LOG_DEBUG, "No Sieve rules exist. No processing is required."); return; } /* * Check to see whether the script is empty and should not be processed. * A script is considered non-empty if it contains at least one semicolon. */ if ( (get_active_script(&u) == NULL) || (strchr(get_active_script(&u), ';') == NULL) ) { SVM_syslog(LOG_DEBUG, "Sieve script is empty. No processing is required."); return; } SV_syslog(LOG_DEBUG, "Rules found. Performing Sieve processing for <%s>", roomname); if (CtdlGetRoom(&CC->room, roomname) != 0) { SV_syslog(LOG_CRIT, "ERROR: cannot load <%s>", roomname); return; } /* Initialize the Sieve parser */ res = sieve2_alloc(&sieve2_context); if (res != SIEVE2_OK) { SV_syslog(LOG_CRIT, "sieve2_alloc() returned %d: %s", res, sieve2_errstr(res)); return; } res = sieve2_callbacks(sieve2_context, ctdl_sieve_callbacks); if (res != SIEVE2_OK) { SV_syslog(LOG_CRIT, "sieve2_callbacks() returned %d: %s", res, sieve2_errstr(res)); goto BAIL; } /* Validate the script */ struct ctdl_sieve my; /* dummy ctdl_sieve struct just to pass "u" slong */ memset(&my, 0, sizeof my); my.u = &u; res = sieve2_validate(sieve2_context, &my); if (res != SIEVE2_OK) { SV_syslog(LOG_CRIT, "sieve2_validate() returned %d: %s", res, sieve2_errstr(res)); goto BAIL; } /* Do something useful */ u.sieve2_context = sieve2_context; orig_lastproc = u.lastproc; CtdlForEachMessage(MSGS_GT, u.lastproc, NULL, NULL, NULL, sieve_do_msg, (void *) &u ); BAIL: res = sieve2_free(&sieve2_context); if (res != SIEVE2_OK) { SV_syslog(LOG_CRIT, "sieve2_free() returned %d: %s", res, sieve2_errstr(res)); } /* Rewrite the config if we have to */ rewrite_ctdl_sieve_config(&u, (u.lastproc > orig_lastproc) ) ; } /* * Perform sieve processing for all rooms which require it */ void perform_sieve_processing(void) { struct RoomProcList *ptr = NULL; if (sieve_list != NULL) { SVM_syslog(LOG_DEBUG, "Begin Sieve processing"); while (sieve_list != NULL) { char spoolroomname[ROOMNAMELEN]; safestrncpy(spoolroomname, sieve_list->name, sizeof spoolroomname); begin_critical_section(S_SIEVELIST); /* pop this record off the list */ ptr = sieve_list; sieve_list = sieve_list->next; free(ptr); /* invalidate any duplicate entries to prevent double processing */ for (ptr=sieve_list; ptr!=NULL; ptr=ptr->next) { if (!strcasecmp(ptr->name, spoolroomname)) { ptr->name[0] = 0; } } end_critical_section(S_SIEVELIST); if (spoolroomname[0] != 0) { sieve_do_room(spoolroomname); } } } } void msiv_load(struct sdm_userdata *u) { char hold_rm[ROOMNAMELEN]; strcpy(hold_rm, CC->room.QRname); /* save current room */ /* Take a spin through the user's personal address book */ if (CtdlGetRoom(&CC->room, USERCONFIGROOM) == 0) { u->config_msgnum = (-1); strcpy(u->config_roomname, CC->room.QRname); CtdlForEachMessage(MSGS_LAST, 1, NULL, SIEVECONFIG, NULL, get_sieve_config_backend, (void *)u ); } if (strcmp(CC->room.QRname, hold_rm)) { CtdlGetRoom(&CC->room, hold_rm); /* return to saved room */ } } void msiv_store(struct sdm_userdata *u, int yes_write_to_disk) { /* * Initialise the sieve configs last processed message number. * We don't need to get the highest message number for the users inbox since the systems * highest message number will be higher than that and loer than this scripts message number * This prevents this new script from processing any old messages in the inbox. * Most importantly it will prevent vacation messages being sent to lots of old messages * in the inbox. */ u->lastproc = CtdlGetCurrentMessageNumber(); rewrite_ctdl_sieve_config(u, yes_write_to_disk); } /* * Select the active script. * (Set script_name to an empty string to disable all scripts) * * Returns 0 on success or nonzero for error. */ int msiv_setactive(struct sdm_userdata *u, char *script_name) { int ok = 0; struct sdm_script *s; /* First see if the supplied value is ok */ if (IsEmptyStr(script_name)) { ok = 1; } else { for (s=u->first_script; s!=NULL; s=s->next) { if (!strcasecmp(s->script_name, script_name)) { ok = 1; } } } if (!ok) return(-1); /* Now set the active script */ for (s=u->first_script; s!=NULL; s=s->next) { if (!strcasecmp(s->script_name, script_name)) { s->script_active = 1; } else { s->script_active = 0; } } return(0); } /* * Fetch a script by name. * * Returns NULL if the named script was not found, or a pointer to the script * if it was found. NOTE: the caller does *not* own the memory returned by * this function. Copy it if you need to keep it. */ char *msiv_getscript(struct sdm_userdata *u, char *script_name) { struct sdm_script *s; for (s=u->first_script; s!=NULL; s=s->next) { if (!strcasecmp(s->script_name, script_name)) { if (s->script_content != NULL) { return (s->script_content); } } } return(NULL); } /* * Delete a script by name. * * Returns 0 if the script was deleted. * 1 if the script was not found. * 2 if the script cannot be deleted because it is active. */ int msiv_deletescript(struct sdm_userdata *u, char *script_name) { struct sdm_script *s = NULL; struct sdm_script *script_to_delete = NULL; for (s=u->first_script; s!=NULL; s=s->next) { if (!strcasecmp(s->script_name, script_name)) { script_to_delete = s; if (s->script_active) { return(2); } } } if (script_to_delete == NULL) return(1); if (u->first_script == script_to_delete) { u->first_script = u->first_script->next; } else for (s=u->first_script; s!=NULL; s=s->next) { if (s->next == script_to_delete) { s->next = s->next->next; } } free(script_to_delete->script_content); free(script_to_delete); return(0); } /* * Add or replace a new script. * NOTE: after this function returns, "u" owns the memory that "script_content" * was pointing to. */ void msiv_putscript(struct sdm_userdata *u, char *script_name, char *script_content) { int replaced = 0; struct sdm_script *s, *sptr; for (s=u->first_script; s!=NULL; s=s->next) { if (!strcasecmp(s->script_name, script_name)) { if (s->script_content != NULL) { free(s->script_content); } s->script_content = script_content; replaced = 1; } } if (replaced == 0) { sptr = malloc(sizeof(struct sdm_script)); safestrncpy(sptr->script_name, script_name, sizeof sptr->script_name); sptr->script_content = script_content; sptr->script_active = 0; sptr->next = u->first_script; u->first_script = sptr; } } /* * Citadel protocol to manage sieve scripts. * This is basically a simplified (read: doesn't resemble IMAP) version * of the 'managesieve' protocol. */ void cmd_msiv(char *argbuf) { char subcmd[256]; struct sdm_userdata u; char script_name[256]; char *script_content = NULL; struct sdm_script *s; int i; int changes_made = 0; memset(&u, 0, sizeof(struct sdm_userdata)); if (CtdlAccessCheck(ac_logged_in)) return; extract_token(subcmd, argbuf, 0, '|', sizeof subcmd); msiv_load(&u); if (!strcasecmp(subcmd, "putscript")) { extract_token(script_name, argbuf, 1, '|', sizeof script_name); if (!IsEmptyStr(script_name)) { cprintf("%d Transmit script now\n", SEND_LISTING); script_content = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0); msiv_putscript(&u, script_name, script_content); changes_made = 1; } else { cprintf("%d Invalid script name.\n", ERROR + ILLEGAL_VALUE); } } else if (!strcasecmp(subcmd, "listscripts")) { cprintf("%d Scripts:\n", LISTING_FOLLOWS); for (s=u.first_script; s!=NULL; s=s->next) { if (s->script_content != NULL) { cprintf("%s|%d|\n", s->script_name, s->script_active); } } cprintf("000\n"); } else if (!strcasecmp(subcmd, "setactive")) { extract_token(script_name, argbuf, 1, '|', sizeof script_name); if (msiv_setactive(&u, script_name) == 0) { cprintf("%d ok\n", CIT_OK); changes_made = 1; } else { cprintf("%d Script '%s' does not exist.\n", ERROR + ILLEGAL_VALUE, script_name ); } } else if (!strcasecmp(subcmd, "getscript")) { extract_token(script_name, argbuf, 1, '|', sizeof script_name); script_content = msiv_getscript(&u, script_name); if (script_content != NULL) { int script_len; cprintf("%d Script:\n", LISTING_FOLLOWS); script_len = strlen(script_content); client_write(script_content, script_len); if (script_content[script_len-1] != '\n') { cprintf("\n"); } cprintf("000\n"); } else { cprintf("%d Invalid script name.\n", ERROR + ILLEGAL_VALUE); } } else if (!strcasecmp(subcmd, "deletescript")) { extract_token(script_name, argbuf, 1, '|', sizeof script_name); i = msiv_deletescript(&u, script_name); if (i == 0) { cprintf("%d ok\n", CIT_OK); changes_made = 1; } else if (i == 1) { cprintf("%d Script '%s' does not exist.\n", ERROR + ILLEGAL_VALUE, script_name ); } else if (i == 2) { cprintf("%d Script '%s' is active and cannot be deleted.\n", ERROR + ILLEGAL_VALUE, script_name ); } else { cprintf("%d unknown error\n", ERROR); } } else { cprintf("%d Invalid subcommand\n", ERROR + CMD_NOT_SUPPORTED); } msiv_store(&u, changes_made); } void ctdl_sieve_init(void) { char *cred = NULL; sieve2_context_t *sieve2_context = NULL; int res; /* * We don't really care about dumping the entire credits to the log * every time the server is initialized. The documentation will suffice * for that purpose. We are making a call to sieve2_credits() in order * to demonstrate that we have successfully linked in to libsieve. */ cred = strdup(sieve2_credits()); if (cred == NULL) return; if (strlen(cred) > 60) { strcpy(&cred[55], "..."); } SV_syslog(LOG_INFO, "%s",cred); free(cred); /* Briefly initialize a Sieve parser instance just so we can list the * extensions that are available. */ res = sieve2_alloc(&sieve2_context); if (res != SIEVE2_OK) { SV_syslog(LOG_CRIT, "sieve2_alloc() returned %d: %s", res, sieve2_errstr(res)); return; } res = sieve2_callbacks(sieve2_context, ctdl_sieve_callbacks); if (res != SIEVE2_OK) { SV_syslog(LOG_CRIT, "sieve2_callbacks() returned %d: %s", res, sieve2_errstr(res)); goto BAIL; } msiv_extensions = strdup(sieve2_listextensions(sieve2_context)); SV_syslog(LOG_INFO, "Extensions: %s", msiv_extensions); BAIL: res = sieve2_free(&sieve2_context); if (res != SIEVE2_OK) { SV_syslog(LOG_CRIT, "sieve2_free() returned %d: %s", res, sieve2_errstr(res)); } } void cleanup_sieve(void) { struct RoomProcList *ptr, *ptr2; if (msiv_extensions != NULL) free(msiv_extensions); msiv_extensions = NULL; begin_critical_section(S_SIEVELIST); ptr=sieve_list; while (ptr != NULL) { ptr2 = ptr->next; free(ptr); ptr = ptr2; } sieve_list = NULL; end_critical_section(S_SIEVELIST); } int serv_sieve_room(struct ctdlroom *room) { if (!strcasecmp(&room->QRname[11], MAILROOM)) { sieve_queue_room(room); } return 0; } void LogSieveDebugEnable(const int n) { SieveDebugEnable = n; } CTDL_MODULE_INIT(sieve) { if (!threading) { CtdlRegisterDebugFlagHook(HKEY("sieve"), LogSieveDebugEnable, &SieveDebugEnable); ctdl_sieve_init(); CtdlRegisterProtoHook(cmd_msiv, "MSIV", "Manage Sieve scripts"); CtdlRegisterRoomHook(serv_sieve_room); CtdlRegisterSessionHook(perform_sieve_processing, EVT_HOUSE, PRIO_HOUSE + 10); CtdlRegisterCleanupHook(cleanup_sieve); } /* return our module name for the log */ return "sieve"; } citadel-9.01/modules/crypto/0000755000000000000000000000000012507024051014532 5ustar rootrootcitadel-9.01/modules/crypto/serv_crypto.h0000644000000000000000000000215112507024051017261 0ustar rootroot /* * Number of days for which self-signed certs are valid. */ #define SIGN_DAYS 3650 /* Ten years */ /* Shared Diffie-Hellman parameters */ #define DH_P "1A74527AEE4EE2568E85D4FB2E65E18C9394B9C80C42507D7A6A0DBE9A9A54B05A9A96800C34C7AA5297095B69C88901EEFD127F969DCA26A54C0E0B5C5473EBAEB00957D2633ECAE3835775425DE66C0DE6D024DBB17445E06E6B0C78415E589B8814F08531D02FD43778451E7685541079CFFB79EF0D26EFEEBBB69D1E80383" #define DH_G "2" #define DH_L 1024 #define CIT_CIPHERS "ALL:RC4+RSA:+SSLv2:+TLSv1:!MD5:@STRENGTH" /* see ciphers(1) */ #ifdef HAVE_OPENSSL void destruct_ssl(void); void init_ssl(void); void client_write_ssl (const char *buf, int nbytes); int client_read_sslbuffer(StrBuf *buf, int timeout); int client_readline_sslbuffer(StrBuf *Target, StrBuf *Buffer, const char **Pos, int timeout); int client_read_sslblob(StrBuf *Target, long want_len, int timeout); void cmd_stls(char *params); void cmd_gtls(char *params); void endtls(void); void ssl_lock(int mode, int n, const char *file, int line); void CtdlStartTLS(char *ok_response, char *nosup_response, char *error_response); extern SSL_CTX *ssl_ctx; #endif citadel-9.01/modules/crypto/serv_crypto.c0000644000000000000000000004353712507024051017271 0ustar rootroot/* * Copyright (c) 1987-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * */ #include #include #include #include #include "sysdep.h" #ifdef HAVE_OPENSSL #include #include #include #endif #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #ifdef HAVE_PTHREAD_H #include #endif #ifdef HAVE_SYS_SELECT_H #include #endif #include #include #include "server.h" #include "serv_crypto.h" #include "sysdep_decls.h" #include "citadel.h" #include "config.h" #include "ctdl_module.h" /* TODO: should we use the standard module init stuff to start this? */ /* TODO: should we register an event handler to call destruct_ssl? */ #ifdef HAVE_OPENSSL SSL_CTX *ssl_ctx; /* SSL context */ pthread_mutex_t **SSLCritters; /* Things needing locking */ static unsigned long id_callback(void) { return (unsigned long) pthread_self(); } void destruct_ssl(void) { int a; for (a = 0; a < CRYPTO_num_locks(); a++) free(SSLCritters[a]); free (SSLCritters); } void init_ssl(void) { const SSL_METHOD *ssl_method; DH *dh; RSA *rsa=NULL; X509_REQ *req = NULL; X509 *cer = NULL; EVP_PKEY *pk = NULL; EVP_PKEY *req_pkey = NULL; X509_NAME *name = NULL; FILE *fp; if (!access(EGD_POOL, F_OK)) RAND_egd(EGD_POOL); if (!RAND_status()) { syslog(LOG_CRIT, "PRNG not adequately seeded, won't do SSL/TLS\n"); return; } SSLCritters = malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t *)); if (!SSLCritters) { syslog(LOG_EMERG, "citserver: can't allocate memory!!\n"); /* Nothing's been initialized, just die */ exit(1); } else { int a; for (a = 0; a < CRYPTO_num_locks(); a++) { SSLCritters[a] = malloc(sizeof(pthread_mutex_t)); if (!SSLCritters[a]) { syslog(LOG_EMERG, "citserver: can't allocate memory!!\n"); /* Nothing's been initialized, just die */ exit(1); } pthread_mutex_init(SSLCritters[a], NULL); } } /* * Initialize SSL transport layer */ SSL_library_init(); SSL_load_error_strings(); ssl_method = SSLv23_server_method(); if (!(ssl_ctx = SSL_CTX_new(ssl_method))) { syslog(LOG_CRIT, "SSL_CTX_new failed: %s\n", ERR_reason_error_string(ERR_get_error())); return; } if (!(SSL_CTX_set_cipher_list(ssl_ctx, CIT_CIPHERS))) { syslog(LOG_CRIT, "SSL: No ciphers available\n"); SSL_CTX_free(ssl_ctx); ssl_ctx = NULL; return; } #if 0 #if SSLEAY_VERSION_NUMBER >= 0x00906000L SSL_CTX_set_mode(ssl_ctx, SSL_CTX_get_mode(ssl_ctx) | SSL_MODE_AUTO_RETRY); #endif #endif CRYPTO_set_locking_callback(ssl_lock); CRYPTO_set_id_callback(id_callback); /* Load DH parameters into the context */ dh = DH_new(); if (!dh) { syslog(LOG_CRIT, "init_ssl() can't allocate a DH object: %s\n", ERR_reason_error_string(ERR_get_error())); SSL_CTX_free(ssl_ctx); ssl_ctx = NULL; return; } if (!(BN_hex2bn(&(dh->p), DH_P))) { syslog(LOG_CRIT, "init_ssl() can't assign DH_P: %s\n", ERR_reason_error_string(ERR_get_error())); SSL_CTX_free(ssl_ctx); ssl_ctx = NULL; return; } if (!(BN_hex2bn(&(dh->g), DH_G))) { syslog(LOG_CRIT, "init_ssl() can't assign DH_G: %s\n", ERR_reason_error_string(ERR_get_error())); SSL_CTX_free(ssl_ctx); ssl_ctx = NULL; return; } dh->length = DH_L; SSL_CTX_set_tmp_dh(ssl_ctx, dh); DH_free(dh); /* Get our certificates in order. * First, create the key/cert directory if it's not there already... */ mkdir(ctdl_key_dir, 0700); /* * Generate a key pair if we don't have one. */ if (access(file_crpt_file_key, R_OK) != 0) { syslog(LOG_INFO, "Generating RSA key pair.\n"); rsa = RSA_generate_key(1024, /* modulus size */ 65537, /* exponent */ NULL, /* no callback */ NULL); /* no callback */ if (rsa == NULL) { syslog(LOG_CRIT, "Key generation failed: %s\n", ERR_reason_error_string(ERR_get_error())); } if (rsa != NULL) { fp = fopen(file_crpt_file_key, "w"); if (fp != NULL) { chmod(file_crpt_file_key, 0600); if (PEM_write_RSAPrivateKey(fp, /* the file */ rsa, /* the key */ NULL, /* no enc */ NULL, /* no passphr */ 0, /* no passphr */ NULL, /* no callbk */ NULL /* no callbk */ ) != 1) { syslog(LOG_CRIT, "Cannot write key: %s\n", ERR_reason_error_string(ERR_get_error())); unlink(file_crpt_file_key); } fclose(fp); } RSA_free(rsa); } } /* * If there is no certificate file on disk, we will be generating a self-signed certificate * in the next step. Therefore, if we have neither a CSR nor a certificate, generate * the CSR in this step so that the next step may commence. */ if ( (access(file_crpt_file_cer, R_OK) != 0) && (access(file_crpt_file_csr, R_OK) != 0) ) { syslog(LOG_INFO, "Generating a certificate signing request.\n"); /* * Read our key from the file. No, we don't just keep this * in memory from the above key-generation function, because * there is the possibility that the key was already on disk * and we didn't just generate it now. */ fp = fopen(file_crpt_file_key, "r"); if (fp) { rsa = PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL); fclose(fp); } if (rsa) { /* Create a public key from the private key */ if (pk=EVP_PKEY_new(), pk != NULL) { EVP_PKEY_assign_RSA(pk, rsa); if (req = X509_REQ_new(), req != NULL) { /* Set the public key */ X509_REQ_set_pubkey(req, pk); X509_REQ_set_version(req, 0L); name = X509_REQ_get_subject_name(req); /* Tell it who we are */ /* X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, "US", -1, -1, 0); X509_NAME_add_entry_by_txt(name, "ST", MBSTRING_ASC, "New York", -1, -1, 0); X509_NAME_add_entry_by_txt(name, "L", MBSTRING_ASC, "Mount Kisco", -1, -1, 0); */ X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char*) config.c_humannode, -1, -1, 0); X509_NAME_add_entry_by_txt(name, "OU", MBSTRING_ASC, (unsigned const char*)"Citadel server", -1, -1, 0); /* X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, config.c_fqdn, -1, -1, 0); */ X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (const unsigned char *)"*", -1, -1, 0); X509_REQ_set_subject_name(req, name); /* Sign the CSR */ if (!X509_REQ_sign(req, pk, EVP_md5())) { syslog(LOG_CRIT, "X509_REQ_sign(): error\n"); } else { /* Write it to disk. */ fp = fopen(file_crpt_file_csr, "w"); if (fp != NULL) { chmod(file_crpt_file_csr, 0600); PEM_write_X509_REQ(fp, req); fclose(fp); } } X509_REQ_free(req); } } RSA_free(rsa); } else { syslog(LOG_CRIT, "Unable to read private key.\n"); } } /* * Generate a self-signed certificate if we don't have one. */ if (access(file_crpt_file_cer, R_OK) != 0) { syslog(LOG_INFO, "Generating a self-signed certificate.\n"); /* Same deal as before: always read the key from disk because * it may or may not have just been generated. */ fp = fopen(file_crpt_file_key, "r"); if (fp) { rsa = PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL); fclose(fp); } /* This also holds true for the CSR. */ req = NULL; cer = NULL; pk = NULL; if (rsa) { if (pk=EVP_PKEY_new(), pk != NULL) { EVP_PKEY_assign_RSA(pk, rsa); } fp = fopen(file_crpt_file_csr, "r"); if (fp) { req = PEM_read_X509_REQ(fp, NULL, NULL, NULL); fclose(fp); } if (req) { if (cer = X509_new(), cer != NULL) { ASN1_INTEGER_set(X509_get_serialNumber(cer), 0); X509_set_issuer_name(cer, req->req_info->subject); X509_set_subject_name(cer, req->req_info->subject); X509_gmtime_adj(X509_get_notBefore(cer),0); X509_gmtime_adj(X509_get_notAfter(cer),(long)60*60*24*SIGN_DAYS); req_pkey = X509_REQ_get_pubkey(req); X509_set_pubkey(cer, req_pkey); EVP_PKEY_free(req_pkey); /* Sign the cert */ if (!X509_sign(cer, pk, EVP_md5())) { syslog(LOG_CRIT, "X509_sign(): error\n"); } else { /* Write it to disk. */ fp = fopen(file_crpt_file_cer, "w"); if (fp != NULL) { chmod(file_crpt_file_cer, 0600); PEM_write_X509(fp, cer); fclose(fp); } } X509_free(cer); } } RSA_free(rsa); } } /* * Now try to bind to the key and certificate. */ SSL_CTX_use_certificate_chain_file(ssl_ctx, file_crpt_file_cer); SSL_CTX_use_PrivateKey_file(ssl_ctx, file_crpt_file_key, SSL_FILETYPE_PEM); if ( !SSL_CTX_check_private_key(ssl_ctx) ) { syslog(LOG_CRIT, "Cannot install certificate: %s\n", ERR_reason_error_string(ERR_get_error())); } /* Finally let the server know we're here */ CtdlRegisterProtoHook(cmd_stls, "STLS", "Start SSL/TLS session"); CtdlRegisterProtoHook(cmd_gtls, "GTLS", "Get SSL/TLS session status"); CtdlRegisterSessionHook(endtls, EVT_STOP, PRIO_STOP + 10); } /* * client_write_ssl() Send binary data to the client encrypted. */ void client_write_ssl(const char *buf, int nbytes) { int retval; int nremain; char junk[1]; nremain = nbytes; while (nremain > 0) { if (SSL_want_write(CC->ssl)) { if ((SSL_read(CC->ssl, junk, 0)) < 1) { syslog(LOG_DEBUG, "SSL_read in client_write: %s\n", ERR_reason_error_string(ERR_get_error())); } } retval = SSL_write(CC->ssl, &buf[nbytes - nremain], nremain); if (retval < 1) { long errval; errval = SSL_get_error(CC->ssl, retval); if (errval == SSL_ERROR_WANT_READ || errval == SSL_ERROR_WANT_WRITE) { sleep(1); continue; } syslog(LOG_DEBUG, "SSL_write got error %ld, ret %d\n", errval, retval); if (retval == -1) syslog(LOG_DEBUG, "errno is %d\n", errno); endtls(); client_write(&buf[nbytes - nremain], nremain); return; } nremain -= retval; } } /* * read data from the encrypted layer. */ int client_read_sslbuffer(StrBuf *buf, int timeout) { char sbuf[16384]; /* OpenSSL communicates in 16k blocks, so let's speak its native tongue. */ int rlen; char junk[1]; SSL *pssl = CC->ssl; if (pssl == NULL) return(-1); while (1) { if (SSL_want_read(pssl)) { if ((SSL_write(pssl, junk, 0)) < 1) { syslog(LOG_DEBUG, "SSL_write in client_read\n"); } } rlen = SSL_read(pssl, sbuf, sizeof(sbuf)); if (rlen < 1) { long errval; errval = SSL_get_error(pssl, rlen); if (errval == SSL_ERROR_WANT_READ || errval == SSL_ERROR_WANT_WRITE) { sleep(1); continue; } syslog(LOG_DEBUG, "SSL_read got error %ld\n", errval); endtls(); return (-1); } StrBufAppendBufPlain(buf, sbuf, rlen, 0); return rlen; } return (0); } int client_readline_sslbuffer(StrBuf *Line, StrBuf *IOBuf, const char **Pos, int timeout) { CitContext *CCC = CC; const char *pos = NULL; const char *pLF; int len, rlen; int nSuccessLess = 0; const char *pch = NULL; if ((Line == NULL) || (Pos == NULL) || (IOBuf == NULL)) { if (Pos != NULL) *Pos = NULL; // *Error = ErrRBLF_PreConditionFailed; return -1; } pos = *Pos; if ((StrLength(IOBuf) > 0) && (pos != NULL) && (pos < ChrPtr(IOBuf) + StrLength(IOBuf))) { pch = pos; pch = strchr(pch, '\n'); if (pch == NULL) { StrBufAppendBufPlain(Line, pos, StrLength(IOBuf) - (pos - ChrPtr(IOBuf)), 0); FlushStrBuf(IOBuf); *Pos = NULL; } else { int n = 0; if ((pch > ChrPtr(IOBuf)) && (*(pch - 1) == '\r')) { n = 1; } StrBufAppendBufPlain(Line, pos, (pch - pos - n), 0); if (StrLength(IOBuf) <= (pch - ChrPtr(IOBuf) + 1)) { FlushStrBuf(IOBuf); *Pos = NULL; } else *Pos = pch + 1; return StrLength(Line); } } pLF = NULL; while ((nSuccessLess < timeout) && (pLF == NULL) && (CCC->ssl != NULL)) { rlen = client_read_sslbuffer(IOBuf, timeout); if (rlen < 1) { // *Error = strerror(errno); // close(*fd); // *fd = -1; return -1; } else if (rlen > 0) { pLF = strchr(ChrPtr(IOBuf), '\n'); } } *Pos = NULL; if (pLF != NULL) { pos = ChrPtr(IOBuf); len = pLF - pos; if (len > 0 && (*(pLF - 1) == '\r') ) len --; StrBufAppendBufPlain(Line, pos, len, 0); if (pLF + 1 >= ChrPtr(IOBuf) + StrLength(IOBuf)) { FlushStrBuf(IOBuf); } else *Pos = pLF + 1; return StrLength(Line); } // *Error = ErrRBLF_NotEnoughSentFromServer; return -1; } int client_read_sslblob(StrBuf *Target, long bytes, int timeout) { long baselen; long RemainRead; int retval = 0; CitContext *CCC = CC; baselen = StrLength(Target); if (StrLength(CCC->RecvBuf.Buf) > 0) { long RemainLen; long TotalLen; const char *pchs; if (CCC->RecvBuf.ReadWritePointer == NULL) CCC->RecvBuf.ReadWritePointer = ChrPtr(CCC->RecvBuf.Buf); pchs = ChrPtr(CCC->RecvBuf.Buf); TotalLen = StrLength(CCC->RecvBuf.Buf); RemainLen = TotalLen - (pchs - CCC->RecvBuf.ReadWritePointer); if (RemainLen > bytes) RemainLen = bytes; if (RemainLen > 0) { StrBufAppendBufPlain(Target, CCC->RecvBuf.ReadWritePointer, RemainLen, 0); CCC->RecvBuf.ReadWritePointer += RemainLen; } if ((ChrPtr(CCC->RecvBuf.Buf) + StrLength(CCC->RecvBuf.Buf)) <= CCC->RecvBuf.ReadWritePointer) { CCC->RecvBuf.ReadWritePointer = NULL; FlushStrBuf(CCC->RecvBuf.Buf); } } if (StrLength(Target) >= bytes + baselen) return 1; CCC->RecvBuf.ReadWritePointer = NULL; while ((StrLength(Target) < bytes + baselen) && (retval >= 0)) { retval = client_read_sslbuffer(CCC->RecvBuf.Buf, timeout); if (retval >= 0) { RemainRead = bytes - (StrLength (Target) - baselen); if (RemainRead < StrLength(CCC->RecvBuf.Buf)) { StrBufAppendBufPlain( Target, ChrPtr(CCC->RecvBuf.Buf), RemainRead, 0); CCC->RecvBuf.ReadWritePointer = ChrPtr(CCC->RecvBuf.Buf) + RemainRead; break; } StrBufAppendBuf(Target, CCC->RecvBuf.Buf, 0); /* todo: Buf > bytes? */ FlushStrBuf(CCC->RecvBuf.Buf); } else { FlushStrBuf(CCC->RecvBuf.Buf); return -1; } } return 1; } /* * CtdlStartTLS() starts SSL/TLS encryption for the current session. It * must be supplied with pre-generated strings for responses of "ok," "no * support for TLS," and "error" so that we can use this in any protocol. */ void CtdlStartTLS(char *ok_response, char *nosup_response, char *error_response) { int retval, bits, alg_bits; if (!ssl_ctx) { syslog(LOG_CRIT, "SSL failed: no ssl_ctx exists?\n"); if (nosup_response != NULL) cprintf("%s", nosup_response); return; } if (!(CC->ssl = SSL_new(ssl_ctx))) { syslog(LOG_CRIT, "SSL_new failed: %s\n", ERR_reason_error_string(ERR_get_error())); if (error_response != NULL) cprintf("%s", error_response); return; } if (!(SSL_set_fd(CC->ssl, CC->client_socket))) { syslog(LOG_CRIT, "SSL_set_fd failed: %s\n", ERR_reason_error_string(ERR_get_error())); SSL_free(CC->ssl); CC->ssl = NULL; if (error_response != NULL) cprintf("%s", error_response); return; } if (ok_response != NULL) cprintf("%s", ok_response); retval = SSL_accept(CC->ssl); if (retval < 1) { /* * Can't notify the client of an error here; they will * discover the problem at the SSL layer and should * revert to unencrypted communications. */ long errval; char error_string[128]; errval = SSL_get_error(CC->ssl, retval); syslog(LOG_CRIT, "SSL_accept failed: retval=%d, errval=%ld, err=%s\n", retval, errval, ERR_error_string(errval, error_string) ); SSL_free(CC->ssl); CC->ssl = NULL; return; } BIO_set_close(CC->ssl->rbio, BIO_NOCLOSE); bits = SSL_CIPHER_get_bits(SSL_get_current_cipher(CC->ssl), &alg_bits); syslog(LOG_INFO, "SSL/TLS using %s on %s (%d of %d bits)\n", SSL_CIPHER_get_name(SSL_get_current_cipher(CC->ssl)), SSL_CIPHER_get_version(SSL_get_current_cipher(CC->ssl)), bits, alg_bits); CC->redirect_ssl = 1; } /* * cmd_stls() starts SSL/TLS encryption for the current session */ void cmd_stls(char *params) { char ok_response[SIZ]; char nosup_response[SIZ]; char error_response[SIZ]; unbuffer_output(); sprintf(ok_response, "%d Begin TLS negotiation now\n", CIT_OK); sprintf(nosup_response, "%d TLS not supported here\n", ERROR + CMD_NOT_SUPPORTED); sprintf(error_response, "%d TLS negotiation error\n", ERROR + INTERNAL_ERROR); CtdlStartTLS(ok_response, nosup_response, error_response); } /* * cmd_gtls() returns status info about the TLS connection */ void cmd_gtls(char *params) { int bits, alg_bits; if (!CC->ssl || !CC->redirect_ssl) { cprintf("%d Session is not encrypted.\n", ERROR); return; } bits = SSL_CIPHER_get_bits(SSL_get_current_cipher(CC->ssl), &alg_bits); cprintf("%d %s|%s|%d|%d\n", CIT_OK, SSL_CIPHER_get_version(SSL_get_current_cipher(CC->ssl)), SSL_CIPHER_get_name(SSL_get_current_cipher(CC->ssl)), alg_bits, bits); } /* * endtls() shuts down the TLS connection * * WARNING: This may make your session vulnerable to a known plaintext * attack in the current implmentation. */ void endtls(void) { if (!CC->ssl) { CC->redirect_ssl = 0; return; } syslog(LOG_INFO, "Ending SSL/TLS\n"); SSL_shutdown(CC->ssl); SSL_free(CC->ssl); CC->ssl = NULL; CC->redirect_ssl = 0; } /* * ssl_lock() callback for OpenSSL mutex locks */ void ssl_lock(int mode, int n, const char *file, int line) { if (mode & CRYPTO_LOCK) pthread_mutex_lock(SSLCritters[n]); else pthread_mutex_unlock(SSLCritters[n]); } #endif /* HAVE_OPENSSL */ citadel-9.01/modules/mrtg/0000755000000000000000000000000012507024051014163 5ustar rootrootcitadel-9.01/modules/mrtg/serv_mrtg.c0000644000000000000000000000737312507024051016351 0ustar rootroot/* * This module supplies statistics about the activity levels of your Citadel * system. We didn't bother writing a reporting module, because there is * already an excellent tool called MRTG (Multi Router Traffic Grapher) which * is available at http://www.mrtg.org that can fetch data using external * scripts. This module supplies data in the format expected by MRTG. * * Copyright (c) 1987-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * */ #include "sysdep.h" #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "control.h" #include "user_ops.h" #include "database.h" #include "msgbase.h" #include "ctdl_module.h" /* * Other functions call this one to output data in MRTG format */ void mrtg_output(long value1, long value2) { time_t uptime_t; int uptime_days, uptime_hours, uptime_minutes; uptime_t = time(NULL) - server_startup_time; uptime_days = (int) (uptime_t / 86400L); uptime_hours = (int) ((uptime_t % 86400L) / 3600L); uptime_minutes = (int) ((uptime_t % 3600L) / 60L); cprintf("%d ok\n", LISTING_FOLLOWS); cprintf("%ld\n", value1); cprintf("%ld\n", value2); cprintf("%d days, %d hours, %d minutes\n", uptime_days, uptime_hours, uptime_minutes); cprintf("%s\n", config.c_humannode); cprintf("000\n"); } /* * Tell us how many users are online */ void mrtg_users(void) { long connected_users = 0; long active_users = 0; struct CitContext *cptr; begin_critical_section(S_SESSION_TABLE); for (cptr = ContextList; cptr != NULL; cptr = cptr->next) { if (cptr->internal_pgm == 0) { ++connected_users; if ( (time(NULL) - (cptr->lastidle)) < 900L) { ++active_users; } } } end_critical_section(S_SESSION_TABLE); mrtg_output(connected_users, active_users); } /* * Volume of messages submitted */ void mrtg_messages(void) { mrtg_output(CitControl.MMhighest, 0L); } struct num_accounts { long total; long active; }; /* * Helper function for mrtg_accounts() */ void tally_account(struct ctdluser *EachUser, void *userdata) { struct num_accounts *n = (struct num_accounts *) userdata; ++n->total; if ( (time(NULL) - EachUser->lastcall) <= 2592000 ) ++n->active; } /* * Number of accounts and active accounts */ void mrtg_accounts(void) { struct num_accounts n = { 0, 0 }; ForEachUser(tally_account, (void *)&n ); mrtg_output(n.total, n.active); } /* * Fetch data for MRTG */ void cmd_mrtg(char *argbuf) { char which[32]; extract_token(which, argbuf, 0, '|', sizeof which); if (!strcasecmp(which, "users")) { mrtg_users(); } else if (!strcasecmp(which, "messages")) { mrtg_messages(); } else if (!strcasecmp(which, "accounts")) { mrtg_accounts(); } else { cprintf("%d Unrecognized keyword '%s'\n", ERROR + ILLEGAL_VALUE, which); } } CTDL_MODULE_INIT(mrtg) { if (!threading) { CtdlRegisterProtoHook(cmd_mrtg, "MRTG", "Supply stats to MRTG"); } /* return our module name for the log */ return "mrtg"; } citadel-9.01/modules/bio/0000755000000000000000000000000012507024051013763 5ustar rootrootcitadel-9.01/modules/bio/serv_bio.c0000644000000000000000000001051312507024051015737 0ustar rootroot/* * This module implementsserver commands related to the display and * manipulation of user "bio" files. * * Copyright (c) 1987-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "ctdl_module.h" #include #include #include /* * enter user bio */ void cmd_ebio(char *cmdbuf) { char buf[SIZ]; FILE *fp; unbuffer_output(); if (!(CC->logged_in)) { cprintf("%d Not logged in.\n",ERROR + NOT_LOGGED_IN); return; } snprintf(buf, sizeof buf, "%s%ld",ctdl_bio_dir,CC->user.usernum); fp = fopen(buf,"w"); if (fp == NULL) { cprintf("%d Cannot create file: %s\n", ERROR + INTERNAL_ERROR, strerror(errno)); return; } cprintf("%d \n",SEND_LISTING); while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) { if (ftell(fp) < config.c_maxmsglen) { fprintf(fp,"%s\n",buf); } } fclose(fp); } /* * read user bio */ void cmd_rbio(char *cmdbuf) { struct ctdluser ruser; char buf[256]; FILE *fp; extract_token(buf, cmdbuf, 0, '|', sizeof buf); if (CtdlGetUser(&ruser, buf) != 0) { cprintf("%d No such user.\n",ERROR + NO_SUCH_USER); return; } snprintf(buf, sizeof buf, "%s%ld",ctdl_bio_dir,ruser.usernum); cprintf("%d OK|%s|%ld|%d|%ld|%ld|%ld\n", LISTING_FOLLOWS, ruser.fullname, ruser.usernum, ruser.axlevel, (long)ruser.lastcall, ruser.timescalled, ruser.posted); fp = fopen(buf,"r"); if (fp == NULL) cprintf("%s has no bio on file.\n", ruser.fullname); else { while (fgets(buf, sizeof buf, fp) != NULL) cprintf("%s",buf); fclose(fp); } cprintf("000\n"); } /* * list of users who have entered bios */ void cmd_lbio(char *cmdbuf) { DIR *filedir = NULL; struct dirent *filedir_entry; struct dirent *d; int dont_resolve_uids; size_t d_namelen; struct ctdluser usbuf; int d_type = 0; d = (struct dirent *)malloc(offsetof(struct dirent, d_name) + PATH_MAX + 2); if (d == NULL) { cprintf("%d Cannot open listing.\n", ERROR + FILE_NOT_FOUND); return; } filedir = opendir (ctdl_bio_dir); if (filedir == NULL) { free(d); cprintf("%d Cannot open listing.\n", ERROR + FILE_NOT_FOUND); return; } dont_resolve_uids = *cmdbuf == '1'; cprintf("%d\n", LISTING_FOLLOWS); while ((readdir_r(filedir, d, &filedir_entry) == 0) && (filedir_entry != NULL)) { #ifdef _DIRENT_HAVE_D_NAMLEN d_namelen = filedir_entry->d_namlen; #else d_namelen = strlen(filedir_entry->d_name); #endif #ifdef _DIRENT_HAVE_D_TYPE d_type = filedir_entry->d_type; #else #ifndef DT_UNKNOWN #define DT_UNKNOWN 0 #define DT_DIR 4 #define DT_REG 8 #define DT_LNK 10 #define IFTODT(mode) (((mode) & 0170000) >> 12) #define DTTOIF(dirtype) ((dirtype) << 12) #endif d_type = DT_UNKNOWN; #endif if ((d_namelen == 1) && (filedir_entry->d_name[0] == '.')) continue; if ((d_namelen == 2) && (filedir_entry->d_name[0] == '.') && (filedir_entry->d_name[1] == '.')) continue; if (d_type == DT_UNKNOWN) { struct stat s; char path[PATH_MAX]; snprintf(path, PATH_MAX, "%s/%s", ctdl_bio_dir, filedir_entry->d_name); if (lstat(path, &s) == 0) { d_type = IFTODT(s.st_mode); } } switch (d_type) { case DT_DIR: break; case DT_LNK: case DT_REG: if (dont_resolve_uids) { filedir_entry->d_name[d_namelen++] = '\n'; filedir_entry->d_name[d_namelen] = '\0'; client_write(filedir_entry->d_name, d_namelen); } else if (CtdlGetUserByNumber(&usbuf,atol(filedir_entry->d_name))==0) cprintf("%s\n", usbuf.fullname); } } free(d); closedir(filedir); cprintf("000\n"); } CTDL_MODULE_INIT(bio) { if (!threading) { CtdlRegisterProtoHook(cmd_ebio, "EBIO", "Enter your bio"); CtdlRegisterProtoHook(cmd_rbio, "RBIO", "Read a user's bio"); CtdlRegisterProtoHook(cmd_lbio, "LBIO", "List users with bios"); } /* return our module name for the log */ return "bio"; } citadel-9.01/modules/notes/0000755000000000000000000000000012507024051014342 5ustar rootrootcitadel-9.01/modules/notes/serv_notes.c0000644000000000000000000001031212507024051016672 0ustar rootroot/* * Handles functions related to yellow sticky notes. * * Copyright (c) 2007-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * */ #include "sysdep.h" #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "user_ops.h" #include "database.h" #include "msgbase.h" #include "ctdl_module.h" /* * Callback function for serv_notes_beforesave() hunts for a vNote in the MIME structure */ void notes_extract_vnote(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata) { struct vnote **v = (struct vnote **) cbuserdata; if (!strcasecmp(cbtype, "text/vnote")) { syslog(LOG_DEBUG, "Part %s contains a vNote! Loading...\n", partnum); if (*v != NULL) { vnote_free(*v); } *v = vnote_new_from_str(content); } } /* * Before-save hook searches for two different types of notes (legacy Kolab/Aethera notes * and modern vNote format notes) and does its best to learn the subject (summary) * and EUID (uid) of the note for Citadel's own nefarious purposes. */ int serv_notes_beforesave(struct CtdlMessage *msg, recptypes *recp) { char *p; int a, i; char uuid[512]; struct vnote *v = NULL; /* First determine if this room has the "notes" view set */ if (CC->room.QRdefaultview != VIEW_NOTES) { return(0); /* not notes; do nothing */ } /* It must be an RFC822 message! */ if (msg->cm_format_type != 4) { return(0); /* You tried to save a non-RFC822 message! */ } /* * If we are in a "notes" view room, and the client has sent an RFC822 * message containing an X-KOrg-Note-Id: field (Aethera does this, as * do some Kolab clients) then set both the Subject and the Exclusive ID * of the message to that. It's going to be a UUID so we want to replace * any existing message containing that UUID. */ strcpy(uuid, ""); p = msg->cm_fields[eMesageText]; a = msg->cm_lengths[eMesageText]; while (--a > 0) { if (!strncasecmp(p, "X-KOrg-Note-Id: ", 16)) { /* Found it */ safestrncpy(uuid, p + 16, sizeof(uuid)); for (i = 0; uuid[i]; ++i) { if ( (uuid[i] == '\r') || (uuid[i] == '\n') ) { uuid[i] = 0; break; } } syslog(LOG_DEBUG, "UUID of note is: %s\n", uuid); if (!IsEmptyStr(uuid)) { CM_SetField(msg, eExclusiveID, uuid, strlen(uuid)); CM_CopyField(msg, eMsgSubject, eExclusiveID); } } p++; } /* Modern clients are using vNote format. Check for one... */ mime_parser(CM_RANGE(msg, eMesageText), *notes_extract_vnote, NULL, NULL, &v, /* user data ptr - put the vnote here */ 0 ); if (v == NULL) return(0); /* no vNotes were found in this message */ /* Set the message EUID to the vNote UID */ if ((v->uid) && (!IsEmptyStr(v->uid))) { syslog(LOG_DEBUG, "UID of vNote is: %s\n", v->uid); CM_SetField(msg, eExclusiveID, v->uid, strlen(v->uid)); } /* Set the message Subject to the vNote Summary */ if ((v->summary) && (!IsEmptyStr(v->summary))) { CM_SetField(msg, eMsgSubject, v->summary, strlen(v->summary)); if (msg->cm_lengths[eMsgSubject] > 72) { strcpy(&msg->cm_fields[eMsgSubject][68], "..."); CM_CutFieldAt(msg, eMsgSubject, 72); } } vnote_free(v); return(0); } CTDL_MODULE_INIT(notes) { if (!threading) { CtdlRegisterMessageHook(serv_notes_beforesave, EVT_BEFORESAVE); } /* return our module name for the log */ return "notes"; } citadel-9.01/modules/migrate/0000755000000000000000000000000012507024051014642 5ustar rootrootcitadel-9.01/modules/migrate/serv_migrate.c0000644000000000000000000012111012507024051017471 0ustar rootroot/* * This module dumps and/or loads the Citadel database in XML format. * * Copyright (c) 1987-2014 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ /* * Explanation of tags: * * 0% nothing * 1% finished exporting config * 2% finished exporting control * 7% finished exporting users * 12% finished exporting openids * 17% finished exporting rooms * 18% finished exporting floors * 25% finished exporting visits * 100% finished exporting messages */ #include "sysdep.h" #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "database.h" #include "msgbase.h" #include "user_ops.h" #include "control.h" #include "euidindex.h" #include "ctdl_module.h" #define END_OF_MESSAGE "---eom---dbd---" char migr_tempfilename1[PATH_MAX]; char migr_tempfilename2[PATH_MAX]; FILE *migr_global_message_list; int total_msgs = 0; /* * Code which implements the export appears in this section */ /* * Output a string to the client with these characters escaped: & < > */ void xml_strout(char *str) { char *c = str; if (str == NULL) { return; } while (*c != 0) { if (*c == '\"') { client_write(""", 6); } else if (*c == '\'') { client_write("'", 6); } else if (*c == '<') { client_write("<", 4); } else if (*c == '>') { client_write(">", 4); } else if (*c == '&') { client_write("&", 5); } else { client_write(c, 1); } ++c; } } /* * Export a user record as XML */ void migr_export_users_backend(struct ctdluser *buf, void *data) { client_write("\n", 7); cprintf("%d\n", buf->version); cprintf("%ld\n", (long)buf->uid); client_write("", 12); xml_strout(buf->password); client_write("\n", 14); cprintf("%u\n", buf->flags); cprintf("%ld\n", buf->timescalled); cprintf("%ld\n", buf->posted); cprintf("%d\n", buf->axlevel); cprintf("%ld\n", buf->usernum); cprintf("%ld\n", (long)buf->lastcall); cprintf("%d\n", buf->USuserpurge); client_write("", 12); xml_strout(buf->fullname); client_write("\n", 14); client_write("\n", 8); } void migr_export_users(void) { ForEachUser(migr_export_users_backend, NULL); } void migr_export_room_msg(long msgnum, void *userdata) { cprintf("%ld\n", msgnum); fprintf(migr_global_message_list, "%ld\n", msgnum); } void migr_export_rooms_backend(struct ctdlroom *buf, void *data) { client_write("\n", 7); client_write("", 8); xml_strout(buf->QRname); client_write("\n", 10); client_write("", 10); xml_strout(buf->QRpasswd); client_write("\n", 12); cprintf("%ld\n", buf->QRroomaide); cprintf("%ld\n", buf->QRhighest); cprintf("%ld\n", (long)buf->QRgen); cprintf("%u\n", buf->QRflags); if (buf->QRflags & QR_DIRECTORY) { client_write("", 11); xml_strout(buf->QRdirname); client_write("\n", 13); } cprintf("%ld\n", buf->QRinfo); cprintf("%d\n", buf->QRfloor); cprintf("%ld\n", (long)buf->QRmtime); cprintf("%d\n", buf->QRep.expire_mode); cprintf("%d\n", buf->QRep.expire_value); cprintf("%ld\n", buf->QRnumber); cprintf("%d\n", buf->QRorder); cprintf("%u\n", buf->QRflags2); cprintf("%d\n", buf->QRdefaultview); client_write("\n", 8); /* message list goes inside this tag */ CtdlGetRoom(&CC->room, buf->QRname); client_write("", 15); client_write("", 8); xml_strout(CC->room.QRname); client_write("\n", 10); client_write("", 11); CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, migr_export_room_msg, NULL); client_write("\n", 13); client_write("\n", 17); } void migr_export_rooms(void) { char cmd[SIZ]; migr_global_message_list = fopen(migr_tempfilename1, "w"); if (migr_global_message_list != NULL) { CtdlForEachRoom(migr_export_rooms_backend, NULL); fclose(migr_global_message_list); } /* * Process the 'global' message list. (Sort it and remove dups. * Dups are ok because a message may be in more than one room, but * this will be handled by exporting the reference count, not by * exporting the message multiple times.) */ snprintf(cmd, sizeof cmd, "sort -n <%s >%s", migr_tempfilename1, migr_tempfilename2); if (system(cmd) != 0) syslog(LOG_ALERT, "Error %d\n", errno); snprintf(cmd, sizeof cmd, "uniq <%s >%s", migr_tempfilename2, migr_tempfilename1); if (system(cmd) != 0) syslog(LOG_ALERT, "Error %d\n", errno); snprintf(cmd, sizeof cmd, "wc -l %s", migr_tempfilename1); FILE *fp = popen(cmd, "r"); if (fp) { fgets(cmd, sizeof cmd, fp); pclose(fp); total_msgs = atoi(cmd); } else { total_msgs = 1; // any nonzero just to keep it from barfing } syslog(LOG_DEBUG, "Total messages to be exported: %d", total_msgs); } void migr_export_floors(void) { struct floor qfbuf, *buf; int i; for (i=0; i < MAXFLOORS; ++i) { client_write("\n", 8); cprintf("%d\n", i); CtdlGetFloor(&qfbuf, i); buf = &qfbuf; cprintf("%u\n", buf->f_flags); client_write("", 8); xml_strout(buf->f_name); client_write("\n", 10); cprintf("%d\n", buf->f_ref_count); cprintf("%d\n", buf->f_ep.expire_mode); cprintf("%d\n", buf->f_ep.expire_value); client_write("\n", 9); } } /* * Return nonzero if the supplied string contains only characters which are valid in a sequence set. */ int is_sequence_set(char *s) { if (!s) return(0); char *c = s; char ch; while (ch = *c++, ch) { if (!strchr("0123456789*,:", ch)) { return(0); } } return(1); } /* * Traverse the visits file... */ void migr_export_visits(void) { visit vbuf; struct cdbdata *cdbv; cdb_rewind(CDB_VISIT); while (cdbv = cdb_next_item(CDB_VISIT), cdbv != NULL) { memset(&vbuf, 0, sizeof(visit)); memcpy(&vbuf, cdbv->ptr, ((cdbv->len > sizeof(visit)) ? sizeof(visit) : cdbv->len)); cdb_free(cdbv); client_write("\n", 8); cprintf("%ld\n", vbuf.v_roomnum); cprintf("%ld\n", vbuf.v_roomgen); cprintf("%ld\n", vbuf.v_usernum); client_write("", 8); if ( (!IsEmptyStr(vbuf.v_seen)) && (is_sequence_set(vbuf.v_seen)) ) { xml_strout(vbuf.v_seen); } else { cprintf("%ld", vbuf.v_lastseen); } client_write("", 9); if ( (!IsEmptyStr(vbuf.v_answered)) && (is_sequence_set(vbuf.v_answered)) ) { client_write("", 12); xml_strout(vbuf.v_answered); client_write("\n", 14); } cprintf("%u\n", vbuf.v_flags); cprintf("%d\n", vbuf.v_view); client_write("\n", 9); } } void migr_export_message(long msgnum) { struct MetaData smi; struct CtdlMessage *msg; struct ser_ret smr; /* We can use a static buffer here because there will never be more than * one of this operation happening at any given time, and it's really best * to just keep it allocated once instead of torturing malloc/free. * Call this function with msgnum "-1" to free the buffer when finished. */ static int encoded_alloc = 0; static char *encoded_msg = NULL; if (msgnum < 0) { if ((encoded_alloc == 0) && (encoded_msg != NULL)) { free(encoded_msg); encoded_alloc = 0; encoded_msg = NULL; } return; } /* Ok, here we go ... */ msg = CtdlFetchMessage(msgnum, 1); if (msg == NULL) return; /* fail silently */ client_write("\n", 10); GetMetaData(&smi, msgnum); cprintf("%ld\n", msgnum); cprintf("%d\n", smi.meta_refcount); client_write("", 23); xml_strout(smi.meta_content_type); client_write("\n", 25); client_write("", 10); CtdlSerializeMessage(&smr, msg); CM_Free(msg); /* Predict the buffer size we need. Expand the buffer if necessary. */ int encoded_len = smr.len * 15 / 10 ; if (encoded_len > encoded_alloc) { encoded_alloc = encoded_len; encoded_msg = realloc(encoded_msg, encoded_alloc); } if (encoded_msg == NULL) { /* Questionable hack that hopes it'll work next time and we only lose one message */ encoded_alloc = 0; } else { /* Once we do the encoding we know the exact size */ encoded_len = CtdlEncodeBase64(encoded_msg, (char *)smr.ser, smr.len, 1); client_write(encoded_msg, encoded_len); } free(smr.ser); client_write("\n", 12); client_write("\n", 11); } void migr_export_openids(void) { struct cdbdata *cdboi; long usernum; char url[512]; cdb_rewind(CDB_OPENID); while (cdboi = cdb_next_item(CDB_OPENID), cdboi != NULL) { if (cdboi->len > sizeof(long)) { client_write("\n", 9); memcpy(&usernum, cdboi->ptr, sizeof(long)); snprintf(url, sizeof url, "%s", (cdboi->ptr)+sizeof(long) ); client_write("", 9); xml_strout(url); client_write("\n", 11); cprintf("%ld\n", usernum); client_write("\n", 10); } cdb_free(cdboi); } } void migr_export_messages(void) { char buf[SIZ]; long msgnum; int count = 0; int progress = 0; int prev_progress = 0; CitContext *Ctx; Ctx = CC; migr_global_message_list = fopen(migr_tempfilename1, "r"); if (migr_global_message_list != NULL) { syslog(LOG_INFO, "Opened %s\n", migr_tempfilename1); while ((Ctx->kill_me == 0) && (fgets(buf, sizeof(buf), migr_global_message_list) != NULL)) { msgnum = atol(buf); if (msgnum > 0L) { migr_export_message(msgnum); ++count; } progress = (count * 74 / total_msgs) + 25 ; if ((progress > prev_progress) && (progress < 100)) { cprintf("%d\n", progress); } prev_progress = progress; } fclose(migr_global_message_list); } if (Ctx->kill_me == 0) syslog(LOG_INFO, "Exported %d messages.\n", count); else syslog(LOG_ERR, "Export aborted due to client disconnect! \n"); migr_export_message(-1L); /* This frees the encoding buffer */ } void migr_do_export(void) { CitContext *Ctx; Ctx = CC; cprintf("%d Exporting all Citadel databases.\n", LISTING_FOLLOWS); Ctx->dont_term = 1; client_write("\n", 40); client_write("\n", 23); cprintf("%d\n", REV_LEVEL); cprintf("%d\n", 0); /* export the config file (this is done using x-macros) */ client_write("\n", 9); client_write("", 12); xml_strout(config.c_nodename); client_write("\n", 14); client_write("", 8); xml_strout(config.c_fqdn); client_write("\n", 10); client_write("", 13); xml_strout(config.c_humannode); client_write("\n", 15); client_write("", 12); xml_strout(config.c_phonenum); client_write("\n", 14); cprintf("%d\n", config.c_ctdluid); cprintf("%d\n", config.c_creataide); cprintf("%d\n", config.c_sleeping); cprintf("%d\n", config.c_initax); cprintf("%d\n", config.c_regiscall); cprintf("%d\n", config.c_twitdetect); client_write("", 12); xml_strout(config.c_twitroom); client_write("\n", 14); client_write("", 14); xml_strout(config.c_moreprompt); client_write("\n", 16); cprintf("%d\n", config.c_restrict); client_write("", 17); xml_strout(config.c_site_location); client_write("\n", 19); client_write("", 10); xml_strout(config.c_sysadm); client_write("\n", 12); cprintf("%d\n", config.c_maxsessions); client_write("", 11); xml_strout(config.c_ip_addr); client_write("\n", 13); cprintf("%d\n", config.c_port_number); cprintf("%d\n", config.c_ep.expire_mode); cprintf("%d\n", config.c_ep.expire_value); cprintf("%d\n", config.c_userpurge); cprintf("%d\n", config.c_roompurge); client_write("", 12); xml_strout(config.c_logpages); client_write("\n", 14); cprintf("%d\n", config.c_createax); cprintf("%ld\n", config.c_maxmsglen); cprintf("%d\n", config.c_min_workers); cprintf("%d\n", config.c_max_workers); cprintf("%d\n", config.c_pop3_port); cprintf("%d\n", config.c_smtp_port); cprintf("%d\n", config.c_rfc822_strict_from); cprintf("%d\n", config.c_aide_zap); cprintf("%d\n", config.c_imap_port); cprintf("%ld\n", config.c_net_freq); cprintf("%d\n", config.c_disable_newu); cprintf("%d\n", config.c_enable_fulltext); client_write("", 12); xml_strout(config.c_baseroom); client_write("\n", 14); client_write("", 12); xml_strout(config.c_aideroom); client_write("\n", 14); cprintf("%d\n", config.c_purge_hour); cprintf("%d\n", config.c_mbxep.expire_mode); cprintf("%d\n", config.c_mbxep.expire_value); client_write("", 13); xml_strout(config.c_ldap_host); client_write("\n", 15); cprintf("%d\n", config.c_ldap_port); client_write("", 16); xml_strout(config.c_ldap_base_dn); client_write("\n", 18); client_write("", 16); xml_strout(config.c_ldap_bind_dn); client_write("\n", 18); client_write("", 16); xml_strout(config.c_ldap_bind_pw); client_write("\n", 18); cprintf("%d\n", config.c_msa_port); cprintf("%d\n", config.c_imaps_port); cprintf("%d\n", config.c_pop3s_port); cprintf("%d\n", config.c_smtps_port); cprintf("%d\n", config.c_auto_cull); cprintf("%d\n", config.c_allow_spoofing); cprintf("%d\n", config.c_journal_email); cprintf("%d\n", config.c_journal_pubmsgs); client_write("", 16); xml_strout(config.c_journal_dest); client_write("\n", 18); client_write("", 20); xml_strout(config.c_default_cal_zone); client_write("\n", 22); cprintf("%d\n", config.c_pftcpdict_port); cprintf("%d\n", config.c_managesieve_port); cprintf("%d\n", config.c_auth_mode); client_write("", 17); xml_strout(config.c_funambol_host); client_write("\n", 19); cprintf("%d\n", config.c_funambol_port); client_write("", 19); xml_strout(config.c_funambol_source); client_write("\n", 21); client_write("", 17); xml_strout(config.c_funambol_auth); client_write("\n", 19); cprintf("%d\n", config.c_rbl_at_greeting); client_write("", 15); xml_strout(config.c_master_user); client_write("\n", 17); client_write("", 15); xml_strout(config.c_master_pass); client_write("\n", 17); client_write("", 17); xml_strout(config.c_pager_program); client_write("\n", 19); cprintf("%d\n", config.c_imap_keep_from); cprintf("%d\n", config.c_xmpp_c2s_port); cprintf("%d\n", config.c_xmpp_s2s_port); cprintf("%ld\n", config.c_pop3_fetch); cprintf("%ld\n", config.c_pop3_fastest); cprintf("%d\n", config.c_spam_flag_only); cprintf("%d\n", config.c_nntp_port); cprintf("%d\n", config.c_nntps_port); client_write("\n", 10); cprintf("%d\n", 1); /* Export the control file */ get_control(); client_write("\n", 10); cprintf("%ld\n", CitControl.MMhighest); cprintf("%u\n", CitControl.MMflags); cprintf("%ld\n", CitControl.MMnextuser); cprintf("%ld\n", CitControl.MMnextroom); cprintf("%d\n", CitControl.version); client_write("\n", 11); cprintf("%d\n", 2); if (Ctx->kill_me == 0) migr_export_users(); cprintf("%d\n", 7); if (Ctx->kill_me == 0) migr_export_openids(); cprintf("%d\n", 12); if (Ctx->kill_me == 0) migr_export_rooms(); cprintf("%d\n", 17); if (Ctx->kill_me == 0) migr_export_floors(); cprintf("%d\n", 18); if (Ctx->kill_me == 0) migr_export_visits(); cprintf("%d\n", 25); if (Ctx->kill_me == 0) migr_export_messages(); client_write("\n", 24); cprintf("%d\n", 100); client_write("000\n", 4); Ctx->dont_term = 0; } /* * Here's the code that implements the import side. It's going to end up being * one big loop with lots of global variables. I don't care. You wouldn't run * multiple concurrent imports anyway. If this offends your delicate sensibilities * then go rewrite it in Ruby on Rails or something. */ int citadel_migrate_data = 0; /* Are we inside a tag pair? */ StrBuf *migr_chardata = NULL; StrBuf *migr_MsgData = NULL; struct ctdluser usbuf; struct ctdlroom qrbuf; char openid_url[512]; long openid_usernum = 0; char FRname[ROOMNAMELEN]; struct floor flbuf; int floornum = 0; visit vbuf; struct MetaData smi; long import_msgnum = 0; /* * This callback stores up the data which appears in between tags. */ void migr_xml_chardata(void *data, const XML_Char *s, int len) { StrBufAppendBufPlain(migr_chardata, s, len, 0); } void migr_xml_start(void *data, const char *el, const char **attr) { int i; /*** GENERAL STUFF ***/ /* Reset chardata_len to zero and init buffer */ FlushStrBuf(migr_chardata); FlushStrBuf(migr_MsgData); if (!strcasecmp(el, "citadel_migrate_data")) { ++citadel_migrate_data; /* As soon as it looks like the input data is a genuine Citadel XML export, * whack the existing database on disk to make room for the new one. */ if (citadel_migrate_data == 1) { for (i = 0; i < MAXCDB; ++i) { cdb_trunc(i); } } return; } if (citadel_migrate_data != 1) { syslog(LOG_ALERT, "Out-of-sequence tag <%s> detected. Warning: ODD-DATA!\n", el); return; } /* When we begin receiving XML for one of these record types, clear out the associated * buffer so we don't accidentally carry over any data from a previous record. */ if (!strcasecmp(el, "user")) memset(&usbuf, 0, sizeof (struct ctdluser)); else if (!strcasecmp(el, "openid")) memset(openid_url, 0, sizeof openid_url); else if (!strcasecmp(el, "room")) memset(&qrbuf, 0, sizeof (struct ctdlroom)); else if (!strcasecmp(el, "room_messages")) memset(FRname, 0, sizeof FRname); else if (!strcasecmp(el, "floor")) memset(&flbuf, 0, sizeof (struct floor)); else if (!strcasecmp(el, "visit")) memset(&vbuf, 0, sizeof (visit)); else if (!strcasecmp(el, "message")) { memset(&smi, 0, sizeof (struct MetaData)); import_msgnum = 0; } } int migr_config(void *data, const char *el) { if (!strcasecmp(el, "c_nodename")) SET_CFGSTRBUF(c_nodename, migr_chardata); else if (!strcasecmp(el, "c_fqdn")) SET_CFGSTRBUF(c_fqdn, migr_chardata); else if (!strcasecmp(el, "c_humannode")) SET_CFGSTRBUF(c_humannode, migr_chardata); else if (!strcasecmp(el, "c_phonenum")) SET_CFGSTRBUF(c_phonenum, migr_chardata); else if (!strcasecmp(el, "c_ctdluid")) config.c_ctdluid = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_creataide")) config.c_creataide = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_sleeping")) config.c_sleeping = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_initax")) config.c_initax = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_regiscall")) config.c_regiscall = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_twitdetect")) config.c_twitdetect = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_twitroom")) SET_CFGSTRBUF(c_twitroom, migr_chardata); else if (!strcasecmp(el, "c_moreprompt")) SET_CFGSTRBUF(c_moreprompt, migr_chardata); else if (!strcasecmp(el, "c_restrict")) config.c_restrict = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_site_location")) SET_CFGSTRBUF(c_site_location, migr_chardata); else if (!strcasecmp(el, "c_sysadm")) SET_CFGSTRBUF(c_sysadm, migr_chardata); else if (!strcasecmp(el, "c_maxsessions")) config.c_maxsessions = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_ip_addr")) SET_CFGSTRBUF(c_ip_addr, migr_chardata); else if (!strcasecmp(el, "c_port_number")) config.c_port_number = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_ep_expire_mode")) config.c_ep.expire_mode = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_ep_expire_value")) config.c_ep.expire_value = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_userpurge")) config.c_userpurge = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_roompurge")) config.c_roompurge = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_logpages")) SET_CFGSTRBUF(c_logpages, migr_chardata); else if (!strcasecmp(el, "c_createax")) config.c_createax = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_maxmsglen")) config.c_maxmsglen = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_min_workers")) config.c_min_workers = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_max_workers")) config.c_max_workers = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_pop3_port")) config.c_pop3_port = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_smtp_port")) config.c_smtp_port = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_rfc822_strict_from")) config.c_rfc822_strict_from = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_aide_zap")) config.c_aide_zap = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_imap_port")) config.c_imap_port = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_net_freq")) config.c_net_freq = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_disable_newu")) config.c_disable_newu = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_enable_fulltext")) config.c_enable_fulltext = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_baseroom")) SET_CFGSTRBUF(c_baseroom, migr_chardata); else if (!strcasecmp(el, "c_aideroom")) SET_CFGSTRBUF(c_aideroom, migr_chardata); else if (!strcasecmp(el, "c_purge_hour")) config.c_purge_hour = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_mbxep_expire_mode")) config.c_mbxep.expire_mode = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_mbxep_expire_value")) config.c_mbxep.expire_value = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_ldap_host")) SET_CFGSTRBUF(c_ldap_host, migr_chardata); else if (!strcasecmp(el, "c_ldap_port")) config.c_ldap_port = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_ldap_base_dn")) SET_CFGSTRBUF(c_ldap_base_dn, migr_chardata); else if (!strcasecmp(el, "c_ldap_bind_dn")) SET_CFGSTRBUF(c_ldap_bind_dn, migr_chardata); else if (!strcasecmp(el, "c_ldap_bind_pw")) SET_CFGSTRBUF(c_ldap_bind_pw, migr_chardata); else if (!strcasecmp(el, "c_msa_port")) config.c_msa_port = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_imaps_port")) config.c_imaps_port = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_pop3s_port")) config.c_pop3s_port = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_smtps_port")) config.c_smtps_port = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_auto_cull")) config.c_auto_cull = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_allow_spoofing")) config.c_allow_spoofing = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_journal_email")) config.c_journal_email = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_journal_pubmsgs")) config.c_journal_pubmsgs = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_journal_dest")) SET_CFGSTRBUF(c_journal_dest, migr_chardata); else if (!strcasecmp(el, "c_default_cal_zone")) SET_CFGSTRBUF(c_default_cal_zone, migr_chardata); else if (!strcasecmp(el, "c_pftcpdict_port")) config.c_pftcpdict_port = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_managesieve_port")) config.c_managesieve_port = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_auth_mode")) config.c_auth_mode = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_funambol_host")) SET_CFGSTRBUF(c_funambol_host, migr_chardata); else if (!strcasecmp(el, "c_funambol_port")) config.c_funambol_port = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_funambol_source")) SET_CFGSTRBUF(c_funambol_source, migr_chardata); else if (!strcasecmp(el, "c_funambol_auth")) SET_CFGSTRBUF(c_funambol_auth, migr_chardata); else if (!strcasecmp(el, "c_rbl_at_greeting")) config.c_rbl_at_greeting = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_master_user")) SET_CFGSTRBUF(c_master_user, migr_chardata); else if (!strcasecmp(el, "c_master_pass")) SET_CFGSTRBUF(c_master_pass, migr_chardata); else if (!strcasecmp(el, "c_pager_program")) SET_CFGSTRBUF(c_pager_program, migr_chardata); else if (!strcasecmp(el, "c_imap_keep_from")) config.c_imap_keep_from = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_xmpp_c2s_port")) config.c_xmpp_c2s_port = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_xmpp_s2s_port")) config.c_xmpp_s2s_port = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_pop3_fetch")) config.c_pop3_fetch = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_pop3_fastest")) config.c_pop3_fastest = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_spam_flag_only")) config.c_spam_flag_only = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_nntp_port")) config.c_nntp_port = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "c_nntps_port")) config.c_nntps_port = atoi(ChrPtr(migr_chardata)); else return 0; return 1; /* Found above...*/ } int migr_controlrecord(void *data, const char *el) { if (!strcasecmp(el, "control_highest")) CitControl.MMhighest = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "control_flags")) CitControl.MMflags = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "control_nextuser")) CitControl.MMnextuser = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "control_nextroom")) CitControl.MMnextroom = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "control_version")) CitControl.version = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "control")) { CitControl.MMfulltext = (-1L); /* always flush */ put_control(); syslog(LOG_INFO, "Completed import of control record\n"); } else return 0; return 1; } int migr_userrecord(void *data, const char *el) { if (!strcasecmp(el, "u_version")) usbuf.version = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "u_uid")) usbuf.uid = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "u_password")) safestrncpy(usbuf.password, ChrPtr(migr_chardata), sizeof usbuf.password); else if (!strcasecmp(el, "u_flags")) usbuf.flags = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "u_timescalled")) usbuf.timescalled = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "u_posted")) usbuf.posted = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "u_axlevel")) usbuf.axlevel = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "u_usernum")) usbuf.usernum = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "u_lastcall")) usbuf.lastcall = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "u_USuserpurge")) usbuf.USuserpurge = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "u_fullname")) safestrncpy(usbuf.fullname, ChrPtr(migr_chardata), sizeof usbuf.fullname); else return 0; return 1; } int migr_roomrecord(void *data, const char *el) { if (!strcasecmp(el, "QRname")) safestrncpy(qrbuf.QRname, ChrPtr(migr_chardata), sizeof qrbuf.QRname); else if (!strcasecmp(el, "QRpasswd")) safestrncpy(qrbuf.QRpasswd, ChrPtr(migr_chardata), sizeof qrbuf.QRpasswd); else if (!strcasecmp(el, "QRroomaide")) qrbuf.QRroomaide = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "QRhighest")) qrbuf.QRhighest = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "QRgen")) qrbuf.QRgen = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "QRflags")) qrbuf.QRflags = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "QRdirname")) safestrncpy(qrbuf.QRdirname, ChrPtr(migr_chardata), sizeof qrbuf.QRdirname); else if (!strcasecmp(el, "QRinfo")) qrbuf.QRinfo = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "QRfloor")) qrbuf.QRfloor = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "QRmtime")) qrbuf.QRmtime = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "QRexpire_mode")) qrbuf.QRep.expire_mode = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "QRexpire_value")) qrbuf.QRep.expire_value = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "QRnumber")) qrbuf.QRnumber = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "QRorder")) qrbuf.QRorder = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "QRflags2")) qrbuf.QRflags2 = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "QRdefaultview")) qrbuf.QRdefaultview = atoi(ChrPtr(migr_chardata)); else return 0; return 1; } int migr_floorrecord(void *data, const char *el) { if (!strcasecmp(el, "f_num")) floornum = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "f_flags")) flbuf.f_flags = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "f_name")) safestrncpy(flbuf.f_name, ChrPtr(migr_chardata), sizeof flbuf.f_name); else if (!strcasecmp(el, "f_ref_count")) flbuf.f_ref_count = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "f_ep_expire_mode")) flbuf.f_ep.expire_mode = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "f_ep_expire_value")) flbuf.f_ep.expire_value = atoi(ChrPtr(migr_chardata)); else return 0; return 1; } int migr_visitrecord(void *data, const char *el) { if (!strcasecmp(el, "v_roomnum")) vbuf.v_roomnum = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "v_roomgen")) vbuf.v_roomgen = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "v_usernum")) vbuf.v_usernum = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "v_seen")) { int is_textual_seen = 0; int i; int max = StrLength(migr_chardata); vbuf.v_lastseen = atol(ChrPtr(migr_chardata)); is_textual_seen = 0; for (i=0; i < max; ++i) if (!isdigit(ChrPtr(migr_chardata)[i])) is_textual_seen = 1; if (is_textual_seen) safestrncpy(vbuf.v_seen, ChrPtr(migr_chardata), sizeof vbuf.v_seen); } else if (!strcasecmp(el, "v_answered")) safestrncpy(vbuf.v_answered, ChrPtr(migr_chardata), sizeof vbuf.v_answered); else if (!strcasecmp(el, "v_flags")) vbuf.v_flags = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "v_view")) vbuf.v_view = atoi(ChrPtr(migr_chardata)); else return 0; return 1; } void migr_xml_end(void *data, const char *el) { const char *ptr; int msgcount = 0; long msgnum = 0L; long *msglist = NULL; int msglist_alloc = 0; /*** GENERAL STUFF ***/ if (!strcasecmp(el, "citadel_migrate_data")) { --citadel_migrate_data; return; } if (citadel_migrate_data != 1) { syslog(LOG_ALERT, "Out-of-sequence tag <%s> detected. Warning: ODD-DATA!\n", el); return; } // syslog(LOG_DEBUG, "END TAG: <%s> DATA: <%s>\n", el, (migr_chardata_len ? migr_chardata : "")); /*** CONFIG ***/ if (!strcasecmp(el, "config")) { config.c_enable_fulltext = 0; /* always disable */ put_config(); syslog(LOG_INFO, "Completed import of server configuration\n"); } else if ((!strncasecmp(el, HKEY("c_"))) && migr_config(data, el)) ; /* Nothing to do anymore */ /*** CONTROL ***/ else if ((!strncasecmp(el, HKEY("control"))) && migr_controlrecord(data, el)) ; /* Nothing to do anymore */ /*** USER ***/ else if ((!strncasecmp(el, HKEY("u_"))) && migr_userrecord(data, el)) ; /* Nothing to do anymore */ else if (!strcasecmp(el, "user")) { CtdlPutUser(&usbuf); syslog(LOG_INFO, "Imported user: %s\n", usbuf.fullname); } /*** OPENID ***/ else if (!strcasecmp(el, "oid_url")) safestrncpy(openid_url, ChrPtr(migr_chardata), sizeof openid_url); else if (!strcasecmp(el, "oid_usernum")) openid_usernum = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "openid")) { /* see serv_openid_rp.c for a description of the record format */ char *oid_data; int oid_data_len; oid_data_len = sizeof(long) + strlen(openid_url) + 1; oid_data = malloc(oid_data_len); memcpy(oid_data, &openid_usernum, sizeof(long)); memcpy(&oid_data[sizeof(long)], openid_url, strlen(openid_url) + 1); cdb_store(CDB_OPENID, openid_url, strlen(openid_url), oid_data, oid_data_len); free(oid_data); syslog(LOG_INFO, "Imported OpenID: %s (%ld)\n", openid_url, openid_usernum); } /*** ROOM ***/ else if ((!strncasecmp(el, HKEY("QR"))) && migr_roomrecord(data, el)) ; /* Nothing to do anymore */ else if (!strcasecmp(el, "room")) { CtdlPutRoom(&qrbuf); syslog(LOG_INFO, "Imported room: %s\n", qrbuf.QRname); } /*** ROOM MESSAGE POINTERS ***/ else if (!strcasecmp(el, "FRname")) safestrncpy(FRname, ChrPtr(migr_chardata), sizeof FRname); else if (!strcasecmp(el, "FRmsglist")) { if (!IsEmptyStr(FRname)) { msgcount = 0; msglist_alloc = 1000; msglist = malloc(sizeof(long) * msglist_alloc); syslog(LOG_DEBUG, "Message list for: %s\n", FRname); ptr = ChrPtr(migr_chardata); while (*ptr != 0) { while ((*ptr != 0) && (!isdigit(*ptr))) { ++ptr; } if ((*ptr != 0) && (isdigit(*ptr))) { msgnum = atol(ptr); if (msgnum > 0L) { if (msgcount >= msglist_alloc) { msglist_alloc *= 2; msglist = realloc(msglist, sizeof(long) * msglist_alloc); } msglist[msgcount++] = msgnum; } } while ((*ptr != 0) && (isdigit(*ptr))) { ++ptr; } } } if (msgcount > 0) { CtdlSaveMsgPointersInRoom(FRname, msglist, msgcount, 0, NULL, 1); } free(msglist); msglist = NULL; msglist_alloc = 0; syslog(LOG_DEBUG, "Imported %d messages.\n", msgcount); if (server_shutting_down) { return; } } /*** FLOORS ***/ else if ((!strncasecmp(el, HKEY("f_"))) && migr_floorrecord(data, el)) ; /* Nothing to do anymore */ else if (!strcasecmp(el, "floor")) { CtdlPutFloor(&flbuf, floornum); syslog(LOG_INFO, "Imported floor #%d (%s)\n", floornum, flbuf.f_name); } /*** VISITS ***/ else if ((!strncasecmp(el, HKEY("v_"))) && migr_visitrecord(data, el)) ; /* Nothing to do anymore */ else if (!strcasecmp(el, "visit")) { put_visit(&vbuf); syslog(LOG_INFO, "Imported visit: %ld/%ld/%ld\n", vbuf.v_roomnum, vbuf.v_roomgen, vbuf.v_usernum); } /*** MESSAGES ***/ else if (!strcasecmp(el, "msg_msgnum")) import_msgnum = atol(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "msg_meta_refcount")) smi.meta_refcount = atoi(ChrPtr(migr_chardata)); else if (!strcasecmp(el, "msg_meta_content_type")) safestrncpy(smi.meta_content_type, ChrPtr(migr_chardata), sizeof smi.meta_content_type); else if (!strcasecmp(el, "msg_text")) { FlushStrBuf(migr_MsgData); StrBufDecodeBase64To(migr_MsgData, migr_MsgData); cdb_store(CDB_MSGMAIN, &import_msgnum, sizeof(long), ChrPtr(migr_MsgData), StrLength(migr_MsgData) + 1); smi.meta_msgnum = import_msgnum; PutMetaData(&smi); syslog(LOG_INFO, "Imported message #%ld, size=%d, refcount=%d, content-type: %s\n", import_msgnum, StrLength(migr_MsgData), smi.meta_refcount, smi.meta_content_type); } /*** MORE GENERAL STUFF ***/ FlushStrBuf(migr_chardata); } /* * Import begins here */ void migr_do_import(void) { StrBuf *Buf; XML_Parser xp; int Finished = 0; unbuffer_output(); migr_chardata = NewStrBufPlain(NULL, SIZ * 20); migr_MsgData = NewStrBufPlain(NULL, SIZ * 20); Buf = NewStrBufPlain(NULL, SIZ); xp = XML_ParserCreate(NULL); if (!xp) { cprintf("%d Failed to create XML parser instance\n", ERROR+INTERNAL_ERROR); return; } XML_SetElementHandler(xp, migr_xml_start, migr_xml_end); XML_SetCharacterDataHandler(xp, migr_xml_chardata); CC->dont_term = 1; cprintf("%d sock it to me\n", SEND_LISTING); unbuffer_output(); client_set_inbound_buf(SIZ * 10); while (!Finished && client_read_random_blob(Buf, -1) >= 0) { if ((StrLength(Buf) > 4) && !strcmp(ChrPtr(Buf) + StrLength(Buf) - 4, "000\n")) { Finished = 1; StrBufCutAt(Buf, StrLength(Buf) - 4, NULL); } if (server_shutting_down) break; // Should we break or return? if (StrLength(Buf) == 0) continue; XML_Parse(xp, ChrPtr(Buf), StrLength(Buf), 0); FlushStrBuf(Buf); } XML_Parse(xp, "", 0, 1); XML_ParserFree(xp); FreeStrBuf(&Buf); FreeStrBuf(&migr_chardata); FreeStrBuf(&migr_MsgData); rebuild_euid_index(); rebuild_usersbynumber(); CC->dont_term = 0; } /* * Dump out the pathnames of directories which can be copied "as is" */ void migr_do_listdirs(void) { cprintf("%d Don't forget these:\n", LISTING_FOLLOWS); cprintf("bio|%s\n", ctdl_bio_dir); cprintf("files|%s\n", ctdl_file_dir); cprintf("userpics|%s\n", ctdl_usrpic_dir); cprintf("messages|%s\n", ctdl_message_dir); cprintf("netconfigs|%s\n", ctdl_netcfg_dir); cprintf("keys|%s\n", ctdl_key_dir); cprintf("images|%s\n", ctdl_image_dir); cprintf("info|%s\n", ctdl_info_dir); cprintf("000\n"); } /* * Common code appears in this section */ void cmd_migr(char *cmdbuf) { char cmd[32]; if (CtdlAccessCheck(ac_internal)) return; if (CtdlTrySingleUser()) { CtdlMakeTempFileName(migr_tempfilename1, sizeof migr_tempfilename1); CtdlMakeTempFileName(migr_tempfilename2, sizeof migr_tempfilename2); extract_token(cmd, cmdbuf, 0, '|', sizeof cmd); if (!strcasecmp(cmd, "export")) { migr_do_export(); } else if (!strcasecmp(cmd, "import")) { migr_do_import(); } else if (!strcasecmp(cmd, "listdirs")) { migr_do_listdirs(); } else { cprintf("%d illegal command\n", ERROR + ILLEGAL_VALUE); } unlink(migr_tempfilename1); unlink(migr_tempfilename2); CtdlEndSingleUser(); } else { cprintf("%d The migrator is already running.\n", ERROR + RESOURCE_BUSY); } } CTDL_MODULE_INIT(migrate) { if (!threading) { CtdlRegisterProtoHook(cmd_migr, "MIGR", "Across-the-wire migration"); } /* return our module name for the log */ return "migrate"; } citadel-9.01/modules/calendar/0000755000000000000000000000000012507024051014763 5ustar rootrootcitadel-9.01/modules/calendar/serv_calendar.h0000644000000000000000000000200112507024051017735 0ustar rootroot/* * iCalendar implementation for Citadel * * * Copyright (c) 1987-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * */ /* * "server_generated_invitations" tells the Citadel server that the * client wants invitations to be generated and sent out by the * server. Set to 1 to enable this functionality. * * "avoid_sending_invitations" is a server-internal variable. It is * set internally during certain transactions and cleared * automatically. */ struct cit_ical { int server_generated_invitations; int avoid_sending_invitations; }; #define CIT_ICAL CC->CIT_ICAL #define MAX_RECUR 1000 citadel-9.01/modules/calendar/serv_calendar.c0000644000000000000000000021775312507024051017756 0ustar rootroot/* * This module implements iCalendar object processing and the Calendar> * room on a Citadel server. It handles iCalendar objects using the * iTIP protocol. See RFCs 2445 and 2446. * * * Copyright (c) 1987-2011 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define PRODID "-//Citadel//NONSGML Citadel Calendar//EN" #include "ctdl_module.h" #include #include "msgbase.h" #include "internet_addressing.h" #include "serv_calendar.h" #include "room_ops.h" #include "euidindex.h" #include "ical_dezonify.h" struct ical_respond_data { char desired_partnum[SIZ]; icalcomponent *cal; }; /* * Utility function to create a new VCALENDAR component with some of the * required fields already set the way we like them. */ icalcomponent *icalcomponent_new_citadel_vcalendar(void) { icalcomponent *encaps; encaps = icalcomponent_new_vcalendar(); if (encaps == NULL) { syslog(LOG_CRIT, "ERROR: could not allocate component!\n"); return NULL; } /* Set the Product ID */ icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID)); /* Set the Version Number */ icalcomponent_add_property(encaps, icalproperty_new_version("2.0")); return(encaps); } /* * Utility function to encapsulate a subcomponent into a full VCALENDAR */ icalcomponent *ical_encapsulate_subcomponent(icalcomponent *subcomp) { icalcomponent *encaps; /* If we're already looking at a full VCALENDAR component, * don't bother ... just return itself. */ if (icalcomponent_isa(subcomp) == ICAL_VCALENDAR_COMPONENT) { return subcomp; } /* Encapsulate the VEVENT component into a complete VCALENDAR */ encaps = icalcomponent_new_citadel_vcalendar(); if (encaps == NULL) return NULL; /* Encapsulate the subcomponent inside */ icalcomponent_add_component(encaps, subcomp); /* Return the object we just created. */ return(encaps); } /* * Write a calendar object into the specified user's calendar room. * If the supplied user is NULL, this function writes the calendar object * to the currently selected room. */ void ical_write_to_cal(struct ctdluser *u, icalcomponent *cal) { char *ser = NULL; long serlen; icalcomponent *encaps = NULL; struct CtdlMessage *msg = NULL; icalcomponent *tmp=NULL; if (cal == NULL) return; /* If the supplied object is a subcomponent, encapsulate it in * a full VCALENDAR component, and save that instead. */ if (icalcomponent_isa(cal) != ICAL_VCALENDAR_COMPONENT) { tmp = icalcomponent_new_clone(cal); encaps = ical_encapsulate_subcomponent(tmp); ical_write_to_cal(u, encaps); icalcomponent_free(tmp); icalcomponent_free(encaps); return; } ser = icalcomponent_as_ical_string_r(cal); if (ser == NULL) return; serlen = strlen(ser); /* If the caller supplied a user, write to that user's default calendar room */ if (u) { /* This handy API function does all the work for us. */ CtdlWriteObject(USERCALENDARROOM, /* which room */ "text/calendar", /* MIME type */ ser, /* data */ serlen + 1, /* length */ u, /* which user */ 0, /* not binary */ 0, /* don't delete others of this type */ 0 /* no flags */ ); } /* If the caller did not supply a user, write to the currently selected room */ if (!u) { struct CitContext *CCC = CC; StrBuf *MsgBody; msg = malloc(sizeof(struct CtdlMessage)); memset(msg, 0, sizeof(struct CtdlMessage)); msg->cm_magic = CTDLMESSAGE_MAGIC; msg->cm_anon_type = MES_NORMAL; msg->cm_format_type = 4; CM_SetField(msg, eAuthor, CCC->user.fullname, strlen(CCC->user.fullname)); CM_SetField(msg, eOriginalRoom, CCC->room.QRname, strlen(CCC->room.QRname)); CM_SetField(msg, eNodeName, CFG_KEY(c_nodename)); CM_SetField(msg, eHumanNode, CFG_KEY(c_humannode)); MsgBody = NewStrBufPlain(NULL, serlen + 100); StrBufAppendBufPlain(MsgBody, HKEY("Content-type: text/calendar\r\n\r\n"), 0); StrBufAppendBufPlain(MsgBody, ser, serlen, 0); CM_SetAsFieldSB(msg, eMesageText, &MsgBody); /* Now write the data */ CtdlSubmitMsg(msg, NULL, "", QP_EADDR); CM_Free(msg); } /* In either case, now we can free the serialized calendar object */ free(ser); } /* * Send a reply to a meeting invitation. * * 'request' is the invitation to reply to. * 'action' is the string "accept" or "decline" or "tentative". * */ void ical_send_a_reply(icalcomponent *request, char *action) { icalcomponent *the_reply = NULL; icalcomponent *vevent = NULL; icalproperty *attendee = NULL; char attendee_string[SIZ]; icalproperty *organizer = NULL; char organizer_string[SIZ]; icalproperty *summary = NULL; char summary_string[SIZ]; icalproperty *me_attend = NULL; recptypes *recp = NULL; icalparameter *partstat = NULL; char *serialized_reply = NULL; char *reply_message_text = NULL; const char *ch; struct CtdlMessage *msg = NULL; recptypes *valid = NULL; *organizer_string = '\0'; strcpy(summary_string, "Calendar item"); if (request == NULL) { syslog(LOG_ERR, "ERROR: trying to reply to NULL event?\n"); return; } the_reply = icalcomponent_new_clone(request); if (the_reply == NULL) { syslog(LOG_ERR, "ERROR: cannot clone request\n"); return; } /* Change the method from REQUEST to REPLY */ icalcomponent_set_method(the_reply, ICAL_METHOD_REPLY); vevent = icalcomponent_get_first_component(the_reply, ICAL_VEVENT_COMPONENT); if (vevent != NULL) { /* Hunt for attendees, removing ones that aren't us. * (Actually, remove them all, cloning our own one so we can * re-insert it later) */ while (attendee = icalcomponent_get_first_property(vevent, ICAL_ATTENDEE_PROPERTY), (attendee != NULL) ) { ch = icalproperty_get_attendee(attendee); if ((ch != NULL) && !strncasecmp(ch, "MAILTO:", 7)) { safestrncpy(attendee_string, ch + 7, sizeof (attendee_string)); striplt(attendee_string); recp = validate_recipients(attendee_string, NULL, 0); if (recp != NULL) { if (!strcasecmp(recp->recp_local, CC->user.fullname)) { if (me_attend) icalproperty_free(me_attend); me_attend = icalproperty_new_clone(attendee); } free_recipients(recp); } } /* Remove it... */ icalcomponent_remove_property(vevent, attendee); icalproperty_free(attendee); } /* We found our own address in the attendee list. */ if (me_attend) { /* Change the partstat from NEEDS-ACTION to ACCEPT or DECLINE */ icalproperty_remove_parameter(me_attend, ICAL_PARTSTAT_PARAMETER); if (!strcasecmp(action, "accept")) { partstat = icalparameter_new_partstat(ICAL_PARTSTAT_ACCEPTED); } else if (!strcasecmp(action, "decline")) { partstat = icalparameter_new_partstat(ICAL_PARTSTAT_DECLINED); } else if (!strcasecmp(action, "tentative")) { partstat = icalparameter_new_partstat(ICAL_PARTSTAT_TENTATIVE); } if (partstat) icalproperty_add_parameter(me_attend, partstat); /* Now insert it back into the vevent. */ icalcomponent_add_property(vevent, me_attend); } /* Figure out who to send this thing to */ organizer = icalcomponent_get_first_property(vevent, ICAL_ORGANIZER_PROPERTY); if (organizer != NULL) { if (icalproperty_get_organizer(organizer)) { strcpy(organizer_string, icalproperty_get_organizer(organizer) ); } } if (!strncasecmp(organizer_string, "MAILTO:", 7)) { strcpy(organizer_string, &organizer_string[7]); striplt(organizer_string); } else { strcpy(organizer_string, ""); } /* Extract the summary string -- we'll use it as the * message subject for the reply */ summary = icalcomponent_get_first_property(vevent, ICAL_SUMMARY_PROPERTY); if (summary != NULL) { if (icalproperty_get_summary(summary)) { strcpy(summary_string, icalproperty_get_summary(summary) ); } } } /* Now generate the reply message and send it out. */ serialized_reply = icalcomponent_as_ical_string_r(the_reply); icalcomponent_free(the_reply); /* don't need this anymore */ if (serialized_reply == NULL) return; reply_message_text = malloc(strlen(serialized_reply) + SIZ); if (reply_message_text != NULL) { sprintf(reply_message_text, "Content-type: text/calendar; charset=\"utf-8\"\r\n\r\n%s\r\n", serialized_reply ); msg = CtdlMakeMessage(&CC->user, organizer_string, /* to */ "", /* cc */ CC->room.QRname, 0, FMT_RFC822, "", "", summary_string, /* Use summary for subject */ NULL, reply_message_text, NULL); if (msg != NULL) { valid = validate_recipients(organizer_string, NULL, 0); CtdlSubmitMsg(msg, valid, "", QP_EADDR); CM_Free(msg); free_recipients(valid); } } free(serialized_reply); } /* * Callback function for mime parser that hunts for calendar content types * and turns them into calendar objects. If something is found, it is placed * in ird->cal, and the caller now owns that memory and is responsible for freeing it. */ void ical_locate_part(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata) { struct ical_respond_data *ird = NULL; ird = (struct ical_respond_data *) cbuserdata; /* desired_partnum can be set to "_HUNT_" to have it just look for * the first part with a content type of text/calendar. Otherwise * we have to only process the right one. */ if (strcasecmp(ird->desired_partnum, "_HUNT_")) { if (strcasecmp(partnum, ird->desired_partnum)) { return; } } if ( (strcasecmp(cbtype, "text/calendar")) && (strcasecmp(cbtype, "application/ics")) ) { return; } if (ird->cal != NULL) { icalcomponent_free(ird->cal); ird->cal = NULL; } ird->cal = icalcomponent_new_from_string(content); } /* * Respond to a meeting request. */ void ical_respond(long msgnum, char *partnum, char *action) { struct CtdlMessage *msg = NULL; struct ical_respond_data ird; if ( (strcasecmp(action, "accept")) && (strcasecmp(action, "decline")) ) { cprintf("%d Action must be 'accept' or 'decline'\n", ERROR + ILLEGAL_VALUE ); return; } msg = CtdlFetchMessage(msgnum, 1); if (msg == NULL) { cprintf("%d Message %ld not found.\n", ERROR + ILLEGAL_VALUE, (long)msgnum ); return; } memset(&ird, 0, sizeof ird); strcpy(ird.desired_partnum, partnum); mime_parser(CM_RANGE(msg, eMesageText), *ical_locate_part, /* callback function */ NULL, NULL, (void *) &ird, /* user data */ 0 ); /* We're done with the incoming message, because we now have a * calendar object in memory. */ CM_Free(msg); /* * Here is the real meat of this function. Handle the event. */ if (ird.cal != NULL) { /* Save this in the user's calendar if necessary */ if (!strcasecmp(action, "accept")) { ical_write_to_cal(&CC->user, ird.cal); } /* Send a reply if necessary */ if (icalcomponent_get_method(ird.cal) == ICAL_METHOD_REQUEST) { ical_send_a_reply(ird.cal, action); } /* We used to delete the invitation after handling it. * We don't do that anymore, but here is the code that handled it: * CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, ""); */ /* Free the memory we allocated and return a response. */ icalcomponent_free(ird.cal); ird.cal = NULL; cprintf("%d ok\n", CIT_OK); return; } else { cprintf("%d No calendar object found\n", ERROR + ROOM_NOT_FOUND); return; } /* should never get here */ } /* * Figure out the UID of the calendar event being referred to in a * REPLY object. This function is recursive. */ void ical_learn_uid_of_reply(char *uidbuf, icalcomponent *cal) { icalcomponent *subcomponent; icalproperty *p; /* If this object is a REPLY, then extract the UID. */ if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) { p = icalcomponent_get_first_property(cal, ICAL_UID_PROPERTY); if (p != NULL) { strcpy(uidbuf, icalproperty_get_comment(p)); } } /* Otherwise, recurse through any VEVENT subcomponents. We do NOT want the * UID of the reply; we want the UID of the invitation being replied to. */ for (subcomponent = icalcomponent_get_first_component(cal, ICAL_VEVENT_COMPONENT); subcomponent != NULL; subcomponent = icalcomponent_get_next_component(cal, ICAL_VEVENT_COMPONENT) ) { ical_learn_uid_of_reply(uidbuf, subcomponent); } } /* * ical_update_my_calendar_with_reply() refers to this callback function; when we * locate the message containing the calendar event we're replying to, this function * gets called. It basically just sticks the message number in a supplied buffer. */ void ical_hunt_for_event_to_update(long msgnum, void *data) { long *msgnumptr; msgnumptr = (long *) data; *msgnumptr = msgnum; } struct original_event_container { icalcomponent *c; }; /* * Callback function for mime parser that hunts for calendar content types * and turns them into calendar objects (called by ical_update_my_calendar_with_reply() * to fetch the object being updated) */ void ical_locate_original_event(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata) { struct original_event_container *oec = NULL; if ( (strcasecmp(cbtype, "text/calendar")) && (strcasecmp(cbtype, "application/ics")) ) { return; } oec = (struct original_event_container *) cbuserdata; if (oec->c != NULL) { icalcomponent_free(oec->c); } oec->c = icalcomponent_new_from_string(content); } /* * Merge updated attendee information from a REPLY into an existing event. */ void ical_merge_attendee_reply(icalcomponent *event, icalcomponent *reply) { icalcomponent *c; icalproperty *e_attendee, *r_attendee; /* First things first. If we're not looking at a VEVENT component, * recurse through subcomponents until we find one. */ if (icalcomponent_isa(event) != ICAL_VEVENT_COMPONENT) { for (c = icalcomponent_get_first_component(event, ICAL_VEVENT_COMPONENT); c != NULL; c = icalcomponent_get_next_component(event, ICAL_VEVENT_COMPONENT) ) { ical_merge_attendee_reply(c, reply); } return; } /* Now do the same thing with the reply. */ if (icalcomponent_isa(reply) != ICAL_VEVENT_COMPONENT) { for (c = icalcomponent_get_first_component(reply, ICAL_VEVENT_COMPONENT); c != NULL; c = icalcomponent_get_next_component(reply, ICAL_VEVENT_COMPONENT) ) { ical_merge_attendee_reply(event, c); } return; } /* Clone the reply, because we're going to rip its guts out. */ reply = icalcomponent_new_clone(reply); /* At this point we're looking at the correct subcomponents. * Iterate through the attendees looking for a match. */ STARTOVER: for (e_attendee = icalcomponent_get_first_property(event, ICAL_ATTENDEE_PROPERTY); e_attendee != NULL; e_attendee = icalcomponent_get_next_property(event, ICAL_ATTENDEE_PROPERTY)) { for (r_attendee = icalcomponent_get_first_property(reply, ICAL_ATTENDEE_PROPERTY); r_attendee != NULL; r_attendee = icalcomponent_get_next_property(reply, ICAL_ATTENDEE_PROPERTY)) { /* Check to see if these two attendees match... */ const char *e, *r; e = icalproperty_get_attendee(e_attendee); r = icalproperty_get_attendee(r_attendee); if ((e != NULL) && (r != NULL) && !strcasecmp(e, r)) { /* ...and if they do, remove the attendee from the event * and replace it with the attendee from the reply. (The * reply's copy will have the same address, but an updated * status.) */ icalcomponent_remove_property(event, e_attendee); icalproperty_free(e_attendee); icalcomponent_remove_property(reply, r_attendee); icalcomponent_add_property(event, r_attendee); /* Since we diddled both sets of attendees, we have to start * the iteration over again. This will not create an infinite * loop because we removed the attendee from the reply. (That's * why we cloned the reply, and that's what we mean by "ripping * its guts out.") */ goto STARTOVER; } } } /* Free the *clone* of the reply. */ icalcomponent_free(reply); } /* * Handle an incoming RSVP (object with method==ICAL_METHOD_REPLY) for a * calendar event. The object has already been deserialized for us; all * we have to do here is hunt for the event in our calendar, merge in the * updated attendee status, and save it again. * * This function returns 0 on success, 1 if the event was not found in the * user's calendar, or 2 if an internal error occurred. */ int ical_update_my_calendar_with_reply(icalcomponent *cal) { char uid[SIZ]; char hold_rm[ROOMNAMELEN]; long msgnum_being_replaced = 0; struct CtdlMessage *msg = NULL; struct original_event_container oec; icalcomponent *original_event; char *serialized_event = NULL; char roomname[ROOMNAMELEN]; char *message_text = NULL; /* Figure out just what event it is we're dealing with */ strcpy(uid, "--==<< InVaLiD uId >>==--"); ical_learn_uid_of_reply(uid, cal); syslog(LOG_DEBUG, "UID of event being replied to is <%s>\n", uid); strcpy(hold_rm, CC->room.QRname); /* save current room */ if (CtdlGetRoom(&CC->room, USERCALENDARROOM) != 0) { CtdlGetRoom(&CC->room, hold_rm); syslog(LOG_CRIT, "cannot get user calendar room\n"); return(2); } /* * Look in the EUID index for a message with * the Citadel EUID set to the value we're looking for. Since * Citadel always sets the message EUID to the iCalendar UID of * the event, this will work. */ msgnum_being_replaced = CtdlLocateMessageByEuid(uid, &CC->room); CtdlGetRoom(&CC->room, hold_rm); /* return to saved room */ syslog(LOG_DEBUG, "msgnum_being_replaced == %ld\n", msgnum_being_replaced); if (msgnum_being_replaced == 0) { return(1); /* no calendar event found */ } /* Now we know the ID of the message containing the event being updated. * We don't actually have to delete it; that'll get taken care of by the * server when we save another event with the same UID. This just gives * us the ability to load the event into memory so we can diddle the * attendees. */ msg = CtdlFetchMessage(msgnum_being_replaced, 1); if (msg == NULL) { return(2); /* internal error */ } oec.c = NULL; mime_parser(CM_RANGE(msg, eMesageText), *ical_locate_original_event, /* callback function */ NULL, NULL, &oec, /* user data */ 0 ); CM_Free(msg); original_event = oec.c; if (original_event == NULL) { syslog(LOG_ERR, "ERROR: Original_component is NULL.\n"); return(2); } /* Merge the attendee's updated status into the event */ ical_merge_attendee_reply(original_event, cal); /* Serialize it */ serialized_event = icalcomponent_as_ical_string_r(original_event); icalcomponent_free(original_event); /* Don't need this anymore. */ if (serialized_event == NULL) return(2); CtdlMailboxName(roomname, sizeof roomname, &CC->user, USERCALENDARROOM); message_text = malloc(strlen(serialized_event) + SIZ); if (message_text != NULL) { sprintf(message_text, "Content-type: text/calendar; charset=\"utf-8\"\r\n\r\n%s\r\n", serialized_event ); msg = CtdlMakeMessage(&CC->user, "", /* No recipient */ "", /* No recipient */ roomname, 0, FMT_RFC822, "", "", "", /* no subject */ NULL, message_text, NULL); if (msg != NULL) { CIT_ICAL->avoid_sending_invitations = 1; CtdlSubmitMsg(msg, NULL, roomname, QP_EADDR); CM_Free(msg); CIT_ICAL->avoid_sending_invitations = 0; } } free(serialized_event); return(0); } /* * Handle an incoming RSVP for an event. (This is the server subcommand part; it * simply extracts the calendar object from the message, deserializes it, and * passes it up to ical_update_my_calendar_with_reply() for processing. */ void ical_handle_rsvp(long msgnum, char *partnum, char *action) { struct CtdlMessage *msg = NULL; struct ical_respond_data ird; int ret; if ( (strcasecmp(action, "update")) && (strcasecmp(action, "ignore")) ) { cprintf("%d Action must be 'update' or 'ignore'\n", ERROR + ILLEGAL_VALUE ); return; } msg = CtdlFetchMessage(msgnum, 1); if (msg == NULL) { cprintf("%d Message %ld not found.\n", ERROR + ILLEGAL_VALUE, (long)msgnum ); return; } memset(&ird, 0, sizeof ird); strcpy(ird.desired_partnum, partnum); mime_parser(CM_RANGE(msg, eMesageText), *ical_locate_part, /* callback function */ NULL, NULL, (void *) &ird, /* user data */ 0 ); /* We're done with the incoming message, because we now have a * calendar object in memory. */ CM_Free(msg); /* * Here is the real meat of this function. Handle the event. */ if (ird.cal != NULL) { /* Update the user's calendar if necessary */ if (!strcasecmp(action, "update")) { ret = ical_update_my_calendar_with_reply(ird.cal); if (ret == 0) { cprintf("%d Your calendar has been updated with this reply.\n", CIT_OK); } else if (ret == 1) { cprintf("%d This event does not exist in your calendar.\n", ERROR + FILE_NOT_FOUND); } else { cprintf("%d An internal error occurred.\n", ERROR + INTERNAL_ERROR); } } else { cprintf("%d This reply has been ignored.\n", CIT_OK); } /* Now that we've processed this message, we don't need it * anymore. So delete it. (Don't do this anymore.) CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, ""); */ /* Free the memory we allocated and return a response. */ icalcomponent_free(ird.cal); ird.cal = NULL; return; } else { cprintf("%d No calendar object found\n", ERROR + ROOM_NOT_FOUND); return; } /* should never get here */ } /* * Search for a property in both the top level and in a VEVENT subcomponent */ icalproperty *ical_ctdl_get_subprop( icalcomponent *cal, icalproperty_kind which_prop ) { icalproperty *p; icalcomponent *c; p = icalcomponent_get_first_property(cal, which_prop); if (p == NULL) { c = icalcomponent_get_first_component(cal, ICAL_VEVENT_COMPONENT); if (c != NULL) { p = icalcomponent_get_first_property(c, which_prop); } } return p; } /* * Check to see if two events overlap. Returns nonzero if they do. * (This function is used in both Citadel and WebCit. If you change it in * one place, change it in the other. Better yet, put it in a library.) */ int ical_ctdl_is_overlap( struct icaltimetype t1start, struct icaltimetype t1end, struct icaltimetype t2start, struct icaltimetype t2end ) { if (icaltime_is_null_time(t1start)) return(0); if (icaltime_is_null_time(t2start)) return(0); /* if either event lacks end time, assume end = start */ if (icaltime_is_null_time(t1end)) memcpy(&t1end, &t1start, sizeof(struct icaltimetype)); else { if (t1end.is_date && icaltime_compare(t1start, t1end)) { /* * the end date is non-inclusive so adjust it by one * day because our test is inclusive, note that a day is * not too much because we are talking about all day * events * if start = end we assume that nevertheless the whole * day is meant */ icaltime_adjust(&t1end, -1, 0, 0, 0); } } if (icaltime_is_null_time(t2end)) memcpy(&t2end, &t2start, sizeof(struct icaltimetype)); else { if (t2end.is_date && icaltime_compare(t2start, t2end)) { icaltime_adjust(&t2end, -1, 0, 0, 0); } } /* First, check for all-day events */ if (t1start.is_date || t2start.is_date) { /* If event 1 ends before event 2 starts, we're in the clear. */ if (icaltime_compare_date_only(t1end, t2start) < 0) return(0); /* If event 2 ends before event 1 starts, we're also ok. */ if (icaltime_compare_date_only(t2end, t1start) < 0) return(0); return(1); } /* syslog(LOG_DEBUG, "Comparing t1start %d:%d t1end %d:%d t2start %d:%d t2end %d:%d \n", t1start.hour, t1start.minute, t1end.hour, t1end.minute, t2start.hour, t2start.minute, t2end.hour, t2end.minute); */ /* Now check for overlaps using date *and* time. */ /* If event 1 ends before event 2 starts, we're in the clear. */ if (icaltime_compare(t1end, t2start) <= 0) return(0); /* syslog(LOG_DEBUG, "first passed\n"); */ /* If event 2 ends before event 1 starts, we're also ok. */ if (icaltime_compare(t2end, t1start) <= 0) return(0); /* syslog(LOG_DEBUG, "second passed\n"); */ /* Otherwise, they overlap. */ return(1); } /* * Phase 6 of "hunt for conflicts" * called by ical_conflicts_phase5() * * Now both the proposed and existing events have been boiled down to start and end times. * Check for overlap and output any conflicts. * * Returns nonzero if a conflict was reported. This allows the caller to stop iterating. */ int ical_conflicts_phase6(struct icaltimetype t1start, struct icaltimetype t1end, struct icaltimetype t2start, struct icaltimetype t2end, long existing_msgnum, char *conflict_event_uid, char *conflict_event_summary, char *compare_uid) { int conflict_reported = 0; /* debugging cruft * time_t tt; tt = icaltime_as_timet_with_zone(t1start, t1start.zone); syslog(LOG_DEBUG, "PROPOSED START: %s", ctime(&tt)); tt = icaltime_as_timet_with_zone(t1end, t1end.zone); syslog(LOG_DEBUG, " PROPOSED END: %s", ctime(&tt)); tt = icaltime_as_timet_with_zone(t2start, t2start.zone); syslog(LOG_DEBUG, "EXISTING START: %s", ctime(&tt)); tt = icaltime_as_timet_with_zone(t2end, t2end.zone); syslog(LOG_DEBUG, " EXISTING END: %s", ctime(&tt)); * debugging cruft */ /* compare and output */ if (ical_ctdl_is_overlap(t1start, t1end, t2start, t2end)) { cprintf("%ld||%s|%s|%d|\n", existing_msgnum, conflict_event_uid, conflict_event_summary, ( ((strlen(compare_uid)>0) &&(!strcasecmp(compare_uid, conflict_event_uid))) ? 1 : 0 ) ); conflict_reported = 1; } return(conflict_reported); } /* * Phase 5 of "hunt for conflicts" * Called by ical_conflicts_phase4() * * We have the proposed event boiled down to start and end times. * Now check it against an existing event. */ void ical_conflicts_phase5(struct icaltimetype t1start, struct icaltimetype t1end, icalcomponent *existing_event, long existing_msgnum, char *compare_uid) { char conflict_event_uid[SIZ]; char conflict_event_summary[SIZ]; struct icaltimetype t2start, t2end; icalproperty *p; /* recur variables */ icalproperty *rrule = NULL; struct icalrecurrencetype recur; icalrecur_iterator *ritr = NULL; struct icaldurationtype dur; int num_recur = 0; /* initialization */ strcpy(conflict_event_uid, ""); strcpy(conflict_event_summary, ""); t2start = icaltime_null_time(); t2end = icaltime_null_time(); /* existing event stuff */ p = ical_ctdl_get_subprop(existing_event, ICAL_DTSTART_PROPERTY); if (p == NULL) return; if (p != NULL) t2start = icalproperty_get_dtstart(p); if (icaltime_is_utc(t2start)) { t2start.zone = icaltimezone_get_utc_timezone(); } else { t2start.zone = icalcomponent_get_timezone(existing_event, icalparameter_get_tzid( icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER) ) ); if (!t2start.zone) { t2start.zone = get_default_icaltimezone(); } } p = ical_ctdl_get_subprop(existing_event, ICAL_DTEND_PROPERTY); if (p != NULL) { t2end = icalproperty_get_dtend(p); if (icaltime_is_utc(t2end)) { t2end.zone = icaltimezone_get_utc_timezone(); } else { t2end.zone = icalcomponent_get_timezone(existing_event, icalparameter_get_tzid( icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER) ) ); if (!t2end.zone) { t2end.zone = get_default_icaltimezone(); } } dur = icaltime_subtract(t2end, t2start); } else { memset (&dur, 0, sizeof(struct icaldurationtype)); } rrule = ical_ctdl_get_subprop(existing_event, ICAL_RRULE_PROPERTY); if (rrule) { recur = icalproperty_get_rrule(rrule); ritr = icalrecur_iterator_new(recur, t2start); } do { p = ical_ctdl_get_subprop(existing_event, ICAL_UID_PROPERTY); if (p != NULL) { strcpy(conflict_event_uid, icalproperty_get_comment(p)); } p = ical_ctdl_get_subprop(existing_event, ICAL_SUMMARY_PROPERTY); if (p != NULL) { strcpy(conflict_event_summary, icalproperty_get_comment(p)); } if (ical_conflicts_phase6(t1start, t1end, t2start, t2end, existing_msgnum, conflict_event_uid, conflict_event_summary, compare_uid)) { num_recur = MAX_RECUR + 1; /* force it out of scope, no need to continue */ } if (rrule) { t2start = icalrecur_iterator_next(ritr); if (!icaltime_is_null_time(t2end)) { const icaltimezone *hold_zone = t2end.zone; t2end = icaltime_add(t2start, dur); t2end.zone = hold_zone; } ++num_recur; } if (icaltime_compare(t2start, t1end) < 0) { num_recur = MAX_RECUR + 1; /* force it out of scope */ } } while ( (rrule) && (!icaltime_is_null_time(t2start)) && (num_recur < MAX_RECUR) ); icalrecur_iterator_free(ritr); } /* * Phase 4 of "hunt for conflicts" * Called by ical_hunt_for_conflicts_backend() * * At this point we've got it boiled down to two icalcomponent events in memory. * If they conflict, output something to the client. */ void ical_conflicts_phase4(icalcomponent *proposed_event, icalcomponent *existing_event, long existing_msgnum) { struct icaltimetype t1start, t1end; icalproperty *p; char compare_uid[SIZ]; /* recur variables */ icalproperty *rrule = NULL; struct icalrecurrencetype recur; icalrecur_iterator *ritr = NULL; struct icaldurationtype dur; int num_recur = 0; /* initialization */ t1end = icaltime_null_time(); *compare_uid = '\0'; /* proposed event stuff */ p = ical_ctdl_get_subprop(proposed_event, ICAL_DTSTART_PROPERTY); if (p == NULL) return; else t1start = icalproperty_get_dtstart(p); if (icaltime_is_utc(t1start)) { t1start.zone = icaltimezone_get_utc_timezone(); } else { t1start.zone = icalcomponent_get_timezone(proposed_event, icalparameter_get_tzid( icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER) ) ); if (!t1start.zone) { t1start.zone = get_default_icaltimezone(); } } p = ical_ctdl_get_subprop(proposed_event, ICAL_DTEND_PROPERTY); if (p != NULL) { t1end = icalproperty_get_dtend(p); if (icaltime_is_utc(t1end)) { t1end.zone = icaltimezone_get_utc_timezone(); } else { t1end.zone = icalcomponent_get_timezone(proposed_event, icalparameter_get_tzid( icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER) ) ); if (!t1end.zone) { t1end.zone = get_default_icaltimezone(); } } dur = icaltime_subtract(t1end, t1start); } else { memset (&dur, 0, sizeof(struct icaldurationtype)); } rrule = ical_ctdl_get_subprop(proposed_event, ICAL_RRULE_PROPERTY); if (rrule) { recur = icalproperty_get_rrule(rrule); ritr = icalrecur_iterator_new(recur, t1start); } p = ical_ctdl_get_subprop(proposed_event, ICAL_UID_PROPERTY); if (p != NULL) { strcpy(compare_uid, icalproperty_get_comment(p)); } do { ical_conflicts_phase5(t1start, t1end, existing_event, existing_msgnum, compare_uid); if (rrule) { t1start = icalrecur_iterator_next(ritr); if (!icaltime_is_null_time(t1end)) { const icaltimezone *hold_zone = t1end.zone; t1end = icaltime_add(t1start, dur); t1end.zone = hold_zone; } ++num_recur; } } while ( (rrule) && (!icaltime_is_null_time(t1start)) && (num_recur < MAX_RECUR) ); icalrecur_iterator_free(ritr); } /* * Phase 3 of "hunt for conflicts" * Called by ical_hunt_for_conflicts() */ void ical_hunt_for_conflicts_backend(long msgnum, void *data) { icalcomponent *proposed_event; struct CtdlMessage *msg = NULL; struct ical_respond_data ird; proposed_event = (icalcomponent *)data; msg = CtdlFetchMessage(msgnum, 1); if (msg == NULL) return; memset(&ird, 0, sizeof ird); strcpy(ird.desired_partnum, "_HUNT_"); mime_parser(CM_RANGE(msg, eMesageText), *ical_locate_part, /* callback function */ NULL, NULL, (void *) &ird, /* user data */ 0 ); CM_Free(msg); if (ird.cal == NULL) return; ical_conflicts_phase4(proposed_event, ird.cal, msgnum); icalcomponent_free(ird.cal); } /* * Phase 2 of "hunt for conflicts" operation. * At this point we have a calendar object which represents the VEVENT that * is proposed for addition to the calendar. Now hunt through the user's * calendar room, and output zero or more existing VEVENTs which conflict * with this one. */ void ical_hunt_for_conflicts(icalcomponent *cal) { char hold_rm[ROOMNAMELEN]; strcpy(hold_rm, CC->room.QRname); /* save current room */ if (CtdlGetRoom(&CC->room, USERCALENDARROOM) != 0) { CtdlGetRoom(&CC->room, hold_rm); cprintf("%d You do not have a calendar.\n", ERROR + ROOM_NOT_FOUND); return; } cprintf("%d Conflicting events:\n", LISTING_FOLLOWS); CtdlForEachMessage(MSGS_ALL, 0, NULL, NULL, NULL, ical_hunt_for_conflicts_backend, (void *) cal ); cprintf("000\n"); CtdlGetRoom(&CC->room, hold_rm); /* return to saved room */ } /* * Hunt for conflicts (Phase 1 -- retrieve the object and call Phase 2) */ void ical_conflicts(long msgnum, char *partnum) { struct CtdlMessage *msg = NULL; struct ical_respond_data ird; msg = CtdlFetchMessage(msgnum, 1); if (msg == NULL) { cprintf("%d Message %ld not found\n", ERROR + ILLEGAL_VALUE, (long)msgnum ); return; } memset(&ird, 0, sizeof ird); strcpy(ird.desired_partnum, partnum); mime_parser(CM_RANGE(msg, eMesageText), *ical_locate_part, /* callback function */ NULL, NULL, (void *) &ird, /* user data */ 0 ); CM_Free(msg); if (ird.cal != NULL) { ical_hunt_for_conflicts(ird.cal); icalcomponent_free(ird.cal); return; } cprintf("%d No calendar object found\n", ERROR + ROOM_NOT_FOUND); } /* * Look for busy time in a VEVENT and add it to the supplied VFREEBUSY. * * fb The VFREEBUSY component to which we are appending * top_level_cal The top-level VCALENDAR component which contains a VEVENT to be added */ void ical_add_to_freebusy(icalcomponent *fb, icalcomponent *top_level_cal) { icalcomponent *cal; icalproperty *p; icalvalue *v; struct icalperiodtype this_event_period = icalperiodtype_null_period(); icaltimetype dtstart; icaltimetype dtend; /* recur variables */ icalproperty *rrule = NULL; struct icalrecurrencetype recur; icalrecur_iterator *ritr = NULL; struct icaldurationtype dur; int num_recur = 0; if (!top_level_cal) return; /* Find the VEVENT component containing an event */ cal = icalcomponent_get_first_component(top_level_cal, ICAL_VEVENT_COMPONENT); if (!cal) return; /* If this event is not opaque, the user isn't publishing it as * busy time, so don't bother doing anything else. */ p = icalcomponent_get_first_property(cal, ICAL_TRANSP_PROPERTY); if (p != NULL) { v = icalproperty_get_value(p); if (v != NULL) { if (icalvalue_get_transp(v) != ICAL_TRANSP_OPAQUE) { return; } } } /* * Now begin calculating the event start and end times. */ p = icalcomponent_get_first_property(cal, ICAL_DTSTART_PROPERTY); if (!p) return; dtstart = icalproperty_get_dtstart(p); if (icaltime_is_utc(dtstart)) { dtstart.zone = icaltimezone_get_utc_timezone(); } else { dtstart.zone = icalcomponent_get_timezone(top_level_cal, icalparameter_get_tzid( icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER) ) ); if (!dtstart.zone) { dtstart.zone = get_default_icaltimezone(); } } dtend = icalcomponent_get_dtend(cal); if (!icaltime_is_null_time(dtend)) { dur = icaltime_subtract(dtend, dtstart); } else { memset (&dur, 0, sizeof(struct icaldurationtype)); } /* Is a recurrence specified? If so, get ready to process it... */ rrule = ical_ctdl_get_subprop(cal, ICAL_RRULE_PROPERTY); if (rrule) { recur = icalproperty_get_rrule(rrule); ritr = icalrecur_iterator_new(recur, dtstart); } do { /* Convert the DTSTART and DTEND properties to an icalperiod. */ this_event_period.start = dtstart; if (!icaltime_is_null_time(dtend)) { this_event_period.end = dtend; } /* Convert the timestamps to UTC. It's ok to do this because we've already expanded * recurrences and this data is never going to get used again. */ this_event_period.start = icaltime_convert_to_zone( this_event_period.start, icaltimezone_get_utc_timezone() ); this_event_period.end = icaltime_convert_to_zone( this_event_period.end, icaltimezone_get_utc_timezone() ); /* Now add it. */ icalcomponent_add_property(fb, icalproperty_new_freebusy(this_event_period)); /* Make sure the DTSTART property of the freebusy *list* is set to * the DTSTART property of the *earliest event*. */ p = icalcomponent_get_first_property(fb, ICAL_DTSTART_PROPERTY); if (p == NULL) { icalcomponent_set_dtstart(fb, this_event_period.start); } else { if (icaltime_compare(this_event_period.start, icalcomponent_get_dtstart(fb)) < 0) { icalcomponent_set_dtstart(fb, this_event_period.start); } } /* Make sure the DTEND property of the freebusy *list* is set to * the DTEND property of the *latest event*. */ p = icalcomponent_get_first_property(fb, ICAL_DTEND_PROPERTY); if (p == NULL) { icalcomponent_set_dtend(fb, this_event_period.end); } else { if (icaltime_compare(this_event_period.end, icalcomponent_get_dtend(fb)) > 0) { icalcomponent_set_dtend(fb, this_event_period.end); } } if (rrule) { dtstart = icalrecur_iterator_next(ritr); if (!icaltime_is_null_time(dtend)) { dtend = icaltime_add(dtstart, dur); dtend.zone = dtstart.zone; dtend.is_utc = dtstart.is_utc; } ++num_recur; } } while ( (rrule) && (!icaltime_is_null_time(dtstart)) && (num_recur < MAX_RECUR) ) ; icalrecur_iterator_free(ritr); } /* * Backend for ical_freebusy() * * This function simply loads the messages in the user's calendar room, * which contain VEVENTs, then strips them of all non-freebusy data, and * adds them to the supplied VCALENDAR. * */ void ical_freebusy_backend(long msgnum, void *data) { icalcomponent *fb; struct CtdlMessage *msg = NULL; struct ical_respond_data ird; fb = (icalcomponent *)data; /* User-supplied data will be the VFREEBUSY component */ msg = CtdlFetchMessage(msgnum, 1); if (msg == NULL) return; memset(&ird, 0, sizeof ird); strcpy(ird.desired_partnum, "_HUNT_"); mime_parser(CM_RANGE(msg, eMesageText), *ical_locate_part, /* callback function */ NULL, NULL, (void *) &ird, /* user data */ 0 ); CM_Free(msg); if (ird.cal) { ical_add_to_freebusy(fb, ird.cal); /* Add VEVENT times to VFREEBUSY */ icalcomponent_free(ird.cal); } } /* * Grab another user's free/busy times */ void ical_freebusy(char *who) { struct ctdluser usbuf; char calendar_room_name[ROOMNAMELEN]; char hold_rm[ROOMNAMELEN]; char *serialized_request = NULL; icalcomponent *encaps = NULL; icalcomponent *fb = NULL; int found_user = (-1); recptypes *recp = NULL; char buf[256]; char host[256]; char type[256]; int i = 0; int config_lines = 0; /* First try an exact match. */ found_user = CtdlGetUser(&usbuf, who); /* If not found, try it as an unqualified email address. */ if (found_user != 0) { strcpy(buf, who); recp = validate_recipients(buf, NULL, 0); syslog(LOG_DEBUG, "Trying <%s>\n", buf); if (recp != NULL) { if (recp->num_local == 1) { found_user = CtdlGetUser(&usbuf, recp->recp_local); } free_recipients(recp); } } /* If still not found, try it as an address qualified with the * primary FQDN of this Citadel node. */ if (found_user != 0) { snprintf(buf, sizeof buf, "%s@%s", who, config.c_fqdn); syslog(LOG_DEBUG, "Trying <%s>\n", buf); recp = validate_recipients(buf, NULL, 0); if (recp != NULL) { if (recp->num_local == 1) { found_user = CtdlGetUser(&usbuf, recp->recp_local); } free_recipients(recp); } } /* Still not found? Try qualifying it with every domain we * might have addresses in. */ if (found_user != 0) { config_lines = num_tokens(inetcfg, '\n'); for (i=0; ((i < config_lines) && (found_user != 0)); ++i) { extract_token(buf, inetcfg, i, '\n', sizeof buf); extract_token(host, buf, 0, '|', sizeof host); extract_token(type, buf, 1, '|', sizeof type); if ( (!strcasecmp(type, "localhost")) || (!strcasecmp(type, "directory")) ) { snprintf(buf, sizeof buf, "%s@%s", who, host); syslog(LOG_DEBUG, "Trying <%s>\n", buf); recp = validate_recipients(buf, NULL, 0); if (recp != NULL) { if (recp->num_local == 1) { found_user = CtdlGetUser(&usbuf, recp->recp_local); } free_recipients(recp); } } } } if (found_user != 0) { cprintf("%d No such user.\n", ERROR + NO_SUCH_USER); return; } CtdlMailboxName(calendar_room_name, sizeof calendar_room_name, &usbuf, USERCALENDARROOM); strcpy(hold_rm, CC->room.QRname); /* save current room */ if (CtdlGetRoom(&CC->room, calendar_room_name) != 0) { cprintf("%d Cannot open calendar\n", ERROR + ROOM_NOT_FOUND); CtdlGetRoom(&CC->room, hold_rm); return; } /* Create a VFREEBUSY subcomponent */ syslog(LOG_DEBUG, "Creating VFREEBUSY component\n"); fb = icalcomponent_new_vfreebusy(); if (fb == NULL) { cprintf("%d Internal error: cannot allocate memory.\n", ERROR + INTERNAL_ERROR); CtdlGetRoom(&CC->room, hold_rm); return; } /* Set the method to PUBLISH */ icalcomponent_set_method(fb, ICAL_METHOD_PUBLISH); /* Set the DTSTAMP to right now. */ icalcomponent_set_dtstamp(fb, icaltime_from_timet(time(NULL), 0)); /* Add the user's email address as ORGANIZER */ sprintf(buf, "MAILTO:%s", who); if (strchr(buf, '@') == NULL) { strcat(buf, "@"); strcat(buf, config.c_fqdn); } for (i=0; buf[i]; ++i) { if (buf[i]==' ') buf[i] = '_'; } icalcomponent_add_property(fb, icalproperty_new_organizer(buf)); /* Add busy time from events */ syslog(LOG_DEBUG, "Adding busy time from events\n"); CtdlForEachMessage(MSGS_ALL, 0, NULL, NULL, NULL, ical_freebusy_backend, (void *)fb ); /* If values for DTSTART and DTEND are still not present, set them * to yesterday and tomorrow as default values. */ if (icalcomponent_get_first_property(fb, ICAL_DTSTART_PROPERTY) == NULL) { icalcomponent_set_dtstart(fb, icaltime_from_timet(time(NULL)-86400L, 0)); } if (icalcomponent_get_first_property(fb, ICAL_DTEND_PROPERTY) == NULL) { icalcomponent_set_dtend(fb, icaltime_from_timet(time(NULL)+86400L, 0)); } /* Put the freebusy component into the calendar component */ syslog(LOG_DEBUG, "Encapsulating\n"); encaps = ical_encapsulate_subcomponent(fb); if (encaps == NULL) { icalcomponent_free(fb); cprintf("%d Internal error: cannot allocate memory.\n", ERROR + INTERNAL_ERROR); CtdlGetRoom(&CC->room, hold_rm); return; } /* Set the method to PUBLISH */ syslog(LOG_DEBUG, "Setting method\n"); icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH); /* Serialize it */ syslog(LOG_DEBUG, "Serializing\n"); serialized_request = icalcomponent_as_ical_string_r(encaps); icalcomponent_free(encaps); /* Don't need this anymore. */ cprintf("%d Free/busy for %s\n", LISTING_FOLLOWS, usbuf.fullname); if (serialized_request != NULL) { client_write(serialized_request, strlen(serialized_request)); free(serialized_request); } cprintf("\n000\n"); /* Go back to the room from which we came... */ CtdlGetRoom(&CC->room, hold_rm); } /* * Backend for ical_getics() * * This is a ForEachMessage() callback function that searches the current room * for calendar events and adds them each into one big calendar component. */ void ical_getics_backend(long msgnum, void *data) { icalcomponent *encaps, *c; struct CtdlMessage *msg = NULL; struct ical_respond_data ird; encaps = (icalcomponent *)data; if (encaps == NULL) return; /* Look for the calendar event... */ msg = CtdlFetchMessage(msgnum, 1); if (msg == NULL) return; memset(&ird, 0, sizeof ird); strcpy(ird.desired_partnum, "_HUNT_"); mime_parser(CM_RANGE(msg, eMesageText), *ical_locate_part, /* callback function */ NULL, NULL, (void *) &ird, /* user data */ 0 ); CM_Free(msg); if (ird.cal == NULL) return; /* Here we go: put the VEVENT into the VCALENDAR. We now no longer * are responsible for "the_request"'s memory -- it will be freed * when we free "encaps". */ /* If the top-level component is *not* a VCALENDAR, we can drop it right * in. This will almost never happen. */ if (icalcomponent_isa(ird.cal) != ICAL_VCALENDAR_COMPONENT) { icalcomponent_add_component(encaps, ird.cal); } /* * In the more likely event that we're looking at a VCALENDAR with the VEVENT * and other components encapsulated inside, we have to extract them. */ else { for (c = icalcomponent_get_first_component(ird.cal, ICAL_ANY_COMPONENT); (c != NULL); c = icalcomponent_get_next_component(ird.cal, ICAL_ANY_COMPONENT)) { /* For VTIMEZONE components, suppress duplicates of the same tzid */ if (icalcomponent_isa(c) == ICAL_VTIMEZONE_COMPONENT) { icalproperty *p = icalcomponent_get_first_property(c, ICAL_TZID_PROPERTY); if (p) { const char *tzid = icalproperty_get_tzid(p); if (!icalcomponent_get_timezone(encaps, tzid)) { icalcomponent_add_component(encaps, icalcomponent_new_clone(c)); } } } /* All other types of components can go in verbatim */ else { icalcomponent_add_component(encaps, icalcomponent_new_clone(c)); } } icalcomponent_free(ird.cal); } } /* * Retrieve all of the calendar items in the current room, and output them * as a single icalendar object. */ void ical_getics(void) { icalcomponent *encaps = NULL; char *ser = NULL; if ( (CC->room.QRdefaultview != VIEW_CALENDAR) &&(CC->room.QRdefaultview != VIEW_TASKS) ) { cprintf("%d Not a calendar room\n", ERROR+NOT_HERE); return; /* Not an iCalendar-centric room */ } encaps = icalcomponent_new_vcalendar(); if (encaps == NULL) { syslog(LOG_ALERT, "ERROR: could not allocate component!\n"); cprintf("%d Could not allocate memory\n", ERROR+INTERNAL_ERROR); return; } cprintf("%d one big calendar\n", LISTING_FOLLOWS); /* Set the Product ID */ icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID)); /* Set the Version Number */ icalcomponent_add_property(encaps, icalproperty_new_version("2.0")); /* Set the method to PUBLISH */ icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH); /* Now go through the room encapsulating all calendar items. */ CtdlForEachMessage(MSGS_ALL, 0, NULL, NULL, NULL, ical_getics_backend, (void *) encaps ); ser = icalcomponent_as_ical_string_r(encaps); icalcomponent_free(encaps); /* Don't need this anymore. */ client_write(ser, strlen(ser)); free(ser); cprintf("\n000\n"); } /* * Helper callback function for ical_putics() to discover which TZID's we need. * Simply put the tzid name string into a hash table. After the callbacks are * done we'll go through them and attach the ones that we have. */ void ical_putics_grabtzids(icalparameter *param, void *data) { const char *tzid = icalparameter_get_tzid(param); HashList *keys = (HashList *) data; if ( (keys) && (tzid) && (!IsEmptyStr(tzid)) ) { Put(keys, tzid, strlen(tzid), strdup(tzid), NULL); } } /* * Delete all of the calendar items in the current room, and replace them * with calendar items from a client-supplied data stream. */ void ical_putics(void) { char *calstream = NULL; icalcomponent *cal; icalcomponent *c; icalcomponent *encaps = NULL; HashList *tzidlist = NULL; HashPos *HashPos; void *Value; const char *Key; long len; /* Only allow this operation if we're in a room containing a calendar or tasks view */ if ( (CC->room.QRdefaultview != VIEW_CALENDAR) &&(CC->room.QRdefaultview != VIEW_TASKS) ) { cprintf("%d Not a calendar room\n", ERROR+NOT_HERE); return; } /* Only allow this operation if we have permission to overwrite the existing calendar */ if (!CtdlDoIHavePermissionToDeleteMessagesFromThisRoom()) { cprintf("%d Permission denied.\n", ERROR+HIGHER_ACCESS_REQUIRED); return; } cprintf("%d Transmit data now\n", SEND_LISTING); calstream = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0); if (calstream == NULL) { return; } cal = icalcomponent_new_from_string(calstream); free(calstream); /* We got our data stream -- now do something with it. */ /* Delete the existing messages in the room, because we are overwriting * the entire calendar with an entire new (or updated) calendar. * (Careful: this opens an S_ROOMS critical section!) */ CtdlDeleteMessages(CC->room.QRname, NULL, 0, ""); /* If the top-level component is *not* a VCALENDAR, we can drop it right * in. This will almost never happen. */ if (icalcomponent_isa(cal) != ICAL_VCALENDAR_COMPONENT) { ical_write_to_cal(NULL, cal); } /* * In the more likely event that we're looking at a VCALENDAR with the VEVENT * and other components encapsulated inside, we have to extract them. */ else { for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT); (c != NULL); c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) { /* Non-VTIMEZONE components each get written as individual messages. * But we also need to attach the relevant VTIMEZONE components to them. */ if ( (icalcomponent_isa(c) != ICAL_VTIMEZONE_COMPONENT) && (encaps = icalcomponent_new_vcalendar()) ) { icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID)); icalcomponent_add_property(encaps, icalproperty_new_version("2.0")); icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH); /* Attach any needed timezones here */ tzidlist = NewHash(1, NULL); if (tzidlist) { icalcomponent_foreach_tzid(c, ical_putics_grabtzids, tzidlist); } HashPos = GetNewHashPos(tzidlist, 0); while (GetNextHashPos(tzidlist, HashPos, &len, &Key, &Value)) { syslog(LOG_DEBUG, "Attaching timezone '%s'\n", (char*) Value); icaltimezone *t = NULL; /* First look for a timezone attached to the original calendar */ t = icalcomponent_get_timezone(cal, Value); /* Try built-in tzdata if the right one wasn't attached */ if (!t) { t = icaltimezone_get_builtin_timezone(Value); } /* I've got a valid timezone to attach. */ if (t) { icalcomponent_add_component(encaps, icalcomponent_new_clone( icaltimezone_get_component(t) ) ); } } DeleteHashPos(&HashPos); DeleteHash(&tzidlist); /* Now attach the component itself (usually a VEVENT or VTODO) */ icalcomponent_add_component(encaps, icalcomponent_new_clone(c)); /* Write it to the message store */ ical_write_to_cal(NULL, encaps); icalcomponent_free(encaps); } } } icalcomponent_free(cal); } /* * All Citadel calendar commands from the client come through here. */ void cmd_ical(char *argbuf) { char subcmd[64]; long msgnum; char partnum[256]; char action[256]; char who[256]; extract_token(subcmd, argbuf, 0, '|', sizeof subcmd); /* Allow "test" and "freebusy" subcommands without logging in. */ if (!strcasecmp(subcmd, "test")) { cprintf("%d This server supports calendaring\n", CIT_OK); return; } if (!strcasecmp(subcmd, "freebusy")) { extract_token(who, argbuf, 1, '|', sizeof who); ical_freebusy(who); return; } if (!strcasecmp(subcmd, "sgi")) { CIT_ICAL->server_generated_invitations = (extract_int(argbuf, 1) ? 1 : 0) ; cprintf("%d %d\n", CIT_OK, CIT_ICAL->server_generated_invitations); return; } if (CtdlAccessCheck(ac_logged_in)) return; if (!strcasecmp(subcmd, "respond")) { msgnum = extract_long(argbuf, 1); extract_token(partnum, argbuf, 2, '|', sizeof partnum); extract_token(action, argbuf, 3, '|', sizeof action); ical_respond(msgnum, partnum, action); return; } if (!strcasecmp(subcmd, "handle_rsvp")) { msgnum = extract_long(argbuf, 1); extract_token(partnum, argbuf, 2, '|', sizeof partnum); extract_token(action, argbuf, 3, '|', sizeof action); ical_handle_rsvp(msgnum, partnum, action); return; } if (!strcasecmp(subcmd, "conflicts")) { msgnum = extract_long(argbuf, 1); extract_token(partnum, argbuf, 2, '|', sizeof partnum); ical_conflicts(msgnum, partnum); return; } if (!strcasecmp(subcmd, "getics")) { ical_getics(); return; } if (!strcasecmp(subcmd, "putics")) { ical_putics(); return; } cprintf("%d Invalid subcommand\n", ERROR + CMD_NOT_SUPPORTED); } /* * We don't know if the calendar room exists so we just create it at login */ void ical_CtdlCreateRoom(void) { struct ctdlroom qr; visit vbuf; /* Create the calendar room if it doesn't already exist */ CtdlCreateRoom(USERCALENDARROOM, 4, "", 0, 1, 0, VIEW_CALENDAR); /* Set expiration policy to manual; otherwise objects will be lost! */ if (CtdlGetRoomLock(&qr, USERCALENDARROOM)) { syslog(LOG_CRIT, "Couldn't get the user calendar room!\n"); return; } qr.QRep.expire_mode = EXPIRE_MANUAL; qr.QRdefaultview = VIEW_CALENDAR; /* 3 = calendar view */ CtdlPutRoomLock(&qr); /* Set the view to a calendar view */ CtdlGetRelationship(&vbuf, &CC->user, &qr); vbuf.v_view = VIEW_CALENDAR; CtdlSetRelationship(&vbuf, &CC->user, &qr); /* Create the tasks list room if it doesn't already exist */ CtdlCreateRoom(USERTASKSROOM, 4, "", 0, 1, 0, VIEW_TASKS); /* Set expiration policy to manual; otherwise objects will be lost! */ if (CtdlGetRoomLock(&qr, USERTASKSROOM)) { syslog(LOG_CRIT, "Couldn't get the user calendar room!\n"); return; } qr.QRep.expire_mode = EXPIRE_MANUAL; qr.QRdefaultview = VIEW_TASKS; CtdlPutRoomLock(&qr); /* Set the view to a task list view */ CtdlGetRelationship(&vbuf, &CC->user, &qr); vbuf.v_view = VIEW_TASKS; CtdlSetRelationship(&vbuf, &CC->user, &qr); /* Create the notes room if it doesn't already exist */ CtdlCreateRoom(USERNOTESROOM, 4, "", 0, 1, 0, VIEW_NOTES); /* Set expiration policy to manual; otherwise objects will be lost! */ if (CtdlGetRoomLock(&qr, USERNOTESROOM)) { syslog(LOG_CRIT, "Couldn't get the user calendar room!\n"); return; } qr.QRep.expire_mode = EXPIRE_MANUAL; qr.QRdefaultview = VIEW_NOTES; CtdlPutRoomLock(&qr); /* Set the view to a notes view */ CtdlGetRelationship(&vbuf, &CC->user, &qr); vbuf.v_view = VIEW_NOTES; CtdlSetRelationship(&vbuf, &CC->user, &qr); return; } /* * ical_send_out_invitations() is called by ical_saving_vevent() when it finds a VEVENT. * * top_level_cal is the highest available level calendar object. * cal is the subcomponent containing the VEVENT. * * Note: if you change the encapsulation code here, change it in WebCit's ical_encapsulate_subcomponent() */ void ical_send_out_invitations(icalcomponent *top_level_cal, icalcomponent *cal) { icalcomponent *the_request = NULL; char *serialized_request = NULL; icalcomponent *encaps = NULL; char *request_message_text = NULL; struct CtdlMessage *msg = NULL; recptypes *valid = NULL; char attendees_string[SIZ]; int num_attendees = 0; char this_attendee[256]; icalproperty *attendee = NULL; char summary_string[SIZ]; icalproperty *summary = NULL; size_t reqsize; icalproperty *p; struct icaltimetype t; const icaltimezone *attached_zones[5] = { NULL, NULL, NULL, NULL, NULL }; int i; const icaltimezone *z; int num_zones_attached = 0; int zone_already_attached; icalparameter *tzidp = NULL; const char *tzidc = NULL; if (cal == NULL) { syslog(LOG_ERR, "ERROR: trying to reply to NULL event?\n"); return; } /* If this is a VCALENDAR component, look for a VEVENT subcomponent. */ if (icalcomponent_isa(cal) == ICAL_VCALENDAR_COMPONENT) { ical_send_out_invitations(top_level_cal, icalcomponent_get_first_component( cal, ICAL_VEVENT_COMPONENT ) ); return; } /* Clone the event */ the_request = icalcomponent_new_clone(cal); if (the_request == NULL) { syslog(LOG_ERR, "ERROR: cannot clone calendar object\n"); return; } /* Extract the summary string -- we'll use it as the * message subject for the request */ strcpy(summary_string, "Meeting request"); summary = icalcomponent_get_first_property(the_request, ICAL_SUMMARY_PROPERTY); if (summary != NULL) { if (icalproperty_get_summary(summary)) { strcpy(summary_string, icalproperty_get_summary(summary) ); } } /* Determine who the recipients of this message are (the attendees) */ strcpy(attendees_string, ""); for (attendee = icalcomponent_get_first_property(the_request, ICAL_ATTENDEE_PROPERTY); attendee != NULL; attendee = icalcomponent_get_next_property(the_request, ICAL_ATTENDEE_PROPERTY)) { const char *ch = icalproperty_get_attendee(attendee); if ((ch != NULL) && !strncasecmp(ch, "MAILTO:", 7)) { safestrncpy(this_attendee, ch + 7, sizeof(this_attendee)); if (!CtdlIsMe(this_attendee, sizeof this_attendee)) { /* don't send an invitation to myself! */ snprintf(&attendees_string[strlen(attendees_string)], sizeof(attendees_string) - strlen(attendees_string), "%s, ", this_attendee ); ++num_attendees; } } } syslog(LOG_DEBUG, "<%d> attendees: <%s>\n", num_attendees, attendees_string); /* If there are no attendees, there are no invitations to send, so... * don't bother putting one together! Punch out, Maverick! */ if (num_attendees == 0) { icalcomponent_free(the_request); return; } /* Encapsulate the VEVENT component into a complete VCALENDAR */ encaps = icalcomponent_new_vcalendar(); if (encaps == NULL) { syslog(LOG_ALERT, "ERROR: could not allocate component!\n"); icalcomponent_free(the_request); return; } /* Set the Product ID */ icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID)); /* Set the Version Number */ icalcomponent_add_property(encaps, icalproperty_new_version("2.0")); /* Set the method to REQUEST */ icalcomponent_set_method(encaps, ICAL_METHOD_REQUEST); /* Look for properties containing timezone parameters, to see if we need to attach VTIMEZONEs */ for (p = icalcomponent_get_first_property(the_request, ICAL_ANY_PROPERTY); p != NULL; p = icalcomponent_get_next_property(the_request, ICAL_ANY_PROPERTY)) { if ( (icalproperty_isa(p) == ICAL_COMPLETED_PROPERTY) || (icalproperty_isa(p) == ICAL_CREATED_PROPERTY) || (icalproperty_isa(p) == ICAL_DATEMAX_PROPERTY) || (icalproperty_isa(p) == ICAL_DATEMIN_PROPERTY) || (icalproperty_isa(p) == ICAL_DTEND_PROPERTY) || (icalproperty_isa(p) == ICAL_DTSTAMP_PROPERTY) || (icalproperty_isa(p) == ICAL_DTSTART_PROPERTY) || (icalproperty_isa(p) == ICAL_DUE_PROPERTY) || (icalproperty_isa(p) == ICAL_EXDATE_PROPERTY) || (icalproperty_isa(p) == ICAL_LASTMODIFIED_PROPERTY) || (icalproperty_isa(p) == ICAL_MAXDATE_PROPERTY) || (icalproperty_isa(p) == ICAL_MINDATE_PROPERTY) || (icalproperty_isa(p) == ICAL_RECURRENCEID_PROPERTY) ) { t = icalproperty_get_dtstart(p); // it's safe to use dtstart for all of them /* Determine the tzid in order for some of the conditions below to work */ tzidp = icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER); if (tzidp) { tzidc = icalparameter_get_tzid(tzidp); } else { tzidc = NULL; } /* First see if there's a timezone attached to the data structure itself */ if (icaltime_is_utc(t)) { z = icaltimezone_get_utc_timezone(); } else { z = icaltime_get_timezone(t); } /* If not, try to determine the tzid from the parameter using attached zones */ if ((!z) && (tzidc)) { z = icalcomponent_get_timezone(top_level_cal, tzidc); } /* Still no good? Try our internal database */ if ((!z) && (tzidc)) { z = icaltimezone_get_builtin_timezone_from_tzid(tzidc); } if (z) { /* We have a valid timezone. Good. Now we need to attach it. */ zone_already_attached = 0; for (i=0; i<5; ++i) { if (z == attached_zones[i]) { /* We've already got this one, no need to attach another. */ ++zone_already_attached; } } if ((!zone_already_attached) && (num_zones_attached < 5)) { /* This is a new one, so attach it. */ attached_zones[num_zones_attached++] = z; } icalproperty_set_parameter(p, icalparameter_new_tzid(icaltimezone_get_tzid(z)) ); } } } /* Encapsulate any timezones we need */ if (num_zones_attached > 0) for (i=0; iuser, NULL, /* No single recipient here */ NULL, /* No single recipient here */ CC->room.QRname, 0, FMT_RFC822, NULL, NULL, summary_string, /* Use summary for subject */ NULL, request_message_text, NULL ); if (msg != NULL) { valid = validate_recipients(attendees_string, NULL, 0); CtdlSubmitMsg(msg, valid, "", QP_EADDR); CM_Free(msg); free_recipients(valid); } } free(serialized_request); } /* * When a calendar object is being saved, determine whether it's a VEVENT * and the user saving it is the organizer. If so, send out invitations * to any listed attendees. * * This function is recursive. The caller can simply supply the same object * as both arguments. When it recurses it will alter the second argument * while holding on to the top level object. This allows us to go back and * grab things like time zones which might be attached. * */ void ical_saving_vevent(icalcomponent *top_level_cal, icalcomponent *cal) { icalcomponent *c; icalproperty *organizer = NULL; char organizer_string[SIZ]; syslog(LOG_DEBUG, "ical_saving_vevent() has been called!\n"); /* Don't send out invitations unless the client wants us to. */ if (CIT_ICAL->server_generated_invitations == 0) { return; } /* Don't send out invitations if we've been asked not to. */ if (CIT_ICAL->avoid_sending_invitations > 0) { return; } strcpy(organizer_string, ""); /* * The VEVENT subcomponent is the one we're interested in. * Send out invitations if, and only if, this user is the Organizer. */ if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) { organizer = icalcomponent_get_first_property(cal, ICAL_ORGANIZER_PROPERTY); if (organizer != NULL) { if (icalproperty_get_organizer(organizer)) { strcpy(organizer_string, icalproperty_get_organizer(organizer)); } } if (!strncasecmp(organizer_string, "MAILTO:", 7)) { strcpy(organizer_string, &organizer_string[7]); striplt(organizer_string); /* * If the user saving the event is listed as the * organizer, then send out invitations. */ if (CtdlIsMe(organizer_string, sizeof organizer_string)) { ical_send_out_invitations(top_level_cal, cal); } } } /* If the component has subcomponents, recurse through them. */ for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT); (c != NULL); c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) { /* Recursively process subcomponent */ ical_saving_vevent(top_level_cal, c); } } /* * Back end for ical_obj_beforesave() * This hunts for the UID of the calendar event (becomes Citadel msg EUID), * the summary of the event (becomes message subject), * and the start time (becomes message date/time). */ void ical_obj_beforesave_backend(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata) { const char* pch; icalcomponent *cal, *nested_event, *nested_todo, *whole_cal; icalproperty *p; char new_uid[256] = ""; struct CtdlMessage *msg = (struct CtdlMessage *) cbuserdata; if (!msg) return; /* We're only interested in calendar data. */ if ( (strcasecmp(cbtype, "text/calendar")) && (strcasecmp(cbtype, "application/ics")) ) { return; } /* Hunt for the UID and drop it in * the "user data" pointer for the MIME parser. When * ical_obj_beforesave() sees it there, it'll set the Exclusive msgid * to that string. */ whole_cal = icalcomponent_new_from_string(content); cal = whole_cal; if (cal != NULL) { if (icalcomponent_isa(cal) == ICAL_VCALENDAR_COMPONENT) { nested_event = icalcomponent_get_first_component( cal, ICAL_VEVENT_COMPONENT); if (nested_event != NULL) { cal = nested_event; } else { nested_todo = icalcomponent_get_first_component( cal, ICAL_VTODO_COMPONENT); if (nested_todo != NULL) { cal = nested_todo; } } } if (cal != NULL) { /* Set the message EUID to the iCalendar UID */ p = ical_ctdl_get_subprop(cal, ICAL_UID_PROPERTY); if (p == NULL) { /* If there's no uid we must generate one */ generate_uuid(new_uid); icalcomponent_add_property(cal, icalproperty_new_uid(new_uid)); p = ical_ctdl_get_subprop(cal, ICAL_UID_PROPERTY); } if (p != NULL) { pch = icalproperty_get_comment(p); if (!IsEmptyStr(pch)) { CM_SetField(msg, eExclusiveID, pch, strlen(pch)); syslog(LOG_DEBUG, "Saving calendar UID <%s>\n", pch); } } /* Set the message subject to the iCalendar summary */ p = ical_ctdl_get_subprop(cal, ICAL_SUMMARY_PROPERTY); if (p != NULL) { pch = icalproperty_get_comment(p); if (!IsEmptyStr(pch)) { char *subj; subj = rfc2047encode(pch, strlen(pch)); CM_SetAsField(msg, eMsgSubject, &subj, strlen(subj)); } } /* Set the message date/time to the iCalendar start time */ p = ical_ctdl_get_subprop(cal, ICAL_DTSTART_PROPERTY); if (p != NULL) { time_t idtstart; idtstart = icaltime_as_timet(icalproperty_get_dtstart(p)); if (idtstart > 0) { CM_SetFieldLONG(msg, eTimestamp, idtstart); } } } icalcomponent_free(cal); if (whole_cal != cal) { icalcomponent_free(whole_cal); } } } /* * See if we need to prevent the object from being saved (we don't allow * MIME types other than text/calendar in "calendar" or "tasks" rooms). * * If the message is being saved, we also set various message header fields * using data found in the iCalendar object. */ int ical_obj_beforesave(struct CtdlMessage *msg, recptypes *recp) { /* First determine if this is a calendar or tasks room */ if ( (CC->room.QRdefaultview != VIEW_CALENDAR) && (CC->room.QRdefaultview != VIEW_TASKS) ) { return(0); /* Not an iCalendar-centric room */ } /* It must be an RFC822 message! */ if (msg->cm_format_type != 4) { syslog(LOG_DEBUG, "Rejecting non-RFC822 message\n"); return(1); /* You tried to save a non-RFC822 message! */ } if (CM_IsEmpty(msg, eMesageText)) { return(1); /* You tried to save a null message! */ } /* Do all of our lovely back-end parsing */ mime_parser(CM_RANGE(msg, eMesageText), *ical_obj_beforesave_backend, NULL, NULL, (void *)msg, 0 ); return(0); } /* * Things we need to do after saving a calendar event. */ void ical_obj_aftersave_backend(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, char *cbid, void *cbuserdata) { icalcomponent *cal; /* We're only interested in calendar items here. */ if ( (strcasecmp(cbtype, "text/calendar")) && (strcasecmp(cbtype, "application/ics")) ) { return; } /* Hunt for the UID and drop it in * the "user data" pointer for the MIME parser. When * ical_obj_beforesave() sees it there, it'll set the Exclusive msgid * to that string. */ if ( (!strcasecmp(cbtype, "text/calendar")) || (!strcasecmp(cbtype, "application/ics")) ) { cal = icalcomponent_new_from_string(content); if (cal != NULL) { ical_saving_vevent(cal, cal); icalcomponent_free(cal); } } } /* * Things we need to do after saving a calendar event. * (This will start back end tasks such as automatic generation of invitations, * if such actions are appropriate.) */ int ical_obj_aftersave(struct CtdlMessage *msg, recptypes *recp) { char roomname[ROOMNAMELEN]; /* * If this isn't the Calendar> room, no further action is necessary. */ /* First determine if this is our room */ CtdlMailboxName(roomname, sizeof roomname, &CC->user, USERCALENDARROOM); if (strcasecmp(roomname, CC->room.QRname)) { return(0); /* Not the Calendar room -- don't do anything. */ } /* It must be an RFC822 message! */ if (msg->cm_format_type != 4) return(1); /* Reject null messages */ if (CM_IsEmpty(msg, eMesageText)) return(1); /* Now recurse through it looking for our icalendar data */ mime_parser(CM_RANGE(msg, eMesageText), *ical_obj_aftersave_backend, NULL, NULL, NULL, 0 ); return(0); } void ical_session_startup(void) { CIT_ICAL = malloc(sizeof(struct cit_ical)); memset(CIT_ICAL, 0, sizeof(struct cit_ical)); } void ical_session_shutdown(void) { free(CIT_ICAL); } /* * Back end for ical_fixed_output() */ void ical_fixed_output_backend(icalcomponent *cal, int recursion_level ) { icalcomponent *c; icalproperty *p; char buf[256]; const char *ch; p = icalcomponent_get_first_property(cal, ICAL_SUMMARY_PROPERTY); if (p != NULL) { cprintf("%s\n", (const char *)icalproperty_get_comment(p)); } p = icalcomponent_get_first_property(cal, ICAL_LOCATION_PROPERTY); if (p != NULL) { cprintf("%s\n", (const char *)icalproperty_get_comment(p)); } p = icalcomponent_get_first_property(cal, ICAL_DESCRIPTION_PROPERTY); if (p != NULL) { cprintf("%s\n", (const char *)icalproperty_get_comment(p)); } /* If the component has attendees, iterate through them. */ for (p = icalcomponent_get_first_property(cal, ICAL_ATTENDEE_PROPERTY); (p != NULL); p = icalcomponent_get_next_property(cal, ICAL_ATTENDEE_PROPERTY)) { ch = icalproperty_get_attendee(p); if ((ch != NULL) && !strncasecmp(ch, "MAILTO:", 7)) { /* screen name or email address */ safestrncpy(buf, ch + 7, sizeof(buf)); striplt(buf); cprintf("%s ", buf); } cprintf("\n"); } /* If the component has subcomponents, recurse through them. */ for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT); (c != 0); c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) { /* Recursively process subcomponent */ ical_fixed_output_backend(c, recursion_level+1); } } /* * Function to output iCalendar data as plain text. Nobody uses MSG0 * anymore, so really this is just so we expose the vCard data to the full * text indexer. */ void ical_fixed_output(char *ptr, int len) { icalcomponent *cal; char *stringy_cal; stringy_cal = malloc(len + 1); safestrncpy(stringy_cal, ptr, len + 1); cal = icalcomponent_new_from_string(stringy_cal); free(stringy_cal); if (cal == NULL) { return; } ical_fixed_output_backend(cal, 0); /* Free the memory we obtained from libical's constructor */ icalcomponent_free(cal); } void serv_calendar_destroy(void) { icaltimezone_free_builtin_timezones(); } /* * Register this module with the Citadel server. */ CTDL_MODULE_INIT(calendar) { if (!threading) { /* Tell libical to return errors instead of aborting if it gets bad data */ icalerror_errors_are_fatal = 0; /* Use our own application prefix in tzid's generated from system tzdata */ icaltimezone_set_tzid_prefix("/citadel.org/"); /* Initialize our hook functions */ CtdlRegisterMessageHook(ical_obj_beforesave, EVT_BEFORESAVE); CtdlRegisterMessageHook(ical_obj_aftersave, EVT_AFTERSAVE); CtdlRegisterSessionHook(ical_CtdlCreateRoom, EVT_LOGIN, PRIO_LOGIN + 1); CtdlRegisterProtoHook(cmd_ical, "ICAL", "Citadel iCal commands"); CtdlRegisterSessionHook(ical_session_startup, EVT_START, PRIO_START + 1); CtdlRegisterSessionHook(ical_session_shutdown, EVT_STOP, PRIO_STOP + 80); CtdlRegisterFixedOutputHook("text/calendar", ical_fixed_output); CtdlRegisterFixedOutputHook("application/ics", ical_fixed_output); CtdlRegisterCleanupHook(serv_calendar_destroy); } /* return our module name for the log */ return "calendar"; } citadel-9.01/modules/inetcfg/0000755000000000000000000000000012507024051014631 5ustar rootrootcitadel-9.01/modules/inetcfg/serv_inetcfg.c0000644000000000000000000000704012507024051017454 0ustar rootroot/* * This module handles the loading/saving and maintenance of the * system's Internet configuration. It's not an optional component; I * wrote it as a module merely to keep things as clean and loosely coupled * as possible. * * Copyright (c) 1987-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * * */ #include "sysdep.h" #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "user_ops.h" #include "database.h" #include "msgbase.h" #include "internet_addressing.h" #include "genstamp.h" #include "domain.h" #include "ctdl_module.h" void inetcfg_setTo(struct CtdlMessage *msg) { char *conf; char buf[SIZ]; if (CM_IsEmpty(msg, eMesageText)) return; conf = strdup(msg->cm_fields[eMesageText]); if (conf != NULL) { do { extract_token(buf, conf, 0, '\n', sizeof buf); strcpy(conf, &conf[strlen(buf)+1]); } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) ); if (inetcfg != NULL) free(inetcfg); inetcfg = conf; } } /* * This handler detects changes being made to the system's Internet * configuration. */ int inetcfg_aftersave(struct CtdlMessage *msg, recptypes *recp) { char *ptr; int linelen; /* If this isn't the configuration room, or if this isn't a MIME * message, don't bother. */ if (strcasecmp(msg->cm_fields[eOriginalRoom], SYSCONFIGROOM)) return(0); if (msg->cm_format_type != 4) return(0); ptr = msg->cm_fields[eMesageText]; while (ptr != NULL) { linelen = strcspn(ptr, "\n"); if (linelen == 0) return(0); /* end of headers */ if (!strncasecmp(ptr, "Content-type: ", 14)) { if (!strncasecmp(&ptr[14], INTERNETCFG, strlen(INTERNETCFG))) { inetcfg_setTo(msg); /* changing configs */ } } ptr = strchr((char *)ptr, '\n'); if (ptr != NULL) ++ptr; } return(0); } void inetcfg_init_backend(long msgnum, void *userdata) { struct CtdlMessage *msg; msg = CtdlFetchMessage(msgnum, 1); if (msg != NULL) { inetcfg_setTo(msg); CM_Free(msg); } } void inetcfg_init(void) { if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) return; CtdlForEachMessage(MSGS_LAST, 1, NULL, INTERNETCFG, NULL, inetcfg_init_backend, NULL); } /*****************************************************************************/ /* MODULE INITIALIZATION STUFF */ /*****************************************************************************/ void clenaup_inetcfg(void) { char *buf; buf = inetcfg; inetcfg = NULL; if (buf != NULL) free(buf); } CTDL_MODULE_INIT(inetcfg) { if (!threading) { CtdlRegisterMessageHook(inetcfg_aftersave, EVT_AFTERSAVE); inetcfg_init(); CtdlRegisterCleanupHook(clenaup_inetcfg); } /* return our module name for the log */ return "inetcfg"; } citadel-9.01/modules/ctdlproto/0000755000000000000000000000000012507024051015224 5ustar rootrootcitadel-9.01/modules/ctdlproto/serv_file.c0000644000000000000000000005304412507024051017354 0ustar rootroot/* * Server functions which handle file transfers and room directories. */ #include #include #include #include "ctdl_module.h" #include "citserver.h" #include "support.h" #include "user_ops.h" /* * Server command to delete a file from a room's directory */ void cmd_delf(char *filename) { char pathname[64]; int a; if (CtdlAccessCheck(ac_room_aide)) return; if ((CC->room.QRflags & QR_DIRECTORY) == 0) { cprintf("%d No directory in this room.\n", ERROR + NOT_HERE); return; } if (IsEmptyStr(filename)) { cprintf("%d You must specify a file name.\n", ERROR + FILE_NOT_FOUND); return; } for (a = 0; !IsEmptyStr(&filename[a]); ++a) { if ( (filename[a] == '/') || (filename[a] == '\\') ) { filename[a] = '_'; } } snprintf(pathname, sizeof pathname, "%s/%s/%s", ctdl_file_dir, CC->room.QRdirname, filename); a = unlink(pathname); if (a == 0) { cprintf("%d File '%s' deleted.\n", CIT_OK, pathname); } else { cprintf("%d File '%s' not found.\n", ERROR + FILE_NOT_FOUND, pathname); } } /* * move a file from one room directory to another */ void cmd_movf(char *cmdbuf) { char filename[PATH_MAX]; char pathname[PATH_MAX]; char newpath[PATH_MAX]; char newroom[ROOMNAMELEN]; char buf[PATH_MAX]; int a; struct ctdlroom qrbuf; extract_token(filename, cmdbuf, 0, '|', sizeof filename); extract_token(newroom, cmdbuf, 1, '|', sizeof newroom); if (CtdlAccessCheck(ac_room_aide)) return; if ((CC->room.QRflags & QR_DIRECTORY) == 0) { cprintf("%d No directory in this room.\n", ERROR + NOT_HERE); return; } if (IsEmptyStr(filename)) { cprintf("%d You must specify a file name.\n", ERROR + FILE_NOT_FOUND); return; } for (a = 0; !IsEmptyStr(&filename[a]); ++a) { if ( (filename[a] == '/') || (filename[a] == '\\') ) { filename[a] = '_'; } } snprintf(pathname, sizeof pathname, "./files/%s/%s", CC->room.QRdirname, filename); if (access(pathname, 0) != 0) { cprintf("%d File '%s' not found.\n", ERROR + FILE_NOT_FOUND, pathname); return; } if (CtdlGetRoom(&qrbuf, newroom) != 0) { cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, newroom); return; } if ((qrbuf.QRflags & QR_DIRECTORY) == 0) { cprintf("%d '%s' is not a directory room.\n", ERROR + NOT_HERE, qrbuf.QRname); return; } snprintf(newpath, sizeof newpath, "./files/%s/%s", qrbuf.QRdirname, filename); if (link(pathname, newpath) != 0) { cprintf("%d Couldn't move file: %s\n", ERROR + INTERNAL_ERROR, strerror(errno)); return; } unlink(pathname); /* this is a crude method of copying the file description */ snprintf(buf, sizeof buf, "cat ./files/%s/filedir |grep \"%s\" >>./files/%s/filedir", CC->room.QRdirname, filename, qrbuf.QRdirname); system(buf); cprintf("%d File '%s' has been moved.\n", CIT_OK, filename); } /* * This code is common to all commands which open a file for downloading, * regardless of whether it's a file from the directory, an image, a network * spool file, a MIME attachment, etc. * It examines the file and displays the OK result code and some information * about the file. NOTE: this stuff is Unix dependent. */ void OpenCmdResult(char *filename, const char *mime_type) { struct stat statbuf; time_t modtime; long filesize; fstat(fileno(CC->download_fp), &statbuf); CC->download_fp_total = statbuf.st_size; filesize = (long) statbuf.st_size; modtime = (time_t) statbuf.st_mtime; cprintf("%d %ld|%ld|%s|%s\n", CIT_OK, filesize, (long)modtime, filename, mime_type); } /* * open a file for downloading */ void cmd_open(char *cmdbuf) { char filename[256]; char pathname[PATH_MAX]; int a; extract_token(filename, cmdbuf, 0, '|', sizeof filename); if (CtdlAccessCheck(ac_logged_in)) return; if ((CC->room.QRflags & QR_DIRECTORY) == 0) { cprintf("%d No directory in this room.\n", ERROR + NOT_HERE); return; } if (IsEmptyStr(filename)) { cprintf("%d You must specify a file name.\n", ERROR + FILE_NOT_FOUND); return; } if (strstr(filename, "../") != NULL) { cprintf("%d syntax error.\n", ERROR + ILLEGAL_VALUE); return; } if (CC->download_fp != NULL) { cprintf("%d You already have a download file open.\n", ERROR + RESOURCE_BUSY); return; } for (a = 0; !IsEmptyStr(&filename[a]); ++a) { if ( (filename[a] == '/') || (filename[a] == '\\') ) { filename[a] = '_'; } } snprintf(pathname, sizeof pathname, "%s/%s/%s", ctdl_file_dir, CC->room.QRdirname, filename); CC->download_fp = fopen(pathname, "r"); if (CC->download_fp == NULL) { cprintf("%d cannot open %s: %s\n", ERROR + INTERNAL_ERROR, pathname, strerror(errno)); return; } OpenCmdResult(filename, "application/octet-stream"); } /* * open an image file */ void cmd_oimg(char *cmdbuf) { char filename[256]; char pathname[PATH_MAX]; char MimeTestBuf[32]; struct ctdluser usbuf; char which_user[USERNAME_SIZE]; int which_floor; int a; int rv; extract_token(filename, cmdbuf, 0, '|', sizeof filename); if (IsEmptyStr(filename)) { cprintf("%d You must specify a file name.\n", ERROR + FILE_NOT_FOUND); return; } if (CC->download_fp != NULL) { cprintf("%d You already have a download file open.\n", ERROR + RESOURCE_BUSY); return; } if (!strcasecmp(filename, "_userpic_")) { extract_token(which_user, cmdbuf, 1, '|', sizeof which_user); if (CtdlGetUser(&usbuf, which_user) != 0) { cprintf("%d No such user.\n", ERROR + NO_SUCH_USER); return; } snprintf(pathname, sizeof pathname, "%s/%ld", ctdl_usrpic_dir, usbuf.usernum); } else if (!strcasecmp(filename, "_floorpic_")) { which_floor = extract_int(cmdbuf, 1); snprintf(pathname, sizeof pathname, "%s/floor.%d", ctdl_image_dir, which_floor); } else if (!strcasecmp(filename, "_roompic_")) { assoc_file_name(pathname, sizeof pathname, &CC->room, ctdl_image_dir); } else { for (a = 0; !IsEmptyStr(&filename[a]); ++a) { filename[a] = tolower(filename[a]); if ( (filename[a] == '/') || (filename[a] == '\\') ) { filename[a] = '_'; } } if (strstr(filename, "../") != NULL) { cprintf("%d syntax error.\n", ERROR + ILLEGAL_VALUE); return; } snprintf(pathname, sizeof pathname, "%s/%s", ctdl_image_dir, filename); } CC->download_fp = fopen(pathname, "rb"); if (CC->download_fp == NULL) { strcat(pathname, ".gif"); CC->download_fp = fopen(pathname, "rb"); } if (CC->download_fp == NULL) { cprintf("%d Cannot open %s: %s\n", ERROR + FILE_NOT_FOUND, pathname, strerror(errno)); return; } rv = fread(&MimeTestBuf[0], 1, 32, CC->download_fp); if (rv == -1) { cprintf("%d Cannot access %s: %s\n", ERROR + FILE_NOT_FOUND, pathname, strerror(errno)); return; } rewind (CC->download_fp); OpenCmdResult(pathname, GuessMimeType(&MimeTestBuf[0], 32)); } /* * open a file for uploading */ void cmd_uopn(char *cmdbuf) { int a; extract_token(CC->upl_file, cmdbuf, 0, '|', sizeof CC->upl_file); extract_token(CC->upl_mimetype, cmdbuf, 1, '|', sizeof CC->upl_mimetype); extract_token(CC->upl_comment, cmdbuf, 2, '|', sizeof CC->upl_comment); if (CtdlAccessCheck(ac_logged_in)) return; if ((CC->room.QRflags & QR_DIRECTORY) == 0) { cprintf("%d No directory in this room.\n", ERROR + NOT_HERE); return; } if (IsEmptyStr(CC->upl_file)) { cprintf("%d You must specify a file name.\n", ERROR + FILE_NOT_FOUND); return; } if (CC->upload_fp != NULL) { cprintf("%d You already have a upload file open.\n", ERROR + RESOURCE_BUSY); return; } for (a = 0; !IsEmptyStr(&CC->upl_file[a]); ++a) { if ( (CC->upl_file[a] == '/') || (CC->upl_file[a] == '\\') ) { CC->upl_file[a] = '_'; } } snprintf(CC->upl_path, sizeof CC->upl_path, "%s/%s/%s", ctdl_file_dir, CC->room.QRdirname, CC->upl_file); snprintf(CC->upl_filedir, sizeof CC->upl_filedir, "%s/%s/filedir", ctdl_file_dir, CC->room.QRdirname); CC->upload_fp = fopen(CC->upl_path, "r"); if (CC->upload_fp != NULL) { fclose(CC->upload_fp); CC->upload_fp = NULL; cprintf("%d '%s' already exists\n", ERROR + ALREADY_EXISTS, CC->upl_path); return; } CC->upload_fp = fopen(CC->upl_path, "wb"); if (CC->upload_fp == NULL) { cprintf("%d Cannot open %s: %s\n", ERROR + INTERNAL_ERROR, CC->upl_path, strerror(errno)); return; } cprintf("%d Ok\n", CIT_OK); } /* * open an image file for uploading */ void cmd_uimg(char *cmdbuf) { int is_this_for_real; char basenm[256]; int which_floor; int a; if (num_parms(cmdbuf) < 2) { cprintf("%d Usage error.\n", ERROR + ILLEGAL_VALUE); return; } is_this_for_real = extract_int(cmdbuf, 0); extract_token(CC->upl_mimetype, cmdbuf, 1, '|', sizeof CC->upl_mimetype); extract_token(basenm, cmdbuf, 2, '|', sizeof basenm); if (CC->upload_fp != NULL) { cprintf("%d You already have an upload file open.\n", ERROR + RESOURCE_BUSY); return; } strcpy(CC->upl_path, ""); for (a = 0; !IsEmptyStr(&basenm[a]); ++a) { basenm[a] = tolower(basenm[a]); if ( (basenm[a] == '/') || (basenm[a] == '\\') ) { basenm[a] = '_'; } } if (CC->user.axlevel >= AxAideU) { snprintf(CC->upl_path, sizeof CC->upl_path, "%s/%s", ctdl_image_dir, basenm); } if (!strcasecmp(basenm, "_userpic_")) { snprintf(CC->upl_path, sizeof CC->upl_path, "%s/%ld.gif", ctdl_usrpic_dir, CC->user.usernum); } if ((!strcasecmp(basenm, "_floorpic_")) && (CC->user.axlevel >= AxAideU)) { which_floor = extract_int(cmdbuf, 2); snprintf(CC->upl_path, sizeof CC->upl_path, "%s/floor.%d.gif", ctdl_image_dir, which_floor); } if ((!strcasecmp(basenm, "_roompic_")) && (is_room_aide())) { assoc_file_name(CC->upl_path, sizeof CC->upl_path, &CC->room, ctdl_image_dir); } if (IsEmptyStr(CC->upl_path)) { cprintf("%d Higher access required.\n", ERROR + HIGHER_ACCESS_REQUIRED); return; } if (is_this_for_real == 0) { cprintf("%d Ok to send image\n", CIT_OK); return; } CC->upload_fp = fopen(CC->upl_path, "wb"); if (CC->upload_fp == NULL) { cprintf("%d Cannot open %s: %s\n", ERROR + INTERNAL_ERROR, CC->upl_path, strerror(errno)); return; } cprintf("%d Ok\n", CIT_OK); CC->upload_type = UPL_IMAGE; } /* * close the download file */ void cmd_clos(char *cmdbuf) { char buf[256]; if (CC->download_fp == NULL) { cprintf("%d You don't have a download file open.\n", ERROR + RESOURCE_NOT_OPEN); return; } fclose(CC->download_fp); CC->download_fp = NULL; if (CC->dl_is_net == 1) { CC->dl_is_net = 0; snprintf(buf, sizeof buf, "%s/%s", ctdl_netout_dir, CC->net_node); unlink(buf); } cprintf("%d Ok\n", CIT_OK); } /* * abort an upload */ void abort_upl(CitContext *who) { if (who->upload_fp != NULL) { fclose(who->upload_fp); who->upload_fp = NULL; unlink(CC->upl_path); } } /* * close the upload file */ void cmd_ucls(char *cmd) { FILE *fp; char upload_notice[512]; static int seq = 0; if (CC->upload_fp == NULL) { cprintf("%d You don't have an upload file open.\n", ERROR + RESOURCE_NOT_OPEN); return; } fclose(CC->upload_fp); CC->upload_fp = NULL; if ((!strcasecmp(cmd, "1")) && (CC->upload_type != UPL_FILE)) { cprintf("%d Upload completed.\n", CIT_OK); if (CC->upload_type == UPL_NET) { char final_filename[PATH_MAX]; snprintf(final_filename, sizeof final_filename, "%s/%s.%04lx.%04x", ctdl_netin_dir, CC->net_node, (long)getpid(), ++seq ); if (link(CC->upl_path, final_filename) == 0) { syslog(LOG_INFO, "UCLS: updoaded %s\n", final_filename); unlink(CC->upl_path); } else { syslog(LOG_ALERT, "Cannot link %s to %s: %s\n", CC->upl_path, final_filename, strerror(errno) ); } /* FIXME ... here we need to trigger a network run */ } CC->upload_type = UPL_FILE; return; } if (!strcasecmp(cmd, "1")) { cprintf("%d File '%s' saved.\n", CIT_OK, CC->upl_path); fp = fopen(CC->upl_filedir, "a"); if (fp == NULL) { fp = fopen(CC->upl_filedir, "w"); } if (fp != NULL) { fprintf(fp, "%s %s %s\n", CC->upl_file, CC->upl_mimetype, CC->upl_comment); fclose(fp); } /* put together an upload notice */ snprintf(upload_notice, sizeof upload_notice, "NEW UPLOAD: '%s'\n %s\n%s\n", CC->upl_file, CC->upl_comment, CC->upl_mimetype); quickie_message(CC->curr_user, NULL, NULL, CC->room.QRname, upload_notice, 0, NULL); } else { abort_upl(CC); cprintf("%d File '%s' aborted.\n", CIT_OK, CC->upl_path); } } /* * read from the download file */ void cmd_read(char *cmdbuf) { long start_pos; size_t bytes; char buf[SIZ]; int rc; /* The client will transmit its requested offset and byte count */ start_pos = extract_long(cmdbuf, 0); bytes = extract_int(cmdbuf, 1); if ((start_pos < 0) || (bytes <= 0)) { cprintf("%d you have to specify a value > 0.\n", ERROR + ILLEGAL_VALUE); return; } if (CC->download_fp == NULL) { cprintf("%d You don't have a download file open.\n", ERROR + RESOURCE_NOT_OPEN); return; } /* If necessary, reduce the byte count to the size of our buffer */ if (bytes > sizeof(buf)) { bytes = sizeof(buf); } rc = fseek(CC->download_fp, start_pos, 0); if (rc < 0) { cprintf("%d your file is smaller then %ld.\n", ERROR + ILLEGAL_VALUE, start_pos); syslog(LOG_ALERT, "your file %s is smaller then %ld. [%s]\n", CC->upl_path, start_pos, strerror(errno)); return; } bytes = fread(buf, 1, bytes, CC->download_fp); if (bytes > 0) { /* Tell the client the actual byte count and transmit it */ cprintf("%d %d\n", BINARY_FOLLOWS, (int)bytes); client_write(buf, bytes); } else { cprintf("%d %s\n", ERROR, strerror(errno)); } } /* * write to the upload file */ void cmd_writ(char *cmdbuf) { int bytes; char *buf; int rv; unbuffer_output(); bytes = extract_int(cmdbuf, 0); if (CC->upload_fp == NULL) { cprintf("%d You don't have an upload file open.\n", ERROR + RESOURCE_NOT_OPEN); return; } if (bytes <= 0) { cprintf("%d you have to specify a value > 0.\n", ERROR + ILLEGAL_VALUE); return; } if (bytes > 100000) { bytes = 100000; } cprintf("%d %d\n", SEND_BINARY, bytes); buf = malloc(bytes + 1); client_read(buf, bytes); rv = fwrite(buf, bytes, 1, CC->upload_fp); if (rv == -1) { syslog(LOG_EMERG, "Couldn't write: %s\n", strerror(errno)); } free(buf); } /* * cmd_ndop() - open a network spool file for downloading */ void cmd_ndop(char *cmdbuf) { char pathname[256]; struct stat statbuf; if (IsEmptyStr(CC->net_node)) { cprintf("%d Not authenticated as a network node.\n", ERROR + NOT_LOGGED_IN); return; } if (CC->download_fp != NULL) { cprintf("%d You already have a download file open.\n", ERROR + RESOURCE_BUSY); return; } snprintf(pathname, sizeof pathname, "%s/%s", ctdl_netout_dir, CC->net_node); /* first open the file in append mode in order to create a * zero-length file if it doesn't already exist */ CC->download_fp = fopen(pathname, "a"); if (CC->download_fp != NULL) fclose(CC->download_fp); /* now open it */ CC->download_fp = fopen(pathname, "r"); if (CC->download_fp == NULL) { cprintf("%d cannot open %s: %s\n", ERROR + INTERNAL_ERROR, pathname, strerror(errno)); return; } /* set this flag so other routines know that the download file * currently open is a network spool file */ CC->dl_is_net = 1; stat(pathname, &statbuf); CC->download_fp_total = statbuf.st_size; cprintf("%d %ld\n", CIT_OK, (long)statbuf.st_size); } /* * cmd_nuop() - open a network spool file for uploading */ void cmd_nuop(char *cmdbuf) { static int seq = 1; if (IsEmptyStr(CC->net_node)) { cprintf("%d Not authenticated as a network node.\n", ERROR + NOT_LOGGED_IN); return; } if (CC->upload_fp != NULL) { cprintf("%d You already have an upload file open.\n", ERROR + RESOURCE_BUSY); return; } snprintf(CC->upl_path, sizeof CC->upl_path, "%s/%s.%04lx.%04x", ctdl_nettmp_dir, CC->net_node, (long)getpid(), ++seq); CC->upload_fp = fopen(CC->upl_path, "r"); if (CC->upload_fp != NULL) { fclose(CC->upload_fp); CC->upload_fp = NULL; cprintf("%d '%s' already exists\n", ERROR + ALREADY_EXISTS, CC->upl_path); return; } CC->upload_fp = fopen(CC->upl_path, "w"); if (CC->upload_fp == NULL) { cprintf("%d Cannot open %s: %s\n", ERROR + INTERNAL_ERROR, CC->upl_path, strerror(errno)); return; } CC->upload_type = UPL_NET; cprintf("%d Ok\n", CIT_OK); } void files_logout_hook(void) { CitContext *CCC = MyContext(); /* * If there is a download in progress, abort it. */ if (CCC->download_fp != NULL) { fclose(CCC->download_fp); CCC->download_fp = NULL; } /* * If there is an upload in progress, abort it. */ if (CCC->upload_fp != NULL) { abort_upl(CCC); } } /* * help_subst() - support routine for help file viewer */ void help_subst(char *strbuf, char *source, char *dest) { char workbuf[SIZ]; int p; while (p = pattern2(strbuf, source), (p >= 0)) { strcpy(workbuf, &strbuf[p + strlen(source)]); strcpy(&strbuf[p], dest); strcat(strbuf, workbuf); } } void do_help_subst(char *buffer) { char buf2[16]; help_subst(buffer, "^nodename", config.c_nodename); help_subst(buffer, "^humannode", config.c_humannode); help_subst(buffer, "^fqdn", config.c_fqdn); help_subst(buffer, "^username", CC->user.fullname); snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum); help_subst(buffer, "^usernum", buf2); help_subst(buffer, "^sysadm", config.c_sysadm); help_subst(buffer, "^variantname", CITADEL); snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions); help_subst(buffer, "^maxsessions", buf2); help_subst(buffer, "^bbsdir", ctdl_message_dir); } typedef const char *ccharp; /* * display system messages or help */ void cmd_mesg(char *mname) { FILE *mfp; char targ[256]; char buf[256]; char buf2[256]; char *dirs[2]; DIR *dp; struct dirent *d; extract_token(buf, mname, 0, '|', sizeof buf); dirs[0] = strdup(ctdl_message_dir); dirs[1] = strdup(ctdl_hlp_dir); snprintf(buf2, sizeof buf2, "%s.%d.%d", buf, CC->cs_clientdev, CC->cs_clienttyp); /* If the client requested "?" then produce a listing */ if (!strcmp(buf, "?")) { cprintf("%d %s\n", LISTING_FOLLOWS, buf); dp = opendir(dirs[1]); if (dp != NULL) { while (d = readdir(dp), d != NULL) { if (d->d_name[0] != '.') { cprintf(" %s\n", d->d_name); } } closedir(dp); } cprintf("000\n"); free(dirs[0]); free(dirs[1]); return; } /* Otherwise, look for the requested file by name. */ else { mesg_locate(targ, sizeof targ, buf2, 2, (const ccharp*)dirs); if (IsEmptyStr(targ)) { snprintf(buf2, sizeof buf2, "%s.%d", buf, CC->cs_clientdev); mesg_locate(targ, sizeof targ, buf2, 2, (const ccharp*)dirs); if (IsEmptyStr(targ)) { mesg_locate(targ, sizeof targ, buf, 2, (const ccharp*)dirs); } } } free(dirs[0]); free(dirs[1]); if (IsEmptyStr(targ)) { cprintf("%d '%s' not found. (Searching in %s and %s)\n", ERROR + FILE_NOT_FOUND, mname, ctdl_message_dir, ctdl_hlp_dir ); return; } mfp = fopen(targ, "r"); if (mfp==NULL) { cprintf("%d Cannot open '%s': %s\n", ERROR + INTERNAL_ERROR, targ, strerror(errno)); return; } cprintf("%d %s\n", LISTING_FOLLOWS,buf); while (fgets(buf, (sizeof buf - 1), mfp) != NULL) { buf[strlen(buf)-1] = 0; do_help_subst(buf); cprintf("%s\n",buf); } fclose(mfp); cprintf("000\n"); } /* * enter system messages or help */ void cmd_emsg(char *mname) { FILE *mfp; char targ[256]; char buf[256]; char *dirs[2]; int a; unbuffer_output(); if (CtdlAccessCheck(ac_aide)) return; extract_token(buf, mname, 0, '|', sizeof buf); for (a=0; !IsEmptyStr(&buf[a]); ++a) { /* security measure */ if (buf[a] == '/') buf[a] = '.'; } dirs[0] = strdup(ctdl_message_dir); dirs[1] = strdup(ctdl_hlp_dir); mesg_locate(targ, sizeof targ, buf, 2, (const ccharp*)dirs); free(dirs[0]); free(dirs[1]); if (IsEmptyStr(targ)) { snprintf(targ, sizeof targ, "%s/%s", ctdl_hlp_dir, buf); } mfp = fopen(targ,"w"); if (mfp==NULL) { cprintf("%d Cannot open '%s': %s\n", ERROR + INTERNAL_ERROR, targ, strerror(errno)); return; } cprintf("%d %s\n", SEND_LISTING, targ); while (client_getln(buf, sizeof buf) >=0 && strcmp(buf, "000")) { fprintf(mfp, "%s\n", buf); } fclose(mfp); } /*****************************************************************************/ /* MODULE INITIALIZATION STUFF */ /*****************************************************************************/ CTDL_MODULE_INIT(file_ops) { if (!threading) { CtdlRegisterSessionHook(files_logout_hook, EVT_LOGOUT, PRIO_LOGOUT + 8); CtdlRegisterProtoHook(cmd_delf, "DELF", "Delete a file"); CtdlRegisterProtoHook(cmd_movf, "MOVF", "Move a file"); CtdlRegisterProtoHook(cmd_open, "OPEN", "Open a download file transfer"); CtdlRegisterProtoHook(cmd_clos, "CLOS", "Close a download file transfer"); CtdlRegisterProtoHook(cmd_uopn, "UOPN", "Open an upload file transfer"); CtdlRegisterProtoHook(cmd_ucls, "UCLS", "Close an upload file transfer"); CtdlRegisterProtoHook(cmd_read, "READ", "File transfer read operation"); CtdlRegisterProtoHook(cmd_writ, "WRIT", "File transfer write operation"); CtdlRegisterProtoHook(cmd_ndop, "NDOP", "Open a network spool file for download"); CtdlRegisterProtoHook(cmd_nuop, "NUOP", "Open a network spool file for upload"); CtdlRegisterProtoHook(cmd_oimg, "OIMG", "Open an image file for download"); CtdlRegisterProtoHook(cmd_uimg, "UIMG", "Upload an image file"); CtdlRegisterProtoHook(cmd_mesg, "MESG", "fetch system banners"); CtdlRegisterProtoHook(cmd_emsg, "EMSG", "submit system banners"); } /* return our Subversion id for the Log */ return "file_ops"; } citadel-9.01/modules/ctdlproto/files.h0000644000000000000000000000024412507024051016477 0ustar rootroot#ifndef FILE_OPS_H #define FILE_OPS_H #include "context.h" void OpenCmdResult (char *, const char *); void abort_upl (CitContext *who); #endif /* FILE_OPS_H */ citadel-9.01/modules/ctdlproto/serv_syscmds.c0000644000000000000000000001043512507024051020117 0ustar rootroot /* * Main source module for the Citadel server * * Copyright (c) 1987-2011 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include "serv_extensions.h" #include "ctdl_module.h" void cmd_log_get(char *argbuf) { long HKLen; const char *ch; HashPos *Pos; void *vptr; if (CtdlAccessCheck(ac_aide)) return; cprintf("%d Log modules enabled:\n", LISTING_FOLLOWS); Pos = GetNewHashPos(LogDebugEntryTable, 0); while (GetNextHashPos(LogDebugEntryTable, Pos, &HKLen, &ch, &vptr)) { LogDebugEntry *E = (LogDebugEntry*)vptr; cprintf("%s|%d\n", ch, *E->LogP); } DeleteHashPos(&Pos); cprintf("000\n"); } void cmd_log_set(char *argbuf) { void *vptr; int lset; int wlen; char which[SIZ] = ""; if (CtdlAccessCheck(ac_aide)) return; wlen = extract_token(which, argbuf, 0, '|', sizeof(which)); if (wlen < 0) wlen = 0; lset = extract_int(argbuf, 1); if (lset != 0) lset = 1; if (GetHash(LogDebugEntryTable, which, wlen, &vptr) && (vptr != NULL)) { LogDebugEntry *E = (LogDebugEntry*)vptr; E->F(lset); cprintf("%d %s|%d\n", CIT_OK, which, lset); } else { cprintf("%d Log setting %s not known\n", ERROR, which); } } /* * Shut down the server */ void cmd_down(char *argbuf) { char *Reply ="%d Shutting down server. Goodbye.\n"; if (CtdlAccessCheck(ac_aide)) return; if (!IsEmptyStr(argbuf)) { int state = CIT_OK; restart_server = extract_int(argbuf, 0); if (restart_server > 0) { Reply = "%d citserver will now shut down and automatically restart.\n"; } if ((restart_server > 0) && !running_as_daemon) { syslog(LOG_ERR, "The user requested restart, but not running as daemon! Geronimooooooo!\n"); Reply = "%d Warning: citserver is not running in daemon mode and is therefore unlikely to restart automatically.\n"; state = ERROR; } cprintf(Reply, state); } else { cprintf(Reply, CIT_OK + SERVER_SHUTTING_DOWN); } CC->kill_me = KILLME_SERVER_SHUTTING_DOWN; server_shutting_down = 1; } /* * Halt the server without exiting the server process. */ void cmd_halt(char *argbuf) { if (CtdlAccessCheck(ac_aide)) return; cprintf("%d Halting server. Goodbye.\n", CIT_OK); server_shutting_down = 1; shutdown_and_halt = 1; } /* * Schedule or cancel a server shutdown */ void cmd_scdn(char *argbuf) { int new_state; int state = CIT_OK; char *Reply = "%d %d\n"; if (CtdlAccessCheck(ac_aide)) return; new_state = extract_int(argbuf, 0); if ((new_state == 2) || (new_state == 3)) { restart_server = 1; if (!running_as_daemon) { syslog(LOG_ERR, "The user requested restart, but not running as deamon! Geronimooooooo!\n"); Reply = "%d %d Warning, not running in deamon mode. maybe we will come up again, but don't lean on it.\n"; state = ERROR; } restart_server = extract_int(argbuf, 0); new_state -= 2; } if ((new_state == 0) || (new_state == 1)) { ScheduledShutdown = new_state; } cprintf(Reply, state, ScheduledShutdown); } /* * Manually initiate log file cull. */ void cmd_cull(char *argbuf) { if (CtdlAccessCheck(ac_internal)) return; cdb_cull_logs(); cprintf("%d Database log file cull completed.\n", CIT_OK); } /*****************************************************************************/ /* MODULE INITIALIZATION STUFF */ /*****************************************************************************/ CTDL_MODULE_INIT(syscmd) { if (!threading) { CtdlRegisterProtoHook(cmd_log_get, "LOGP", "Print Log-parameters"); CtdlRegisterProtoHook(cmd_log_set, "LOGS", "Set Log-parameters"); CtdlRegisterProtoHook(cmd_down, "DOWN", "perform a server shutdown"); CtdlRegisterProtoHook(cmd_halt, "HALT", "halt the server without exiting the server process"); CtdlRegisterProtoHook(cmd_scdn, "SCDN", "schedule or cancel a server shutdown"); CtdlRegisterProtoHook(cmd_cull, "CULL", "Cull database logs"); } /* return our id for the Log */ return "syscmd"; } citadel-9.01/modules/ctdlproto/serv_session.c0000644000000000000000000001452312507024051020117 0ustar rootroot/* * Server functions which perform operations on user objects. * * Copyright (c) 1987-2011 by the citadel.org team * * This program is open source software; you can redistribute it and/or * modify it under the terms of the GNU General Public License, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include "citserver.h" #include "svn_revision.h" #include "ctdl_module.h" void cmd_noop(char *argbuf) { cprintf("%d%cok\n", CIT_OK, CtdlCheckExpress() ); } void cmd_qnop(char *argbuf) { /* do nothing, this command returns no response */ } /* * Set or unset asynchronous protocol mode */ void cmd_asyn(char *argbuf) { int new_state; new_state = extract_int(argbuf, 0); if ((new_state == 0) || (new_state == 1)) { CC->is_async = new_state; } cprintf("%d %d\n", CIT_OK, CC->is_async); } /* * cmd_info() - tell the client about this server */ void cmd_info(char *cmdbuf) { cprintf("%d Server info:\n", LISTING_FOLLOWS); cprintf("%d\n", CC->cs_pid); cprintf("%s\n", config.c_nodename); cprintf("%s\n", config.c_humannode); cprintf("%s\n", config.c_fqdn); cprintf("%s\n", CITADEL); cprintf("%d\n", REV_LEVEL); cprintf("%s\n", config.c_site_location); cprintf("%s\n", config.c_sysadm); cprintf("%d\n", SERVER_TYPE); cprintf("%s\n", config.c_moreprompt); cprintf("1\n"); /* 1 = yes, this system supports floors */ cprintf("1\n"); /* 1 = we support the extended paging options */ cprintf("\n"); /* nonce no longer supported */ cprintf("1\n"); /* 1 = yes, this system supports the QNOP command */ #ifdef HAVE_LDAP cprintf("1\n"); /* 1 = yes, this server is LDAP-enabled */ #else cprintf("0\n"); /* 1 = no, this server is not LDAP-enabled */ #endif if ((config.c_auth_mode == AUTHMODE_NATIVE) && (config.c_disable_newu == 0)) { cprintf("%d\n", config.c_disable_newu); } else { cprintf("1\n"); /* "create new user" does not work with non-native auth modes */ } cprintf("%s\n", config.c_default_cal_zone); /* thread load averages -- temporarily disabled during refactoring of this code */ cprintf("0\n"); /* load average */ cprintf("0\n"); /* worker average */ cprintf("0\n"); /* thread count */ cprintf("1\n"); /* yes, Sieve mail filtering is supported */ cprintf("%d\n", config.c_enable_fulltext); cprintf("%s\n", svn_revision()); if (config.c_auth_mode == AUTHMODE_NATIVE) { cprintf("%d\n", openid_level_supported); /* OpenID is enabled when using native auth */ } else { cprintf("0\n"); /* OpenID is disabled when using non-native auth */ } cprintf("%d\n", config.c_guest_logins); cprintf("000\n"); } /* * echo */ void cmd_echo(char *etext) { cprintf("%d %s\n", CIT_OK, etext); } /* * get the paginator prompt */ void cmd_more(char *argbuf) { cprintf("%d %s\n", CIT_OK, config.c_moreprompt); } /* * the client is identifying itself to the server */ void cmd_iden(char *argbuf) { CitContext *CCC = MyContext(); int dev_code; int cli_code; int rev_level; char desc[128]; char from_host[128]; if (num_parms(argbuf)<4) { cprintf("%d usage error\n", ERROR + ILLEGAL_VALUE); return; } dev_code = extract_int(argbuf,0); cli_code = extract_int(argbuf,1); rev_level = extract_int(argbuf,2); extract_token(desc, argbuf, 3, '|', sizeof desc); safestrncpy(from_host, config.c_fqdn, sizeof from_host); from_host[sizeof from_host - 1] = 0; if (num_parms(argbuf)>=5) extract_token(from_host, argbuf, 4, '|', sizeof from_host); CCC->cs_clientdev = dev_code; CCC->cs_clienttyp = cli_code; CCC->cs_clientver = rev_level; safestrncpy(CCC->cs_clientname, desc, sizeof CCC->cs_clientname); CCC->cs_clientname[31] = 0; /* For local sockets and public clients, trust the hostname supplied by the client */ if ( (CCC->is_local_socket) || (CtdlIsPublicClient()) ) { safestrncpy(CCC->cs_host, from_host, sizeof CCC->cs_host); CCC->cs_host[sizeof CCC->cs_host - 1] = 0; CCC->cs_addr[0] = 0; } syslog(LOG_NOTICE, "Client %d/%d/%01d.%02d (%s) from %s\n", dev_code, cli_code, (rev_level / 100), (rev_level % 100), desc, CCC->cs_host ); cprintf("%d Ok\n",CIT_OK); } /* * Terminate another running session */ void cmd_term(char *cmdbuf) { int session_num; int terminated = 0; session_num = extract_int(cmdbuf, 0); terminated = CtdlTerminateOtherSession(session_num); if (terminated < 0) { cprintf("%d You can't kill your own session.\n", ERROR + ILLEGAL_VALUE); return; } if (terminated & TERM_FOUND) { if (terminated == TERM_KILLED) { cprintf("%d Session terminated.\n", CIT_OK); } else { cprintf("%d You are not allowed to do that.\n", ERROR + HIGHER_ACCESS_REQUIRED); } } else { cprintf("%d No such session.\n", ERROR + ILLEGAL_VALUE); } } void cmd_time(char *argbuf) { time_t tv; struct tm tmp; tv = time(NULL); localtime_r(&tv, &tmp); /* timezone and daylight global variables are not portable. */ #ifdef HAVE_STRUCT_TM_TM_GMTOFF cprintf("%d %ld|%ld|%d|%ld\n", CIT_OK, (long)tv, tmp.tm_gmtoff, tmp.tm_isdst, server_startup_time); #else cprintf("%d %ld|%ld|%d|%ld\n", CIT_OK, (long)tv, timezone, tmp.tm_isdst, server_startup_time); #endif } /*****************************************************************************/ /* MODULE INITIALIZATION STUFF */ /*****************************************************************************/ CTDL_MODULE_INIT(serv_session) { if (!threading) { CtdlRegisterProtoHook(cmd_noop, "NOOP", "no operation"); CtdlRegisterProtoHook(cmd_qnop, "QNOP", "no operation with no response"); ; CtdlRegisterProtoHook(cmd_asyn, "ASYN", "enable asynchronous server responses"); CtdlRegisterProtoHook(cmd_info, "INFO", "fetch server capabilities and configuration"); CtdlRegisterProtoHook(cmd_echo, "ECHO", "echo text back to the client"); CtdlRegisterProtoHook(cmd_more, "MORE", "fetch the paginator prompt"); CtdlRegisterProtoHook(cmd_iden, "IDEN", "identify the client software and location"); CtdlRegisterProtoHook(cmd_term, "TERM", "terminate another running session"); CtdlRegisterProtoHook(cmd_time, "TIME", "fetch the date and time from the server"); } /* return our id for the Log */ return "serv_session"; } citadel-9.01/modules/ctdlproto/serv_rooms.c0000644000000000000000000007074312507024051017601 0ustar rootroot/* * Server functions which perform operations on room objects. * * Copyright (c) 1987-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include /* for cmd_rdir to read contents of the directory */ #include #include "citserver.h" #include "ctdl_module.h" #include "room_ops.h" /* * Back-back-end for all room listing commands */ void list_roomname(struct ctdlroom *qrbuf, int ra, int current_view, int default_view) { char truncated_roomname[ROOMNAMELEN]; /* For my own mailbox rooms, chop off the owner prefix */ if ( (qrbuf->QRflags & QR_MAILBOX) && (atol(qrbuf->QRname) == CC->user.usernum) ) { safestrncpy(truncated_roomname, qrbuf->QRname, sizeof truncated_roomname); safestrncpy(truncated_roomname, &truncated_roomname[11], sizeof truncated_roomname); cprintf("%s", truncated_roomname); } /* For all other rooms, just display the name in its entirety */ else { cprintf("%s", qrbuf->QRname); } /* ...and now the other parameters */ cprintf("|%u|%d|%d|%d|%d|%d|%d|%ld|\n", qrbuf->QRflags, (int) qrbuf->QRfloor, (int) qrbuf->QRorder, (int) qrbuf->QRflags2, ra, current_view, default_view, qrbuf->QRmtime ); } /* * cmd_lrms() - List all accessible rooms, known or forgotten */ void cmd_lrms_backend(struct ctdlroom *qrbuf, void *data) { int FloorBeingSearched = (-1); int ra; int view; FloorBeingSearched = *(int *)data; CtdlRoomAccess(qrbuf, &CC->user, &ra, &view); if ((( ra & (UA_KNOWN | UA_ZAPPED))) && ((qrbuf->QRfloor == (FloorBeingSearched)) || ((FloorBeingSearched) < 0))) list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview); } void cmd_lrms(char *argbuf) { int FloorBeingSearched = (-1); if (!IsEmptyStr(argbuf)) FloorBeingSearched = extract_int(argbuf, 0); if (CtdlAccessCheck(ac_logged_in_or_guest)) return; CtdlGetUser(&CC->user, CC->curr_user); cprintf("%d Accessible rooms:\n", LISTING_FOLLOWS); CtdlForEachRoom(cmd_lrms_backend, &FloorBeingSearched); cprintf("000\n"); } /* * cmd_lkra() - List all known rooms */ void cmd_lkra_backend(struct ctdlroom *qrbuf, void *data) { int FloorBeingSearched = (-1); int ra; int view; FloorBeingSearched = *(int *)data; CtdlRoomAccess(qrbuf, &CC->user, &ra, &view); if ((( ra & (UA_KNOWN))) && ((qrbuf->QRfloor == (FloorBeingSearched)) || ((FloorBeingSearched) < 0))) list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview); } void cmd_lkra(char *argbuf) { int FloorBeingSearched = (-1); if (!IsEmptyStr(argbuf)) FloorBeingSearched = extract_int(argbuf, 0); if (CtdlAccessCheck(ac_logged_in_or_guest)) return; CtdlGetUser(&CC->user, CC->curr_user); cprintf("%d Known rooms:\n", LISTING_FOLLOWS); CtdlForEachRoom(cmd_lkra_backend, &FloorBeingSearched); cprintf("000\n"); } void cmd_lprm_backend(struct ctdlroom *qrbuf, void *data) { int FloorBeingSearched = (-1); int ra; int view; FloorBeingSearched = *(int *)data; CtdlRoomAccess(qrbuf, &CC->user, &ra, &view); if ( ((qrbuf->QRflags & QR_PRIVATE) == 0) && ((qrbuf->QRflags & QR_MAILBOX) == 0) && ((qrbuf->QRfloor == (FloorBeingSearched)) || ((FloorBeingSearched) < 0))) list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview); } void cmd_lprm(char *argbuf) { int FloorBeingSearched = (-1); if (!IsEmptyStr(argbuf)) FloorBeingSearched = extract_int(argbuf, 0); cprintf("%d Public rooms:\n", LISTING_FOLLOWS); CtdlForEachRoom(cmd_lprm_backend, &FloorBeingSearched); cprintf("000\n"); } /* * cmd_lkrn() - List all known rooms with new messages */ void cmd_lkrn_backend(struct ctdlroom *qrbuf, void *data) { int FloorBeingSearched = (-1); int ra; int view; FloorBeingSearched = *(int *)data; CtdlRoomAccess(qrbuf, &CC->user, &ra, &view); if ((ra & UA_KNOWN) && (ra & UA_HASNEWMSGS) && ((qrbuf->QRfloor == (FloorBeingSearched)) || ((FloorBeingSearched) < 0))) list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview); } void cmd_lkrn(char *argbuf) { int FloorBeingSearched = (-1); if (!IsEmptyStr(argbuf)) FloorBeingSearched = extract_int(argbuf, 0); if (CtdlAccessCheck(ac_logged_in_or_guest)) return; CtdlGetUser(&CC->user, CC->curr_user); cprintf("%d Rooms w/ new msgs:\n", LISTING_FOLLOWS); CtdlForEachRoom(cmd_lkrn_backend, &FloorBeingSearched); cprintf("000\n"); } /* * cmd_lkro() - List all known rooms */ void cmd_lkro_backend(struct ctdlroom *qrbuf, void *data) { int FloorBeingSearched = (-1); int ra; int view; FloorBeingSearched = *(int *)data; CtdlRoomAccess(qrbuf, &CC->user, &ra, &view); if ((ra & UA_KNOWN) && ((ra & UA_HASNEWMSGS) == 0) && ((qrbuf->QRfloor == (FloorBeingSearched)) || ((FloorBeingSearched) < 0))) list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview); } void cmd_lkro(char *argbuf) { int FloorBeingSearched = (-1); if (!IsEmptyStr(argbuf)) FloorBeingSearched = extract_int(argbuf, 0); if (CtdlAccessCheck(ac_logged_in_or_guest)) return; CtdlGetUser(&CC->user, CC->curr_user); cprintf("%d Rooms w/o new msgs:\n", LISTING_FOLLOWS); CtdlForEachRoom(cmd_lkro_backend, &FloorBeingSearched); cprintf("000\n"); } /* * cmd_lzrm() - List all forgotten rooms */ void cmd_lzrm_backend(struct ctdlroom *qrbuf, void *data) { int FloorBeingSearched = (-1); int ra; int view; FloorBeingSearched = *(int *)data; CtdlRoomAccess(qrbuf, &CC->user, &ra, &view); if ((ra & UA_GOTOALLOWED) && (ra & UA_ZAPPED) && ((qrbuf->QRfloor == (FloorBeingSearched)) || ((FloorBeingSearched) < 0))) list_roomname(qrbuf, ra, view, qrbuf->QRdefaultview); } void cmd_lzrm(char *argbuf) { int FloorBeingSearched = (-1); if (!IsEmptyStr(argbuf)) FloorBeingSearched = extract_int(argbuf, 0); if (CtdlAccessCheck(ac_logged_in_or_guest)) return; CtdlGetUser(&CC->user, CC->curr_user); cprintf("%d Zapped rooms:\n", LISTING_FOLLOWS); CtdlForEachRoom(cmd_lzrm_backend, &FloorBeingSearched); cprintf("000\n"); } /* * cmd_goto() - goto a new room */ void cmd_goto(char *gargs) { struct ctdlroom QRscratch; int c; int ok = 0; int ra; char augmented_roomname[ROOMNAMELEN]; char towhere[ROOMNAMELEN]; char password[32]; int transiently = 0; if (CtdlAccessCheck(ac_logged_in_or_guest)) return; extract_token(towhere, gargs, 0, '|', sizeof towhere); extract_token(password, gargs, 1, '|', sizeof password); transiently = extract_int(gargs, 2); CtdlGetUser(&CC->user, CC->curr_user); /* * Handle some of the macro named rooms */ convert_room_name_macros(towhere, sizeof towhere); /* First try a regular match */ c = CtdlGetRoom(&QRscratch, towhere); /* Then try a mailbox name match */ if (c != 0) { CtdlMailboxName(augmented_roomname, sizeof augmented_roomname, &CC->user, towhere); c = CtdlGetRoom(&QRscratch, augmented_roomname); if (c == 0) safestrncpy(towhere, augmented_roomname, sizeof towhere); } /* And if the room was found... */ if (c == 0) { /* Let internal programs go directly to any room. */ if (CC->internal_pgm) { memcpy(&CC->room, &QRscratch, sizeof(struct ctdlroom)); CtdlUserGoto(NULL, 1, transiently, NULL, NULL, NULL, NULL); return; } /* See if there is an existing user/room relationship */ CtdlRoomAccess(&QRscratch, &CC->user, &ra, NULL); /* normal clients have to pass through security */ if (ra & UA_GOTOALLOWED) { ok = 1; } if (ok == 1) { if ((QRscratch.QRflags & QR_MAILBOX) && ((ra & UA_GOTOALLOWED))) { memcpy(&CC->room, &QRscratch, sizeof(struct ctdlroom)); CtdlUserGoto(NULL, 1, transiently, NULL, NULL, NULL, NULL); return; } else if ((QRscratch.QRflags & QR_PASSWORDED) && ((ra & UA_KNOWN) == 0) && (strcasecmp(QRscratch.QRpasswd, password)) && (CC->user.axlevel < AxAideU) ) { cprintf("%d wrong or missing passwd\n", ERROR + PASSWORD_REQUIRED); return; } else if ((QRscratch.QRflags & QR_PRIVATE) && ((QRscratch.QRflags & QR_PASSWORDED) == 0) && ((QRscratch.QRflags & QR_GUESSNAME) == 0) && ((ra & UA_KNOWN) == 0) && (CC->user.axlevel < AxAideU) ) { syslog(LOG_DEBUG, "Failed to acquire private room\n"); } else { memcpy(&CC->room, &QRscratch, sizeof(struct ctdlroom)); CtdlUserGoto(NULL, 1, transiently, NULL, NULL, NULL, NULL); return; } } } cprintf("%d room '%s' not found\n", ERROR + ROOM_NOT_FOUND, towhere); } void cmd_whok(char *cmdbuf) { struct ctdluser temp; struct cdbdata *cdbus; int ra; cprintf("%d Who knows room:\n", LISTING_FOLLOWS); cdb_rewind(CDB_USERS); while (cdbus = cdb_next_item(CDB_USERS), cdbus != NULL) { memset(&temp, 0, sizeof temp); memcpy(&temp, cdbus->ptr, sizeof temp); cdb_free(cdbus); CtdlRoomAccess(&CC->room, &temp, &ra, NULL); if ((!IsEmptyStr(temp.fullname)) && (CC->room.QRflags & QR_INUSE) && (ra & UA_KNOWN) ) cprintf("%s\n", temp.fullname); } cprintf("000\n"); } /* * RDIR command for room directory */ void cmd_rdir(char *cmdbuf) { char buf[256]; char comment[256]; FILE *fd; struct stat statbuf; DIR *filedir = NULL; struct dirent *filedir_entry; int d_namelen; char buf2[SIZ]; char mimebuf[64]; long len; if (CtdlAccessCheck(ac_logged_in)) return; CtdlGetRoom(&CC->room, CC->room.QRname); CtdlGetUser(&CC->user, CC->curr_user); if ((CC->room.QRflags & QR_DIRECTORY) == 0) { cprintf("%d not here.\n", ERROR + NOT_HERE); return; } if (((CC->room.QRflags & QR_VISDIR) == 0) && (CC->user.axlevel < AxAideU) && (CC->user.usernum != CC->room.QRroomaide)) { cprintf("%d not here.\n", ERROR + HIGHER_ACCESS_REQUIRED); return; } snprintf(buf, sizeof buf, "%s/%s", ctdl_file_dir, CC->room.QRdirname); filedir = opendir (buf); if (filedir == NULL) { cprintf("%d not here.\n", ERROR + HIGHER_ACCESS_REQUIRED); return; } cprintf("%d %s|%s/%s\n", LISTING_FOLLOWS, config.c_fqdn, ctdl_file_dir, CC->room.QRdirname); snprintf(buf, sizeof buf, "%s/%s/filedir", ctdl_file_dir, CC->room.QRdirname); fd = fopen(buf, "r"); if (fd == NULL) fd = fopen("/dev/null", "r"); while ((filedir_entry = readdir(filedir))) { if (strcasecmp(filedir_entry->d_name, "filedir") && filedir_entry->d_name[0] != '.') { #ifdef _DIRENT_HAVE_D_NAMELEN d_namelen = filedir_entry->d_namlen; #else d_namelen = strlen(filedir_entry->d_name); #endif snprintf(buf, sizeof buf, "%s/%s/%s", ctdl_file_dir, CC->room.QRdirname, filedir_entry->d_name); stat(buf, &statbuf); /* stat the file */ if (!(statbuf.st_mode & S_IFREG)) { snprintf(buf2, sizeof buf2, "\"%s\" appears in the file directory for room \"%s\" but is not a regular file. Directories, named pipes, sockets, etc. are not usable in Citadel room directories.\n", buf, CC->room.QRname ); CtdlAideMessage(buf2, "Unusable data found in room directory"); continue; /* not a useable file type so don't show it */ } safestrncpy(comment, "", sizeof comment); fseek(fd, 0L, 0); /* rewind descriptions file */ /* Get the description from the descriptions file */ while ((fgets(buf, sizeof buf, fd) != NULL) && (IsEmptyStr(comment))) { buf[strlen(buf) - 1] = 0; if ((!strncasecmp(buf, filedir_entry->d_name, d_namelen)) && (buf[d_namelen] == ' ')) safestrncpy(comment, &buf[d_namelen + 1], sizeof comment); } len = extract_token (mimebuf, comment, 0,' ', 64); if ((len <0) || strchr(mimebuf, '/') == NULL) { snprintf (mimebuf, 64, "application/octetstream"); len = 0; } cprintf("%s|%ld|%s|%s\n", filedir_entry->d_name, (long)statbuf.st_size, mimebuf, &comment[len]); } } fclose(fd); closedir(filedir); cprintf("000\n"); } /* * get room parameters (admin or room admin command) */ void cmd_getr(char *cmdbuf) { if (CtdlAccessCheck(ac_room_aide)) return; CtdlGetRoom(&CC->room, CC->room.QRname); cprintf("%d%c%s|%s|%s|%d|%d|%d|%d|%d|\n", CIT_OK, CtdlCheckExpress(), ((CC->room.QRflags & QR_MAILBOX) ? &CC->room.QRname[11] : CC->room.QRname), ((CC->room.QRflags & QR_PASSWORDED) ? CC->room.QRpasswd : ""), ((CC->room.QRflags & QR_DIRECTORY) ? CC->room.QRdirname : ""), CC->room.QRflags, (int) CC->room.QRfloor, (int) CC->room.QRorder, CC->room.QRdefaultview, CC->room.QRflags2 ); } /* * set room parameters (admin or room admin command) */ void cmd_setr(char *args) { char buf[256]; int new_order = 0; int r; int new_floor; char new_name[ROOMNAMELEN]; if (CtdlAccessCheck(ac_logged_in)) return; if (num_parms(args) >= 6) { new_floor = extract_int(args, 5); } else { new_floor = (-1); /* don't change the floor */ } /* When is a new name more than just a new name? When the old name * has a namespace prefix. */ if (CC->room.QRflags & QR_MAILBOX) { sprintf(new_name, "%010ld.", atol(CC->room.QRname) ); } else { safestrncpy(new_name, "", sizeof new_name); } extract_token(&new_name[strlen(new_name)], args, 0, '|', (sizeof new_name - strlen(new_name))); r = CtdlRenameRoom(CC->room.QRname, new_name, new_floor); if (r == crr_room_not_found) { cprintf("%d Internal error - room not found?\n", ERROR + INTERNAL_ERROR); } else if (r == crr_already_exists) { cprintf("%d '%s' already exists.\n", ERROR + ALREADY_EXISTS, new_name); } else if (r == crr_noneditable) { cprintf("%d Cannot edit this room.\n", ERROR + NOT_HERE); } else if (r == crr_invalid_floor) { cprintf("%d Target floor does not exist.\n", ERROR + INVALID_FLOOR_OPERATION); } else if (r == crr_access_denied) { cprintf("%d You do not have permission to edit '%s'\n", ERROR + HIGHER_ACCESS_REQUIRED, CC->room.QRname); } else if (r != crr_ok) { cprintf("%d Error: CtdlRenameRoom() returned %d\n", ERROR + INTERNAL_ERROR, r); } if (r != crr_ok) { return; } CtdlGetRoom(&CC->room, new_name); /* Now we have to do a bunch of other stuff */ if (num_parms(args) >= 7) { new_order = extract_int(args, 6); if (new_order < 1) new_order = 1; if (new_order > 127) new_order = 127; } CtdlGetRoomLock(&CC->room, CC->room.QRname); /* Directory room */ extract_token(buf, args, 2, '|', sizeof buf); buf[15] = 0; safestrncpy(CC->room.QRdirname, buf, sizeof CC->room.QRdirname); /* Default view */ if (num_parms(args) >= 8) { CC->room.QRdefaultview = extract_int(args, 7); } /* Second set of flags */ if (num_parms(args) >= 9) { CC->room.QRflags2 = extract_int(args, 8); } /* Misc. flags */ CC->room.QRflags = (extract_int(args, 3) | QR_INUSE); /* Clean up a client boo-boo: if the client set the room to * guess-name or passworded, ensure that the private flag is * also set. */ if ((CC->room.QRflags & QR_GUESSNAME) || (CC->room.QRflags & QR_PASSWORDED)) CC->room.QRflags |= QR_PRIVATE; /* Some changes can't apply to BASEROOM */ if (!strncasecmp(CC->room.QRname, config.c_baseroom, ROOMNAMELEN)) { CC->room.QRorder = 0; CC->room.QRpasswd[0] = '\0'; CC->room.QRflags &= ~(QR_PRIVATE & QR_PASSWORDED & QR_GUESSNAME & QR_PREFONLY & QR_MAILBOX); CC->room.QRflags |= QR_PERMANENT; } else { /* March order (doesn't apply to AIDEROOM) */ if (num_parms(args) >= 7) CC->room.QRorder = (char) new_order; /* Room password */ extract_token(buf, args, 1, '|', sizeof buf); buf[10] = 0; safestrncpy(CC->room.QRpasswd, buf, sizeof CC->room.QRpasswd); /* Kick everyone out if the client requested it * (by changing the room's generation number) */ if (extract_int(args, 4)) { time(&CC->room.QRgen); } } /* Some changes can't apply to AIDEROOM */ if (!strncasecmp(CC->room.QRname, config.c_baseroom, ROOMNAMELEN)) { CC->room.QRorder = 0; CC->room.QRflags &= ~QR_MAILBOX; CC->room.QRflags |= QR_PERMANENT; } /* Write the room record back to disk */ CtdlPutRoomLock(&CC->room); /* Create a room directory if necessary */ if (CC->room.QRflags & QR_DIRECTORY) { snprintf(buf, sizeof buf,"%s/%s", ctdl_file_dir, CC->room.QRdirname); mkdir(buf, 0755); } snprintf(buf, sizeof buf, "The room \"%s\" has been edited by %s.\n", CC->room.QRname, (CC->logged_in ? CC->curr_user : "an administrator") ); CtdlAideMessage(buf, "Room modification Message"); cprintf("%d Ok\n", CIT_OK); } /* * get the name of the room admin for this room */ void cmd_geta(char *cmdbuf) { struct ctdluser usbuf; if (CtdlAccessCheck(ac_logged_in)) return; if (CtdlGetUserByNumber(&usbuf, CC->room.QRroomaide) == 0) { cprintf("%d %s\n", CIT_OK, usbuf.fullname); } else { cprintf("%d \n", CIT_OK); } } /* * set the room admin for this room */ void cmd_seta(char *new_ra) { struct ctdluser usbuf; long newu; char buf[SIZ]; int post_notice; if (CtdlAccessCheck(ac_room_aide)) return; if (CtdlGetUser(&usbuf, new_ra) != 0) { newu = (-1L); } else { newu = usbuf.usernum; } CtdlGetRoomLock(&CC->room, CC->room.QRname); post_notice = 0; if (CC->room.QRroomaide != newu) { post_notice = 1; } CC->room.QRroomaide = newu; CtdlPutRoomLock(&CC->room); /* * We have to post the change notice _after_ writing changes to * the room table, otherwise it would deadlock! */ if (post_notice == 1) { if (!IsEmptyStr(usbuf.fullname)) snprintf(buf, sizeof buf, "%s is now the room admin for \"%s\".\n", usbuf.fullname, CC->room.QRname); else snprintf(buf, sizeof buf, "There is now no room admin for \"%s\".\n", CC->room.QRname); CtdlAideMessage(buf, "Admin Room Modification"); } cprintf("%d Ok\n", CIT_OK); } /* * retrieve info file for this room */ void cmd_rinf(char *gargs) { char filename[PATH_MAX]; char buf[SIZ]; FILE *info_fp; assoc_file_name(filename, sizeof filename, &CC->room, ctdl_info_dir); info_fp = fopen(filename, "r"); if (info_fp == NULL) { cprintf("%d No info file.\n", ERROR + FILE_NOT_FOUND); return; } cprintf("%d Info:\n", LISTING_FOLLOWS); while (fgets(buf, sizeof buf, info_fp) != NULL) { if (!IsEmptyStr(buf)) buf[strlen(buf) - 1] = 0; cprintf("%s\n", buf); } cprintf("000\n"); fclose(info_fp); } /* * admin command: kill the current room */ void cmd_kill(char *argbuf) { char deleted_room_name[ROOMNAMELEN]; char msg[SIZ]; int kill_ok; kill_ok = extract_int(argbuf, 0); if (CtdlDoIHavePermissionToDeleteThisRoom(&CC->room) == 0) { cprintf("%d Can't delete this room.\n", ERROR + NOT_HERE); return; } if (kill_ok) { if (CC->room.QRflags & QR_MAILBOX) { safestrncpy(deleted_room_name, &CC->room.QRname[11], sizeof deleted_room_name); } else { safestrncpy(deleted_room_name, CC->room.QRname, sizeof deleted_room_name); } /* Do the dirty work */ CtdlScheduleRoomForDeletion(&CC->room); /* Return to the Lobby */ CtdlUserGoto(config.c_baseroom, 0, 0, NULL, NULL, NULL, NULL); /* tell the world what we did */ snprintf(msg, sizeof msg, "The room \"%s\" has been deleted by %s.\n", deleted_room_name, (CC->logged_in ? CC->curr_user : "an administrator") ); CtdlAideMessage(msg, "Room Purger Message"); cprintf("%d '%s' deleted.\n", CIT_OK, deleted_room_name); } else { cprintf("%d ok to delete.\n", CIT_OK); } } /* * create a new room */ void cmd_cre8(char *args) { int cre8_ok; char new_room_name[ROOMNAMELEN]; int new_room_type; char new_room_pass[32]; int new_room_floor; int new_room_view; char *notification_message = NULL; unsigned newflags; struct floor *fl; int avoid_access = 0; cre8_ok = extract_int(args, 0); extract_token(new_room_name, args, 1, '|', sizeof new_room_name); new_room_name[ROOMNAMELEN - 1] = 0; new_room_type = extract_int(args, 2); extract_token(new_room_pass, args, 3, '|', sizeof new_room_pass); avoid_access = extract_int(args, 5); new_room_view = extract_int(args, 6); new_room_pass[9] = 0; new_room_floor = 0; if ((IsEmptyStr(new_room_name)) && (cre8_ok == 1)) { cprintf("%d Invalid room name.\n", ERROR + ILLEGAL_VALUE); return; } if (!strcasecmp(new_room_name, MAILROOM)) { cprintf("%d '%s' already exists.\n", ERROR + ALREADY_EXISTS, new_room_name); return; } if (num_parms(args) >= 5) { fl = CtdlGetCachedFloor(extract_int(args, 4)); if (fl == NULL) { cprintf("%d Invalid floor number.\n", ERROR + INVALID_FLOOR_OPERATION); return; } else if ((fl->f_flags & F_INUSE) == 0) { cprintf("%d Invalid floor number.\n", ERROR + INVALID_FLOOR_OPERATION); return; } else { new_room_floor = extract_int(args, 4); } } if (CtdlAccessCheck(ac_logged_in)) return; if (CC->user.axlevel < config.c_createax && !CC->internal_pgm) { cprintf("%d You need higher access to create rooms.\n", ERROR + HIGHER_ACCESS_REQUIRED); return; } if ((IsEmptyStr(new_room_name)) && (cre8_ok == 0)) { cprintf("%d Ok to create rooms.\n", CIT_OK); return; } if ((new_room_type < 0) || (new_room_type > 5)) { cprintf("%d Invalid room type.\n", ERROR + ILLEGAL_VALUE); return; } if (new_room_type == 5) { if (CC->user.axlevel < AxAideU) { cprintf("%d Higher access required\n", ERROR + HIGHER_ACCESS_REQUIRED); return; } } /* Check to make sure the requested room name doesn't already exist */ newflags = CtdlCreateRoom(new_room_name, new_room_type, new_room_pass, new_room_floor, 0, avoid_access, new_room_view); if (newflags == 0) { cprintf("%d '%s' already exists.\n", ERROR + ALREADY_EXISTS, new_room_name); return; } if (cre8_ok == 0) { cprintf("%d OK to create '%s'\n", CIT_OK, new_room_name); return; } /* If we reach this point, the room needs to be created. */ newflags = CtdlCreateRoom(new_room_name, new_room_type, new_room_pass, new_room_floor, 1, 0, new_room_view); /* post a message in Aide> describing the new room */ notification_message = malloc(1024); snprintf(notification_message, 1024, "A new room called \"%s\" has been created by %s%s%s%s%s%s\n", new_room_name, (CC->logged_in ? CC->curr_user : "an administrator"), ((newflags & QR_MAILBOX) ? " [personal]" : ""), ((newflags & QR_PRIVATE) ? " [private]" : ""), ((newflags & QR_GUESSNAME) ? " [hidden]" : ""), ((newflags & QR_PASSWORDED) ? " Password: " : ""), ((newflags & QR_PASSWORDED) ? new_room_pass : "") ); CtdlAideMessage(notification_message, "Room Creation Message"); free(notification_message); cprintf("%d '%s' has been created.\n", CIT_OK, new_room_name); } void cmd_einf(char *ok) { /* enter info file for current room */ FILE *fp; char infofilename[SIZ]; char buf[SIZ]; unbuffer_output(); if (CtdlAccessCheck(ac_room_aide)) return; if (atoi(ok) == 0) { cprintf("%d Ok.\n", CIT_OK); return; } assoc_file_name(infofilename, sizeof infofilename, &CC->room, ctdl_info_dir); syslog(LOG_DEBUG, "opening\n"); fp = fopen(infofilename, "w"); syslog(LOG_DEBUG, "checking\n"); if (fp == NULL) { cprintf("%d Cannot open %s: %s\n", ERROR + INTERNAL_ERROR, infofilename, strerror(errno)); return; } cprintf("%d Send info...\n", SEND_LISTING); do { client_getln(buf, sizeof buf); if (strcmp(buf, "000")) fprintf(fp, "%s\n", buf); } while (strcmp(buf, "000")); fclose(fp); /* now update the room index so people will see our new info */ CtdlGetRoomLock(&CC->room, CC->room.QRname); /* lock so no one steps on us */ CC->room.QRinfo = CC->room.QRhighest + 1L; CtdlPutRoomLock(&CC->room); } /* * cmd_lflr() - List all known floors */ void cmd_lflr(char *gargs) { int a; struct floor flbuf; if (CtdlAccessCheck(ac_logged_in_or_guest)) return; cprintf("%d Known floors:\n", LISTING_FOLLOWS); for (a = 0; a < MAXFLOORS; ++a) { CtdlGetFloor(&flbuf, a); if (flbuf.f_flags & F_INUSE) { cprintf("%d|%s|%d\n", a, flbuf.f_name, flbuf.f_ref_count); } } cprintf("000\n"); } /* * create a new floor */ void cmd_cflr(char *argbuf) { char new_floor_name[256]; struct floor flbuf; int cflr_ok; int free_slot = (-1); int a; extract_token(new_floor_name, argbuf, 0, '|', sizeof new_floor_name); cflr_ok = extract_int(argbuf, 1); if (CtdlAccessCheck(ac_aide)) return; if (IsEmptyStr(new_floor_name)) { cprintf("%d Blank floor name not allowed.\n", ERROR + ILLEGAL_VALUE); return; } for (a = 0; a < MAXFLOORS; ++a) { CtdlGetFloor(&flbuf, a); /* note any free slots while we're scanning... */ if (((flbuf.f_flags & F_INUSE) == 0) && (free_slot < 0)) free_slot = a; /* check to see if it already exists */ if ((!strcasecmp(flbuf.f_name, new_floor_name)) && (flbuf.f_flags & F_INUSE)) { cprintf("%d Floor '%s' already exists.\n", ERROR + ALREADY_EXISTS, flbuf.f_name); return; } } if (free_slot < 0) { cprintf("%d There is no space available for a new floor.\n", ERROR + INVALID_FLOOR_OPERATION); return; } if (cflr_ok == 0) { cprintf("%d ok to create...\n", CIT_OK); return; } lgetfloor(&flbuf, free_slot); flbuf.f_flags = F_INUSE; flbuf.f_ref_count = 0; safestrncpy(flbuf.f_name, new_floor_name, sizeof flbuf.f_name); lputfloor(&flbuf, free_slot); cprintf("%d %d\n", CIT_OK, free_slot); } /* * delete a floor */ void cmd_kflr(char *argbuf) { struct floor flbuf; int floor_to_delete; int kflr_ok; int delete_ok; floor_to_delete = extract_int(argbuf, 0); kflr_ok = extract_int(argbuf, 1); if (CtdlAccessCheck(ac_aide)) return; lgetfloor(&flbuf, floor_to_delete); delete_ok = 1; if ((flbuf.f_flags & F_INUSE) == 0) { cprintf("%d Floor %d not in use.\n", ERROR + INVALID_FLOOR_OPERATION, floor_to_delete); delete_ok = 0; } else { if (flbuf.f_ref_count != 0) { cprintf("%d Cannot delete; floor contains %d rooms.\n", ERROR + INVALID_FLOOR_OPERATION, flbuf.f_ref_count); delete_ok = 0; } else { if (kflr_ok == 1) { cprintf("%d Ok\n", CIT_OK); } else { cprintf("%d Ok to delete...\n", CIT_OK); } } } if ((delete_ok == 1) && (kflr_ok == 1)) flbuf.f_flags = 0; lputfloor(&flbuf, floor_to_delete); } /* * edit a floor */ void cmd_eflr(char *argbuf) { struct floor flbuf; int floor_num; int np; np = num_parms(argbuf); if (np < 1) { cprintf("%d Usage error.\n", ERROR + ILLEGAL_VALUE); return; } if (CtdlAccessCheck(ac_aide)) return; floor_num = extract_int(argbuf, 0); lgetfloor(&flbuf, floor_num); if ((flbuf.f_flags & F_INUSE) == 0) { lputfloor(&flbuf, floor_num); cprintf("%d Floor %d is not in use.\n", ERROR + INVALID_FLOOR_OPERATION, floor_num); return; } if (np >= 2) extract_token(flbuf.f_name, argbuf, 1, '|', sizeof flbuf.f_name); lputfloor(&flbuf, floor_num); cprintf("%d Ok\n", CIT_OK); } /* * cmd_stat() - return the modification time of the current room (maybe other things in the future) */ void cmd_stat(char *gargs) { if (CtdlAccessCheck(ac_logged_in_or_guest)) return; CtdlGetRoom(&CC->room, CC->room.QRname); cprintf("%d %s|%ld|\n", CIT_OK, CC->room.QRname, CC->room.QRmtime); } /*****************************************************************************/ /* MODULE INITIALIZATION STUFF */ /*****************************************************************************/ CTDL_MODULE_INIT(rooms) { if (!threading) { CtdlRegisterProtoHook(cmd_lrms, "LRMS", "List rooms"); CtdlRegisterProtoHook(cmd_lkra, "LKRA", "List all known rooms"); CtdlRegisterProtoHook(cmd_lkrn, "LKRN", "List known rooms with new messages"); CtdlRegisterProtoHook(cmd_lkro, "LKRO", "List known rooms without new messages"); CtdlRegisterProtoHook(cmd_lzrm, "LZRM", "List zapped rooms"); CtdlRegisterProtoHook(cmd_lprm, "LPRM", "List public rooms"); CtdlRegisterProtoHook(cmd_goto, "GOTO", "Goto a named room"); CtdlRegisterProtoHook(cmd_stat, "STAT", "Get mtime of the current room"); CtdlRegisterProtoHook(cmd_whok, "WHOK", "List users who know this room"); CtdlRegisterProtoHook(cmd_rdir, "RDIR", "List files in room directory"); CtdlRegisterProtoHook(cmd_getr, "GETR", "Get room parameters"); CtdlRegisterProtoHook(cmd_setr, "SETR", "Set room parameters"); CtdlRegisterProtoHook(cmd_geta, "GETA", "Get the room admin name"); CtdlRegisterProtoHook(cmd_seta, "SETA", "Set the room admin for this room"); CtdlRegisterProtoHook(cmd_rinf, "RINF", "Fetch room info file"); CtdlRegisterProtoHook(cmd_kill, "KILL", "Kill (delete) the current room"); CtdlRegisterProtoHook(cmd_cre8, "CRE8", "Create a new room"); CtdlRegisterProtoHook(cmd_einf, "EINF", "Enter info file for the current room"); CtdlRegisterProtoHook(cmd_lflr, "LFLR", "List all known floors"); CtdlRegisterProtoHook(cmd_cflr, "CFLR", "Create a new floor"); CtdlRegisterProtoHook(cmd_kflr, "KFLR", "Kill a floor"); CtdlRegisterProtoHook(cmd_eflr, "EFLR", "Edit a floor"); } /* return our Subversion id for the Log */ return "rooms"; } citadel-9.01/modules/ctdlproto/serv_messages.c0000644000000000000000000005143512507024051020246 0ustar rootroot/* * represent messages to the citadel clients * * Copyright (c) 1987-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include "citserver.h" #include "ctdl_module.h" #include "internet_addressing.h" #include "user_ops.h" #include "room_ops.h" extern char *msgkeys[]; /* * Back end for the MSGS command: output message number only. */ void simple_listing(long msgnum, void *userdata) { cprintf("%ld\n", msgnum); } /* * Back end for the MSGS command: output header summary. */ void headers_listing(long msgnum, void *userdata) { struct CtdlMessage *msg; msg = CtdlFetchMessage(msgnum, 0); if (msg == NULL) { cprintf("%ld|0|||||\n", msgnum); return; } cprintf("%ld|%s|%s|%s|%s|%s|\n", msgnum, (!CM_IsEmpty(msg, eTimestamp) ? msg->cm_fields[eTimestamp] : "0"), (!CM_IsEmpty(msg, eAuthor) ? msg->cm_fields[eAuthor] : ""), (!CM_IsEmpty(msg, eNodeName) ? msg->cm_fields[eNodeName] : ""), (!CM_IsEmpty(msg, erFc822Addr) ? msg->cm_fields[erFc822Addr] : ""), (!CM_IsEmpty(msg, eMsgSubject) ? msg->cm_fields[eMsgSubject] : "") ); CM_Free(msg); } /* * Back end for the MSGS command: output EUID header. */ void headers_euid(long msgnum, void *userdata) { struct CtdlMessage *msg; msg = CtdlFetchMessage(msgnum, 0); if (msg == NULL) { cprintf("%ld||\n", msgnum); return; } cprintf("%ld|%s|%s\n", msgnum, (!CM_IsEmpty(msg, eExclusiveID) ? msg->cm_fields[eExclusiveID] : ""), (!CM_IsEmpty(msg, eTimestamp) ? msg->cm_fields[eTimestamp] : "0")); CM_Free(msg); } /* * cmd_msgs() - get list of message #'s in this room * implements the MSGS server command using CtdlForEachMessage() */ void cmd_msgs(char *cmdbuf) { int mode = 0; char which[16]; char buf[256]; char tfield[256]; char tvalue[256]; int cm_ref = 0; int i; int with_template = 0; struct CtdlMessage *template = NULL; char search_string[1024]; ForEachMsgCallback CallBack; if (CtdlAccessCheck(ac_logged_in_or_guest)) return; extract_token(which, cmdbuf, 0, '|', sizeof which); cm_ref = extract_int(cmdbuf, 1); extract_token(search_string, cmdbuf, 1, '|', sizeof search_string); with_template = extract_int(cmdbuf, 2); switch (extract_int(cmdbuf, 3)) { default: case MSG_HDRS_BRIEF: CallBack = simple_listing; break; case MSG_HDRS_ALL: CallBack = headers_listing; break; case MSG_HDRS_EUID: CallBack = headers_euid; break; } strcat(which, " "); if (!strncasecmp(which, "OLD", 3)) mode = MSGS_OLD; else if (!strncasecmp(which, "NEW", 3)) mode = MSGS_NEW; else if (!strncasecmp(which, "FIRST", 5)) mode = MSGS_FIRST; else if (!strncasecmp(which, "LAST", 4)) mode = MSGS_LAST; else if (!strncasecmp(which, "GT", 2)) mode = MSGS_GT; else if (!strncasecmp(which, "LT", 2)) mode = MSGS_LT; else if (!strncasecmp(which, "SEARCH", 6)) mode = MSGS_SEARCH; else mode = MSGS_ALL; if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) { cprintf("%d Full text index is not enabled on this server.\n", ERROR + CMD_NOT_SUPPORTED); return; } if (with_template) { unbuffer_output(); cprintf("%d Send template then receive message list\n", START_CHAT_MODE); template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage)); memset(template, 0, sizeof(struct CtdlMessage)); template->cm_magic = CTDLMESSAGE_MAGIC; template->cm_anon_type = MES_NORMAL; while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) { long tValueLen; extract_token(tfield, buf, 0, '|', sizeof tfield); tValueLen = extract_token(tvalue, buf, 1, '|', sizeof tvalue); for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) { if (!strcasecmp(tfield, msgkeys[i])) { CM_SetField(template, i, tvalue, tValueLen); } } } buffer_output(); } else { cprintf("%d \n", LISTING_FOLLOWS); } CtdlForEachMessage(mode, ( (mode == MSGS_SEARCH) ? 0 : cm_ref ), ( (mode == MSGS_SEARCH) ? search_string : NULL ), NULL, template, CallBack, NULL); if (template != NULL) CM_Free(template); cprintf("000\n"); } /* * display a message (mode 0 - Citadel proprietary) */ void cmd_msg0(char *cmdbuf) { long msgid; int headers_only = HEADERS_ALL; msgid = extract_long(cmdbuf, 0); headers_only = extract_int(cmdbuf, 1); CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0, NULL, NULL, NULL); return; } /* * display a message (mode 2 - RFC822) */ void cmd_msg2(char *cmdbuf) { long msgid; int headers_only = HEADERS_ALL; msgid = extract_long(cmdbuf, 0); headers_only = extract_int(cmdbuf, 1); CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0, NULL, NULL, NULL); } /* * display a message (mode 3 - IGnet raw format - internal programs only) */ void cmd_msg3(char *cmdbuf) { long msgnum; struct CtdlMessage *msg = NULL; struct ser_ret smr; if (CC->internal_pgm == 0) { cprintf("%d This command is for internal programs only.\n", ERROR + HIGHER_ACCESS_REQUIRED); return; } msgnum = extract_long(cmdbuf, 0); msg = CtdlFetchMessage(msgnum, 1); if (msg == NULL) { cprintf("%d Message %ld not found.\n", ERROR + MESSAGE_NOT_FOUND, msgnum); return; } CtdlSerializeMessage(&smr, msg); CM_Free(msg); if (smr.len == 0) { cprintf("%d Unable to serialize message\n", ERROR + INTERNAL_ERROR); return; } cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len); client_write((char *)smr.ser, (int)smr.len); free(smr.ser); } /* * Display a message using MIME content types */ void cmd_msg4(char *cmdbuf) { long msgid; char section[64]; msgid = extract_long(cmdbuf, 0); extract_token(section, cmdbuf, 1, '|', sizeof section); CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0, NULL, NULL, NULL); } /* * Client tells us its preferred message format(s) */ void cmd_msgp(char *cmdbuf) { if (!strcasecmp(cmdbuf, "dont_decode")) { CC->msg4_dont_decode = 1; cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK); } else { safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats)); cprintf("%d Preferred MIME formats have been set.\n", CIT_OK); } } /* * Open a component of a MIME message as a download file */ void cmd_opna(char *cmdbuf) { long msgid; char desired_section[128]; msgid = extract_long(cmdbuf, 0); extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section); safestrncpy(CC->download_desired_section, desired_section, sizeof CC->download_desired_section); CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0, NULL, NULL, NULL); } /* * Open a component of a MIME message and transmit it all at once */ void cmd_dlat(char *cmdbuf) { long msgid; char desired_section[128]; msgid = extract_long(cmdbuf, 0); extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section); safestrncpy(CC->download_desired_section, desired_section, sizeof CC->download_desired_section); CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0, NULL, NULL, NULL); } /* * message entry - mode 0 (normal) */ void cmd_ent0(char *entargs) { struct CitContext *CCC = CC; int post = 0; char recp[SIZ]; char cc[SIZ]; char bcc[SIZ]; char supplied_euid[128]; int anon_flag = 0; int format_type = 0; char newusername[256]; char newuseremail[256]; struct CtdlMessage *msg; int anonymous = 0; char errmsg[SIZ]; int err = 0; recptypes *valid = NULL; recptypes *valid_to = NULL; recptypes *valid_cc = NULL; recptypes *valid_bcc = NULL; char subject[SIZ]; int subject_required = 0; int do_confirm = 0; long msgnum; int i, j; char buf[256]; int newuseremail_ok = 0; char references[SIZ]; char *ptr; unbuffer_output(); post = extract_int(entargs, 0); extract_token(recp, entargs, 1, '|', sizeof recp); anon_flag = extract_int(entargs, 2); format_type = extract_int(entargs, 3); extract_token(subject, entargs, 4, '|', sizeof subject); extract_token(newusername, entargs, 5, '|', sizeof newusername); do_confirm = extract_int(entargs, 6); extract_token(cc, entargs, 7, '|', sizeof cc); extract_token(bcc, entargs, 8, '|', sizeof bcc); switch(CC->room.QRdefaultview) { case VIEW_NOTES: case VIEW_WIKI: case VIEW_WIKIMD: extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid); break; default: supplied_euid[0] = 0; break; } extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail); extract_token(references, entargs, 11, '|', sizeof references); for (ptr=references; *ptr != 0; ++ptr) { if (*ptr == '!') *ptr = '|'; } /* first check to make sure the request is valid. */ err = CtdlDoIHavePermissionToPostInThisRoom( errmsg, sizeof errmsg, NULL, POST_LOGGED_IN, (!IsEmptyStr(references)) /* is this a reply? or a top-level post? */ ); if (err) { cprintf("%d %s\n", err, errmsg); return; } /* Check some other permission type things. */ if (IsEmptyStr(newusername)) { strcpy(newusername, CCC->user.fullname); } if ( (CCC->user.axlevel < AxAideU) && (strcasecmp(newusername, CCC->user.fullname)) && (strcasecmp(newusername, CCC->cs_inet_fn)) ) { cprintf("%d You don't have permission to author messages as '%s'.\n", ERROR + HIGHER_ACCESS_REQUIRED, newusername ); return; } if (IsEmptyStr(newuseremail)) { newuseremail_ok = 1; } if (!IsEmptyStr(newuseremail)) { if (!strcasecmp(newuseremail, CCC->cs_inet_email)) { newuseremail_ok = 1; } else if (!IsEmptyStr(CCC->cs_inet_other_emails)) { j = num_tokens(CCC->cs_inet_other_emails, '|'); for (i=0; ics_inet_other_emails, i, '|', sizeof buf); if (!strcasecmp(newuseremail, buf)) { newuseremail_ok = 1; } } } } if (!newuseremail_ok) { cprintf("%d You don't have permission to author messages as '%s'.\n", ERROR + HIGHER_ACCESS_REQUIRED, newuseremail ); return; } CCC->cs_flags |= CS_POSTING; /* In mailbox rooms we have to behave a little differently -- * make sure the user has specified at least one recipient. Then * validate the recipient(s). We do this for the Mail> room, as * well as any room which has the "Mailbox" view set - unless it * is the DRAFTS room which does not require recipients */ if ( ( ( (CCC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CCC->room.QRname[11], MAILROOM)) ) || ( (CCC->room.QRflags & QR_MAILBOX) && (CCC->curr_view == VIEW_MAILBOX) ) ) && (strcasecmp(&CCC->room.QRname[11], USERDRAFTROOM)) !=0 ) { if (CCC->user.axlevel < AxProbU) { strcpy(recp, "sysop"); strcpy(cc, ""); strcpy(bcc, ""); } valid_to = validate_recipients(recp, NULL, 0); if (valid_to->num_error > 0) { cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg); free_recipients(valid_to); return; } valid_cc = validate_recipients(cc, NULL, 0); if (valid_cc->num_error > 0) { cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg); free_recipients(valid_to); free_recipients(valid_cc); return; } valid_bcc = validate_recipients(bcc, NULL, 0); if (valid_bcc->num_error > 0) { cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg); free_recipients(valid_to); free_recipients(valid_cc); free_recipients(valid_bcc); return; } /* Recipient required, but none were specified */ if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) { free_recipients(valid_to); free_recipients(valid_cc); free_recipients(valid_bcc); cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER); return; } if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) { if (CtdlCheckInternetMailPermission(&CCC->user)==0) { cprintf("%d You do not have permission " "to send Internet mail.\n", ERROR + HIGHER_ACCESS_REQUIRED); free_recipients(valid_to); free_recipients(valid_cc); free_recipients(valid_bcc); return; } } if ( ( (valid_to->num_internet + valid_to->num_ignet + valid_cc->num_internet + valid_cc->num_ignet + valid_bcc->num_internet + valid_bcc->num_ignet) > 0) && (CCC->user.axlevel < AxNetU) ) { cprintf("%d Higher access required for network mail.\n", ERROR + HIGHER_ACCESS_REQUIRED); free_recipients(valid_to); free_recipients(valid_cc); free_recipients(valid_bcc); return; } if ((RESTRICT_INTERNET == 1) && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) && ((CCC->user.flags & US_INTERNET) == 0) && (!CCC->internal_pgm)) { cprintf("%d You don't have access to Internet mail.\n", ERROR + HIGHER_ACCESS_REQUIRED); free_recipients(valid_to); free_recipients(valid_cc); free_recipients(valid_bcc); return; } } /* Is this a room which has anonymous-only or anonymous-option? */ anonymous = MES_NORMAL; if (CCC->room.QRflags & QR_ANONONLY) { anonymous = MES_ANONONLY; } if (CCC->room.QRflags & QR_ANONOPT) { if (anon_flag == 1) { /* only if the user requested it */ anonymous = MES_ANONOPT; } } if ((CCC->room.QRflags & QR_MAILBOX) == 0) { recp[0] = 0; } /* Recommend to the client that the use of a message subject is * strongly recommended in this room, if either the SUBJECTREQ flag * is set, or if there is one or more Internet email recipients. */ if (CCC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1; if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1; if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1; if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1; /* If we're only checking the validity of the request, return * success without creating the message. */ if (post == 0) { cprintf("%d %s|%d\n", CIT_OK, ((valid_to != NULL) ? valid_to->display_recp : ""), subject_required); free_recipients(valid_to); free_recipients(valid_cc); free_recipients(valid_bcc); return; } /* We don't need these anymore because we'll do it differently below */ free_recipients(valid_to); free_recipients(valid_cc); free_recipients(valid_bcc); /* Read in the message from the client. */ if (do_confirm) { cprintf("%d send message\n", START_CHAT_MODE); } else { cprintf("%d send message\n", SEND_LISTING); } msg = CtdlMakeMessage(&CCC->user, recp, cc, CCC->room.QRname, anonymous, format_type, newusername, newuseremail, subject, ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL), NULL, references); /* Put together one big recipients struct containing to/cc/bcc all in * one. This is for the envelope. */ char *all_recps = malloc(SIZ * 3); strcpy(all_recps, recp); if (!IsEmptyStr(cc)) { if (!IsEmptyStr(all_recps)) { strcat(all_recps, ","); } strcat(all_recps, cc); } if (!IsEmptyStr(bcc)) { if (!IsEmptyStr(all_recps)) { strcat(all_recps, ","); } strcat(all_recps, bcc); } if (!IsEmptyStr(all_recps)) { valid = validate_recipients(all_recps, NULL, 0); } else { valid = NULL; } free(all_recps); if ((valid != NULL) && (valid->num_room == 1)) { /* posting into an ML room? set the envelope from * to the actual mail address so others get a valid * reply-to-header. */ CM_SetField(msg, eenVelopeTo, valid->recp_orgroom, strlen(valid->recp_orgroom)); } if (msg != NULL) { msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR); if (do_confirm) { cprintf("%ld\n", msgnum); if (StrLength(CCC->StatusMessage) > 0) { cprintf("%s\n", ChrPtr(CCC->StatusMessage)); } else if (msgnum >= 0L) { client_write(HKEY("Message accepted.\n")); } else { client_write(HKEY("Internal error.\n")); } if (!CM_IsEmpty(msg, eExclusiveID)) { cprintf("%s\n", msg->cm_fields[eExclusiveID]); } else { cprintf("\n"); } cprintf("000\n"); } CM_Free(msg); } if (valid != NULL) { free_recipients(valid); } return; } /* * Delete message from current room */ void cmd_dele(char *args) { int num_deleted; int i; char msgset[SIZ]; char msgtok[32]; long *msgs; int num_msgs = 0; extract_token(msgset, args, 0, '|', sizeof msgset); num_msgs = num_tokens(msgset, ','); if (num_msgs < 1) { cprintf("%d Nothing to do.\n", CIT_OK); return; } if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) { cprintf("%d Higher access required.\n", ERROR + HIGHER_ACCESS_REQUIRED); return; } /* * Build our message set to be moved/copied */ msgs = malloc(num_msgs * sizeof(long)); for (i=0; iroom.QRname, msgs, num_msgs, ""); free(msgs); if (num_deleted) { cprintf("%d %d message%s deleted.\n", CIT_OK, num_deleted, ((num_deleted != 1) ? "s" : "")); } else { cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND); } } /* * move or copy a message to another room */ void cmd_move(char *args) { char msgset[SIZ]; char msgtok[32]; long *msgs; int num_msgs = 0; char targ[ROOMNAMELEN]; struct ctdlroom qtemp; int err; int is_copy = 0; int ra; int permit = 0; int i; extract_token(msgset, args, 0, '|', sizeof msgset); num_msgs = num_tokens(msgset, ','); if (num_msgs < 1) { cprintf("%d Nothing to do.\n", CIT_OK); return; } extract_token(targ, args, 1, '|', sizeof targ); convert_room_name_macros(targ, sizeof targ); targ[ROOMNAMELEN - 1] = 0; is_copy = extract_int(args, 2); if (CtdlGetRoom(&qtemp, targ) != 0) { cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ); return; } if (!strcasecmp(qtemp.QRname, CC->room.QRname)) { cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS); return; } CtdlGetUser(&CC->user, CC->curr_user); CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL); /* Check for permission to perform this operation. * Remember: "CC->room" is source, "qtemp" is target. */ permit = 0; /* Admins can move/copy */ if (CC->user.axlevel >= AxAideU) permit = 1; /* Room aides can move/copy */ if (CC->user.usernum == CC->room.QRroomaide) permit = 1; /* Permit move/copy from personal rooms */ if ((CC->room.QRflags & QR_MAILBOX) && (qtemp.QRflags & QR_MAILBOX)) permit = 1; /* Permit only copy from public to personal room */ if ( (is_copy) && (!(CC->room.QRflags & QR_MAILBOX)) && (qtemp.QRflags & QR_MAILBOX)) permit = 1; /* Permit message removal from collaborative delete rooms */ if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1; /* Users allowed to post into the target room may move into it too. */ if ((CC->room.QRflags & QR_MAILBOX) && (qtemp.QRflags & UA_POSTALLOWED)) permit = 1; /* User must have access to target room */ if (!(ra & UA_KNOWN)) permit = 0; if (!permit) { cprintf("%d Higher access required.\n", ERROR + HIGHER_ACCESS_REQUIRED); return; } /* * Build our message set to be moved/copied */ msgs = malloc(num_msgs * sizeof(long)); for (i=0; iroom.QRname, msgs, num_msgs, ""); } free(msgs); cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") ); } /*****************************************************************************/ /* MODULE INITIALIZATION STUFF */ /*****************************************************************************/ CTDL_MODULE_INIT(ctdl_message) { if (!threading) { CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room"); CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format"); CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format"); CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)"); CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format"); CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output"); CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download"); CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment"); CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message"); CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message"); CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room"); } /* return our Subversion id for the Log */ return "ctdl_message"; } citadel-9.01/modules/ctdlproto/serv_user.c0000644000000000000000000004440312507024051017412 0ustar rootroot/* * Server functions which perform operations on user objects. * * Copyright (c) 1987-2011 by the citadel.org team * * This program is open source software; you can redistribute it and/or * modify it under the terms of the GNU General Public License, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "support.h" #include "control.h" #include "ctdl_module.h" #include "citserver.h" #include "user_ops.h" #include "internet_addressing.h" /* * USER cmd */ void cmd_user(char *cmdbuf) { char username[256]; int a; CON_syslog(LOG_DEBUG, "cmd_user(%s)\n", cmdbuf); extract_token(username, cmdbuf, 0, '|', sizeof username); CON_syslog(LOG_DEBUG, "username: %s\n", username); striplt(username); CON_syslog(LOG_DEBUG, "username: %s\n", username); a = CtdlLoginExistingUser(NULL, username); switch (a) { case login_already_logged_in: cprintf("%d Already logged in.\n", ERROR + ALREADY_LOGGED_IN); return; case login_too_many_users: cprintf("%d %s: " "Too many users are already online " "(maximum is %d)\n", ERROR + MAX_SESSIONS_EXCEEDED, config.c_nodename, config.c_maxsessions); return; case login_ok: cprintf("%d Password required for %s\n", MORE_DATA, CC->curr_user); return; case login_not_found: cprintf("%d %s not found.\n", ERROR + NO_SUCH_USER, username); return; default: cprintf("%d Internal error\n", ERROR + INTERNAL_ERROR); } } void cmd_pass(char *buf) { char password[SIZ]; int a; long len; memset(password, 0, sizeof(password)); len = extract_token(password, buf, 0, '|', sizeof password); a = CtdlTryPassword(password, len); switch (a) { case pass_already_logged_in: cprintf("%d Already logged in.\n", ERROR + ALREADY_LOGGED_IN); return; case pass_no_user: cprintf("%d You must send a name with USER first.\n", ERROR + USERNAME_REQUIRED); return; case pass_wrong_password: cprintf("%d Wrong password.\n", ERROR + PASSWORD_REQUIRED); return; case pass_ok: logged_in_response(); return; } } /* * cmd_newu() - create a new user account and log in as that user */ void cmd_newu(char *cmdbuf) { int a; long len; char username[SIZ]; if (config.c_auth_mode != AUTHMODE_NATIVE) { cprintf("%d This system does not use native mode authentication.\n", ERROR + NOT_HERE); return; } if (config.c_disable_newu) { cprintf("%d Self-service user account creation " "is disabled on this system.\n", ERROR + NOT_HERE); return; } if (CC->logged_in) { cprintf("%d Already logged in.\n", ERROR + ALREADY_LOGGED_IN); return; } if (CC->nologin) { cprintf("%d %s: Too many users are already online (maximum is %d)\n", ERROR + MAX_SESSIONS_EXCEEDED, config.c_nodename, config.c_maxsessions); } extract_token(username, cmdbuf, 0, '|', sizeof username); strproc(username); len = cutuserkey(username); if (IsEmptyStr(username)) { cprintf("%d You must supply a user name.\n", ERROR + USERNAME_REQUIRED); return; } if ((!strcasecmp(username, "bbs")) || (!strcasecmp(username, "new")) || (!strcasecmp(username, "."))) { cprintf("%d '%s' is an invalid login name.\n", ERROR + ILLEGAL_VALUE, username); return; } a = create_user(username, len, 1); if (a == 0) { logged_in_response(); } else if (a == ERROR + ALREADY_EXISTS) { cprintf("%d '%s' already exists.\n", ERROR + ALREADY_EXISTS, username); return; } else if (a == ERROR + INTERNAL_ERROR) { cprintf("%d Internal error - user record disappeared?\n", ERROR + INTERNAL_ERROR); return; } else { cprintf("%d unknown error\n", ERROR + INTERNAL_ERROR); } } /* * set password - citadel protocol implementation */ void cmd_setp(char *new_pw) { if (CtdlAccessCheck(ac_logged_in)) { return; } if ( (CC->user.uid != CTDLUID) && (CC->user.uid != (-1)) ) { cprintf("%d Not allowed. Use the 'passwd' command.\n", ERROR + NOT_HERE); return; } if (CC->is_master) { cprintf("%d The master prefix password cannot be changed with this command.\n", ERROR + NOT_HERE); return; } if (!strcasecmp(new_pw, "GENERATE_RANDOM_PASSWORD")) { char random_password[17]; snprintf(random_password, sizeof random_password, "%08lx%08lx", random(), random()); CtdlSetPassword(random_password); cprintf("%d %s\n", CIT_OK, random_password); } else { strproc(new_pw); if (IsEmptyStr(new_pw)) { cprintf("%d Password unchanged.\n", CIT_OK); return; } CtdlSetPassword(new_pw); cprintf("%d Password changed.\n", CIT_OK); } } /* * cmd_creu() - administratively create a new user account (do not log in to it) */ void cmd_creu(char *cmdbuf) { int a; long len; char username[SIZ]; char password[SIZ]; struct ctdluser tmp; if (CtdlAccessCheck(ac_aide)) { return; } extract_token(username, cmdbuf, 0, '|', sizeof username); strproc(username); strproc(password); if (IsEmptyStr(username)) { cprintf("%d You must supply a user name.\n", ERROR + USERNAME_REQUIRED); return; } len = cutuserkey(username); extract_token(password, cmdbuf, 1, '|', sizeof password); a = create_user(username, len, 0); if (a == 0) { if (!IsEmptyStr(password)) { CtdlGetUserLock(&tmp, username); safestrncpy(tmp.password, password, sizeof(tmp.password)); CtdlPutUserLock(&tmp); } cprintf("%d User '%s' created %s.\n", CIT_OK, username, (!IsEmptyStr(password)) ? "and password set" : "with no password"); return; } else if (a == ERROR + ALREADY_EXISTS) { cprintf("%d '%s' already exists.\n", ERROR + ALREADY_EXISTS, username); return; } else if ( (config.c_auth_mode != AUTHMODE_NATIVE) && (a == ERROR + NO_SUCH_USER) ) { cprintf("%d User accounts are not created within Citadel in host authentication mode.\n", ERROR + NO_SUCH_USER); return; } else { cprintf("%d An error occurred creating the user account.\n", ERROR + INTERNAL_ERROR); } } /* * get user parameters */ void cmd_getu(char *cmdbuf) { if (CtdlAccessCheck(ac_logged_in)) return; CtdlGetUser(&CC->user, CC->curr_user); cprintf("%d 80|24|%d|\n", CIT_OK, (CC->user.flags & US_USER_SET) ); } /* * set user parameters */ void cmd_setu(char *new_parms) { if (CtdlAccessCheck(ac_logged_in)) return; if (num_parms(new_parms) < 3) { cprintf("%d Usage error.\n", ERROR + ILLEGAL_VALUE); return; } CtdlLockGetCurrentUser(); CC->user.flags = CC->user.flags & (~US_USER_SET); CC->user.flags = CC->user.flags | (extract_int(new_parms, 2) & US_USER_SET); CtdlPutCurrentUserLock(); cprintf("%d Ok\n", CIT_OK); } /* * set last read pointer */ void cmd_slrp(char *new_ptr) { long newlr; visit vbuf; visit original_vbuf; if (CtdlAccessCheck(ac_logged_in)) { return; } if (!strncasecmp(new_ptr, "highest", 7)) { newlr = CC->room.QRhighest; } else { newlr = atol(new_ptr); } CtdlLockGetCurrentUser(); CtdlGetRelationship(&vbuf, &CC->user, &CC->room); memcpy(&original_vbuf, &vbuf, sizeof(visit)); vbuf.v_lastseen = newlr; snprintf(vbuf.v_seen, sizeof vbuf.v_seen, "*:%ld", newlr); /* Only rewrite the record if it changed */ if ( (vbuf.v_lastseen != original_vbuf.v_lastseen) || (strcmp(vbuf.v_seen, original_vbuf.v_seen)) ) { CtdlSetRelationship(&vbuf, &CC->user, &CC->room); } CtdlPutCurrentUserLock(); cprintf("%d %ld\n", CIT_OK, newlr); } void cmd_seen(char *argbuf) { long target_msgnum = 0L; int target_setting = 0; if (CtdlAccessCheck(ac_logged_in)) { return; } if (num_parms(argbuf) != 2) { cprintf("%d Invalid parameters\n", ERROR + ILLEGAL_VALUE); return; } target_msgnum = extract_long(argbuf, 0); target_setting = extract_int(argbuf, 1); CtdlSetSeen(&target_msgnum, 1, target_setting, ctdlsetseen_seen, NULL, NULL); cprintf("%d OK\n", CIT_OK); } void cmd_gtsn(char *argbuf) { visit vbuf; if (CtdlAccessCheck(ac_logged_in)) { return; } /* Learn about the user and room in question */ CtdlGetRelationship(&vbuf, &CC->user, &CC->room); cprintf("%d ", CIT_OK); client_write(vbuf.v_seen, strlen(vbuf.v_seen)); client_write(HKEY("\n")); } /* * INVT and KICK commands */ void cmd_invt_kick(char *iuser, int op) { /* * These commands are only allowed by admins, room admins, * and room namespace owners */ if (is_room_aide()) { /* access granted */ } else if ( ((atol(CC->room.QRname) == CC->user.usernum) ) && (CC->user.usernum != 0) ) { /* access granted */ } else { /* access denied */ cprintf("%d Higher access or room ownership required.\n", ERROR + HIGHER_ACCESS_REQUIRED); return; } if (!strncasecmp(CC->room.QRname, config.c_baseroom, ROOMNAMELEN)) { cprintf("%d Can't add/remove users from this room.\n", ERROR + NOT_HERE); return; } if (CtdlInvtKick(iuser, op) != 0) { cprintf("%d No such user.\n", ERROR + NO_SUCH_USER); return; } cprintf("%d %s %s %s.\n", CIT_OK, iuser, ((op == 1) ? "invited to" : "kicked out of"), CC->room.QRname); return; } void cmd_invt(char *iuser) {cmd_invt_kick(iuser, 1);} void cmd_kick(char *iuser) {cmd_invt_kick(iuser, 0);} /* * forget (Zap) the current room */ void cmd_forg(char *argbuf) { if (CtdlAccessCheck(ac_logged_in)) { return; } if (CtdlForgetThisRoom() == 0) { cprintf("%d Ok\n", CIT_OK); } else { cprintf("%d You may not forget this room.\n", ERROR + NOT_HERE); } } /* * Get Next Unregistered User */ void cmd_gnur(char *argbuf) { struct cdbdata *cdbus; struct ctdluser usbuf; if (CtdlAccessCheck(ac_aide)) { return; } if ((CitControl.MMflags & MM_VALID) == 0) { cprintf("%d There are no unvalidated users.\n", CIT_OK); return; } /* There are unvalidated users. Traverse the user database, * and return the first user we find that needs validation. */ cdb_rewind(CDB_USERS); while (cdbus = cdb_next_item(CDB_USERS), cdbus != NULL) { memset(&usbuf, 0, sizeof(struct ctdluser)); memcpy(&usbuf, cdbus->ptr, ((cdbus->len > sizeof(struct ctdluser)) ? sizeof(struct ctdluser) : cdbus->len)); cdb_free(cdbus); if ((usbuf.flags & US_NEEDVALID) && (usbuf.axlevel > AxDeleted)) { cprintf("%d %s\n", MORE_DATA, usbuf.fullname); cdb_close_cursor(CDB_USERS); return; } } /* If we get to this point, there are no more unvalidated users. * Therefore we clear the "users need validation" flag. */ begin_critical_section(S_CONTROL); get_control(); CitControl.MMflags = CitControl.MMflags & (~MM_VALID); put_control(); end_critical_section(S_CONTROL); cprintf("%d *** End of registration.\n", CIT_OK); } /* * validate a user */ void cmd_vali(char *v_args) { char user[128]; int newax; struct ctdluser userbuf; extract_token(user, v_args, 0, '|', sizeof user); newax = extract_int(v_args, 1); if (CtdlAccessCheck(ac_aide) || (newax > AxAideU) || (newax < AxDeleted)) { return; } if (CtdlGetUserLock(&userbuf, user) != 0) { cprintf("%d '%s' not found.\n", ERROR + NO_SUCH_USER, user); return; } userbuf.axlevel = newax; userbuf.flags = (userbuf.flags & ~US_NEEDVALID); CtdlPutUserLock(&userbuf); /* If the access level was set to zero, delete the user */ if (newax == 0) { if (purge_user(user) == 0) { cprintf("%d %s Deleted.\n", CIT_OK, userbuf.fullname); return; } } cprintf("%d User '%s' validated.\n", CIT_OK, userbuf.fullname); } /* * List users (searchstring may be empty to list all users) */ void cmd_list(char *cmdbuf) { char searchstring[256]; extract_token(searchstring, cmdbuf, 0, '|', sizeof searchstring); striplt(searchstring); cprintf("%d \n", LISTING_FOLLOWS); ForEachUser(ListThisUser, (void *)searchstring ); cprintf("000\n"); } /* * assorted info we need to check at login */ void cmd_chek(char *argbuf) { int mail = 0; int regis = 0; int vali = 0; if (CtdlAccessCheck(ac_logged_in)) { return; } CtdlGetUser(&CC->user, CC->curr_user); /* no lock is needed here */ if ((REGISCALL != 0) && ((CC->user.flags & US_REGIS) == 0)) regis = 1; if (CC->user.axlevel >= AxAideU) { get_control(); if (CitControl.MMflags & MM_VALID) vali = 1; } /* check for mail */ mail = InitialMailCheck(); cprintf("%d %d|%d|%d|%s|\n", CIT_OK, mail, regis, vali, CC->cs_inet_email); } /* * check to see if a user exists */ void cmd_qusr(char *who) { struct ctdluser usbuf; if (CtdlGetUser(&usbuf, who) == 0) { cprintf("%d %s\n", CIT_OK, usbuf.fullname); } else { cprintf("%d No such user.\n", ERROR + NO_SUCH_USER); } } /* * Administrative Get User Parameters */ void cmd_agup(char *cmdbuf) { struct ctdluser usbuf; char requested_user[128]; if (CtdlAccessCheck(ac_aide)) { return; } extract_token(requested_user, cmdbuf, 0, '|', sizeof requested_user); if (CtdlGetUser(&usbuf, requested_user) != 0) { cprintf("%d No such user.\n", ERROR + NO_SUCH_USER); return; } cprintf("%d %s|%s|%u|%ld|%ld|%d|%ld|%ld|%d\n", CIT_OK, usbuf.fullname, usbuf.password, usbuf.flags, usbuf.timescalled, usbuf.posted, (int) usbuf.axlevel, usbuf.usernum, (long)usbuf.lastcall, usbuf.USuserpurge); } /* * Administrative Set User Parameters */ void cmd_asup(char *cmdbuf) { struct ctdluser usbuf; char requested_user[128]; char notify[SIZ]; int np; int newax; int deleted = 0; if (CtdlAccessCheck(ac_aide)) return; extract_token(requested_user, cmdbuf, 0, '|', sizeof requested_user); if (CtdlGetUserLock(&usbuf, requested_user) != 0) { cprintf("%d No such user.\n", ERROR + NO_SUCH_USER); return; } np = num_parms(cmdbuf); if (np > 1) extract_token(usbuf.password, cmdbuf, 1, '|', sizeof usbuf.password); if (np > 2) usbuf.flags = extract_int(cmdbuf, 2); if (np > 3) usbuf.timescalled = extract_int(cmdbuf, 3); if (np > 4) usbuf.posted = extract_int(cmdbuf, 4); if (np > 5) { newax = extract_int(cmdbuf, 5); if ((newax >= AxDeleted) && (newax <= AxAideU)) { usbuf.axlevel = newax; } } if (np > 7) { usbuf.lastcall = extract_long(cmdbuf, 7); } if (np > 8) { usbuf.USuserpurge = extract_int(cmdbuf, 8); } CtdlPutUserLock(&usbuf); if (usbuf.axlevel == AxDeleted) { if (purge_user(requested_user) == 0) { deleted = 1; } } if (deleted) { snprintf(notify, SIZ, "User \"%s\" has been deleted by %s.\n", usbuf.fullname, (CC->logged_in ? CC->user.fullname : "an administrator") ); CtdlAideMessage(notify, "User Deletion Message"); } cprintf("%d Ok", CIT_OK); if (deleted) cprintf(" (%s deleted)", requested_user); cprintf("\n"); } /* * Citadel protocol command to do the same */ void cmd_isme(char *argbuf) { char addr[256]; if (CtdlAccessCheck(ac_logged_in)) return; extract_token(addr, argbuf, 0, '|', sizeof addr); if (CtdlIsMe(addr, sizeof addr)) { cprintf("%d %s\n", CIT_OK, addr); } else { cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE); } } /* * Set the preferred view for the current user/room combination */ void cmd_view(char *cmdbuf) { int requested_view; visit vbuf; if (CtdlAccessCheck(ac_logged_in)) { return; } requested_view = extract_int(cmdbuf, 0); CtdlGetRelationship(&vbuf, &CC->user, &CC->room); vbuf.v_view = requested_view; CtdlSetRelationship(&vbuf, &CC->user, &CC->room); cprintf("%d ok\n", CIT_OK); } /* * Rename a user */ void cmd_renu(char *cmdbuf) { int retcode; char oldname[USERNAME_SIZE]; char newname[USERNAME_SIZE]; if (CtdlAccessCheck(ac_aide)) { return; } extract_token(oldname, cmdbuf, 0, '|', sizeof oldname); extract_token(newname, cmdbuf, 1, '|', sizeof newname); retcode = rename_user(oldname, newname); switch(retcode) { case RENAMEUSER_OK: cprintf("%d '%s' has been renamed to '%s'.\n", CIT_OK, oldname, newname); return; case RENAMEUSER_LOGGED_IN: cprintf("%d '%s' is currently logged in and cannot be renamed.\n", ERROR + ALREADY_LOGGED_IN , oldname); return; case RENAMEUSER_NOT_FOUND: cprintf("%d '%s' does not exist.\n", ERROR + NO_SUCH_USER, oldname); return; case RENAMEUSER_ALREADY_EXISTS: cprintf("%d A user named '%s' already exists.\n", ERROR + ALREADY_EXISTS, newname); return; } cprintf("%d An unknown error occurred.\n", ERROR); } void cmd_quit(char *argbuf) { cprintf("%d Goodbye.\n", CIT_OK); CC->kill_me = KILLME_CLIENT_LOGGED_OUT; } void cmd_lout(char *argbuf) { if (CC->logged_in) CtdlUserLogout(); cprintf("%d logged out.\n", CIT_OK); } /*****************************************************************************/ /* MODULE INITIALIZATION STUFF */ /*****************************************************************************/ CTDL_MODULE_INIT(serv_user) { if (!threading) { CtdlRegisterProtoHook(cmd_user, "USER", "Submit username for login"); CtdlRegisterProtoHook(cmd_pass, "PASS", "Complete login by submitting a password"); CtdlRegisterProtoHook(cmd_quit, "QUIT", "log out and disconnect from server"); CtdlRegisterProtoHook(cmd_lout, "LOUT", "log out but do not disconnect from server"); CtdlRegisterProtoHook(cmd_creu, "CREU", "Create User"); CtdlRegisterProtoHook(cmd_setp, "SETP", "Set the password for an account"); CtdlRegisterProtoHook(cmd_getu, "GETU", "Get User parameters"); CtdlRegisterProtoHook(cmd_setu, "SETU", "Set User parameters"); CtdlRegisterProtoHook(cmd_slrp, "SLRP", "Set Last Read Pointer"); CtdlRegisterProtoHook(cmd_invt, "INVT", "Invite a user to a room"); CtdlRegisterProtoHook(cmd_kick, "KICK", "Kick a user out of a room"); CtdlRegisterProtoHook(cmd_forg, "FORG", "Forget a room"); CtdlRegisterProtoHook(cmd_gnur, "GNUR", "Get Next Unregistered User"); CtdlRegisterProtoHook(cmd_vali, "VALI", "Validate new users"); CtdlRegisterProtoHook(cmd_list, "LIST", "List users"); CtdlRegisterProtoHook(cmd_chek, "CHEK", "assorted info we need to check at login"); CtdlRegisterProtoHook(cmd_qusr, "QUSR", "check to see if a user exists"); CtdlRegisterProtoHook(cmd_agup, "AGUP", "Administratively Get User Parameters"); CtdlRegisterProtoHook(cmd_asup, "ASUP", "Administratively Set User Parameters"); CtdlRegisterProtoHook(cmd_seen, "SEEN", "Manipulate seen/unread message flags"); CtdlRegisterProtoHook(cmd_gtsn, "GTSN", "Fetch seen/unread message flags"); CtdlRegisterProtoHook(cmd_view, "VIEW", "Set preferred view for user/room combination"); CtdlRegisterProtoHook(cmd_renu, "RENU", "Rename a user"); CtdlRegisterProtoHook(cmd_newu, "NEWU", "Log in as a new user"); CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user"); } /* return our Subversion id for the Log */ return "user"; } citadel-9.01/modules/networkclient/0000755000000000000000000000000012507024051016102 5ustar rootrootcitadel-9.01/modules/networkclient/serv_networkclient.c0000644000000000000000000006446712507024051022216 0ustar rootroot/* * This module handles shared rooms, inter-Citadel mail, and outbound * mailing list processing. * * Copyright (c) 2000-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ** NOTE ** A word on the S_NETCONFIGS semaphore: * This is a fairly high-level type of critical section. It ensures that no * two threads work on the netconfigs files at the same time. Since we do * so many things inside these, here are the rules: * 1. begin_critical_section(S_NETCONFIGS) *before* begin_ any others. * 2. Do *not* perform any I/O with the client during these sections. * */ #include "sysdep.h" #include #include #include #include #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #ifdef HAVE_SYSCALL_H # include #else # if HAVE_SYS_SYSCALL_H # include # endif #endif #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "support.h" #include "config.h" #include "user_ops.h" #include "database.h" #include "msgbase.h" #include "internet_addressing.h" #include "clientsocket.h" #include "citadel_dirs.h" #include "threads.h" #include "context.h" #include "ctdl_module.h" struct CitContext networker_client_CC; #define NODE ChrPtr(((AsyncNetworker*)IO->Data)->node) #define N ((AsyncNetworker*)IO->Data)->n int NetworkClientDebugEnabled = 0; #define NCDBGLOG(LEVEL) if ((LEVEL != LOG_DEBUG) || (NetworkClientDebugEnabled != 0)) #define EVN_syslog(LEVEL, FORMAT, ...) \ NCDBGLOG(LEVEL) syslog(LEVEL, \ "%s[%ld]CC[%d]NW[%s][%ld]" FORMAT, \ IOSTR, IO->ID, CCID, NODE, N, __VA_ARGS__) #define EVNM_syslog(LEVEL, FORMAT) \ NCDBGLOG(LEVEL) syslog(LEVEL, \ "%s[%ld]CC[%d]NW[%s][%ld]" FORMAT, \ IOSTR, IO->ID, CCID, NODE, N) #define EVNCS_syslog(LEVEL, FORMAT, ...) \ NCDBGLOG(LEVEL) syslog(LEVEL, "%s[%ld]NW[%s][%ld]" FORMAT, \ IOSTR, IO->ID, NODE, N, __VA_ARGS__) #define EVNCSM_syslog(LEVEL, FORMAT) \ NCDBGLOG(LEVEL) syslog(LEVEL, "%s[%ld]NW[%s][%ld]" FORMAT, \ IOSTR, IO->ID, NODE, N) typedef enum _eNWCState { eGreating, eAuth, eNDOP, eREAD, eReadBLOB, eCLOS, eNUOP, eWRIT, eWriteBLOB, eUCLS, eQUIT }eNWCState; typedef enum _eNWCVState { eNWCVSLookup, eNWCVSConnecting, eNWCVSConnFail, eNWCVSGreating, eNWCVSAuth, eNWCVSAuthFailNTT, eNWCVSAuthFail, eNWCVSNDOP, eNWCVSNDOPDone, eNWCVSNUOP, eNWCVSNUOPDone, eNWCVSFail }eNWCVState; ConstStr NWCStateStr[] = { {HKEY("Looking up Host")}, {HKEY("Connecting host")}, {HKEY("Failed to connect")}, {HKEY("Rread Greeting")}, {HKEY("Authenticating")}, {HKEY("Auth failed by NTT")}, {HKEY("Auth failed")}, {HKEY("Downloading")}, {HKEY("Downloading Success")}, {HKEY("Uploading Spoolfile")}, {HKEY("Uploading done")}, {HKEY("failed")} }; void SetNWCState(AsyncIO *IO, eNWCVState State) { CitContext* CCC = IO->CitContext; memcpy(CCC->cs_clientname, NWCStateStr[State].Key, NWCStateStr[State].len + 1); } typedef struct _async_networker { AsyncIO IO; DNSQueryParts HostLookup; eNWCState State; long n; StrBuf *SpoolFileName; StrBuf *tempFileName; StrBuf *node; StrBuf *host; StrBuf *port; StrBuf *secret; StrBuf *Url; } AsyncNetworker; typedef eNextState(*NWClientHandler)(AsyncNetworker* NW); eNextState nwc_get_one_host_ip(AsyncIO *IO); eNextState nwc_connect_ip(AsyncIO *IO); eNextState NWC_SendQUIT(AsyncNetworker *NW); eNextState NWC_DispatchWriteDone(AsyncIO *IO); void DeleteNetworker(void *vptr) { AsyncNetworker *NW = (AsyncNetworker *)vptr; FreeStrBuf(&NW->SpoolFileName); FreeStrBuf(&NW->tempFileName); FreeStrBuf(&NW->node); FreeStrBuf(&NW->host); FreeStrBuf(&NW->port); FreeStrBuf(&NW->secret); FreeStrBuf(&NW->Url); FreeStrBuf(&NW->IO.ErrMsg); FreeAsyncIOContents(&NW->IO); if (NW->HostLookup.VParsedDNSReply != NULL) { NW->HostLookup.DNSReplyFree(NW->HostLookup.VParsedDNSReply); NW->HostLookup.VParsedDNSReply = NULL; } free(NW); } #define NWC_DBG_SEND() EVN_syslog(LOG_DEBUG, ": > %s", ChrPtr(NW->IO.SendBuf.Buf)) #define NWC_DBG_READ() EVN_syslog(LOG_DEBUG, ": < %s\n", ChrPtr(NW->IO.IOBuf)) #define NWC_OK (strncasecmp(ChrPtr(NW->IO.IOBuf), "+OK", 3) == 0) eNextState NWC_SendFailureMessage(AsyncIO *IO) { AsyncNetworker *NW = IO->Data; long lens[2]; const char *strs[2]; EVN_syslog(LOG_DEBUG, "NWC: %s\n", __FUNCTION__); strs[0] = ChrPtr(NW->node); lens[0] = StrLength(NW->node); strs[1] = ChrPtr(NW->IO.ErrMsg); lens[1] = StrLength(NW->IO.ErrMsg); CtdlAideFPMessage( ChrPtr(NW->IO.ErrMsg), "Networker error", 2, strs, (long*) &lens, CCID, IO->ID, EvGetNow(IO)); return eAbort; } eNextState FinalizeNetworker(AsyncIO *IO) { AsyncNetworker *NW = (AsyncNetworker *)IO->Data; CtdlNetworkTalkingTo(SKEY(NW->node), NTT_REMOVE); DeleteNetworker(IO->Data); return eAbort; } eNextState NWC_ReadGreeting(AsyncNetworker *NW) { char connected_to[SIZ]; AsyncIO *IO = &NW->IO; SetNWCState(IO, eNWCVSGreating); NWC_DBG_READ(); /* Read the server greeting */ /* Check that the remote is who we think it is and warn the Aide if not */ extract_token (connected_to, ChrPtr(NW->IO.IOBuf), 1, ' ', sizeof connected_to); if (strcmp(connected_to, ChrPtr(NW->node)) != 0) { if (NW->IO.ErrMsg == NULL) NW->IO.ErrMsg = NewStrBuf(); StrBufPrintf(NW->IO.ErrMsg, "Connected to node \"%s\" but I was expecting to connect to node \"%s\".", connected_to, ChrPtr(NW->node)); EVN_syslog(LOG_ERR, "%s\n", ChrPtr(NW->IO.ErrMsg)); return EventQueueDBOperation(IO, NWC_SendFailureMessage, 1); } return eSendReply; } eNextState NWC_SendAuth(AsyncNetworker *NW) { AsyncIO *IO = &NW->IO; SetNWCState(IO, eNWCVSAuth); /* We're talking to the correct node. Now identify ourselves. */ StrBufPrintf(NW->IO.SendBuf.Buf, "NETP %s|%s\n", config.c_nodename, ChrPtr(NW->secret)); NWC_DBG_SEND(); return eSendReply; } eNextState NWC_ReadAuthReply(AsyncNetworker *NW) { AsyncIO *IO = &NW->IO; NWC_DBG_READ(); if (ChrPtr(NW->IO.IOBuf)[0] == '2') { return eSendReply; } else { int Error = atol(ChrPtr(NW->IO.IOBuf)); if (NW->IO.ErrMsg == NULL) NW->IO.ErrMsg = NewStrBuf(); StrBufPrintf(NW->IO.ErrMsg, "Connected to node \"%s\" but my secret wasn't accurate.\nReason was:%s\n", ChrPtr(NW->node), ChrPtr(NW->IO.IOBuf) + 4); if (Error == 552) { SetNWCState(IO, eNWCVSAuthFailNTT); EVN_syslog(LOG_INFO, "Already talking to %s; skipping this time.\n", ChrPtr(NW->node)); } else { SetNWCState(IO, eNWCVSAuthFailNTT); EVN_syslog(LOG_ERR, "%s\n", ChrPtr(NW->IO.ErrMsg)); return EventQueueDBOperation(IO, NWC_SendFailureMessage, 1); } return eAbort; } } eNextState NWC_SendNDOP(AsyncNetworker *NW) { AsyncIO *IO = &NW->IO; SetNWCState(IO, eNWCVSNDOP); NW->tempFileName = NewStrBuf(); NW->SpoolFileName = NewStrBuf(); StrBufPrintf(NW->SpoolFileName, "%s/%s.%lx%x", ctdl_netin_dir, ChrPtr(NW->node), time(NULL),// TODO: get time from libev rand()); StrBufStripSlashes(NW->SpoolFileName, 1); StrBufPrintf(NW->tempFileName, "%s/%s.%lx%x", ctdl_nettmp_dir, ChrPtr(NW->node), time(NULL),// TODO: get time from libev rand()); StrBufStripSlashes(NW->tempFileName, 1); /* We're talking to the correct node. Now identify ourselves. */ StrBufPlain(NW->IO.SendBuf.Buf, HKEY("NDOP\n")); NWC_DBG_SEND(); return eSendReply; } eNextState NWC_ReadNDOPReply(AsyncNetworker *NW) { AsyncIO *IO = &NW->IO; int TotalSendSize; NWC_DBG_READ(); if (ChrPtr(NW->IO.IOBuf)[0] == '2') { int LogLevel = LOG_DEBUG; NW->IO.IOB.TotalSentAlready = 0; TotalSendSize = atol (ChrPtr(NW->IO.IOBuf) + 4); if (TotalSendSize > 0) LogLevel = LOG_INFO; EVN_syslog(LogLevel, "Expecting to transfer %d bytes to %s\n", TotalSendSize, ChrPtr(NW->tempFileName)); if (TotalSendSize <= 0) { NW->State = eNUOP - 1; } else { int fd; fd = open(ChrPtr(NW->tempFileName), O_EXCL|O_CREAT|O_NONBLOCK|O_WRONLY, S_IRUSR|S_IWUSR); if (fd < 0) { SetNWCState(IO, eNWCVSFail); EVN_syslog(LOG_CRIT, "cannot open %s: %s\n", ChrPtr(NW->tempFileName), strerror(errno)); NW->State = eQUIT - 1; return eAbort; } FDIOBufferInit(&NW->IO.IOB, &NW->IO.RecvBuf, fd, TotalSendSize); } return eSendReply; } else { SetNWCState(IO, eNWCVSFail); return eAbort; } } eNextState NWC_SendREAD(AsyncNetworker *NW) { AsyncIO *IO = &NW->IO; eNextState rc; if (NW->IO.IOB.TotalSentAlready < NW->IO.IOB.TotalSendSize) { /* * If shutting down we can exit here and unlink the temp file. * this shouldn't loose us any messages. */ if (server_shutting_down) { FDIOBufferDelete(&NW->IO.IOB); unlink(ChrPtr(NW->tempFileName)); FDIOBufferDelete(&IO->IOB); SetNWCState(IO, eNWCVSFail); return eAbort; } StrBufPrintf(NW->IO.SendBuf.Buf, "READ "LOFF_T_FMT"|%ld\n", NW->IO.IOB.TotalSentAlready, NW->IO.IOB.TotalSendSize); /* ((NW->IO.IOB.TotalSendSize - NW->IO.IOB.TotalSentAlready > IGNET_PACKET_SIZE) ? IGNET_PACKET_SIZE : (NW->IO.IOB.TotalSendSize - NW->IO.IOB.TotalSentAlready)) ); */ NWC_DBG_SEND(); return eSendReply; } else { NW->State = eCLOS; rc = NWC_DispatchWriteDone(&NW->IO); NWC_DBG_SEND(); return rc; } } eNextState NWC_ReadREADState(AsyncNetworker *NW) { AsyncIO *IO = &NW->IO; NWC_DBG_READ(); if (ChrPtr(NW->IO.IOBuf)[0] == '6') { NW->IO.IOB.ChunkSendRemain = NW->IO.IOB.ChunkSize = atol(ChrPtr(NW->IO.IOBuf)+4); return eReadFile; } FDIOBufferDelete(&IO->IOB); return eAbort; } eNextState NWC_ReadREADBlobDone(AsyncNetworker *NW); eNextState NWC_ReadREADBlob(AsyncNetworker *NW) { eNextState rc; AsyncIO *IO = &NW->IO; NWC_DBG_READ(); if (NW->IO.IOB.TotalSendSize == NW->IO.IOB.TotalSentAlready) { NW->State ++; FDIOBufferDelete(&NW->IO.IOB); if (link(ChrPtr(NW->tempFileName), ChrPtr(NW->SpoolFileName)) != 0) { EVN_syslog(LOG_ALERT, "Could not link %s to %s: %s\n", ChrPtr(NW->tempFileName), ChrPtr(NW->SpoolFileName), strerror(errno)); } else { EVN_syslog(LOG_INFO, "moved %s to %s\n", ChrPtr(NW->tempFileName), ChrPtr(NW->SpoolFileName)); } unlink(ChrPtr(NW->tempFileName)); rc = NWC_DispatchWriteDone(&NW->IO); NW->State --; return rc; } else { NW->State --; NW->IO.IOB.ChunkSendRemain = NW->IO.IOB.ChunkSize; return eSendReply; //NWC_DispatchWriteDone(&NW->IO); } } eNextState NWC_ReadREADBlobDone(AsyncNetworker *NW) { eNextState rc; AsyncIO *IO = &NW->IO; /* we don't have any data to debug print here. */ if (NW->IO.IOB.TotalSentAlready >= NW->IO.IOB.TotalSendSize) { NW->State ++; FDIOBufferDelete(&NW->IO.IOB); if (link(ChrPtr(NW->tempFileName), ChrPtr(NW->SpoolFileName)) != 0) { EVN_syslog(LOG_ALERT, "Could not link %s to %s: %s\n", ChrPtr(NW->tempFileName), ChrPtr(NW->SpoolFileName), strerror(errno)); } else { EVN_syslog(LOG_INFO, "moved %s to %s\n", ChrPtr(NW->tempFileName), ChrPtr(NW->SpoolFileName)); } unlink(ChrPtr(NW->tempFileName)); rc = NWC_DispatchWriteDone(&NW->IO); NW->State --; return rc; } else { NW->State --; NW->IO.IOB.ChunkSendRemain = NW->IO.IOB.ChunkSize; return NWC_DispatchWriteDone(&NW->IO); } } eNextState NWC_SendCLOS(AsyncNetworker *NW) { AsyncIO *IO = &NW->IO; SetNWCState(IO, eNWCVSNDOPDone); StrBufPlain(NW->IO.SendBuf.Buf, HKEY("CLOS\n")); NWC_DBG_SEND(); return eSendReply; } eNextState NWC_ReadCLOSReply(AsyncNetworker *NW) { AsyncIO *IO = &NW->IO; NWC_DBG_READ(); FDIOBufferDelete(&IO->IOB); if (ChrPtr(NW->IO.IOBuf)[0] != '2') return eTerminateConnection; return eSendReply; } eNextState NWC_SendNUOP(AsyncNetworker *NW) { AsyncIO *IO = &NW->IO; eNextState rc; long TotalSendSize; struct stat statbuf; int fd; SetNWCState(IO, eNWCVSNUOP); StrBufPrintf(NW->SpoolFileName, "%s/%s", ctdl_netout_dir, ChrPtr(NW->node)); StrBufStripSlashes(NW->SpoolFileName, 1); fd = open(ChrPtr(NW->SpoolFileName), O_EXCL|O_NONBLOCK|O_RDONLY); if (fd < 0) { if (errno != ENOENT) { EVN_syslog(LOG_CRIT, "cannot open %s: %s\n", ChrPtr(NW->SpoolFileName), strerror(errno)); } NW->State = eQUIT; rc = NWC_SendQUIT(NW); NWC_DBG_SEND(); return rc; } if (fstat(fd, &statbuf) == -1) { EVN_syslog(LOG_CRIT, "FSTAT FAILED %s [%s]--\n", ChrPtr(NW->SpoolFileName), strerror(errno)); if (fd > 0) close(fd); return eAbort; } TotalSendSize = statbuf.st_size; if (TotalSendSize == 0) { EVNM_syslog(LOG_DEBUG, "Nothing to send.\n"); NW->State = eQUIT; rc = NWC_SendQUIT(NW); NWC_DBG_SEND(); if (fd > 0) close(fd); return rc; } else { EVN_syslog(LOG_INFO, "sending %s to %s\n", ChrPtr(NW->SpoolFileName), ChrPtr(NW->node)); } FDIOBufferInit(&NW->IO.IOB, &NW->IO.SendBuf, fd, TotalSendSize); StrBufPlain(NW->IO.SendBuf.Buf, HKEY("NUOP\n")); NWC_DBG_SEND(); return eSendReply; } eNextState NWC_ReadNUOPReply(AsyncNetworker *NW) { AsyncIO *IO = &NW->IO; NWC_DBG_READ(); if (ChrPtr(NW->IO.IOBuf)[0] != '2') { FDIOBufferDelete(&IO->IOB); return eAbort; } return eSendReply; } eNextState NWC_SendWRIT(AsyncNetworker *NW) { AsyncIO *IO = &NW->IO; StrBufPrintf(NW->IO.SendBuf.Buf, "WRIT "LOFF_T_FMT"\n", NW->IO.IOB.TotalSendSize - NW->IO.IOB.TotalSentAlready); NWC_DBG_SEND(); return eSendReply; } eNextState NWC_ReadWRITReply(AsyncNetworker *NW) { AsyncIO *IO = &NW->IO; NWC_DBG_READ(); if (ChrPtr(NW->IO.IOBuf)[0] != '7') { FDIOBufferDelete(&IO->IOB); return eAbort; } NW->IO.IOB.ChunkSendRemain = NW->IO.IOB.ChunkSize = atol(ChrPtr(NW->IO.IOBuf)+4); return eSendFile; } eNextState NWC_SendBlobDone(AsyncNetworker *NW) { AsyncIO *IO = &NW->IO; eNextState rc; if (NW->IO.IOB.TotalSentAlready >= IO->IOB.TotalSendSize) { NW->State ++; FDIOBufferDelete(&IO->IOB); rc = NWC_DispatchWriteDone(IO); NW->State --; return rc; } else { NW->State --; IO->IOB.ChunkSendRemain = IO->IOB.ChunkSize; rc = NWC_DispatchWriteDone(IO); NW->State --; return rc; } } eNextState NWC_SendUCLS(AsyncNetworker *NW) { AsyncIO *IO = &NW->IO; StrBufPlain(NW->IO.SendBuf.Buf, HKEY("UCLS 1\n")); NWC_DBG_SEND(); return eSendReply; } eNextState NWC_ReadUCLS(AsyncNetworker *NW) { AsyncIO *IO = &NW->IO; NWC_DBG_READ(); EVN_syslog(LOG_NOTICE, "Sent %s [%ld] octets to <%s>\n", ChrPtr(NW->SpoolFileName), NW->IO.IOB.ChunkSize, ChrPtr(NW->node)); if (ChrPtr(NW->IO.IOBuf)[0] == '2') { EVN_syslog(LOG_DEBUG, "Removing <%s>\n", ChrPtr(NW->SpoolFileName)); unlink(ChrPtr(NW->SpoolFileName)); } FDIOBufferDelete(&IO->IOB); SetNWCState(IO, eNWCVSNUOPDone); return eSendReply; } eNextState NWC_SendQUIT(AsyncNetworker *NW) { AsyncIO *IO = &NW->IO; StrBufPlain(NW->IO.SendBuf.Buf, HKEY("QUIT\n")); NWC_DBG_SEND(); return eSendReply; } eNextState NWC_ReadQUIT(AsyncNetworker *NW) { AsyncIO *IO = &NW->IO; NWC_DBG_READ(); return eAbort; } NWClientHandler NWC_ReadHandlers[] = { NWC_ReadGreeting, NWC_ReadAuthReply, NWC_ReadNDOPReply, NWC_ReadREADState, NWC_ReadREADBlob, NWC_ReadCLOSReply, NWC_ReadNUOPReply, NWC_ReadWRITReply, NWC_SendBlobDone, NWC_ReadUCLS, NWC_ReadQUIT}; long NWC_ConnTimeout = 100; const long NWC_SendTimeouts[] = { 100, 100, 100, 100, 100, 100, 100, 100 }; const ConstStr NWC[] = { {HKEY("Connection broken during ")}, {HKEY("Connection broken during ")}, {HKEY("Connection broken during ")}, {HKEY("Connection broken during ")}, {HKEY("Connection broken during ")}, {HKEY("Connection broken during ")}, {HKEY("Connection broken during ")}, {HKEY("Connection broken during ")} }; NWClientHandler NWC_SendHandlers[] = { NULL, NWC_SendAuth, NWC_SendNDOP, NWC_SendREAD, NWC_ReadREADBlobDone, NWC_SendCLOS, NWC_SendNUOP, NWC_SendWRIT, NWC_SendBlobDone, NWC_SendUCLS, NWC_SendQUIT }; const long NWC_ReadTimeouts[] = { 100, 100, 100, 100, 100, 100, 100, 100, 100, 100 }; eNextState nwc_get_one_host_ip_done(AsyncIO *IO) { AsyncNetworker *NW = IO->Data; struct hostent *hostent; QueryCbDone(IO); hostent = NW->HostLookup.VParsedDNSReply; if ((NW->HostLookup.DNSStatus == ARES_SUCCESS) && (hostent != NULL) ) { memset(&NW->IO.ConnectMe->Addr, 0, sizeof(struct in6_addr)); if (NW->IO.ConnectMe->IPv6) { memcpy(&NW->IO.ConnectMe->Addr.sin6_addr.s6_addr, &hostent->h_addr_list[0], sizeof(struct in6_addr)); NW->IO.ConnectMe->Addr.sin6_family = hostent->h_addrtype; NW->IO.ConnectMe->Addr.sin6_port = htons(atol(ChrPtr(NW->port)));//// TODO use the one from the URL. } else { struct sockaddr_in *addr = (struct sockaddr_in*) &NW->IO.ConnectMe->Addr; /* Bypass the ns lookup result like this: IO->Addr.sin_addr.s_addr = inet_addr("127.0.0.1"); */ // addr->sin_addr.s_addr = htonl((uint32_t)&hostent->h_addr_list[0]); memcpy(&addr->sin_addr.s_addr, hostent->h_addr_list[0], sizeof(uint32_t)); addr->sin_family = hostent->h_addrtype; addr->sin_port = htons(504);/// default citadel port } return nwc_connect_ip(IO); } else return eAbort; } eNextState nwc_get_one_host_ip(AsyncIO *IO) { AsyncNetworker *NW = IO->Data; /* * here we start with the lookup of one host. */ EVN_syslog(LOG_DEBUG, "NWC: %s\n", __FUNCTION__); EVN_syslog(LOG_DEBUG, "NWC client[%ld]: looking up %s-Record %s : %d ...\n", NW->n, (NW->IO.ConnectMe->IPv6)? "aaaa": "a", NW->IO.ConnectMe->Host, NW->IO.ConnectMe->Port); QueueQuery((NW->IO.ConnectMe->IPv6)? ns_t_aaaa : ns_t_a, NW->IO.ConnectMe->Host, &NW->IO, &NW->HostLookup, nwc_get_one_host_ip_done); IO->NextState = eReadDNSReply; return IO->NextState; } /** * @brief lineread Handler; understands when to read more POP3 lines, and when this is a one-lined reply. */ eReadState NWC_ReadServerStatus(AsyncIO *IO) { // AsyncNetworker *NW = IO->Data; eReadState Finished = eBufferNotEmpty; switch (IO->NextState) { case eSendDNSQuery: case eReadDNSReply: case eDBQuery: case eConnect: case eTerminateConnection: case eAbort: Finished = eReadFail; break; case eSendReply: case eSendMore: case eReadMore: case eReadMessage: Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf); break; case eReadFile: case eSendFile: case eReadPayload: break; } return Finished; } eNextState NWC_FailNetworkConnection(AsyncIO *IO) { SetNWCState(IO, eNWCVSConnFail); return EventQueueDBOperation(IO, NWC_SendFailureMessage, 1); } void NWC_SetTimeout(eNextState NextTCPState, AsyncNetworker *NW) { double Timeout = 0.0; //EVN_syslog(LOG_DEBUG, "%s - %d\n", __FUNCTION__, NextTCPState); switch (NextTCPState) { case eSendMore: case eSendReply: case eReadMessage: Timeout = NWC_ReadTimeouts[NW->State]; break; case eReadFile: case eSendFile: case eReadPayload: Timeout = 100000; break; case eSendDNSQuery: case eReadDNSReply: case eDBQuery: case eReadMore: case eConnect: case eTerminateConnection: case eAbort: return; } if (Timeout > 0) { AsyncIO *IO = &NW->IO; EVN_syslog(LOG_DEBUG, "%s - %d %f\n", __FUNCTION__, NextTCPState, Timeout); SetNextTimeout(&NW->IO, Timeout*100); } } eNextState NWC_DispatchReadDone(AsyncIO *IO) { EVN_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); AsyncNetworker *NW = IO->Data; eNextState rc; rc = NWC_ReadHandlers[NW->State](NW); if ((rc != eReadMore) && (rc != eAbort) && (rc != eDBQuery)) { NW->State++; } NWC_SetTimeout(rc, NW); return rc; } eNextState NWC_DispatchWriteDone(AsyncIO *IO) { EVN_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); AsyncNetworker *NW = IO->Data; eNextState rc; rc = NWC_SendHandlers[NW->State](NW); NWC_SetTimeout(rc, NW); return rc; } /*****************************************************************************/ /* Networker CLIENT ERROR CATCHERS */ /*****************************************************************************/ eNextState NWC_Terminate(AsyncIO *IO) { EVN_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); FinalizeNetworker(IO); return eAbort; } eNextState NWC_TerminateDB(AsyncIO *IO) { EVN_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); FinalizeNetworker(IO); return eAbort; } eNextState NWC_Timeout(AsyncIO *IO) { AsyncNetworker *NW = IO->Data; EVN_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); if (NW->IO.ErrMsg == NULL) NW->IO.ErrMsg = NewStrBuf(); StrBufPrintf(NW->IO.ErrMsg, "Timeout while talking to %s \r\n", ChrPtr(NW->host)); return NWC_FailNetworkConnection(IO); } eNextState NWC_ConnFail(AsyncIO *IO) { AsyncNetworker *NW = IO->Data; EVN_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); if (NW->IO.ErrMsg == NULL) NW->IO.ErrMsg = NewStrBuf(); StrBufPrintf(NW->IO.ErrMsg, "failed to connect %s \r\n", ChrPtr(NW->host)); return NWC_FailNetworkConnection(IO); } eNextState NWC_DNSFail(AsyncIO *IO) { AsyncNetworker *NW = IO->Data; EVN_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); if (NW->IO.ErrMsg == NULL) NW->IO.ErrMsg = NewStrBuf(); StrBufPrintf(NW->IO.ErrMsg, "failed to look up %s \r\n", ChrPtr(NW->host)); return NWC_FailNetworkConnection(IO); } eNextState NWC_Shutdown(AsyncIO *IO) { EVN_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); FinalizeNetworker(IO); return eAbort; } eNextState nwc_connect_ip(AsyncIO *IO) { AsyncNetworker *NW = IO->Data; SetNWCState(&NW->IO, eNWCVSConnecting); EVN_syslog(LOG_DEBUG, "%s\n", __FUNCTION__); EVN_syslog(LOG_NOTICE, "Connecting to <%s> at %s:%s\n", ChrPtr(NW->node), ChrPtr(NW->host), ChrPtr(NW->port)); return EvConnectSock(IO, NWC_ConnTimeout, NWC_ReadTimeouts[0], 1); } static int NetworkerCount = 0; void RunNetworker(AsyncNetworker *NW) { NW->n = NetworkerCount++; CtdlNetworkTalkingTo(SKEY(NW->node), NTT_ADD); syslog(LOG_DEBUG, "NW[%s][%ld]: polling\n", ChrPtr(NW->node), NW->n); ParseURL(&NW->IO.ConnectMe, NW->Url, 504); InitIOStruct(&NW->IO, NW, eReadMessage, NWC_ReadServerStatus, NWC_DNSFail, NWC_DispatchWriteDone, NWC_DispatchReadDone, NWC_Terminate, NWC_TerminateDB, NWC_ConnFail, NWC_Timeout, NWC_Shutdown); safestrncpy(((CitContext *)NW->IO.CitContext)->cs_host, ChrPtr(NW->host), sizeof(((CitContext *)NW->IO.CitContext)->cs_host)); if (NW->IO.ConnectMe->IsIP) { SetNWCState(&NW->IO, eNWCVSLookup); QueueEventContext(&NW->IO, nwc_connect_ip); } else { /* uneducated admin has chosen to add DNS to the equation... */ SetNWCState(&NW->IO, eNWCVSConnecting); QueueEventContext(&NW->IO, nwc_get_one_host_ip); } } /* * Poll other Citadel nodes and transfer inbound/outbound network data. * Set "full" to nonzero to force a poll of every node, or to zero to poll * only nodes to which we have data to send. */ void network_poll_other_citadel_nodes(int full_poll, HashList *ignetcfg) { const char *key; long len; HashPos *Pos; void *vCfg; AsyncNetworker *NW; StrBuf *SpoolFileName; int poll = 0; if (GetCount(ignetcfg) ==0) { syslog(LOG_DEBUG, "network: no neighbor nodes are configured - not polling.\n"); return; } become_session(&networker_client_CC); SpoolFileName = NewStrBufPlain(ctdl_netout_dir, -1); Pos = GetNewHashPos(ignetcfg, 0); while (GetNextHashPos(ignetcfg, Pos, &len, &key, &vCfg)) { /* Use the string tokenizer to grab one line at a time */ if(server_shutting_down) return;/* TODO free stuff*/ CtdlNodeConf *pNode = (CtdlNodeConf*) vCfg; poll = 0; NW = (AsyncNetworker*)malloc(sizeof(AsyncNetworker)); memset(NW, 0, sizeof(AsyncNetworker)); NW->node = NewStrBufDup(pNode->NodeName); NW->host = NewStrBufDup(pNode->Host); NW->port = NewStrBufDup(pNode->Port); NW->secret = NewStrBufDup(pNode->Secret); if ( (StrLength(NW->node) != 0) && (StrLength(NW->secret) != 0) && (StrLength(NW->host) != 0) && (StrLength(NW->port) != 0)) { poll = full_poll; if (poll == 0) { StrBufAppendBufPlain(SpoolFileName, HKEY("/"), 0); StrBufAppendBuf(SpoolFileName, NW->node, 0); StrBufStripSlashes(SpoolFileName, 1); if (access(ChrPtr(SpoolFileName), R_OK) == 0) { poll = 1; } } } if (poll && (StrLength(NW->host) > 0) && strcmp("0.0.0.0", ChrPtr(NW->host))) { NW->Url = NewStrBuf(); StrBufPrintf(NW->Url, "citadel://%s@%s:%s", ChrPtr(NW->secret), ChrPtr(NW->host), ChrPtr(NW->port)); if (!CtdlNetworkTalkingTo(SKEY(NW->node), NTT_CHECK)) { RunNetworker(NW); continue; } } DeleteNetworker(NW); } FreeStrBuf(&SpoolFileName); DeleteHashPos(&Pos); } void network_do_clientqueue(void) { HashList *working_ignetcfg; int full_processing = 1; static time_t last_run = 0L; /* * Run the full set of processing tasks no more frequently * than once every n seconds */ if ( (time(NULL) - last_run) < config.c_net_freq ) { full_processing = 0; syslog(LOG_DEBUG, "Network full processing in %ld seconds.\n", config.c_net_freq - (time(NULL)- last_run) ); } working_ignetcfg = CtdlLoadIgNetCfg(); /* * Poll other Citadel nodes. Maybe. If "full_processing" is set * then we poll everyone. Otherwise we only poll nodes we have stuff * to send to. */ network_poll_other_citadel_nodes(full_processing, working_ignetcfg); DeleteHash(&working_ignetcfg); } void LogDebugEnableNetworkClient(const int n) { NetworkClientDebugEnabled = n; } /* * Module entry point */ CTDL_MODULE_INIT(network_client) { if (!threading) { CtdlFillSystemContext(&networker_client_CC, "CitNetworker"); CtdlRegisterSessionHook(network_do_clientqueue, EVT_TIMER, PRIO_SEND + 10); CtdlRegisterDebugFlagHook(HKEY("networkclient"), LogDebugEnableNetworkClient, &NetworkClientDebugEnabled); } return "networkclient"; } citadel-9.01/README.txt0000644000000000000000000000075012507024051013242 0ustar rootroot * Full documentation is at http://www.citadel.org. * The condensed version: 1. Create a user on your system under which to run Citadel 2. Install supported versions of Berkeley DB, libical, and libsieve. 3. ./configure && make && make install 4. Run the "setup" program * After installing Citadel, you'll probably want to install WebCit so that you can access the Citadel system using a web browser. * Keep in mind that there is a FAQ at http://www.citadel.org citadel-9.01/migrate_aliases.sh0000755000000000000000000000153412507024051015235 0ustar rootroot#!/bin/bash if test -z "$1"; then echo "Usage: $0 mail.aliases" exit fi CITALIAS=$1 if test -f /etc/aliases; then # don't work with temp fils, so they can't get hijacked. # sorry users with megabytes of aliases. NLINES=`cat /etc/aliases | \ sed -e "s; *;;g" \ -e "s;\t*;;g" | \ grep -v ^root: | \ grep -v ^# | \ sed -e "s;:root;,room_aide;" \ -e "s;:;,;" |wc -l` for ((i=1; i <= $NLINES; i++)); do ALIAS=` cat /etc/aliases | \ sed -e "s; *;;g" \ -e "s;\t*;;g" | \ grep -v ^root: | \ grep -v ^# | \ sed -e "s;:root;,room_aide;" \ -e "s;:;,;" |head -n $i |tail -n 1` ORG=`echo $ALIAS|sed "s;,.*;;"` if grep "$ORG" "$CITALIAS"; then echo "Ignoring Alias $ORG as its already there" else echo "$ALIAS" >>$CITALIAS fi done else echo "no /etc/aliases found." fi citadel-9.01/debian/0000755000000000000000000000000012507024051012764 5ustar rootrootcitadel-9.01/help/0000755000000000000000000000000012507024051012472 5ustar rootrootcitadel-9.01/help/intro0000644000000000000000000001431512507024051013554 0ustar rootroot Welcome to ^humannode! New User's Introduction to the site This is an introduction to ^humannode and to the Citadel BBS concept. It is intended for new users so that they can more easily become aquainted with the bbs system. Of course, old users might learn something new each time they read through it. Full help for the BBS commands can be obtained by typing <.H>elp SUMMARY The CITADEL BBS room concept ---------------------------- The term BBS stands for "Bulletin-Board System". The analogy is appropriate: one posts messages so that others may read them. In order to organize the posts, people can post in different areas of the BBS, called rooms. In order to post in a certain room, you need to be "in" that room. Your current prompt is usually the room that you are in, followed the greater-than-sign, such as: Lobby> The easiest way to traverse the room structure is with the "Goto" command, on the "G" key. Pressing "G" will take you to the next room in the "scanlist" (see below) that has new messages in it. You can read these new messages with the "N" key. Once you've "Gotoed" every room in the system (or all of the ones you choose to read) you return to the "Lobby," the first and last room in the system. If new messages get posted to rooms you've already read during your session you will be brought BACK to those rooms so you can read them. Scanlist -------- All the room names are stored in a scanlist, which is just a string containing all the room names. When you oto or kip a room, you are placed in the next room in your scanlist THAT HAS NEW MESSAGES. If you have no new messages in any of the rooms on your scanlist, you will keep going to the Lobby>. You can choose not to read certain rooms (that don't interest you) by "Z"apping them. When you ap a room, you are merely deleting it from your scanlist (but not from anybody else's). You can use the <.G>oto (note the period before the G. You can also use ump on some systems) to go to any room in the system. You don't have to type in the complete name of a room to "jump" to it; you merely need to type in enough to distinguish it from the other rooms. Left-aligned matches carry a heavier weight, so if you typed (for example) ".Goto TECH", you might be taken to a room called "Tech Area>" even if it found a room called "Biotech/Ethics>" first. To return to a room you have previously apped, use the <.G>oto command to enter it, and it will be re-inserted into your scanlist. In the case of returning to Zapped rooms, you must type the room name in its entirety. REMEMBER, rooms with no new messages will not show on your scanlist! You must <.G>oto to a room with no new messages. Incidentally, you cannot change the order of the rooms on your scanlist. It's the same for everybody. Special rooms ------------- There are two special rooms on ^humannode that you should know about. The first is the Lobby>. It's used for system announcements and other such administrativia. You cannot ap the Lobby>. Each time you first login, you will be placed in the Lobby>. The second is Mail>. In Mail>, when you post a messages, you are prompted to enter the person (handle) who you want to send the message to. Only the person who you send the message to can read the message. NO ONE else can read it, not even the admins. Mail> is the first room on the scanlist, and is un-appable, so you can be sure that the person will get the message. System admins ------------- These people, along with the room admins, keep the site running smoothly. Among the many things that admins do are: create rooms, delete rooms, set access levels, invite users, check registration, grant room admin status, and countless other things. They have access to the Aide> room, a special room only for admins. If you enter a mail message to "Sysop" it will be placed in the Aide> room so that the next admin online will read it and deal with it. Admins cannot ap rooms. All the rooms are always on each admin's scanlist. Admins can read *any* and *every* room, but they *CAN* *NOT* read other user's Mail! Room admins ----------- Room admins are granted special privileges in specific rooms. They are *NOT* true system admins; their power extends only over the rooms that they control, and they answer to the system admins. A room admin's job is to keep the topic of the their room on track, with nudges in the right direction now and then. A room admin can also move an off topic post to another room, or delete a post, if he/she feels it is necessary. Currently, very few rooms have room admins. Most rooms do not need their own specific room admin. Being a room admin requires a certain amount of trust, due to the additional privileges granted. Citadel messages ---------------- Most of the time, the bbs code does not print a lot of messages to your screen. This is a great benefit once you become familiar with the system, because you do not have endless menus and screens to navigate through. nevertheless, there are some messages which you might see from time to time. "There were messages posted while you were entering." This is also known as "simulposting." When you start entering a message, the system knows where you last left off. When you save your message, the system checks to see if any messages were entered while you were typing. This is so that you know whether you need to go back and re-read the last few messages. This message may appear in any room. "*** You have new mail" This message is essentially the same as the above message, but can appear at any time. It simply means that new mail has arrived for you while you are logged in. Simply go to the Mail> room to read it. Who list -------- The ho command shows you the names of all users who are currently online. It also shows you the name of the room they are currently in. If they are in any type of private room, however, the room name will simply display as "". Along with this information is displayed the name of the host computer the user is logged in from. (This file was shamelessly swiped from QuartzBBS. Thanks to its original author for putting it together.) citadel-9.01/help/nice0000644000000000000000000001576212507024051013346 0ustar rootroot The following are a few points of general BBS etiquette. If you wish to maintain your welcome on whatever system you happen to call, it would be to your advantage to observe these few rules. Feel free to download this and display spread it around. 1. Don't habitually hang up on a system. Every Sysop is aware that accidental disconnections happen once in a while but we do tend to get annoyed with people who hang up every single time they call because they are either too lazy to terminate properly or they labor under the mistaken assumption that the 10 seconds they save online is going to significantly alter their phone bill. "Call Waiting" is not an acceptable excuse for long. If you have it and intend to use the line to call BBS's you should either have it disconnected or find some other way to circumvent it. 2. Don't do dumb things like leave yourself a message that says "Just testing to see if this thing works". Where do you think all those other messages came from if it didn't work? Also, don't leave whiney messages that say "Please leave me a message". If ever there was a person to ignore, it is the one who begs someone to leave him a message. If you want to get messages, start by reading the ones that are already online and getting involved in the conversations that exist. 3. Don't use the local equivalent of a chat command unless you really have some clear cut notion of what you want to say and why. Almost any Sysop is more than happy to answer questions or offer help concerning his system. Unfortunately, because about 85% of the people who call want to chat and about 99% of those people have absolutely nothing to say besides "How old are you?" or something equally irrelevant, fewer Sysops even bother answering their pagers every day. 4. When you are offered a place to leave comments when exiting a system, don't try to use this area to ask the Sysop questions. It is very rude to the other callers to expect the Sysop to carry on a half visible conversation with someone. If you have a question or statement to make and expect the Sysop to respond to it, it should always be made in the section where all the other messages are kept. This allows the Sysop to help many people with the same problem with the least amount of effort on his part. 5. Before you log on with your favorite pseudonym, make sure that handles are allowed. Some Sysops do not want people using handles on the system. The reasons vary, but everyone should still be willing to take full responsibility for his actions or comments instead of slinging mud from behind a phoney name. Also when signing on, why not sign on just like you would introduce yourself in your own society? How many of you usually introduce yourselves as Joe W Smutz the 3rd or 4th? 6. Take the time to log on properly. If the BBS asks you the city where you are calling from, remember that there is no such place as RIV, HB, ANA or any of a thousand other abbreviations people use instead of their proper city. You may think that everyone knows what RIV is supposed to mean, but every BBS has people calling from all around the country and I assure you that someone from Podunk, Iowa has no idea what you are talking about. 7. Don't go out of your way to make rude observations like "Gee, this system is slow". Every BBS is a tradeoff of features. You can generally assume that if someone is running a particular brand of software, that he is either happy with it or he will decide to find another system he likes better. It does nobody any good when you make comments about something that you perceive to be a flaw when it is running the way the Sysop wants it to. Constructive criticism is somewhat more welcome. If you have an alternative method that seems to make good sense then run it up the flagpole. 8. When leaving messages, stop and ask yourself whether it is necessary to make it private. Unless there is some particular reason that everyone should not know what you are saying, do not make it private. We do not call them PUBLIC bulletin boards for nothing, folks. It is very irritating to other callers when there are huge blank spots in the messages that they can not read and it stifles interaction between callers. 9. If your favorite BBS has a time limit, observe it. If it doesn't, set a limit for yourself and abide by it instead. Do not tie up a system as a new user and run right to the other numbers list. There is probably very little that is more annoying to any Sysop than to have his board completely passed over by you on your way to another board. 10. Have the common courtesy to pay attention to what passes in front of your face. When a BBS displays your name and asks "Is this you?", don't say yes when you can see perfectly well that it is misspelled. Also, do not start asking questions about simple operation of a system until you have thoroughly read all of the instructions that are available to you. I assure you that it is not any fun to answer a question for the thousandth time when the answer is prominently displayed in the system bulletins or instructions. Use some common sense when you ask your questions. The person who said "There is no such thing as a stupid question" obviously never operated a BBS. 11. Don't be personally abusive. It does not matter whether you like a Sysop or think he/she is a jerk. The fact remains that he/she has a large investment in making his computer available, usually out of the goodness of his/her heart. If you don't like a Sysop or his/her system, just remember that you can change the channel any time you want. Besides, whether you are aware of it or not, if you make yourself enough of an annoyance to any Sysop, he/she can take the time to trace you down and make your life, or that of your parents, miserable. Along those lines, don't be abusive of other users on the system. It doesn't matter what you think of him/her/them, but "If you don't have something nice to say, don't say it." If you think someone is being too abusive/whatever, let the Sysop know. It is his/her system, and upon him/her lies the responsibilty of dealing with problem users. If you think that he/she is not doing a good enough job, do not call back. 12. Lastly and ****** MOST IMPORTANTLY ****** keep firmly in mind that you are a *** GUEST *** on any BBS you happen to call. Do not think of logging on as one of your basic human rights. Every person that has ever put a computer system online for the use of other people has spent a lot of time and money to do so. While he/she does not expect nonstop pats on the back, it seems reasonable that he/she should at least be able to expect fair treatment from his/her callers. This includes following any of the rules for system use he/she has laid out without grumping about it. Every Sysop has his/her own idea of how he/she wants his/her system to be run. It is really none of your business why he/she wants to run it the way he/she does. Your business is to either abide by what he says, or call some other BBS where you feel that you can obey the rules. citadel-9.01/help/network0000644000000000000000000000027512507024051014112 0ustar rootroot Welcome to the network. Messages entered in a network room will appear in that room on all other systems carrying it (The name of the room, however, may be different on other systems). citadel-9.01/help/summary0000644000000000000000000001107312507024051014114 0ustar rootrootExtended commands are available using the period ( . ) key. To use a dot command, press the . key, and then enter the first letter of each word in the command. The words will appear as you enter the keys. You can also backspace over partially entered commands. The following commands are available: <.> elp: Displays help files. Type .H followed by a help file name. You are now reading <.H>elp SUMMARY <.> oto: Jumps directly to the room you specify. You can also type a partial room name, just enough to make it unique, and it'll find the room you're looking for. As with the regular oto command, messages in the current room will be marked as read. <.> kip, goto: This is similar to <.G>oto, except it doesn't mark messages in the current room as read. <.> list apped rooms Shows all rooms you've apped (forgotten) Terminate (logoff) commands: <.> erminate and uit Log off and disconnect. <.> erminate and tay online Log in as a different user. Read commands: <.> ead ew messages Same as ew <.> ead ld msgs reverse Same as ld <.> ead ast five msgs Same as ast5 <.> read ast: Allows you to specify how many messages you wish to read. <.> ead ser listing: Lists all users on the system if you just hit enter, otherwise you can specify a partial match <.> ead extfile formatted File 'download' commands. <.> ead file using modem <.> ead file using modem <.> ead file using modem <.> ead ile unformatted <.> ead irectory <.> ead nfo file Read the room info file. <.> ead io Read other users' "bio" files. <.> ead onfiguration Display your 'preferences'. <.> ead ystem info Display system statistics. Enter commands: <.> nter essage Post a message in this room. <.> nter message with ditor Post using a full-screen editor. <.> nter SCII message Post 'raw' (use this when "pasting" a message from your clipboard). <.> nter

    assword Change your password. <.> nter onfiguration Change your 'preferences'. <.> nter a new oom Create a new room. <.> nter reistration Register (name, address, etc.) <.> nter io Enter/change your "bio" file. <.> nter extfile File 'upload' commands. <.> nter file using modem <.> nter file using modem <.> nter file using modem Wholist commands: <.> holist ong Same as ho is online, but displays more detailed information. <.> holist oomname Masquerade your room name (other users see the name you enter rather than the actual name of the room you're in) <.> holist ostname Masquerade your host name <.> nter sername Masquerade your user name (Admins only) <.> holist tealth mode Enter/exit "stealth mode" (when in stealth mode you are invisible on the wholist) Floor commands (if using floor mode) ;onfigure floor mode - turn floor mode on or off ;oto floor: - jump to a specific floor ;nown rooms - list all rooms on all floors ;kip to floor: - skip current floor, jump to another ;ap floor - zap (forget) all rooms on this floor Administrative commands: <.> dmin ill this room <.> dmin dit this room <.> dmin ho knows room <.> dmin edit ser <.> dmin alidate new users <.> dmin enter nfo file <.> dmin oom nvite user <.> dmin oom ick out user <.> dmin ile elete <.> dmin ile end over net <.> dmin ile ove <.> dmin essage edit: <.> dmin

    ost <.> dmin ystem configuration <.> dmin erminate server ow <.> dmin erminate server cheduled citadel-9.01/help/mail0000644000000000000000000000154412507024051013343 0ustar rootrootTo send mail on this system, go to the Mail> room (using the command .G Mail) and press E to enter a message. You will be prompted with: Enter Recipient: At this point you may enter the name of another user on the system. Private mail is only readable by the sender and recipient. There is no need to delete mail after it is read; it will scroll out automatically. To send mail to another user on the Citadel network, simply type the user's name, followed by @ and then the system name. For example, Enter Recipient: Joe Schmoe @ citadrool To send mail to a user on the Internet, type their network address at the recipient prompt. Citadel will send it over the network: Enter Recipient: ajc@herring.fishnet.com Your e-mail address on other Citadels is ^username @ ^nodename Your e-mail address on the Internet is cit^usernum@^fqdn citadel-9.01/help/policy0000644000000000000000000000007512507024051013716 0ustar rootroot < this new user policy resides in ./messages/newuser > citadel-9.01/help/software0000644000000000000000000000044212507024051014247 0ustar rootroot Citadel is the premier "online community" (i.e. Bulletin Board System) software. It runs on all POSIX-compliant systems, including Linux. It is an advanced client/server application, and is being actively maintained. For more info, visit UNCENSORED! BBS at uncensored.citadel.org citadel-9.01/help/floors0000644000000000000000000000330312507024051013720 0ustar rootroot Floors ------ Floors in ^variantname are used to group rooms into related subject areas, just as rooms are used to group messages into manageable groups. You, as a user, do NOT have to use floors. If you choose not to, you suffer no penalty; you will not lose access to any rooms. You may use .EC or ;C (the latter is easier to use) to decide if you want to use floors. Feel free to experiment. Floor options are accessed two ways. First, if you are in floor mode, the oto and kip commands take you to the next room with new messages on the current floor; if there are none left, then the system will automatically switch floors (and let you know) and put you in the first room with new messages on that level. (Notice that your pattern of basic use of ^variantname therefore doesn't really change.) Direct access to floor options is via the use of a ";" command. The following commands are currently available (more can be added if needed): <;C>onfigure This command toggles your floor mode. <;G>oto FLOORNAME This command causes the system to take you to the named floor. <;K>nown rooms on floors List all rooms on all floors. This is a very readable way to get a list of all rooms on the system. <;S>kip FLOORNAME This command causes the system to mark all rooms on the current floor as Skipped and takes you to the floor that you specify. <;Z>Forget floor This command causes you to forget all the rooms currently on the current floor. Unfortunately, it doesn't apply to rooms that are subsequently created or moved to this floor. (Sorry.) Feel free to experiment, you can't hurt yourself or the system with the floor stuff unless you ZForget a floor by accident. citadel-9.01/help/hours0000644000000000000000000000006012507024051013551 0ustar rootroot^humannode is up 24 hours a day, 7 days a week. citadel-9.01/help/aide0000644000000000000000000000360512507024051013323 0ustar rootrootThe following commands are available only to Admins. A subset of these commands are available to room aides when they are currently in the room they are room aide for. <.> dmin ill this room (Delete the current room) <.> dmin dit this room (Edit the current room's parameters) <.> dmin ho knows room (List users with access to this room) <.> dmin edit ser (Change user's access level, password, etc.) <.> dmin alidate new users (Process new user registrations) <.> dmin enter nfo file (Create/change this room's banner) <.> dmin oom nvite user (Grant access to an invitation-only room) <.> dmin oom ick out user (Revoke access to an invitation-only room) <.> dmin ile elete (Delete a file from the room's directory) <.> dmin ile end over net (Transmit a file to another node) <.> dmin ile ove (Move a file to another room's directory) <.> dmin essage edit: (Edit system banners) <.> dmin

    ost (Post a message on behalf of another user) <.> dmin ystem configuration eneral (Edit global site config) <.> dmin ystem configuration nternet (Edit Internet domains) <.> dmin ystem configuration check essage base (Internal checks) <.> dmin ystem configuration etwork (Netting with other Citadels) <.> dmin ystem configuration network ilter list <.> dmin erminate server ow (Shut down Citadel server now) <.> dmin erminate server cheduled (Shut down Citadel server later) <.> dmin mailing ist recipients (For mailing list rooms) <.> dmin mailing list igest recipients (For mailing list rooms) <.> dmin etwork room sharing (Replication with other Citadels) In addition, the ove and elete commands are available at the message prompt. citadel-9.01/locate_host.c0000644000000000000000000002177712507024051014230 0ustar rootroot/* * Functions which handle hostname/address lookups and resolution * * Copyright (c) 1987-2011 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "sysdep.h" #include #include #include #include #include #include #include #include #include "context.h" #ifdef HAVE_RESOLV_H #include #ifdef HAVE_ARPA_NAMESER_COMPAT_H #include #endif #include #endif #include "domain.h" #include "locate_host.h" /** START:some missing macros on OpenBSD 3.9 */ #ifndef NS_CMPRSFLGS #define NS_CMPRSFLGS 0xc0 #endif #if !defined(NS_MAXCDNAME) && defined (MAXCDNAME) #define NS_MAXCDNAME MAXCDNAME #endif #if !defined(NS_INT16SZ) && defined(INT16SZ) #define NS_INT16SZ INT16SZ #define NS_INT32SZ INT32SZ #endif #ifndef NS_GET16 # define NS_GET16 GETSHORT #endif /** END:some missing macros on OpenBSD 3.9 */ /* * Given an open client socket, return the host name and IP address at the other end. * (IPv4 and IPv6 compatible) */ void locate_host(char *tbuf, size_t n, char *abuf, size_t na, int client_socket) { struct sockaddr_in6 clientaddr; unsigned int addrlen = sizeof(clientaddr); tbuf[0] = 0; abuf[0] = 0; getpeername(client_socket, (struct sockaddr *)&clientaddr, &addrlen); getnameinfo((struct sockaddr *)&clientaddr, addrlen, tbuf, n, NULL, 0, 0); getnameinfo((struct sockaddr *)&clientaddr, addrlen, abuf, na, NULL, 0, NI_NUMERICHOST); /* Convert IPv6-mapped IPv4 addresses back to traditional dotted quad. * * Other code here, such as the RBL check, will expect IPv4 addresses to be represented * as dotted-quad, even if they come in over a hybrid IPv6/IPv4 socket. */ if ( (strlen(abuf) > 7) && (!strncasecmp(abuf, "::ffff:", 7)) ) { if (!strcmp(abuf, tbuf)) strcpy(tbuf, &tbuf[7]); strcpy(abuf, &abuf[7]); } } /* * RBL check written by Edward S. Marshall [http://rblcheck.sourceforge.net] */ #define RESULT_SIZE 4096 /* What is the longest result text we support? */ int rblcheck_backend(char *domain, char *txtbuf, int txtbufsize) { int a, b, c; char *result = NULL; u_char fixedans[ PACKETSZ ]; u_char *answer; int need_to_free_answer = 0; const u_char *cp; u_char *rp; const u_char *cend; const u_char *rend; int len; char *p = NULL; static int res_initted = 0; if (!res_initted) { /* only have to do this once */ res_init(); res_initted = 1; } /* Make our DNS query. */ answer = fixedans; if (server_shutting_down) { if (txtbuf != NULL) { snprintf(txtbuf, txtbufsize, "System shutting down"); } return (1); } len = res_query(domain, C_IN, T_A, answer, PACKETSZ); /* Was there a problem? If so, the domain doesn't exist. */ if (len == -1) { if (txtbuf != NULL) { strcpy(txtbuf, ""); } return(0); } if( len > PACKETSZ ) { answer = malloc(len); need_to_free_answer = 1; len = res_query(domain, C_IN, T_A, answer, len); if( len == -1 ) { if (txtbuf != NULL) { snprintf(txtbuf, txtbufsize, "Message rejected due to known spammer source IP address"); } if (need_to_free_answer) free(answer); return(1); } } if (server_shutting_down) { if (txtbuf != NULL) snprintf(txtbuf, txtbufsize, "System shutting down"); if (need_to_free_answer) free(answer); return (1); } result = (char *) malloc(RESULT_SIZE); result[ 0 ] = '\0'; /* Make another DNS query for textual data; this shouldn't * be a performance hit, since it'll now be cached at the * nameserver we're using. */ len = res_query(domain, C_IN, T_TXT, answer, PACKETSZ); if (server_shutting_down) { if (txtbuf != NULL) { snprintf(txtbuf, txtbufsize, "System shutting down"); } if (need_to_free_answer) free(answer); free(result); return (1); } /* Just in case there's no TXT record... */ if (len ==(-1)) { if (txtbuf != NULL) { snprintf(txtbuf, txtbufsize, "Message rejected due to known spammer source IP address"); } if (need_to_free_answer) free(answer); free(result); return(1); } /* Skip the header and the address we queried. */ cp = answer + sizeof( HEADER ); while( *cp != '\0' ) { a = *cp++; while( a-- ) cp++; } /* This seems to be a bit of magic data that we need to * skip. I wish there were good online documentation * for programming for libresolv, so I'd know what I'm * skipping here. Anyone reading this, feel free to * enlighten me. */ cp += 1 + NS_INT16SZ + NS_INT32SZ; /* Skip the type, class and ttl. */ cp += (NS_INT16SZ * 2) + NS_INT32SZ; /* Get the length and end of the buffer. */ NS_GET16(c, cp); cend = cp + c; /* Iterate over any multiple answers we might have. In * this context, it's unlikely, but anyway. */ rp = (u_char *) result; rend = (u_char *) result + RESULT_SIZE - 1; while (cp < cend && rp < rend) { a = *cp++; if( a != 0 ) for (b = a; b > 0 && cp < cend && rp < rend; b--) { if (*cp == '\n' || *cp == '"' || *cp == '\\') { *rp++ = '\\'; } *rp++ = *cp++; } } *rp = '\0'; if (txtbuf != NULL) { long len; len = snprintf(txtbuf, txtbufsize, "%s", result); /* Remove nonprintable characters */ for (p = txtbuf; *p != '\0'; p++) { if (!isprint(*p)) { memmove (p, p + 1, len - (p - txtbuf) - 1); } } } if (need_to_free_answer) free(answer); free(result); return(1); } /* * Check to see if the client host is on some sort of spam list (RBL) * If spammer, returns nonzero and places reason in 'message_to_spammer' */ int rbl_check(char *message_to_spammer) { char tbuf[256] = ""; int suffix_pos = 0; int rbl; int rc; int num_rbl; char rbl_domains[SIZ]; char txt_answer[1024]; struct timeval tx_start; struct timeval tx_finish; rc = 0; strcpy(message_to_spammer, "ok"); gettimeofday(&tx_start, NULL); /* start a stopwatch for performance timing */ if ((strchr(CC->cs_addr, '.')) && (!strchr(CC->cs_addr, ':'))) { int a1, a2, a3, a4; sscanf(CC->cs_addr, "%d.%d.%d.%d", &a1, &a2, &a3, &a4); snprintf(tbuf, sizeof tbuf, "%d.%d.%d.%d.", a4, a3, a2, a1); suffix_pos = strlen(tbuf); } else if ((!strchr(CC->cs_addr, '.')) && (strchr(CC->cs_addr, ':'))) { int num_colons = 0; int i = 0; char workbuf[sizeof tbuf]; char *ptr; /* tedious code to expand and reverse an IPv6 address */ safestrncpy(tbuf, CC->cs_addr, sizeof tbuf); num_colons = haschar(tbuf, ':'); if ((num_colons < 2) || (num_colons > 7)) goto finish_rbl; /* badly formed address */ /* expand the "::" shorthand */ while (num_colons < 7) { ptr = strstr(tbuf, "::"); if (!ptr) goto finish_rbl; /* badly formed address */ ++ptr; strcpy(workbuf, ptr); strcpy(ptr, ":"); strcat(ptr, workbuf); ++num_colons; } /* expand to 32 hex characters with no colons */ strcpy(workbuf, tbuf); strcpy(tbuf, "00000000000000000000000000000000"); for (i=0; i<8; ++i) { char tokbuf[5]; extract_token(tokbuf, workbuf, i, ':', sizeof tokbuf); memcpy(&tbuf[ (i*4) + (4-strlen(tokbuf)) ], tokbuf, strlen(tokbuf) ); } if (strlen(tbuf) != 32) goto finish_rbl; /* now reverse it and add dots */ strcpy(workbuf, tbuf); for (i=0; i<32; ++i) { tbuf[i*2] = workbuf[31-i]; tbuf[(i*2)+1] = '.'; } tbuf[64] = 0; suffix_pos = 64; } else { goto finish_rbl; /* unknown address format */ } /* See if we have any RBL domains configured */ num_rbl = get_hosts(rbl_domains, "rbl"); if (num_rbl < 1) { goto finish_rbl; } /* Try all configured RBL's */ for (rbl=0; rblh_addr_list[0]; a1 = ((*i++) & 0xff); a2 = ((*i++) & 0xff); a3 = ((*i++) & 0xff); a4 = ((*i++) & 0xff); sprintf(addr, "%d.%d.%d.%d", a1, a2, a3, a4); return(0); } citadel-9.01/locate_host.h0000644000000000000000000000036212507024051014220 0ustar rootrootvoid locate_host(char *tbuf, size_t n, char *abuf, size_t na, int client_socket); int rbl_check(char *message_to_spammer); int hostname_to_dotted_quad(char *addr, char *host); int rblcheck_backend(char *domain, char *txtbuf, int txtbufsize); citadel-9.01/citadel_urlshorteners.rc0000644000000000000000000000115112507024051016472 0ustar rootroot# these URLs will be expanded when fetching them from # RSS-Feeds with short contents (like twitter feeds) # we expect to find a 30x location header, if we find it, # we put it in front of that URL, so the full information # whether one wants to click that or not can be expanded. http://bit.ly/ http://krz.ch/ http://flic.kr/ http://sns.ly/ http://wp.me/ http://ow.ly/ http://tinyurl.com/ http://goo.gl/ http://dld.bz/ http://tiny.cc/ http://j.mp/ http://su.pr/ #Posting Images to twitter: http://yfrog.com/ http://t.co/ http://nblo.gs/ http://0x4d.ch/ http://fb.me/ http://tagi.ch/ http://post.ly/ http://aol.it/ citadel-9.01/snprintf.c0000644000000000000000000000347512507024051013562 0ustar rootroot/* * Replacements for snprintf() and vsnprintf() * * modified from Sten Gunterberg's BUGTRAQ post of 22 Jul 1997 * --nathan bryant * * Use it only if you have the "spare" cycles needed to effectively * do every snprintf operation twice! Why is that? Because everything * is first vfprintf()'d to /dev/null to determine the number of bytes. * Perhaps a bit slow for demanding applications on slow machines, * no problem for a fast machine with some spare cycles. * * You don't have a /dev/null? Every Linux contains one for free! * * Because the format string is never even looked at, all current and * possible future printf-conversions should be handled just fine. * * Written July 1997 by Sten Gunterberg (gunterberg@ergon.ch) */ #include #include #include #include static int needed (const char *fmt, va_list argp) { static FILE *sink = NULL; /* ok, there's a small race here that could result in the sink being * opened more than once if we're threaded, but I'd rather ignore it than * spend cycles synchronizing :-) */ if (sink == NULL) { if ((sink = fopen("/dev/null", "w")) == NULL) { perror("/dev/null"); exit(1); } } return vfprintf(sink, fmt, argp); } int vsnprintf (char *buf, size_t max, const char *fmt, va_list argp) { char *p; int size; if ((p = malloc(needed(fmt, argp) + 1)) == NULL) { fprintf(stderr, "vsnprintf: malloc failed, aborting\n"); abort(); } if ((size = vsprintf(p, fmt, argp)) >= max) size = -1; strncpy(buf, p, max); buf[max - 1] = 0; free(p); return size; } int snprintf (char *buf, size_t max, const char *fmt, ...) { va_list argp; int bytes; va_start(argp, fmt); bytes = vsnprintf(buf, max, fmt, argp); va_end(argp); return bytes; } citadel-9.01/sysdep.h.in0000644000000000000000000002515212507024062013636 0ustar rootroot/* sysdep.h.in. Generated from configure.ac by autoheader. */ /* where to search our automatic config files */ #undef AUTO_ETC_DIR /* define this to the Citadel home directory */ #undef CTDLDIR /* define, if the user suplied a data-directory to use. */ #undef DATA_DIR /* the socket from Pseudo Random Generator */ #undef EGD_POOL /* whether we have NLS support */ #undef ENABLE_NLS /* where to search our config files */ #undef ETC_DIR /* whats the matching format string for pid_t? */ #undef F_PID_T /* whats the matching format string for uid_t? */ #undef F_UID_T /* whats the matching format string for xpid_t? */ #undef F_XPID_T /* Define to 1 if the `getpgrp' function requires zero arguments. */ #undef GETPGRP_VOID /* Define to 1 if you have the header file. */ #undef HAVE_ARPA_NAMESER_COMPAT_H /* Define to 1 if you have the header file. */ #undef HAVE_ARPA_NAMESER_H /* should we search our automatic config in an alternate place? */ #undef HAVE_AUTO_ETC_DIR /* Define to 1 if you have the `backtrace' function. */ #undef HAVE_BACKTRACE /* Define to 1 if you have the `connect' function. */ #undef HAVE_CONNECT /* Define to 1 if you have the `crypt' function. */ #undef HAVE_CRYPT /* Define to use c-ares library */ #undef HAVE_C_ARES /* define if using OS X/Darwin */ #undef HAVE_DARWIN /* use alternate database location? */ #undef HAVE_DATA_DIR /* Define to 1 if you have the header file. */ #undef HAVE_DB4_DB_H /* Define to 1 if you have the header file. */ #undef HAVE_DB_H /* Define to 1 if you have the header file, and it defines `DIR'. */ #undef HAVE_DIRENT_H /* Define to 1 if you have the header file. */ #undef HAVE_DL_H /* Define to 1 if you don't have `vprintf' but do have `_doprnt.' */ #undef HAVE_DOPRNT /* should we search our system config in an alternate place? */ #undef HAVE_ETC_DIR /* Define to 1 if you have the header file. */ #undef HAVE_FCNTL_H /* Define to 1 if you have the `flock' function. */ #undef HAVE_FLOCK /* Define to 1 if you have the `gethostbyname' function. */ #undef HAVE_GETHOSTBYNAME /* Define to 1 if you have the `getloadavg' function. */ #undef HAVE_GETLOADAVG /* Define to 1 if you have the `getpwnam_r' function. */ #undef HAVE_GETPWNAM_R /* Define to 1 if you have the `getpwuid_r' function. */ #undef HAVE_GETPWUID_R /* Define to 1 if you have the `getspnam' function. */ #undef HAVE_GETSPNAM /* Define to 1 if you have the `gettext' function. */ #undef HAVE_GETTEXT /* Define to 1 if you have the `getutxline' function. */ #undef HAVE_GETUTXLINE /* use alternate database location? */ #undef HAVE_HELP_DIR /* whether we have iconv for charset conversion */ #undef HAVE_ICONV /* Define to 1 if you have the header file. */ #undef HAVE_INTTYPES_H /* define this if you have OpenLDAP client available */ #undef HAVE_LDAP /* Define to 1 if you have the header file. */ #undef HAVE_LDAP_H /* Define to 1 if you have the `nsl' library (-lnsl). */ #undef HAVE_LIBNSL /* Define to 1 if you have the `pthread' library (-lpthread). */ #undef HAVE_LIBPTHREAD /* Define to 1 if you have the `pthreads' library (-lpthreads). */ #undef HAVE_LIBPTHREADS /* Define to 1 if you have the `rt' library (-lrt). */ #undef HAVE_LIBRT /* Define to 1 if you have the `socket' library (-lsocket). */ #undef HAVE_LIBSOCKET /* Define to 1 if you have the header file. */ #undef HAVE_LIMITS_H /* Define to 1 if you have the header file. */ #undef HAVE_MALLOC_H /* Define to 1 if you have the header file. */ #undef HAVE_MEMORY_H /* Define to 1 if you have the `mkdir' function. */ #undef HAVE_MKDIR /* Define to 1 if you have the `mkfifo' function. */ #undef HAVE_MKFIFO /* Define to 1 if you have the `mktime' function. */ #undef HAVE_MKTIME /* Define to 1 if you have the header file, and it defines `DIR'. */ #undef HAVE_NDIR_H /* Define to 1 if you have the header file. */ #undef HAVE_NETINET_IN_H /* define this if the OS has broken non-reentrant gethostby{name,addr}() */ #undef HAVE_NONREENTRANT_NETDB /* Define if you have OpenSSL. */ #undef HAVE_OPENSSL /* Define to 1 if you have the `pam_start' function. */ #undef HAVE_PAM_START /* Define to 1 if you have the header file. */ #undef HAVE_PATHS_H /* define this if you have the pthread_cancel() function */ #undef HAVE_PTHREAD_CANCEL /* Define to 1 if you have the header file. */ #undef HAVE_PTHREAD_H /* Define to 1 if you have the `resizeterm' function. */ #undef HAVE_RESIZETERM /* define this if you have the resolv.h header file. */ #undef HAVE_RESOLV_H /* Define to 1 if you have the `rmdir' function. */ #undef HAVE_RMDIR /* should we put our non volatile files elsewhere? */ #undef HAVE_RUN_DIR /* Define to 1 if you have the `select' function. */ #undef HAVE_SELECT /* Define to 1 if you have the `socket' function. */ #undef HAVE_SOCKET /* enable alternate spool dir? */ #undef HAVE_SPOOL_DIR /* should we activate an alternate static text location? */ #undef HAVE_STATICDATA_DIR /* Define to 1 if you have the header file. */ #undef HAVE_STDINT_H /* Define to 1 if you have the header file. */ #undef HAVE_STDLIB_H /* Define to 1 if you have the `strcasecmp' function. */ #undef HAVE_STRCASECMP /* Define to 1 if you have the `strerror' function. */ #undef HAVE_STRERROR /* Define to 1 if you have the `strftime_l' function. */ #undef HAVE_STRFTIME_L /* Define to 1 if you have the header file. */ #undef HAVE_STRINGS_H /* Define to 1 if you have the header file. */ #undef HAVE_STRING_H /* Define to 1 if you have the `strncasecmp' function. */ #undef HAVE_STRNCASECMP /* Define to 1 if `tm_gmtoff' is a member of `struct tm'. */ #undef HAVE_STRUCT_TM_TM_GMTOFF /* Define if struct ucred is present from sys/socket.h */ #undef HAVE_STRUCT_UCRED /* Define to 1 if you have the header file. */ #undef HAVE_SYSCALL_H /* Define to 1 if you have the header file. */ #undef HAVE_SYSLOG_H /* Define to 1 if you have the header file, and it defines `DIR'. */ #undef HAVE_SYS_DIR_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_IOCTL_H /* Define to 1 if you have the header file, and it defines `DIR'. */ #undef HAVE_SYS_NDIR_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_PRCTL_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_SELECT_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_STAT_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_SYSCALL_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_TIME_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_TYPES_H /* Define to 1 if you have that is POSIX.1 compatible. */ #undef HAVE_SYS_WAIT_H /* Define to 1 if you have the header file. */ #undef HAVE_TERMIOS_H /* Define if you don't have `tm_gmtoff' but do have the external variable `timezone'. */ #undef HAVE_TIMEZONE /* Define to 1 if you have the header file. */ #undef HAVE_UNISTD_H /* Define to 1 if you have the `uselocale' function. */ #undef HAVE_USELOCALE /* should we put our helper binaries to another location? */ #undef HAVE_UTILBIN_DIR /* Define to 1 if you have the header file. */ #undef HAVE_UTMPX_H /* Define to 1 if you have the header file. */ #undef HAVE_UTMP_H /* define this if struct utmp has an ut_host member */ #undef HAVE_UT_HOST /* define this if struct utmp has an ut_type member */ #undef HAVE_UT_TYPE /* Define to 1 if you have the `vprintf' function. */ #undef HAVE_VPRINTF /* Define to 1 if you have the `vw_printw' function. */ #undef HAVE_VW_PRINTW /* Define to 1 if you have the `wcolor_set' function. */ #undef HAVE_WCOLOR_SET /* Define to 1 if you have the `wresize' function. */ #undef HAVE_WRESIZE /* define, if the user suplied a helpfile-directory to use. */ #undef HELP_DIR /* where to find our pot files */ #undef LOCALEDIR /* Define to the address where bug reports for this package should be sent. */ #undef PACKAGE_BUGREPORT /* Define to the full name of this package. */ #undef PACKAGE_NAME /* Define to the full name and version of this package. */ #undef PACKAGE_STRING /* Define to the one symbol short name of this package. */ #undef PACKAGE_TARNAME /* Define to the home page for this package. */ #undef PACKAGE_URL /* Define to the version of this package. */ #undef PACKAGE_VERSION /* Define as the return type of signal handlers (`int' or `void'). */ #undef RETSIGTYPE /* define, where the config should go in unix style */ #undef RUN_DIR /* The size of `char', as computed by sizeof. */ #undef SIZEOF_CHAR /* The size of `int', as computed by sizeof. */ #undef SIZEOF_INT /* The size of `loff_t', as computed by sizeof. */ #undef SIZEOF_LOFF_T /* The size of `long', as computed by sizeof. */ #undef SIZEOF_LONG /* The size of `short', as computed by sizeof. */ #undef SIZEOF_SHORT /* The size of `size_t', as computed by sizeof. */ #undef SIZEOF_SIZE_T /* do we need to use solaris call syntax? */ #undef SOLARIS_GETPWUID /* where do we place our spool dirs? */ #undef SPOOL_DIR /* were should we put our keys? */ #undef SSL_DIR /* where do we put our static text data? */ #undef STATICDATA_DIR /* Define to 1 if you have the ANSI C header files. */ #undef STDC_HEADERS /* Define to 1 if you can safely include both and . */ #undef TIME_WITH_SYS_TIME /* Define to 1 if your declares `struct tm'. */ #undef TM_IN_SYS_TIME /* Enable extensions on AIX 3, Interix. */ #ifndef _ALL_SOURCE # undef _ALL_SOURCE #endif /* Enable GNU extensions on systems that have them. */ #ifndef _GNU_SOURCE # undef _GNU_SOURCE #endif /* Enable threading extensions on Solaris. */ #ifndef _POSIX_PTHREAD_SEMANTICS # undef _POSIX_PTHREAD_SEMANTICS #endif /* Enable extensions on HP NonStop. */ #ifndef _TANDEM_SOURCE # undef _TANDEM_SOURCE #endif /* Enable general extensions on Solaris. */ #ifndef __EXTENSIONS__ # undef __EXTENSIONS__ #endif /* were to put our helper programs */ #undef UTILBIN_DIR /* Define to 1 if on MINIX. */ #undef _MINIX /* Define to 2 if the system does not provide POSIX.1 features except with this defined. */ #undef _POSIX_1_SOURCE /* Define to 1 if you need to in order for `stat' and other things to work. */ #undef _POSIX_SOURCE /* Define to empty if `const' does not conform to ANSI C. */ #undef const /* Define to `int' if does not define. */ #undef pid_t /* Define to `unsigned int' if does not define. */ #undef size_t citadel-9.01/svn_revision.c0000644000000000000000000000033112507024062014431 0ustar rootroot/* * Subversion / GIT revision functions * * Autogenerated at make/release time * * Do not modify this file * */ const char *svn_revision (void) { const char *SVN_Version = "a845b4f"; return SVN_Version; } citadel-9.01/openldap/0000755000000000000000000000000012507024051013344 5ustar rootrootcitadel-9.01/openldap/rfc2739.schema0000644000000000000000000001307112507024051015627 0ustar rootroot# (c) 2004 Martin Konold # (c) 2006 Art Cancro # This schema is derived from RFC 2739 and may act as a substitute # when used with OpenLDAP as the original schema from RFC 2739 # is syntactically not accepted by OpenLDAP 2.2.14 # The version of this file as distributed in the Citadel upstream packages # contains text claiming copyright by the Internet Society and including # the IETF RFC license, which does not meet Debian's Free Software # Guidelines. However, apart from short and obvious comments, the text of # this file is purely a functional interface specification, which is not # subject to that license and is not copyrightable under US law. # # The license statement is retained below so as not to remove credit, but # as best as we can determine, it is not applicable to the contents of # this file. # Copyright (C) The Internet Society (2000). All Rights Reserved. # # This document and translations of it may be copied and furnished to # others, and derivative works that comment on or otherwise explain it # or assist in its implementation may be prepared, copied, published # and distributed, in whole or in part, without restriction of any # kind, provided that the above copyright notice and this paragraph are # included on all such copies and derivative works. However, this # document itself may not be modified in any way, such as by removing # the copyright notice or references to the Internet Society or other # Internet organizations, except as needed for the purpose of # developing Internet standards in which case the procedures for # copyrights defined in the Internet Standards process must be # followed, or as required to translate it into languages other than # English. # # The limited permissions granted above are perpetual and will not be # revoked by the Internet Society or its successors or assigns. # # This document and the information contained herein is provided on an # "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING # TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING # BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION # HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF # MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. # If you are using OpenLDAP, slapd.conf should declare schemas like this: # # include /etc/openldap/schema/core.schema # include /etc/openldap/schema/cosine.schema # include /etc/openldap/schema/inetorgperson.schema # include /etc/openldap/schema/rfc2739.schema # include /etc/openldap/schema/citadel.schema ################################ # rfc 2739 calendar attributes # ################################ # contains the URI to a snapshot of the user's entire # default calendar attributetype ( 1.2.840.113556.1.4.478 NAME 'calCalURI' DESC 'RFC2739: URI of entire default calendar' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 USAGE userApplications ) # contains the URI to the user's default # busy time data attributetype (1.2.840.113556.1.4.479 NAME 'calFBURL' DESC 'RFC2739: URI to the users default freebusy data' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 USAGE userApplications ) # contains a URI that can be used to communicate with # the user's calendar attributetype (1.2.840.113556.1.4.480 NAME 'calCAPURI' DESC 'RFC2739: URI used to communicate with the users calendar' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 USAGE userApplications ) # contains a URI that points to the location to which event # requests should be sent for that user attributetype (1.2.840.113556.1.4.481 NAME 'calCalAdrURI' DESC 'RFC2739: URI for event equests destination' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 USAGE userApplications ) # multi-valued property containing URIs to snapshots of # other calendars that the user may have attributetype (1.2.840.113556.1.4.482 NAME 'calOtherCalURIs' DESC 'RFC2739: multi-value URI for snapshots of other calendars' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 USAGE userApplications ) # multi-valued property containing URIs to snapshots of other # free/busy data that the user may have attributetype (1.2.840.113556.1.4.483 NAME 'calOtherFBURLs' DESC 'RFC2739: multi-value URI for other free/busy data' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 USAGE userApplications ) # multi-valued property containing URI to other calendars that # the user may have attributetype (1.2.840.113556.1.4.484 NAME 'calOtherCAPURIs' DESC 'RFC2739: multi-value URI to other calendars' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 USAGE userApplications ) # URIs to other locations that a user may want # event requests sent to attributetype (1.2.840.113556.1.4.485 NAME 'calOtherCalAdrURIs' DESC 'RFC2739: multi-value URI to other request destinations' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 USAGE userApplications ) objectclass (1.2.840.113556.1.5.87 NAME 'calEntry' DESC 'RFC2739: Calendar Entry' SUP top AUXILIARY MAY ( calCalURI $ calFBURL $ calOtherCalURIs $ calOtherFBURLs $ calCAPURI $ calOtherCAPURIs ) ) citadel-9.01/openldap/citadel.schema0000644000000000000000000000376712507024051016150 0ustar rootroot# $Id$ # (c) 2006 Art Cancro # This file is distributed under the terms of the GNU General Public License v3. # This schema depends on the core.schema, cosine.schema and the inetorgperson.schema # as provided by third parties such as OpenLDAP. It also depends on rfc2739 schema, # which is included in the Citadel distribution. # # If you are using OpenLDAP, slapd.conf should declare schemas like this: # # include /etc/openldap/schema/core.schema # include /etc/openldap/schema/cosine.schema # include /etc/openldap/schema/inetorgperson.schema # include /etc/openldap/schema/rfc2739.schema # include /etc/openldap/schema/citadel.schema ############################################################################## # # Our OID tree so far looks like this: # # 1.3.6.1.4.1.25404 citadel.org # 1.3.6.1.4.1.25404.1 Citadel server project - LDAP schema # 1.3.6.1.4.1.25404.1.1 Custom attributes # 1.3.6.1.4.1.25404.1.2 Custom object classes # ############################################################################## ###################### # citadel attributes # ###################### # alias used to provide alternative rfc822 email addresses for citadel users attributetype ( 1.3.6.1.4.1.25404.1.1.1 NAME 'alias' DESC 'RFC1274: RFC822 Mailbox' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} ) # euid used to provide unique identifier for GAB entries attributetype ( 1.3.6.1.4.1.25404.1.1.2 NAME 'euid' DESC 'unique GAB entry' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.44{256} ) ########################## # citadel object classes # ########################## # citadel account # we use a STRUCTURAL in order to ease integration # with existing inetOrgPerson objects objectclass ( 1.3.6.1.4.1.25404.1.2.1 NAME 'citadelInetOrgPerson' DESC 'Citadel Internet Organizational Person' SUP inetOrgPerson STRUCTURAL MUST ( euid ) MAY ( c $ alias $ calFBURL ) ) ) citadel-9.01/utils/0000755000000000000000000000000012507024051012702 5ustar rootrootcitadel-9.01/utils/ctdlmigrate.c0000644000000000000000000002257612507024051015361 0ustar rootroot/* * Across-the-wire migration utility for Citadel * * Yes, we used goto, and gets(), and committed all sorts of other heinous sins here. * The scope of this program isn't wide enough to make a difference. If you don't like * it you can rewrite it. * * Copyright (c) 2009-2012 citadel.org * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * (Note: a useful future enhancement might be to support "-h" on both sides) * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "citadel.h" #include "axdefs.h" #include "sysdep.h" #include "config.h" #include "citadel_dirs.h" #if HAVE_BACKTRACE #include #endif /* * Replacement for gets() that doesn't throw a compiler warning. * We're only using it for some simple prompts, so we don't need * to worry about attackers exploiting it. */ void getz(char *buf) { char *ptr; ptr = fgets(buf, SIZ, stdin); if (!ptr) { buf[0] = 0; return; } ptr = strchr(buf, '\n'); if (ptr) *ptr = 0; } int main(int argc, char *argv[]) { int relh=0; int home=0; char relhome[PATH_MAX]=""; char ctdldir[PATH_MAX]=CTDLDIR; char yesno[5]; char sendcommand[PATH_MAX]; int cmdexit; char cmd[PATH_MAX]; char buf[PATH_MAX]; char socket_path[PATH_MAX]; char remote_user[SIZ]; char remote_host[SIZ]; char remote_sendcommand[PATH_MAX]; FILE *sourcefp = NULL; FILE *targetfp = NULL; int linecount = 0; char spinning[4] = "-\\|/" ; int exitcode = 0; calc_dirs_n_files(relh, home, relhome, ctdldir, 0); CtdlMakeTempFileName(socket_path, sizeof socket_path); cmdexit = system("clear"); printf( "-------------------------------------------\n" "Over-the-wire migration utility for Citadel\n" "-------------------------------------------\n" "\n" "This utility is designed to migrate your Citadel installation\n" "to a new host system via a network connection, without disturbing\n" "the source system. The target may be a different CPU architecture\n" "and/or operating system. The source system should be running\n" "Citadel %d.%02d or newer, and the target system should be running\n" "either the same version or a newer version. You will also need\n" "the 'rsync' utility, and OpenSSH v4 or newer.\n" "\n" "You must run this utility on the TARGET SYSTEM. Any existing data\n" "on this system will be ERASED.\n" "\n" "Do you wish to continue? " , EXPORT_REV_MIN / 100, EXPORT_REV_MIN % 100 ); if ((fgets(yesno, sizeof yesno, stdin) == NULL) || (tolower(yesno[0]) != 'y')) { exit(0); } printf("\n\nGreat! First we will check some things out here on our target\n" "system to make sure it is ready to receive data.\n\n"); printf("Locating 'sendcommand' and checking connectivity to Citadel...\n"); snprintf(sendcommand, sizeof sendcommand, "%s/sendcommand", ctdl_utilbin_dir); snprintf(cmd, sizeof cmd, "%s 'NOOP'", sendcommand); cmdexit = system(cmd); if (cmdexit != 0) { printf("\nctdlmigrate was unable to attach to the Citadel server\n" "here on the target system. Is Citadel running?\n\n"); exit(1); } printf("\nOK, this side is ready to go. Now we must connect to the source system.\n\n"); printf("Enter the host name or IP address of the source system\n" "(example: ctdl.foo.org)\n" "--> "); getz(remote_host); printf("\nEnter the name of a user on %s who has full access to Citadel files\n" "(usually root)\n--> ", remote_host); getz(remote_user); printf("\nEstablishing an SSH connection to the source system...\n\n"); unlink(socket_path); snprintf(cmd, sizeof cmd, "ssh -MNf -S %s %s@%s", socket_path, remote_user, remote_host); cmdexit = system(cmd); printf("\n"); if (cmdexit != 0) { printf("This program was unable to establish an SSH session to the source system.\n\n"); exitcode = cmdexit; goto THEEND; } printf("\nTesting a command over the connection...\n\n"); snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s 'echo Remote commands are executing successfully.'", socket_path, remote_user, remote_host); cmdexit = system(cmd); printf("\n"); if (cmdexit != 0) { printf("Remote commands are not succeeding.\n\n"); exitcode = cmdexit; goto THEEND; } printf("\nLocating the remote 'sendcommand' and Citadel installation...\n"); snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/local/citadel/sendcommand"); snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP", socket_path, remote_user, remote_host, remote_sendcommand); cmdexit = system(cmd); if (cmdexit != 0) { snprintf(remote_sendcommand, sizeof remote_sendcommand, "/usr/sbin/sendcommand"); snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP", socket_path, remote_user, remote_host, remote_sendcommand); cmdexit = system(cmd); } if (cmdexit != 0) { printf("\nUnable to locate Citadel programs on the remote system. Please enter\n" "the name of the directory on %s which contains the 'sendcommand' program.\n" "(example: /opt/foo/citadel)\n" "--> ", remote_host); getz(buf); snprintf(remote_sendcommand, sizeof remote_sendcommand, "%s/sendcommand", buf); snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s NOOP", socket_path, remote_user, remote_host, remote_sendcommand); cmdexit = system(cmd); } printf("\n"); if (cmdexit != 0) { printf("ctdlmigrate was unable to attach to the remote Citadel system.\n\n"); exitcode = cmdexit; goto THEEND; } printf("ctdlmigrate will now begin a database migration...\n"); snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s -w3600 MIGR export", socket_path, remote_user, remote_host, remote_sendcommand); sourcefp = popen(cmd, "r"); if (!sourcefp) { printf("\n%s\n\n", strerror(errno)); exitcode = 2; goto THEEND; } snprintf(cmd, sizeof cmd, "%s -w3600 MIGR import", sendcommand); targetfp = popen(cmd, "w"); if (!targetfp) { printf("\n%s\n\n", strerror(errno)); exitcode = 3; goto THEEND; } while (fgets(buf, sizeof buf, sourcefp) != NULL) { if (fwrite(buf, strlen(buf), 1, targetfp) < 1) { exitcode = 4; printf("%s\n", strerror(errno)); goto FAIL; } ++linecount; if ((linecount % 100) == 0) { printf("%c\r", spinning[((linecount / 100) % 4)]); fflush(stdout); } } FAIL: if (sourcefp) pclose(sourcefp); if (targetfp) pclose(targetfp); if (exitcode != 0) goto THEEND; /* We need to copy a bunch of other stuff, and will do so using rsync */ snprintf(cmd, sizeof cmd, "ssh -S %s %s@%s %s MIGR listdirs", socket_path, remote_user, remote_host, remote_sendcommand); sourcefp = popen(cmd, "r"); if (!sourcefp) { printf("\n%s\n\n", strerror(errno)); exitcode = 2; goto THEEND; } while ((fgets(buf, sizeof buf, sourcefp)) && (strcmp(buf, "000"))) { buf[strlen(buf)-1] = 0; if (!strncasecmp(buf, "bio|", 4)) { snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/", socket_path, remote_user, remote_host, &buf[4], ctdl_bio_dir); } else if (!strncasecmp(buf, "files|", 6)) { snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/", socket_path, remote_user, remote_host, &buf[6], ctdl_file_dir); } else if (!strncasecmp(buf, "userpics|", 9)) { snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/", socket_path, remote_user, remote_host, &buf[9], ctdl_usrpic_dir); } else if (!strncasecmp(buf, "messages|", 9)) { snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/", socket_path, remote_user, remote_host, &buf[9], ctdl_message_dir); } else if (!strncasecmp(buf, "netconfigs|", 11)) { snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/", socket_path, remote_user, remote_host, &buf[11], ctdl_netcfg_dir); } else if (!strncasecmp(buf, "keys|", 5)) { snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/", socket_path, remote_user, remote_host, &buf[5], ctdl_key_dir); } else if (!strncasecmp(buf, "images|", 7)) { snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/", socket_path, remote_user, remote_host, &buf[7], ctdl_image_dir); } else if (!strncasecmp(buf, "info|", 5)) { snprintf(cmd, sizeof cmd, "rsync -va --rsh='ssh -S %s' %s@%s:%s/ %s/", socket_path, remote_user, remote_host, &buf[5], ctdl_info_dir); } else { strcpy(cmd, "false"); /* cheap and sleazy way to throw an error */ } printf("%s\n", cmd); cmdexit = system(cmd); if (cmdexit != 0) { exitcode += cmdexit; } } pclose(sourcefp); THEEND: if (exitcode == 0) { printf("\n\n *** Citadel migration was successful! *** \n\n"); } else { printf("\n\n *** Citadel migration was unsuccessful. *** \n\n"); } printf("\nShutting down the socket connection...\n\n"); snprintf(cmd, sizeof cmd, "ssh -S %s -N -O exit %s@%s", socket_path, remote_user, remote_host); cmdexit = system(cmd); printf("\n"); if (cmdexit != 0) { printf("There was an error shutting down the socket.\n\n"); exitcode = cmdexit; } unlink(socket_path); exit(exitcode); } citadel-9.01/utils/msgform.c0000644000000000000000000000661212507024051014525 0ustar rootroot/* * This is simply a filter that converts Citadel binary message format * to readable, formatted output. * * If the -q (quiet or qwk) flag is used, only the message text prints, and * then it stops at the end of the first message it prints. * * This utility isn't very useful anymore. * */ #include "sysdep.h" #include #include #include #include #include #include #include #include #include int qwk = 0; int fpgetfield(FILE * fp, char *string); int fmout(int width, FILE * fp); #ifndef HAVE_STRERROR /* * replacement strerror() for systems that don't have it */ char *strerror(int e) { static char buf[32]; snprintf(buf, sizeof buf, "errno = %d", e); return (buf); } #endif int main(int argc, char **argv) { struct tm tm; int a, b, e, aflag; char bbb[1024]; char subject[1024]; FILE *fp; time_t now; if (argc == 2) if (!strcmp(argv[1], "-q")) qwk = 1; fp = stdin; if (argc == 2) if (strcmp(argv[1], "-q")) { fp = fopen(argv[1], "r"); if (fp == NULL) { fprintf(stderr, "%s: cannot open %s: %s\n", argv[0], argv[1], strerror(errno)); exit(errno); } } TOP: do { e = getc(fp); if (e < 0) exit(0); } while (e != 255); strcpy(subject, ""); getc(fp); aflag = getc(fp); if (qwk == 0) printf(" "); do { b = getc(fp); if (b == 'M') { if (qwk == 0) { printf("\n"); if (!IsEmptyStr(subject)) printf("Subject: %s\n", subject); } if (aflag != 1) fmout(80, fp); else while (a = getc(fp), a > 0) { if (a == 13) putc(10, stdout); else putc(a, stdout); } } if ((b != 'M') && (b > 0)) fpgetfield(fp, bbb); if (b == 'U') strcpy(subject, bbb); if (qwk == 0) { if (b == 'A') printf("from %s ", bbb); if (b == 'N') printf("@%s ", bbb); if (b == 'O') printf("in %s> ", bbb); if (b == 'R') printf("to %s ", bbb); if (b == 'T') { now = atol(bbb); localtime_r(&now, &tm); strcpy(bbb, asctime(&tm)); bbb[strlen(bbb) - 1] = 0; printf("%s ", &bbb[4]); } } } while ((b != 'M') && (b > 0)); if (qwk == 0) printf("\n"); if (qwk == 1) exit(0); goto TOP; } int fpgetfield(FILE * fp, char *string) { /* level-2 break out next null-terminated string */ int a, b; strcpy(string, ""); a = 0; do { b = getc(fp); if (b < 1) { string[a] = 0; return (0); } string[a] = b; ++a; } while (b != 0); return (0); } int fmout(int width, FILE * fp) { int a, b, c; int real = 0; int old = 0; char aaa[140]; strcpy(aaa, ""); old = 255; c = 1; /* c is the current pos */ FMTA: old = real; a = getc(fp); real = a; if (a <= 0) goto FMTEND; if (((a == 13) || (a == 10)) && (old != 13) && (old != 10)) a = 32; if (((old == 13) || (old == 10)) && (isspace(real))) { printf("\n"); c = 1; } if (a > 126) goto FMTA; if (a > 32) { if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) { printf("\n%s", aaa); c = strlen(aaa); aaa[0] = 0; } b = strlen(aaa); aaa[b] = a; aaa[b + 1] = 0; } if (a == 32) { if ((strlen(aaa) + c) > (width - 5)) { printf("\n"); c = 1; } printf("%s ", aaa); ++c; c = c + strlen(aaa); strcpy(aaa, ""); goto FMTA; } if ((a == 13) || (a == 10)) { printf("%s\n", aaa); c = 1; strcpy(aaa, ""); goto FMTA; } goto FMTA; FMTEND: printf("\n"); return (0); } citadel-9.01/utils/aidepost.c0000644000000000000000000000605012507024051014657 0ustar rootroot/* * This is just a little hack to copy standard input to a message in Aide> * * Copyright (c) 1987-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include "citadel.h" #include "citadel_dirs.h" /* * Simplified function to generate a message in our format */ static void ap_make_message(FILE *fp, char *target_room, char *author, char *subject) { int a; long bb, cc; time_t now; time(&now); putc(255, fp); putc(MES_NORMAL, fp); putc(1, fp); fprintf(fp, "Proom_aide"); putc(0, fp); fprintf(fp, "T%ld", (long)now); putc(0, fp); fprintf(fp, "A%s", author); putc(0, fp); fprintf(fp, "O%s", target_room); putc(0, fp); if (strlen(subject) > 0) { fprintf(fp, "U%s%c", subject, 0); } putc('M', fp); bb = ftell(fp); while (a = getc(stdin), a > 0) { if (a != 8) putc(a, fp); else { cc = ftell(fp); if (cc != bb) fseek(fp, (-1L), 1); } } putc(0, fp); putc(0, fp); putc(0, fp); } int main(int argc, char **argv) { char tempspool[64]; char target_room[ROOMNAMELEN]; char author[64]; char subject[256]; FILE *tempfp, *spoolfp; int ch; int i; int relh=0; int home=0; char relhome[PATH_MAX]=""; char ctdldir[PATH_MAX]=CTDLDIR; calc_dirs_n_files(relh, home, relhome, ctdldir, 0); strcpy(target_room, "Aide"); strcpy(author, "Citadel"); strcpy(subject, ""); for (i=1; i= 0)) { putc(ch, spoolfp); } fclose(tempfp); return(fclose(spoolfp)); } citadel-9.01/utils/base64.c0000644000000000000000000001726112507024051014141 0ustar rootroot/* * Encode or decode file as MIME base64 (RFC 1341) * Public domain by John Walker, August 11 1997 * Modified slightly for the Citadel system, June 1999 * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #define TRUE 1 #define FALSE 0 #define LINELEN 72 /* Encoded line length (max 76) */ typedef unsigned char byte; /* Byte type */ FILE *fi; /* Input file */ FILE *fo; /* Output file */ static byte iobuf[256]; /* I/O buffer */ static int iolen = 0; /* Bytes left in I/O buffer */ static int iocp = 256; /* Character removal pointer */ static int ateof = FALSE; /* EOF encountered */ static byte dtable[256]; /* Encode / decode table */ static int linelength = 0; /* Length of encoded output line */ static char eol[] = "\r\n"; /* End of line sequence */ static int errcheck = TRUE; /* Check decode input for errors ? */ /* INBUF -- Fill input buffer with data */ static int inbuf(void) { int l; if (ateof) { return FALSE; } l = fread(iobuf, 1, sizeof iobuf, fi); /* Read input buffer */ if (l <= 0) { if (ferror(fi)) { exit(1); } ateof = TRUE; return FALSE; } iolen = l; iocp = 0; return TRUE; } /* INCHAR -- Return next character from input */ static int inchar(void) { if (iocp >= iolen) { if (!inbuf()) { return EOF; } } return iobuf[iocp++]; } /* OCHAR -- Output an encoded character, inserting line breaks where required. */ static void ochar(int c) { if (linelength >= LINELEN) { if (fputs(eol, fo) == EOF) { exit(1); } linelength = 0; } if (putc(((byte) c), fo) == EOF) { exit(1); } linelength++; } /* ENCODE -- Encode binary file into base64. */ static void encode(void) { int i, hiteof = FALSE; /* Fill dtable with character encodings. */ for (i = 0; i < 26; i++) { dtable[i] = 'A' + i; dtable[26 + i] = 'a' + i; } for (i = 0; i < 10; i++) { dtable[52 + i] = '0' + i; } dtable[62] = '+'; dtable[63] = '/'; while (!hiteof) { byte igroup[3], ogroup[4]; int c, n; igroup[0] = igroup[1] = igroup[2] = 0; for (n = 0; n < 3; n++) { c = inchar(); if (c == EOF) { hiteof = TRUE; break; } igroup[n] = (byte) c; } if (n > 0) { ogroup[0] = dtable[igroup[0] >> 2]; ogroup[1] = dtable[((igroup[0] & 3) << 4) | (igroup[1] >> 4)]; ogroup[2] = dtable[((igroup[1] & 0xF) << 2) | (igroup[2] >> 6)]; ogroup[3] = dtable[igroup[2] & 0x3F]; /* Replace characters in output stream with "=" pad characters if fewer than three characters were read from the end of the input stream. */ if (n < 3) { ogroup[3] = '='; if (n < 2) { ogroup[2] = '='; } } for (i = 0; i < 4; i++) { ochar(ogroup[i]); } } } if (fputs(eol, fo) == EOF) { exit(1); } } /* INSIG -- Return next significant input */ static int insig(void) { int c; /*CONSTANTCONDITION*/ while (TRUE) { c = inchar(); if (c == EOF || (c > ' ')) { return c; } } /*NOTREACHED*/ } /* DECODE -- Decode base64. */ static void decode(void) { int i; for (i = 0; i < 255; i++) { dtable[i] = 0x80; } for (i = 'A'; i <= 'Z'; i++) { dtable[i] = 0 + (i - 'A'); } for (i = 'a'; i <= 'z'; i++) { dtable[i] = 26 + (i - 'a'); } for (i = '0'; i <= '9'; i++) { dtable[i] = 52 + (i - '0'); } dtable['+'] = 62; dtable['/'] = 63; dtable['='] = 0; /*CONSTANTCONDITION*/ while (TRUE) { byte a[4], b[4], o[3]; for (i = 0; i < 4; i++) { int c = insig(); if (c == EOF) { if (errcheck && (i > 0)) { fprintf(stderr, "Input file incomplete.\n"); exit(1); } return; } if (dtable[c] & 0x80) { if (errcheck) { fprintf(stderr, "Illegal character '%c' in input file.\n", c); exit(1); } /* Ignoring errors: discard invalid character. */ i--; continue; } a[i] = (byte) c; b[i] = (byte) dtable[c]; } o[0] = (b[0] << 2) | (b[1] >> 4); o[1] = (b[1] << 4) | (b[2] >> 2); o[2] = (b[2] << 6) | b[3]; i = a[2] == '=' ? 1 : (a[3] == '=' ? 2 : 3); if (fwrite(o, i, 1, fo) == EOF) { exit(1); } if (i < 3) { return; } } } /* USAGE -- Print how-to-call information. */ static void usage(char *pname) { fprintf(stderr, "%s -- Encode/decode file as base64. Call:\n", pname); fprintf(stderr, " %s [-e[ncode] / -d[ecode]] [-n] [infile] [outfile]\n", pname); fprintf(stderr, "\n"); fprintf(stderr, "Options:\n"); fprintf(stderr, " -D Decode base64 encoded file\n"); fprintf(stderr, " -E Encode file into base64\n"); fprintf(stderr, " -N Ignore errors when decoding\n"); fprintf(stderr, " -U Print this message\n"); fprintf(stderr, "\n"); fprintf(stderr, "by John Walker\n"); fprintf(stderr, " WWW: http://www.fourmilab.ch/\n"); } /* Main program */ int main(int argc, char *argv[]) { int i, f = 0, decoding = FALSE; char *cp, opt; fi = stdin; fo = stdout; for (i = 1; i < argc; i++) { cp = argv[i]; if (*cp == '-') { opt = *(++cp); if (islower(opt)) { opt = toupper(opt); } switch (opt) { case 'D': /* -D Decode */ decoding = TRUE; break; case 'E': /* -E Encode */ decoding = FALSE; break; case 'N': /* -N Suppress error checking */ errcheck = FALSE; break; case 'U': /* -U Print how-to-call information */ case '?': usage(argv[0]); return 0; } } else { switch (f) { /** Warning! On systems which distinguish text mode and binary I/O (MS-DOS, Macintosh, etc.) the modes in these open statements will have to be made conditional based upon whether an encode or decode is being done, which will have to be specified earlier. But it's worse: if input or output is from standard input or output, the mode will have to be changed on the fly, which is generally system and compiler dependent. 'Twasn't me who couldn't conform to Unix CR/LF convention, so don't ask me to write the code to work around Apple and Microsoft's incompatible standards. **/ case 0: if (strcmp(cp, "-") != 0) { if ((fi = fopen(cp, "r")) == NULL) { fprintf(stderr, "Cannot open input file %s\n", cp); return 2; } } f++; break; case 1: if (strcmp(cp, "-") != 0) { if ((fo = fopen(cp, "w")) == NULL) { fprintf(stderr, "Cannot open output file %s\n", cp); return 2; } } f++; break; default: fprintf(stderr, "Too many file names specified.\n"); usage(argv[0]); return 2; } } } if (decoding) { decode(); } else { encode(); } return 0; } citadel-9.01/utils/setup.c0000644000000000000000000010236712507024051014217 0ustar rootroot/* * Citadel setup utility * * Copyright (c) 1987-2014 by the citadel.org team * * This program is open source software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #define SHOW_ME_VAPPEND_PRINTF #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "citadel.h" #include "axdefs.h" #include "sysdep.h" #include "citadel_dirs.h" #if HAVE_BACKTRACE #include #endif #ifdef ENABLE_NLS #ifdef HAVE_XLOCALE_H #include #endif #include #include #define _(string) gettext(string) #else #define _(string) (string) #endif #define UI_TEXT 0 /* Default setup type -- text only */ #define UI_DIALOG 2 /* Use the 'whiptail' or 'dialog' program */ #define UI_SILENT 3 /* Silent running, for use in scripts */ #define SERVICE_NAME "citadel" #define PROTO_NAME "tcp" #define NSSCONF "/etc/nsswitch.conf" typedef enum _SetupStep { eCitadelHomeDir = 0, eSysAdminName = 1, eSysAdminPW = 2, eUID = 3, eIP_ADDR = 4, eCTDL_Port = 5, eAuthType = 6, eLDAP_Host = 7, eLDAP_Port = 8, eLDAP_Base_DN = 9, eLDAP_Bind_DN = 10, eLDAP_Bind_PW = 11, eMaxQuestions = 12 } eSteupStep; ///"CREATE_XINETD_ENTRY"; /* Environment variables, don't translate! */ const char *EnvNames [eMaxQuestions] = { "HOME_DIRECTORY", "SYSADMIN_NAME", "SYSADMIN_PW", "CITADEL_UID", "IP_ADDR", "CITADEL_PORT", "ENABLE_UNIX_AUTH", "LDAP_HOST", "LDAP_PORT", "LDAP_BASE_DN", "LDAP_BIND_DN", "LDAP_BIND_PW" }; int setup_type = (-1); int using_web_installer = 0; int enable_home = 1; char admin_pass[SIZ]; char admin_cmd[SIZ]; int serv_sock = (-1) ; char configs[NUM_CONFIGS][1024]; const char *setup_titles[eMaxQuestions]; const char *setup_text[eMaxQuestions]; char *program_title; void SetTitles(void) { int have_run_dir; #ifndef HAVE_RUN_DIR have_run_dir = 1; #else have_run_dir = 0; #endif #ifdef ENABLE_NLS setlocale(LC_MESSAGES, getenv("LANG")); bindtextdomain("citadel-setup", LOCALEDIR"/locale"); textdomain("citadel-setup"); bind_textdomain_codeset("citadel-setup","UTF8"); #endif setup_titles[eCitadelHomeDir] = _("Citadel Home Directory"); if (have_run_dir) setup_text[eCitadelHomeDir] = _( "Enter the full pathname of the directory in which the Citadel\n" "installation you are creating or updating resides. If you\n" "specify a directory other than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n"); else setup_text[eCitadelHomeDir] = _( "Enter the subdirectory name for an alternate installation of " "Citadel. To do a default installation just leave it blank." "If you specify a directory other than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" "note that it may not have a leading /"); setup_titles[eSysAdminName] = _("Citadel administrator username:"); setup_text[eSysAdminName] = _( "Please enter the name of the Citadel user account that should be granted " "administrative privileges once created. If using internal authentication " "this user account will be created if it does not exist. For external " "authentication this user account has to exist."); setup_titles[eSysAdminPW] = _("Administrator password:"); setup_text[eSysAdminPW] = _( "Enter a password for the system administrator. When setup\n" "completes it will attempt to create the administrator user\n" "and set the password specified here.\n"); setup_titles[eUID] = _("Citadel User ID:"); setup_text[eUID] = _( "Citadel needs to run under its own user ID. This would\n" "typically be called \"citadel\", but if you are running Citadel\n" "as a public site, you might also call it \"bbs\" or \"guest\".\n" "The server will run under this user ID. Please specify that\n" "user ID here. You may specify either a user name or a numeric\n" "UID.\n"); setup_titles[eIP_ADDR] = _("Listening address for the Citadel server:"); setup_text[eIP_ADDR] = _( "Please specify the IP address which the server should be listening to. " "You can name a specific IPv4 or IPv6 address, or you can specify\n" "\"*\" for \"any address\", \"::\" for \"any IPv6 address\", or \"0.0.0.0\"\n" "for \"any IPv4 address\". If you leave this blank, Citadel will\n" "listen on all addresses. " "This can usually be left to the default unless multiple instances of Citadel " "are running on the same computer."); setup_titles[eCTDL_Port] = _("Server port number:"); setup_text[eCTDL_Port] = _( "Specify the TCP port number on which your server will run.\n" "Normally, this will be port 504, which is the official port\n" "assigned by the IANA for Citadel servers. You will only need\n" "to specify a different port number if you run multiple instances\n" "of Citadel on the same computer and there is something else\n" "already using port 504.\n"); setup_titles[eAuthType] = _("Authentication method to use:"); setup_text[eAuthType] = _( "Please choose the user authentication mode. By default Citadel will use its " "own internal user accounts database. If you choose Host, Citadel users will " "have accounts on the host system, authenticated via /etc/passwd or a PAM " "source. LDAP chooses an RFC 2307 compliant directory server, the last option " "chooses the nonstandard MS Active Directory LDAP scheme." "\n" "Do not change this option unless you are sure it is required, since changing " "back requires a full reinstall of Citadel." "\n" " 0. Self contained authentication\n" " 1. Host system integrated authentication\n" " 2. External LDAP - RFC 2307 compliant directory\n" " 3. External LDAP - nonstandard MS Active Directory\n" "\n" "For help: http://www.citadel.org/doku.php/faq:installation:authmodes\n" "\n" "ANSWER \"0\" UNLESS YOU COMPLETELY UNDERSTAND THIS OPTION.\n"); setup_titles[eLDAP_Host] = _("LDAP host:"); setup_text[eLDAP_Host] = _( "Please enter the host name or IP address of your LDAP server.\n"); setup_titles[eLDAP_Port] = _("LDAP port number:"); setup_text[eLDAP_Port] = _( "Please enter the port number of the LDAP service (usually 389).\n"); setup_titles[eLDAP_Base_DN] = _("LDAP base DN:"); setup_text[eLDAP_Base_DN] = _( "Please enter the Base DN to search for authentication\n" "(for example: dc=example,dc=com)\n"); setup_titles[eLDAP_Bind_DN] = _("LDAP bind DN:"); setup_text[eLDAP_Bind_DN] = _( "Please enter the DN of an account to use for binding to the LDAP server for " "performing queries. The account does not require any other privileges. If " "your LDAP server allows anonymous queries, you can leave this blank.\n"); setup_titles[eLDAP_Bind_PW] = _("LDAP bind password:"); setup_text[eLDAP_Bind_PW] = _( "If you entered a Bind DN in the previous question, you must now enter\n" "the password associated with that account. Otherwise, you can leave this\n" "blank.\n"); #if 0 // Debug loading of locales... Strace does a better job though. printf("Message catalog directory: %s\n", bindtextdomain("citadel-setup", LOCALEDIR"/locale")); printf("Text domain: %s\n", textdomain("citadel-setup")); printf("Text domain Charset: %s\n", bind_textdomain_codeset("citadel-setup","UTF8")); { int i; for (i = 0; i < eMaxQuestions; i++) printf("%s - %s\n", setup_titles[i], _(setup_titles[i])); exit(0); } #endif } /* * Print the stack frame for a backtrace */ void cit_backtrace(void) { #ifdef HAVE_BACKTRACE void *stack_frames[50]; size_t size, i; char **strings; size = backtrace(stack_frames, sizeof(stack_frames) / sizeof(void*)); strings = backtrace_symbols(stack_frames, size); for (i = 0; i < size; i++) { if (strings != NULL) fprintf(stderr, "%s\n", strings[i]); else fprintf(stderr, "%p\n", stack_frames[i]); } free(strings); #endif } void title(const char *text) { if (setup_type == UI_TEXT) { printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<%s>\n", text); } } int yesno(const char *question, int default_value) { int i = 0; int answer = 0; char buf[SIZ]; switch (setup_type) { case UI_TEXT: do { printf("%s\n%s [%s] --> ", question, _("Yes/No"), ( default_value ? _("Yes") : _("No") ) ); if (fgets(buf, sizeof buf, stdin)) { answer = tolower(buf[0]); if ((buf[0]==0) || (buf[0]==13) || (buf[0]==10)) { answer = default_value; } else if (answer == 'y') { answer = 1; } else if (answer == 'n') { answer = 0; } } } while ((answer < 0) || (answer > 1)); break; case UI_DIALOG: snprintf(buf, sizeof buf, "exec %s --backtitle '%s' %s --yesno '%s' 15 75", getenv("CTDL_DIALOG"), program_title, ( default_value ? "" : "--defaultno" ), question); i = system(buf); if (i == 0) { answer = 1; } else { answer = 0; } break; case UI_SILENT: break; } return (answer); } void important_message(const char *title, const char *msgtext) { char buf[SIZ]; switch (setup_type) { case UI_TEXT: printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); printf(" %s \n\n%s\n\n", title, msgtext); printf("%s", _("Press return to continue...")); if (fgets(buf, sizeof buf, stdin)) {;} break; case UI_DIALOG: snprintf(buf, sizeof buf, "exec %s --backtitle '%s' --msgbox '%s' 19 72", getenv("CTDL_DIALOG"), program_title, msgtext); int rv = system(buf); if (rv != 0) { fprintf(stderr, _("failed to run the dialog command\n")); } break; case UI_SILENT: fprintf(stderr, "%s\n", msgtext); break; } } void important_msgnum(int msgnum) { important_message(_("Important Message"), setup_text[msgnum]); } void display_error(char *error_message_format, ...) { StrBuf *Msg; va_list arg_ptr; Msg = NewStrBuf(); va_start(arg_ptr, error_message_format); StrBufVAppendPrintf(Msg, error_message_format, arg_ptr); va_end(arg_ptr); important_message(_("Error"), ChrPtr(Msg)); FreeStrBuf(&Msg); } void progress(char *text, long int curr, long int cmax) { static long dots_printed = 0L; long a = 0; static FILE *fp = NULL; char buf[SIZ]; switch (setup_type) { case UI_TEXT: if (curr == 0) { printf("%s\n", text); printf("...................................................."); printf("..........................\r"); dots_printed = 0; } else if (curr == cmax) { printf("\r%79s\n", ""); } else { a = (curr * 100) / cmax; a = a * 78; a = a / 100; while (dots_printed < a) { printf("*"); ++dots_printed; } } fflush(stdout); break; case UI_DIALOG: if (curr == 0) { snprintf(buf, sizeof buf, "exec %s --backtitle '%s' --gauge '%s' 7 72 0", getenv("CTDL_DIALOG"), program_title, text); fp = popen(buf, "w"); if (fp != NULL) { fprintf(fp, "0\n"); fflush(fp); } } else if (curr == cmax) { if (fp != NULL) { fprintf(fp, "100\n"); pclose(fp); fp = NULL; } } else { a = (curr * 100) / cmax; if (fp != NULL) { fprintf(fp, "%ld\n", a); fflush(fp); } } break; case UI_SILENT: break; default: assert(1==0); /* If we got here then the developer is a moron */ } } int uds_connectsock(char *sockpath) { int s; struct sockaddr_un addr; memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, sockpath, sizeof addr.sun_path); s = socket(AF_UNIX, SOCK_STREAM, 0); if (s < 0) { return(-1); } if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) { close(s); return(-1); } return s; } /* * input binary data from socket */ void serv_read(char *buf, int bytes) { int len, rlen; len = 0; while (len < bytes) { rlen = read(serv_sock, &buf[len], bytes - len); if (rlen < 1) { return; } len = len + rlen; } } /* * send binary to server */ void serv_write(char *buf, int nbytes) { int bytes_written = 0; int retval; while (bytes_written < nbytes) { retval = write(serv_sock, &buf[bytes_written], nbytes - bytes_written); if (retval < 1) { return; } bytes_written = bytes_written + retval; } } /* * input string from socket - implemented in terms of serv_read() */ void serv_gets(char *buf) { int i; /* Read one character at a time. */ for (i = 0;; i++) { serv_read(&buf[i], 1); if (buf[i] == '\n' || i == (SIZ-1)) break; } /* If we got a long line, discard characters until the newline. */ if (i == (SIZ-1)) { while (buf[i] != '\n') { serv_read(&buf[i], 1); } } /* Strip all trailing nonprintables (crlf) */ buf[i] = 0; } /* * send line to server - implemented in terms of serv_write() */ void serv_puts(char *buf) { serv_write(buf, strlen(buf)); serv_write("\n", 1); } /* * On systems which use xinetd, see if we can offer to install Citadel as * the default telnet target. */ void check_xinetd_entry(void) { char *filename = "/etc/xinetd.d/telnet"; FILE *fp; char buf[SIZ]; int already_citadel = 0; int rv; fp = fopen(filename, "r+"); if (fp == NULL) return; /* Not there. Oh well... */ while (fgets(buf, sizeof buf, fp) != NULL) { if (strstr(buf, "/citadel") != NULL) { already_citadel = 1; } } fclose(fp); if (already_citadel) return; /* Already set up this way. */ /* Otherwise, prompt the user to create an entry. */ if (getenv("CREATE_XINETD_ENTRY") != NULL) { if (strcasecmp(getenv("CREATE_XINETD_ENTRY"), "yes")) { return; } } else { snprintf(buf, sizeof buf, _("Setup can configure the \"xinetd\" service to automatically\n" "connect incoming telnet sessions to Citadel, bypassing the\n" "host system login: prompt. Would you like to do this?\n" ) ); if (yesno(buf, 1) == 0) { return; } } fp = fopen(filename, "w"); fprintf(fp, "# description: telnet service for Citadel users\n" "service telnet\n" "{\n" " disable = no\n" " flags = REUSE\n" " socket_type = stream\n" " wait = no\n" " user = root\n" " server = /usr/sbin/in.telnetd\n" " server_args = -h -L %s/citadel\n" " log_on_failure += USERID\n" "}\n", ctdl_bin_dir); fclose(fp); /* Now try to restart the service */ rv = system("/etc/init.d/xinetd restart >/dev/null 2>&1"); if (rv != 0) { display_error(_("failed to restart xinetd.\n")); } } /* * Offer to disable other MTA's */ void disable_other_mta(const char *mta) { char buf[SIZ]; FILE *fp; int lines = 0; int rv; snprintf(buf, sizeof buf, "/bin/ls -l /etc/rc*.d/S*%s 2>/dev/null; " "/bin/ls -l /etc/rc.d/rc*.d/S*%s 2>/dev/null", mta, mta ); fp = popen(buf, "r"); if (fp == NULL) return; while (fgets(buf, sizeof buf, fp) != NULL) { ++lines; } fclose(fp); if (lines == 0) return; /* Nothing to do. */ /* Offer to replace other MTA with the vastly superior Citadel :) */ snprintf(buf, sizeof buf, "%s \"%s\" %s%s%s%s%s%s%s", _("You appear to have the "), mta, _(" email program\n" "running on your system. If you want Citadel mail\n" "connected with "), mta, _(" you will have to manually integrate\n" "them. It is preferable to disable "), mta, _(", and use Citadel's\n" "SMTP, POP3, and IMAP services.\n\n" "May we disable "), mta, _("so that Citadel has access to ports\n" "25, 110, and 143?\n") ); if (yesno(buf, 1) == 0) { return; } snprintf(buf, sizeof buf, "for x in /etc/rc*.d/S*%s; do mv $x `echo $x |sed s/S/K/g`; done >/dev/null 2>&1", mta); rv = system(buf); if (rv != 0) display_error("%s %s.\n", _("failed to disable other mta"), mta); snprintf(buf, sizeof buf, "/etc/init.d/%s stop >/dev/null 2>&1", mta); rv = system(buf); if (rv != 0) display_error(" %s.\n", _("failed to disable other mta"), mta); } const char *other_mtas[] = { "courier-authdaemon", "courier-imap", "courier-imap-ssl", "courier-pop", "courier-pop3", "courier-pop3d", "cyrmaster", "cyrus", "dovecot", "exim", "exim4", "imapd", "mta", "pop3d", "popd", "postfix", "qmail", "saslauthd", "sendmail", "vmailmgrd", "" }; void disable_other_mtas(void) { int i = 0; if ((getenv("ACT_AS_MTA") == NULL) || (getenv("ACT_AS_MTA") && strcasecmp(getenv("ACT_AS_MTA"), "yes") == 0)) { /* Offer to disable other MTA's on the system. */ while (!IsEmptyStr(other_mtas[i])) { disable_other_mta(other_mtas[i]); i++; } } } void strprompt(const char *prompt_title, const char *prompt_text, char *Target, char *DefValue) { char buf[SIZ] = ""; char setupmsg[SIZ]; char dialog_result[PATH_MAX]; FILE *fp = NULL; int rv; strcpy(setupmsg, ""); switch (setup_type) { case UI_TEXT: title(prompt_title); printf("\n%s\n", prompt_text); printf("%s\n%s\n", _("This is currently set to:"), Target); printf("%s\n", _("Enter new value or press return to leave unchanged:")); if (fgets(buf, sizeof buf, stdin)){ buf[strlen(buf) - 1] = 0; } if (!IsEmptyStr(buf)) strcpy(Target, buf); break; case UI_DIALOG: CtdlMakeTempFileName(dialog_result, sizeof dialog_result); snprintf(buf, sizeof buf, "exec %s --backtitle '%s' --nocancel --inputbox '%s' 19 72 '%s' 2>%s", getenv("CTDL_DIALOG"), program_title, prompt_text, Target, dialog_result); rv = system(buf); if (rv != 0) { fprintf(stderr, "failed to run whiptail or dialog\n"); } fp = fopen(dialog_result, "r"); if (fp != NULL) { if (fgets(Target, sizeof buf, fp)) { if (Target[strlen(Target)-1] == 10) { Target[strlen(Target)-1] = 0; } } fclose(fp); unlink(dialog_result); } break; case UI_SILENT: if (*DefValue != '\0') strcpy(Target, DefValue); break; } } void set_bool_val(int msgpos, int *ip, char *DefValue) { title(setup_titles[msgpos]); *ip = yesno(setup_text[msgpos], *ip); } void set_str_val(int msgpos, char *Target, char *DefValue) { strprompt(setup_titles[msgpos], setup_text[msgpos], Target, DefValue ); } /* like set_str_val() but make sure we ended up with a numeric value */ void set_int_val(int msgpos, char *target, char *DefValue) { while(1) { set_str_val(msgpos, target, DefValue); if (!strcmp(target, "0")) return; if (atoi(target) != 0) return; } } void edit_value(int curr) { int i; struct passwd *pw; char ctdluidname[256]; char *Value = NULL; if (setup_type == UI_SILENT) { Value = getenv(EnvNames[curr]); } if (Value == NULL) { Value = ""; } switch (curr) { case eSysAdminName: set_str_val(curr, configs[13], Value); break; case eSysAdminPW: set_str_val(curr, admin_pass, Value); break; case eUID: if (setup_type == UI_SILENT) { if (Value) { sprintf(configs[69], "%d", atoi(Value)); } } else { #ifdef __CYGWIN__ strcpy(configs[69], "0"); /* work-around for Windows */ #else i = atoi(configs[69]); pw = getpwuid(i); if (pw == NULL) { set_int_val(curr, configs[69], Value); sprintf(configs[69], "%d", i); } else { strcpy(ctdluidname, pw->pw_name); set_str_val(curr, ctdluidname, Value); pw = getpwnam(ctdluidname); if (pw != NULL) { sprintf(configs[69], "%d", pw->pw_uid); } else if (atoi(ctdluidname) > 0) { sprintf(configs[69], "%d", atoi(ctdluidname)); } } #endif } break; case eIP_ADDR: set_str_val(curr, configs[37], Value); break; case eCTDL_Port: set_int_val(curr, configs[68], Value); break; case eAuthType: if (setup_type == UI_SILENT) { const char *auth; //config.c_auth_mode = AUTHMODE_NATIVE; auth = Value; if (auth != NULL) { if ((strcasecmp(auth, "yes") == 0) || (strcasecmp(auth, "host") == 0)) { //config.c_auth_mode = AUTHMODE_HOST; } else if (strcasecmp(auth, "ldap") == 0){ //config.c_auth_mode = AUTHMODE_LDAP; } else if ((strcasecmp(auth, "ldap_ad") == 0) || (strcasecmp(auth, "active directory") == 0)){ //config.c_auth_mode = AUTHMODE_LDAP_AD; } } } else { set_int_val(curr, configs[52], Value); } break; case eLDAP_Host: if (IsEmptyStr(configs[32])) { strcpy(configs[32], "localhost"); } set_str_val(curr, configs[32], Value); break; case eLDAP_Port: if (atoi(configs[33]) == 0) { strcpy(configs[33], "389"); } set_int_val(curr, configs[33], Value); break; case eLDAP_Base_DN: set_str_val(curr, configs[34], Value); break; case eLDAP_Bind_DN: set_str_val(curr, configs[35], Value); break; case eLDAP_Bind_PW: set_str_val(curr, configs[36], Value); break; } } /* * Figure out what type of user interface we're going to use */ int discover_ui(void) { /* Use "whiptail" or "dialog" if we have it */ if (getenv("CTDL_DIALOG") != NULL) { return UI_DIALOG; } return UI_TEXT; } /* * Strip "db" entries out of /etc/nsswitch.conf */ void fixnss(void) { FILE *fp_read; int fd_write; char buf[256]; char buf_nc[256]; char question[512]; int i; int file_changed = 0; char new_filename[64]; int rv; fp_read = fopen(NSSCONF, "r"); if (fp_read == NULL) { return; } strcpy(new_filename, "/tmp/ctdl_fixnss_XXXXXX"); fd_write = mkstemp(new_filename); if (fd_write < 0) { fclose(fp_read); return; } while (fgets(buf, sizeof buf, fp_read) != NULL) { strcpy(buf_nc, buf); for (i=0; buf_nc[i]; ++i) { if (buf_nc[i] == '#') { buf_nc[i] = 0; break; } } for (i=0; i 0) { if ((isspace(buf_nc[i+2])) || (buf_nc[i+2]==0)) { file_changed = 1; strcpy(&buf_nc[i], &buf_nc[i+2]); strcpy(&buf[i], &buf[i+2]); if (buf[i]==32) { strcpy(&buf_nc[i], &buf_nc[i+1]); strcpy(&buf[i], &buf[i+1]); } } } } } long buflen = strlen(buf); if (write(fd_write, buf, buflen) != buflen) { fclose(fp_read); close(fd_write); unlink(new_filename); return; } } fclose(fp_read); if (!file_changed) { unlink(new_filename); return; } snprintf(question, sizeof question, _( "\n" "/etc/nsswitch.conf is configured to use the 'db' module for\n" "one or more services. This is not necessary on most systems,\n" "and it is known to crash the Citadel server when delivering\n" "mail to the Internet.\n" "\n" "Do you want this module to be automatically disabled?\n" "\n" ) ); if (yesno(question, 1)) { snprintf(buf, sizeof buf, "/bin/mv -f %s %s", new_filename, NSSCONF); rv = system(buf); if (rv != 0) { fprintf(stderr, "failed to edit %s.\n", NSSCONF); } chmod(NSSCONF, 0644); } unlink(new_filename); } #if 0 important_message(_("Setup finished"), _("Setup of the Citadel server is complete.\n" "If you will be using WebCit, please run its\n" "setup program now; otherwise, run './citadel'\n" "to log in.\n")); important_message(_("Setup failed"), _("Setup is finished, but the Citadel server failed to start.\n" "Go back and check your configuration.\n") important_message(_("Setup finished"), _("Setup is finished. You may now start the server.")); #endif #define GetDefaultVALINT(CFGNAME, DEFL) GetDefaultValInt(&config.CFGNAME, "CITADEL_"#CFGNAME, DEFL) void GetDefaultValInt(int *WhereTo, const char *VarName, int DefVal) { const char *ch; if (*WhereTo == 0) *WhereTo = DefVal; if ((setup_type == UI_SILENT) && (ch = getenv(VarName), ch != NULL)) { *WhereTo = atoi(ch); } } #define GetDefaultVALCHAR(CFGNAME, DEFL) GetDefaultValChar(&config.CFGNAME, "CITADEL_"#CFGNAME, DEFL) void GetDefaultValChar(char *WhereTo, const char *VarName, char DefVal) { const char *ch; if (*WhereTo == 0) *WhereTo = DefVal; if ((setup_type == UI_SILENT) && (ch = getenv(VarName), ch != NULL)) { *WhereTo = atoi(ch); } } #define GetDefaultVALSTR(CFGNAME, DEFL) GetDefaultValStr(&config.CFGNAME[0], sizeof(config.CFGNAME), "CITADEL_"#CFGNAME, DEFL) void GetDefaultValStr(char *WhereTo, size_t nMax, const char *VarName, const char *DefVal) { const char *ch; if (*WhereTo == '\0') safestrncpy(WhereTo, DefVal, nMax); if ((setup_type == UI_SILENT) && (ch = getenv(VarName), ch != NULL)) { safestrncpy(WhereTo, ch, nMax); } } void set_default_values(void) { #if 0 struct passwd *pw; struct utsname my_utsname; struct hostent *he; /* Determine our host name, in case we need to use it as a default */ uname(&my_utsname); /* set some sample/default values in place of blanks... */ GetDefaultVALSTR(c_nodename, my_utsname.nodename); strtok(config.c_nodename, "."); if (IsEmptyStr(config.c_fqdn) ) { if ((he = gethostbyname(my_utsname.nodename)) != NULL) { safestrncpy(config.c_fqdn, he->h_name, sizeof config.c_fqdn); } else { safestrncpy(config.c_fqdn, my_utsname.nodename, sizeof config.c_fqdn); } } GetDefaultVALSTR(c_humannode, _("My System")); GetDefaultVALSTR(c_phonenum, _("US 800 555 1212")); GetDefaultVALCHAR(c_initax, 4); GetDefaultVALSTR(c_moreprompt, ""); GetDefaultVALSTR(c_twitroom, "Trashcan"); GetDefaultVALSTR(c_baseroom, BASEROOM); GetDefaultVALSTR(c_aideroom, "Aide"); GetDefaultVALINT(c_port_number, 504); GetDefaultVALINT(c_sleeping, 900); if (config.c_ctdluid == 0) { pw = getpwnam("citadel"); if (pw != NULL) { config.c_ctdluid = pw->pw_uid; } } if (config.c_ctdluid == 0) { pw = getpwnam("bbs"); if (pw != NULL) { config.c_ctdluid = pw->pw_uid; } } if (config.c_ctdluid == 0) { pw = getpwnam("guest"); if (pw != NULL) { config.c_ctdluid = pw->pw_uid; } } if (config.c_createax == 0) { config.c_createax = 3; } /* * Negative values for maxsessions are not allowed. */ if (config.c_maxsessions < 0) { config.c_maxsessions = 0; } /* We need a system default message expiry policy, because this is * the top level and there's no 'higher' policy to fall back on. * By default, do not expire messages at all. */ if (config.c_ep.expire_mode == 0) { config.c_ep.expire_mode = EXPIRE_MANUAL; config.c_ep.expire_value = 0; } /* * Default port numbers for various services */ GetDefaultVALINT(c_smtp_port, 25); GetDefaultVALINT(c_pop3_port, 110); GetDefaultVALINT(c_imap_port, 143); GetDefaultVALINT(c_msa_port, 587); GetDefaultVALINT(c_smtps_port, 465); GetDefaultVALINT(c_pop3s_port, 995); GetDefaultVALINT(c_imaps_port, 993); GetDefaultVALINT(c_pftcpdict_port, -1); GetDefaultVALINT(c_managesieve_port, 2020); GetDefaultVALINT(c_xmpp_c2s_port, 5222); GetDefaultVALINT(c_xmpp_s2s_port, 5269); GetDefaultVALINT(c_nntp_port, 119); GetDefaultVALINT(c_nntps_port, 563); #endif } int main(int argc, char *argv[]) { int a, i; int curr; char buf[1024]; char aaa[128]; int info_only = 0; int relh = 0; int home = 0; int nRetries = 0; char relhome[PATH_MAX]=""; char ctdldir[PATH_MAX]=CTDLDIR; struct passwd *pw; gid_t gid; char *activity = NULL; /* Keep a mild groove on */ program_title = _("Citadel setup program"); /* set an invalid setup type */ setup_type = (-1); /* Check to see if we're running the web installer */ if (getenv("CITADEL_INSTALLER") != NULL) { using_web_installer = 1; } /* parse command line args */ for (a = 0; a < argc; ++a) { if (!strncmp(argv[a], "-u", 2)) { strcpy(aaa, argv[a]); strcpy(aaa, &aaa[2]); setup_type = atoi(aaa); } else if (!strcmp(argv[a], "-i")) { info_only = 1; } else if (!strcmp(argv[a], "-q")) { setup_type = UI_SILENT; } else if (!strncmp(argv[a], "-h", 2)) { relh=argv[a][2]!='/'; if (!relh) { safestrncpy(ctdl_home_directory, &argv[a][2], sizeof ctdl_home_directory); } else { safestrncpy(relhome, &argv[a][2], sizeof relhome); } home = 1; } } calc_dirs_n_files(relh, home, relhome, ctdldir, 0); SetTitles(); /* If a setup type was not specified, try to determine automatically * the best one to use out of all available types. */ if (setup_type < 0) { setup_type = discover_ui(); } if (info_only == 1) { important_message(_("Citadel Setup"), CITADEL); exit(0); } enable_home = ( relh | home ); if (chdir(ctdl_run_dir) != 0) { display_error("%s: [%s]\n", _("The directory you specified does not exist"), ctdl_run_dir); exit(errno); } /* * Connect to the running Citadel server. */ while ((serv_sock < 0) && (nRetries < 10)) { serv_sock = uds_connectsock(file_citadel_admin_socket); nRetries ++; if (serv_sock < 0) sleep(1); } if (serv_sock < 0) { display_error( "%s: %s %s\n", _("Setup could not connect to a running Citadel server."), strerror(errno), file_citadel_admin_socket ); exit(1); } /* * read the server greeting */ serv_gets(buf); if (buf[0] != '2') { display_error("%s\n", buf); exit(2); } /* * Are we connected to the correct Citadel server? */ serv_puts("INFO"); serv_gets(buf); if (buf[0] != '1') { display_error("%s\n", buf); exit(3); } a = 0; while (serv_gets(buf), strcmp(buf, "000")) { if (a == 5) { if (atoi(buf) != REV_LEVEL) { display_error("%s\n", _("Your setup program and Citadel server are from different versions.") ); exit(4); } } ++a; } /* * Load the server's configuration */ serv_puts("CONF GET"); serv_gets(buf); if (buf[0] != '1') { display_error("%s\n", buf); exit(5); } memset(configs, 0, sizeof configs); a = 0; while (serv_gets(buf), strcmp(buf, "000")) { if (a < NUM_CONFIGS) { safestrncpy(configs[a], buf, sizeof(configs[a])); } ++a; } /* * Now begin. */ /* _("Citadel Setup"), */ if (setup_type == UI_TEXT) { printf("\n\n\n *** %s ***\n\n", program_title); } if (setup_type == UI_DIALOG) { system("clear 2>/dev/null"); } set_default_values(); /* Go through a series of dialogs prompting for config info */ for (curr = 1; curr < eMaxQuestions; ++curr) { edit_value(curr); if ( (curr == 6) && (atoi(configs[52]) != AUTHMODE_LDAP) && (atoi(configs[52]) != AUTHMODE_LDAP_AD) ) { curr += 5; /* skip LDAP questions if we're not authenticating against LDAP */ } if (curr == eSysAdminName) { if (atoi(configs[52]) == AUTHMODE_NATIVE) { /* for native auth mode, fetch the admin's existing pw */ snprintf(buf, sizeof buf, "AGUP %s", configs[13]); serv_puts(buf); serv_gets(buf); if (buf[0] == '2') { extract_token(admin_pass, &buf[4], 1, '|', sizeof admin_pass); } } else { ++curr; /* skip the password question for non-native auth modes */ } } } if ((pw = getpwuid(atoi(configs[69]))) == NULL) { gid = getgid(); } else { gid = pw->pw_gid; } if (create_run_directories(atoi(configs[69]), gid) != 0) { display_error("%s\n", _("failed to create directories")); } activity = _("Reconfiguring Citadel server"); progress(activity, 0, NUM_CONFIGS+3); sleep(1); /* Let the message appear briefly */ serv_puts("CONF SET"); serv_gets(buf); if (buf[0] == '4') { for (i=0; i #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "citadel.h" #include "sysdep.h" #include "citadel_dirs.h" /* These pipes are used to talk to the chkpwd daemon, which is forked during startup */ int chkpwd_write_pipe[2]; int chkpwd_read_pipe[2]; /* * Validate a password on the host unix system by talking to the chkpwd daemon */ static int validpw(uid_t uid, const char *pass) { char buf[256]; int rv; rv = write(chkpwd_write_pipe[1], &uid, sizeof(uid_t)); if (rv == -1) { printf( "Communicatino with chkpwd broken: %s\n", strerror(errno)); return 0; } rv = write(chkpwd_write_pipe[1], pass, 256); if (rv == -1) { printf( "Communicatino with chkpwd broken: %s\n", strerror(errno)); return 0; } rv = read(chkpwd_read_pipe[0], buf, 4); if (rv == -1) { printf( "Communicatino with chkpwd broken: %s\n", strerror(errno)); return 0; } if (!strncmp(buf, "PASS", 4)) { printf("pass\n"); return(1); } printf("fail\n"); return 0; } /* * Start up the chkpwd daemon so validpw() has something to talk to */ void start_chkpwd_daemon(void) { pid_t chkpwd_pid; struct stat filestats; int i; printf("Starting chkpwd daemon for host authentication mode\n"); if ((stat(file_chkpwd, &filestats)==-1) || (filestats.st_size==0)){ printf("didn't find chkpwd daemon in %s: %s\n", file_chkpwd, strerror(errno)); abort(); } if (pipe(chkpwd_write_pipe) != 0) { printf("Unable to create pipe for chkpwd daemon: %s\n", strerror(errno)); abort(); } if (pipe(chkpwd_read_pipe) != 0) { printf("Unable to create pipe for chkpwd daemon: %s\n", strerror(errno)); abort(); } chkpwd_pid = fork(); if (chkpwd_pid < 0) { printf("Unable to fork chkpwd daemon: %s\n", strerror(errno)); abort(); } if (chkpwd_pid == 0) { dup2(chkpwd_write_pipe[0], 0); dup2(chkpwd_read_pipe[1], 1); for (i=2; i<256; ++i) close(i); execl(file_chkpwd, file_chkpwd, NULL); printf("Unable to exec chkpwd daemon: %s\n", strerror(errno)); abort(); exit(errno); } } int main(int argc, char **argv) { char buf[256]; struct passwd *p; int uid; char ctdldir[PATH_MAX]=CTDLDIR; calc_dirs_n_files(0,0,"", ctdldir, 0); printf("\n\n ** host auth mode test utility **\n\n"); start_chkpwd_daemon(); if (getuid() != 0){ printf("\n\nERROR: you need to be root to run this!\n\n"); return(1); } while(1) { printf("\n\nUsername: "); fgets(buf, sizeof buf, stdin); buf[strlen(buf)-1] = 0; p = getpwnam(buf); if (p == NULL) { printf("Not found\n"); } else { uid = p->pw_uid; printf(" uid: %d\n", uid); printf("Password: "); fgets(buf, sizeof buf, stdin); buf[strlen(buf)-1] = 0; validpw(uid, buf); } } return(0); } citadel-9.01/utils/sendcommand.c0000644000000000000000000001427012507024051015342 0ustar rootroot/* * Command-line utility to transmit a server command. * * Copyright (c) 1987-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "citadel.h" #include "include/citadel_dirs.h" #include int serv_sock = (-1); int uds_connectsock(char *sockpath) { int s; struct sockaddr_un addr; memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, sockpath, sizeof addr.sun_path); s = socket(AF_UNIX, SOCK_STREAM, 0); if (s < 0) { fprintf(stderr, "sendcommand: Can't create socket: %s\n", strerror(errno)); exit(3); } if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) { fprintf(stderr, "sendcommand: can't connect: %s\n", strerror(errno)); close(s); exit(3); } return s; } /* * input binary data from socket */ void serv_read(char *buf, int bytes) { int len, rlen; len = 0; while (len < bytes) { rlen = read(serv_sock, &buf[len], bytes - len); if (rlen < 1) { return; } len = len + rlen; } } /* * send binary to server */ void serv_write(char *buf, int nbytes) { int bytes_written = 0; int retval; while (bytes_written < nbytes) { retval = write(serv_sock, &buf[bytes_written], nbytes - bytes_written); if (retval < 1) { return; } bytes_written = bytes_written + retval; } } /* * input string from socket - implemented in terms of serv_read() */ void serv_gets(char *buf) { int i; /* Read one character at a time. */ for (i = 0;; i++) { serv_read(&buf[i], 1); if (buf[i] == '\n' || i == (SIZ-1)) break; } /* If we got a long line, discard characters until the newline. */ if (i == (SIZ-1)) { while (buf[i] != '\n') { serv_read(&buf[i], 1); } } /* Strip all trailing nonprintables (crlf) */ buf[i] = 0; } /* * send line to server - implemented in terms of serv_write() */ void serv_puts(char *buf) { serv_write(buf, strlen(buf)); serv_write("\n", 1); } /* * Main loop. Do things and have fun. */ int main(int argc, char **argv) { int a; int watchdog = 60; char buf[SIZ]; int xfermode = 0; int relh=0; int home=0; char relhome[PATH_MAX]=""; char ctdldir[PATH_MAX]=CTDLDIR; StartLibCitadel(SIZ); /* Parse command line */ while ((a = getopt(argc, argv, "h:w:")) != EOF) { switch (a) { case 'h': relh=optarg[0]!='/'; if (!relh) { strncpy(ctdl_home_directory, optarg, sizeof ctdl_home_directory); } else { strncpy(relhome, optarg, sizeof relhome); } home = 1; break; case 'w': watchdog = atoi(optarg); break; default: fprintf(stderr, "sendcommand: usage: sendcommand [-h server_dir] [-w watchdog_timeout]\n"); return(1); } } calc_dirs_n_files(relh, home, relhome, ctdldir, 0); fprintf(stderr, "sendcommand: started (pid=%d) connecting to Citadel server at %s\n", (int) getpid(), file_citadel_admin_socket ); fflush(stderr); alarm(watchdog); serv_sock = uds_connectsock(file_citadel_admin_socket); serv_gets(buf); fprintf(stderr, "%s\n", buf); strcpy(buf, ""); for (a=optind; a= 0) alarm(watchdog); /* reset the watchdog timer */ if (ErrStr != NULL) fprintf(stderr, "Error while piping stuff: %s\n", ErrStr); FDIOBufferDelete(&FDIO); FreeStrBuf(&IOB.Buf); serv_puts("000"); } if ((xfermode == '1') || (xfermode == '8')) { /* receive text */ IOBuffer IOB; StrBuf *Line, *OutBuf; int Finished = 0; memset(&IOB, 0, sizeof(IOB)); IOB.Buf = NewStrBufPlain(NULL, SIZ); IOB.fd = serv_sock; Line = NewStrBufPlain(NULL, SIZ); OutBuf = NewStrBufPlain(NULL, SIZ * 10); while (!Finished && (StrBuf_read_one_chunk_callback (serv_sock, 0, &IOB) >= 0)) { eReadState State; State = eReadSuccess; while (!Finished && (State == eReadSuccess)) { if (IOBufferStrLength(&IOB) == 0) { State = eMustReadMore; break; } State = StrBufChunkSipLine(Line, &IOB); switch (State) { case eReadSuccess: if (!strcmp(ChrPtr(Line), "000")) { Finished = 1; break; } StrBufAppendBuf(OutBuf, Line, 0); StrBufAppendBufPlain(OutBuf, HKEY("\n"), 0); alarm(watchdog); /* reset the watchdog timer */ break; case eBufferNotEmpty: break; case eMustReadMore: continue; case eReadFail: fprintf(stderr, "WTF? Exit!\n"); exit(-1); break; } if (StrLength(OutBuf) > 5*SIZ) { fwrite(ChrPtr(OutBuf), 1, StrLength(OutBuf), stdout); FlushStrBuf(OutBuf); } } } if (StrLength(OutBuf) > 0) { fwrite(ChrPtr(OutBuf), 1, StrLength(OutBuf), stdout); } FreeStrBuf(&Line); FreeStrBuf(&OutBuf); FreeStrBuf(&IOB.Buf); } if (xfermode == '6') { /* receive binary */ size_t len = atoi(&buf[4]); size_t bytes_remaining = len; while (bytes_remaining > 0) { size_t this_block = bytes_remaining; if (this_block > SIZ) this_block = SIZ; serv_read(buf, this_block); fwrite(buf, this_block, 1, stdout); bytes_remaining -= this_block; } } close(serv_sock); alarm(0); /* cancel the watchdog timer */ fprintf(stderr, "sendcommand: processing ended.\n"); if (xfermode == '5') { return(1); } return(0); } citadel-9.01/utils/chkpwd.c0000644000000000000000000000217512507024051014333 0ustar rootroot/* * a setuid helper program for machines which use shadow passwords * by Nathan Bryant, March 1999 * * Copyright (c) 1987-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include "auth.h" #include "config.h" #include "citadel_dirs.h" #include "citadel.h" int main(void) { uid_t uid; char buf[SIZ]; while (1) { buf[0] = '\0'; read(0, &uid, sizeof(uid_t)); /* uid */ read(0, buf, 256); /* password */ if (buf[0] == '\0') return (0); if (validate_password(uid, buf)) { write(1, "PASS", 4); } else { write(1, "FAIL", 4); } } return(0); } citadel-9.01/utils/stress.c0000644000000000000000000002717612507024051014406 0ustar rootroot /* This message is exactly 1024 bytes */ char* const message = "The point of this little file is to stress test a Citadel server.\n" "It spawns n threads, where n is a command line parameter, each of\n" "which writes 1000 messages total to the server.\n" "\n" "-n is a command line parameter indicating how many users to simulate\n" "(default 100). WARNING: Your system must be capable of creating this\n" "many threads!\n" "\n" "-w is a command line parameter indicating how long to wait in seconds\n" "between posting each message (default 10). The actual interval\n" "will be randomized between w / 3 and w * 3.\n" "\n" "A run is expected to take approximately three hours, given default\n" "values, and assuming the server can keep up. If the run takes much\n" "longer than this, there may be a performance problem with the server.\n" "For best results, the test should be run from a different machine than\n" "the server, but connected via a fast network link (e.g. 100Base-T).\n" "\n" "To get baseline results, run the test with -n 1 (simulating 1 user)\n" "on a machine with no other users logged in.\n" "\n" "Example:\n" "stress -n 500 -w 25 myserver > stress.csv\n"; /* The program tries to be as small and as fast as possible. Wherever * possible, we avoid allocating memory on the heap. We do not pass data * between threads. We do only a minimal amount of calculation. In * particular, we only output raw timing data for the run; we do not * collate it, average it, or do anything else with it. See below. * The program does, however, use the same CtdlIPC functions as the * standard Citadel text client, and incurs the same overhead as that * program, using those functions. * * The program first creates a new user with a randomized username which * begins with "testuser". It then creates 100 rooms named test0 through * test99. If they already exist, this condition is ignored. * * The program then creates n threads, all of which wait on a conditional * before they do anything. Once all of the threads have been created, * they are signaled, and begin execution. Each thread logs in to the * Citadel server separately, simulating a user login, then takes a * timestamp from the operating system. * * Each thread selects a room from 0-99 randomly, then writes a small * (1KB) test message to that room. 1K was chosen because it seems to * represent an average message size for messages we expect to see. * After writing the message, the thread sleeps for w seconds (sleep(w);) * and repeats the process, until it has written 1,000 messages. The * program provides a status display to standard error, unless w <= 2, in * which case status display is disabled. * * After posting all messages, each thread takes a second timestamp, and * subtracts the first timestamp. The resulting value (in seconds) is * sent to standard output, followed by the minimum, average, and maximum * amounts of time (in milliseconds) it took to post a message. The * thread then exits. * * Once all threads have exited, the program exits. */ #include #include #include #include #include #include #include "sysdep.h" #include #include "citadel_ipc.h" #ifndef HAVE_PTHREAD_H #error This program requires threads #endif static int w = 10; /* see above */ static int n = 100; /* see above */ static int m = 1000; /* Number of messages to send; see above */ static volatile int count = 0; /* Total count of messages posted */ static volatile int total = 0; /* Total messages to be posted */ static pthread_mutex_t count_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t arg_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t output_mutex = PTHREAD_MUTEX_INITIALIZER; static char username[12]; static char password[12]; /* * Mutex for the random number generator * We don't assume that rand_r() is present, so we have to * provide our own locking for rand() */ static pthread_mutex_t rand_mutex = PTHREAD_MUTEX_INITIALIZER; /* * Conditional. All the threads wait for this signal to actually * start bombarding the server. */ static pthread_mutex_t start_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t start_cond = PTHREAD_COND_INITIALIZER; /* * This is the worker thread. It logs in and creates the 1,000 messages * as described above. */ void* worker(void* data) { CtdlIPC* ipc; /* My connection to the server */ void** args; /* Args sent in */ int r; /* IPC return code */ char aaa[SIZ]; /* Generic buffer */ int c; /* Message count */ time_t start, end; /* Timestamps */ struct ctdlipcmessage msg; /* The message we will post */ int argc_; char** argv_; long tmin = LONG_MAX, trun = 0, tmax = LONG_MIN; args = (void*)data; argc_ = (int)args[0]; argv_ = (char**)args[1]; /* Setup the message we will be posting */ msg.text = message; msg.anonymous = 0; msg.type = 1; strcpy(msg.recipient, ""); strcpy(msg.subject, "Test message; ignore"); strcpy(msg.author, username); pthread_mutex_lock(&arg_mutex); ipc = CtdlIPC_new(argc_, argv_, NULL, NULL); pthread_mutex_unlock(&arg_mutex); if (!ipc) return NULL; /* oops, something happened... */ CtdlIPC_chat_recv(ipc, aaa); if (aaa[0] != '2') { fprintf(stderr, "Citadel refused me: %s\n", &aaa[4]); return NULL; /* server ran out of connections maybe? */ } CtdlIPCIdentifySoftware(ipc, 8, 8, REV_LEVEL, "Citadel stress tester", "localhost", aaa); /* we're lying, the server knows */ r = CtdlIPCQueryUsername(ipc, username, aaa); if (r / 100 == 2) { /* testuser already exists (from previous run?) */ r = CtdlIPCTryLogin(ipc, username, aaa); if (r / 100 != 3) { fprintf(stderr, "Citadel refused username: %s\n", aaa); CtdlIPC_delete_ptr(&ipc); return NULL; /* Gawd only knows what went wrong */ } r = CtdlIPCTryPassword(ipc, password, aaa); if (r / 100 != 2) { fprintf(stderr, "Citadel refused password: %s\n", aaa); CtdlIPC_delete_ptr(&ipc); return NULL; /* Gawd only knows what went wrong */ } } else { /* testuser doesn't yet exist */ r = CtdlIPCCreateUser(ipc, username, 1, aaa); if (r / 100 != 2) { fprintf(stderr, "Citadel refused create user: %s\n", aaa); CtdlIPC_delete_ptr(&ipc); return NULL; /* Gawd only knows what went wrong */ } r = CtdlIPCChangePassword(ipc, password, aaa); if (r / 100 != 2) { fprintf(stderr, "Citadel refused change password: %s\n", aaa); CtdlIPC_delete_ptr(&ipc); return NULL; /* Gawd only knows what went wrong */ } } /* Wait for the rest of the threads */ pthread_mutex_lock(&start_mutex); pthread_cond_wait(&start_cond, &start_mutex); pthread_mutex_unlock(&start_mutex); /* And now the fun begins! Send out a whole shitload of messages */ start = time(NULL); for (c = 0; c < m; c++) { int rm; char room[7]; struct ctdlipcroom *rret; struct timeval tv; long tstart, tend; int wait; /* Wait for a while */ pthread_mutex_lock(&rand_mutex); /* See Numerical Recipes in C or Knuth vol. 2 ch. 3 */ /* Randomize between w/3 to w*3 (yes, it's complicated) */ wait = (int)((1.0+2.7*(float)w)*rand()/(RAND_MAX+(float)w/3.0)); /* range 0-99 */ pthread_mutex_unlock(&rand_mutex); sleep(wait); /* Select the room to goto */ pthread_mutex_lock(&rand_mutex); /* See Numerical Recipes in C or Knuth vol. 2 ch. 3 */ rm = (int)(100.0*rand()/(RAND_MAX+1.0)); /* range 0-99 */ pthread_mutex_unlock(&rand_mutex); /* Goto the selected room */ sprintf(room, "test%d", rm); /* Create the room if not existing. Ignore the return */ r = CtdlIPCCreateRoom(ipc, 1, room, 0, NULL, 0, aaa); if (r / 100 != 2 && r != 574) { /* Already exists */ fprintf(stderr, "Citadel refused room create: %s\n", aaa); pthread_mutex_lock(&count_mutex); total -= m - c; pthread_mutex_unlock(&count_mutex); CtdlIPC_delete_ptr(&ipc); return NULL; } gettimeofday(&tv, NULL); tstart = tv.tv_sec * 1000 + tv.tv_usec / 1000; /* cvt to msec */ r = CtdlIPCGotoRoom(ipc, room, "", &rret, aaa); if (r / 100 != 2) { fprintf(stderr, "Citadel refused room change: %s\n", aaa); pthread_mutex_lock(&count_mutex); total -= m - c; pthread_mutex_unlock(&count_mutex); CtdlIPC_delete_ptr(&ipc); return NULL; } /* Post the message */ r = CtdlIPCPostMessage(ipc, 1, NULL, &msg, aaa); if (r / 100 != 4) { fprintf(stderr, "Citadel refused message entry: %s\n", aaa); pthread_mutex_lock(&count_mutex); total -= m - c; pthread_mutex_unlock(&count_mutex); CtdlIPC_delete_ptr(&ipc); return NULL; } /* Do a status update */ pthread_mutex_lock(&count_mutex); count++; pthread_mutex_unlock(&count_mutex); fprintf(stderr, " %d/%d=%d%% \r", count, total, (int)(100 * count / total)); gettimeofday(&tv, NULL); tend = tv.tv_sec * 1000 + tv.tv_usec / 1000; /* cvt to msec */ tend -= tstart; if (tend < tmin) tmin = tend; if (tend > tmax) tmax = tend; trun += tend; } end = time(NULL); pthread_mutex_lock(&output_mutex); fprintf(stderr, " \r"); printf("%ld %ld %ld %ld\n", end - start, tmin, trun / c, tmax); pthread_mutex_unlock(&output_mutex); return (void*)(end - start); } /* * Shift argument list */ int shift(int argc, char **argv, int start, int count) { int i; for (i = start; i < argc - count; ++i) argv[i] = argv[i + count]; return argc - count; } /* * Main loop. Start a shitload of threads, all of which will attempt to * kick a Citadel server square in the nuts. */ int main(int argc, char** argv) { void* data[2]; /* pass args to worker thread */ pthread_t* threads; /* A shitload of threads */ pthread_attr_t attr; /* Thread attributes (we use defaults) */ int i; /* Counters */ long runtime; /* Run time for each thread */ /* Read argument list */ for (i = 0; i < argc; i++) { if (!strcmp(argv[i], "-n")) { n = atoi(argv[i + 1]); argc = shift(argc, argv, i, 2); } if (!strcmp(argv[i], "-w")) { w = atoi(argv[i + 1]); argc = shift(argc, argv, i, 2); } if (!strcmp(argv[i], "-m")) { m = atoi(argv[i + 1]); argc = shift(argc, argv, i, 2); } if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { fprintf(stderr, "Read stress.c for usage info\n"); return 1; } } data[0] = (void*)argc; /* pass args to worker thread */ data[1] = (void*)argv; /* pass args to worker thread */ /* This is how many total messages will be posted */ total = n * m; /* Pick a randomized username */ pthread_mutex_lock(&rand_mutex); /* See Numerical Recipes in C or Knuth vol. 2 ch. 3 */ i = (int)(100.0*rand()/(RAND_MAX+1.0)); /* range 0-99 */ pthread_mutex_unlock(&rand_mutex); sprintf(username, "testuser%d", i); strcpy(password, username); /* First, memory for our shitload of threads */ threads = calloc(n, sizeof(pthread_t)); if (!threads) { perror("Not enough memory"); return 1; } /* Then thread attributes (all defaults for now) */ pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); /* Then, create some threads */ fprintf(stderr, "Creating threads \r"); for (i = 0; i < n; ++i) { pthread_create(&threads[i], &attr, worker, (void*)data); /* Give thread #0 time to create the user account */ if (i == 0) sleep(3); } //fprintf(stderr, "Starting in %d seconds\r", n); //sleep(n); fprintf(stderr, " \r"); /* Then, signal the conditional they all are waiting on */ pthread_mutex_lock(&start_mutex); pthread_cond_broadcast(&start_cond); pthread_mutex_unlock(&start_mutex); /* Then wait for them to exit */ for (i = 0; i < n; i++) { pthread_join(threads[i], (void*)&runtime); /* We're ignoring this value for now... TODO */ } fprintf(stderr, "\r \r"); return 0; } citadel-9.01/utils/citmail.c0000644000000000000000000001643412507024051014500 0ustar rootroot/* * This program attempts to act like a local MDA if you're using sendmail or * some other non-Citadel MTA. It basically just contacts the Citadel LMTP * listener on a unix domain socket and transmits the message. * * Copyright (c) 1987-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "sysdep.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "citadel.h" #include "citadel_dirs.h" int serv_sock; int debug = 0; void strip_trailing_nonprint(char *buf) { while ( (!IsEmptyStr(buf)) && (!isprint(buf[strlen(buf) - 1])) ) buf[strlen(buf) - 1] = 0; } void timeout(int signum) { exit(signum); } int uds_connectsock(char *sockpath) { int s; struct sockaddr_un addr; memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, sockpath, sizeof addr.sun_path); s = socket(AF_UNIX, SOCK_STREAM, 0); if (s < 0) { fprintf(stderr, "citmail: Can't create socket: %s\n", strerror(errno)); exit(3); } if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) { fprintf(stderr, "citmail: can't connect: %s\n", strerror(errno)); close(s); exit(3); } return s; } /* * input binary data from socket */ void serv_read(char *buf, int bytes) { int len, rlen; len = 0; while (len < bytes) { rlen = read(serv_sock, &buf[len], bytes - len); if (rlen < 1) { return; } len = len + rlen; } } /* * send binary to server */ void serv_write(char *buf, int nbytes) { int bytes_written = 0; int retval; while (bytes_written < nbytes) { retval = write(serv_sock, &buf[bytes_written], nbytes - bytes_written); if (retval < 1) { return; } bytes_written = bytes_written + retval; } } /* * input string from socket - implemented in terms of serv_read() */ void serv_gets(char *buf) { int i; /* Read one character at a time. */ for (i = 0;; i++) { serv_read(&buf[i], 1); if (buf[i] == '\n' || i == (SIZ-1)) break; } /* If we got a long line, discard characters until the newline. */ if (i == (SIZ-1)) while (buf[i] != '\n') serv_read(&buf[i], 1); /* Strip all trailing nonprintables (crlf) */ buf[i] = 0; strip_trailing_nonprint(buf); if (debug) fprintf(stderr, "> %s\n", buf); } /* * send line to server - implemented in terms of serv_write() */ void serv_puts(char *buf) { if (debug) fprintf(stderr, "< %s\n", buf); serv_write(buf, strlen(buf)); serv_write("\n", 1); } void cleanup(int exitcode) { char buf[1024]; if (exitcode != 0) { fprintf(stderr, "citmail: error #%d occurred while sending mail.\n", exitcode); fprintf(stderr, "Please check your Citadel configuration.\n"); } serv_puts("QUIT"); serv_gets(buf); exit(exitcode); } int main(int argc, char **argv) { char buf[1024]; char fromline[1024]; FILE *fp; int i; struct passwd *pw; int from_header = 0; int in_body = 0; int relh=0; int home=0; char relhome[PATH_MAX]=""; char ctdldir[PATH_MAX]=CTDLDIR; char *sp, *ep; char hostname[256]; char **recipients = NULL; int num_recipients = 0; int to_or_cc = 0; int read_recipients_from_headers = 0; char *add_these_recipients = NULL; for (i=1; ipw_name, hostname); while (fgets(buf, 1024, stdin) != NULL) { if ( ( (buf[0] == 13) || (buf[0] == 10)) && (in_body == 0) ) { in_body = 1; if (from_header == 0) { fprintf(fp, "%s%s", fromline, buf); } } if (in_body == 0 && !strncasecmp(buf, "From:", 5)) { strcpy(fromline, buf); from_header = 1; } if (read_recipients_from_headers) { add_these_recipients = NULL; if ((isspace(buf[0])) && (to_or_cc)) { add_these_recipients = buf; } else { if ((!strncasecmp(buf, "To:", 3)) || (!strncasecmp(buf, "Cc:", 3))) { to_or_cc = 1; } else { to_or_cc = 0; } if (to_or_cc) { add_these_recipients = &buf[3]; } } if (add_these_recipients) { int num_recp_on_this_line; char this_recp[256]; num_recp_on_this_line = num_tokens(add_these_recipients, ','); for (i=0; i #include #include #include #include #include #include #include #include #include #include "citadel.h" #include "server.h" #include "citserver.h" #include "sysdep_decls.h" #include "support.h" #include "config.h" #include "ical_dezonify.h" #include "ctdl_module.h" /* * Figure out which time zone needs to be used for timestamps that are * not UTC and do not have a time zone specified. */ icaltimezone *get_default_icaltimezone(void) { icaltimezone *zone = NULL; char *default_zone_name = config.c_default_cal_zone; //char *default_zone_name = "America/New_York"; if (!zone) { zone = icaltimezone_get_builtin_timezone(default_zone_name); } if (!zone) { syslog(LOG_ALERT, "Unable to load '%s' time zone. Defaulting to UTC.\n", default_zone_name); zone = icaltimezone_get_utc_timezone(); } if (!zone) { syslog(LOG_ALERT, "Unable to load UTC time zone!\n"); } return zone; } /* * Back end function for ical_dezonify() * * We supply this with the master component, the relevant component, * and the property (which will be a DTSTART, DTEND, etc.) * which we want to convert to UTC. */ void ical_dezonify_backend(icalcomponent *cal, icalcomponent *rcal, icalproperty *prop) { icaltimezone *t = NULL; icalparameter *param; const char *tzid = NULL; struct icaltimetype TheTime; int utc_declared_as_tzid = 0; /**< Component declared 'TZID=GMT' instead of using Z syntax */ /* Give me nothing and I will give you nothing in return. */ if (cal == NULL) return; /* Hunt for a TZID parameter in this property. */ param = icalproperty_get_first_parameter(prop, ICAL_TZID_PARAMETER); /* Get the stringish name of this TZID. */ if (param != NULL) { tzid = icalparameter_get_tzid(param); /* Convert it to an icaltimezone type. */ if (tzid != NULL) { /* syslog(LOG_DEBUG, " * Stringy supplied timezone is: '%s'\n", tzid); */ if ( (!strcasecmp(tzid, "UTC")) || (!strcasecmp(tzid, "GMT")) ) { utc_declared_as_tzid = 1; /* syslog(LOG_DEBUG, " * ...and we handle that internally.\n"); */ } else { /* try attached first */ t = icalcomponent_get_timezone(cal, tzid); /* syslog(LOG_DEBUG, " * ...and I %s have tzdata for that zone.\n", (t ? "DO" : "DO NOT") ); */ /* then try built-in timezones */ if (!t) { t = icaltimezone_get_builtin_timezone(tzid); /* if (t) { syslog(LOG_DEBUG, " * Using system tzdata!\n"); } */ } } } } /* Now we know the timezone. Convert to UTC. */ if (icalproperty_isa(prop) == ICAL_DTSTART_PROPERTY) { TheTime = icalproperty_get_dtstart(prop); } else if (icalproperty_isa(prop) == ICAL_DTEND_PROPERTY) { TheTime = icalproperty_get_dtend(prop); } else if (icalproperty_isa(prop) == ICAL_DUE_PROPERTY) { TheTime = icalproperty_get_due(prop); } else if (icalproperty_isa(prop) == ICAL_EXDATE_PROPERTY) { TheTime = icalproperty_get_exdate(prop); } else { return; } /* syslog(LOG_DEBUG, " * Was: %s\n", icaltime_as_ical_string(TheTime)); */ if (TheTime.is_utc) { /* syslog(LOG_DEBUG, " * This property is ALREADY UTC.\n"); */ } else if (utc_declared_as_tzid) { /* syslog(LOG_DEBUG, " * Replacing '%s' TZID with 'Z' suffix.\n", tzid); */ TheTime.is_utc = 1; } else { /* Do the conversion. */ if (t != NULL) { /* syslog(LOG_DEBUG, " * Timezone prop found. Converting to UTC.\n"); */ } else { /* syslog(LOG_DEBUG, " * Converting default timezone to UTC.\n"); */ } if (t == NULL) { t = get_default_icaltimezone(); } icaltimezone_convert_time(&TheTime, t, icaltimezone_get_utc_timezone() ); TheTime.is_utc = 1; } icalproperty_remove_parameter_by_kind(prop, ICAL_TZID_PARAMETER); /* syslog(LOG_DEBUG, " * Now: %s\n", icaltime_as_ical_string(TheTime)); */ /* Now add the converted property back in. */ if (icalproperty_isa(prop) == ICAL_DTSTART_PROPERTY) { icalproperty_set_dtstart(prop, TheTime); } else if (icalproperty_isa(prop) == ICAL_DTEND_PROPERTY) { icalproperty_set_dtend(prop, TheTime); } else if (icalproperty_isa(prop) == ICAL_DUE_PROPERTY) { icalproperty_set_due(prop, TheTime); } else if (icalproperty_isa(prop) == ICAL_EXDATE_PROPERTY) { icalproperty_set_exdate(prop, TheTime); } } /* * Recursive portion of ical_dezonify() */ void ical_dezonify_recurse(icalcomponent *cal, icalcomponent *rcal) { icalcomponent *c; icalproperty *p; /* * Recurse through all subcomponents *except* VTIMEZONE ones. */ for (c=icalcomponent_get_first_component( rcal, ICAL_ANY_COMPONENT); c != NULL; c = icalcomponent_get_next_component( rcal, ICAL_ANY_COMPONENT) ) { if (icalcomponent_isa(c) != ICAL_VTIMEZONE_COMPONENT) { ical_dezonify_recurse(cal, c); } } /* * Now look for DTSTART and DTEND properties */ for (p=icalcomponent_get_first_property(rcal, ICAL_ANY_PROPERTY); p != NULL; p = icalcomponent_get_next_property(rcal, ICAL_ANY_PROPERTY) ) { if ( (icalproperty_isa(p) == ICAL_DTSTART_PROPERTY) || (icalproperty_isa(p) == ICAL_DTEND_PROPERTY) || (icalproperty_isa(p) == ICAL_DUE_PROPERTY) || (icalproperty_isa(p) == ICAL_EXDATE_PROPERTY) ) { ical_dezonify_backend(cal, rcal, p); } } } /* * Convert all DTSTART and DTEND properties in all subcomponents to UTC. * This function will search any VTIMEZONE subcomponents to learn the * relevant timezone information. */ void ical_dezonify(icalcomponent *cal) { icalcomponent *vt = NULL; /* syslog(LOG_DEBUG, "ical_dezonify() started\n"); */ /* Convert all times to UTC */ ical_dezonify_recurse(cal, cal); /* Strip out VTIMEZONE subcomponents -- we don't need them anymore */ while (vt = icalcomponent_get_first_component( cal, ICAL_VTIMEZONE_COMPONENT), vt != NULL) { icalcomponent_remove_component(cal, vt); icalcomponent_free(vt); } /* syslog(LOG_DEBUG, "ical_dezonify() completed\n"); */ } citadel-9.01/Make_modules0000644000000000000000000000475012507024062014102 0ustar rootroot# # Make_modules # This file is to be included by Makefile to dynamically add modules to the build process # THIS FILE WAS AUTO GENERATED BY mk_modules_init.sh DO NOT EDIT THIS FILE # SERV_MODULES = \ modules/autocompletion/serv_autocompletion.o \ modules/bio/serv_bio.o \ modules/blog/serv_blog.o \ modules/c-ares-dns/serv_c-ares-dns.o \ modules/calendar/serv_calendar.o \ modules/checkpoint/serv_checkpoint.o \ modules/clamav/serv_virus.o \ modules/crypto/serv_crypto.o \ modules/ctdlproto/serv_file.o \ modules/ctdlproto/serv_messages.o \ modules/ctdlproto/serv_rooms.o \ modules/ctdlproto/serv_session.o \ modules/ctdlproto/serv_syscmds.o \ modules/ctdlproto/serv_user.o \ modules/dspam/serv_dspam.o \ modules/eventclient/serv_eventclient.o \ modules/expire/expire_policy.o \ modules/expire/serv_expire.o \ modules/extnotify/extnotify_main.o \ modules/extnotify/funambol65.o \ modules/fulltext/crc16.o \ modules/fulltext/ft_wordbreaker.o \ modules/fulltext/serv_fulltext.o \ modules/imap/imap_acl.o \ modules/imap/imap_fetch.o \ modules/imap/imap_list.o \ modules/imap/imap_metadata.o \ modules/imap/imap_misc.o \ modules/imap/imap_search.o \ modules/imap/imap_store.o \ modules/imap/imap_tools.o \ modules/imap/serv_imap.o \ modules/inetcfg/serv_inetcfg.o \ modules/instmsg/serv_instmsg.o \ modules/listsub/serv_listsub.o \ modules/managesieve/serv_managesieve.o \ modules/migrate/serv_migrate.o \ modules/mrtg/serv_mrtg.o \ modules/network/serv_netfilter.o \ modules/network/serv_netmail.o \ modules/network/serv_netspool.o \ modules/network/serv_network.o \ modules/networkclient/serv_networkclient.o \ modules/newuser/serv_newuser.o \ modules/nntp/serv_nntp.o \ modules/nntp/wildmat.o \ modules/notes/serv_notes.o \ modules/openid/serv_openid_rp.o \ modules/pop3/serv_pop3.o \ modules/pop3client/serv_pop3client.o \ modules/roomchat/serv_roomchat.o \ modules/rssclient/rss_atom_parser.o \ modules/rssclient/serv_rssclient.o \ modules/rwho/serv_rwho.o \ modules/sieve/serv_sieve.o \ modules/smtp/serv_smtp.o \ modules/smtp/serv_smtpeventclient.o \ modules/smtp/serv_smtpqueue.o \ modules/smtp/smtp_clienthandlers.o \ modules/smtp/smtp_util.o \ modules/spam/serv_spam.o \ modules/test/serv_test.o \ modules/upgrade/serv_upgrade.o \ modules/urldeshortener/serv_expand_shorter_urls.o \ modules/vcard/serv_vcard.o \ modules/wiki/serv_wiki.o \ modules/xmpp/serv_xmpp.o \ modules/xmpp/xmpp_messages.o \ modules/xmpp/xmpp_presence.o \ modules/xmpp/xmpp_query_namespace.o \ modules/xmpp/xmpp_queue.o \ modules/xmpp/xmpp_sasl_service.o citadel-9.01/funambol_newmail_soap.xml0000644000000000000000000000437312507024051016634 0ustar rootroot ^notifyuser <java version="1.5.0_13" class="java.beans.XMLDecoder"> <array class="com.funambol.framework.core.Alert" length="1"> <void index="0"> <object class="com.funambol.framework.core.Alert"> <void property="cmdID"> <object class="com.funambol.framework.core.CmdID"/> </void> <void property="data"> <int>206</int> </void> <void property="items"> <void method="add"> <object class="com.funambol.framework.core.Item"> <void property="meta"> <object class="com.funambol.framework.core.Meta"> <void property="metInf"> <void property="type"> <string>application/vnd.omads-email+xml</string> </void> </void> </object> </void> <void property="target"> <object class="com.funambol.framework.core.Target"> <void property="locURI"> <string>^syncsource</string> </void> </object> </void> </object> </void> </void> </object> </void> </array> </java> 1 citadel-9.01/auth.c0000644000000000000000000000631212507024051012651 0ustar rootroot/* * system-level password checking for host auth mode * by Nathan Bryant, March 1999 * updated by Trey van Riper, June 2005 * * Copyright (c) 1999-2009 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #if defined(__linux) || defined(__sun) /* needed for crypt(): */ #define _XOPEN_SOURCE #define _XOPEN_SOURCE_EXTENDED 1 #endif #include #include #include #include #include #include "auth.h" #include "sysdep.h" #ifdef HAVE_GETSPNAM #include #endif #ifdef HAVE_PAM_START #include /* * struct appdata: passed to the conversation function */ struct appdata { const char *name; const char *pw; }; /* * conv(): the PAM conversation function. this assumes that a * PAM_PROMPT_ECHO_ON is asking for a username, and a PAM_PROMPT_ECHO_OFF is * asking for a password. esoteric authentication modules will fail with this * code, but we can't really support them with the existing client protocol * anyway. the failure mode should be to deny access, in any case. */ static int conv(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr) { struct pam_response *temp_resp; struct appdata *data = appdata_ptr; if ((temp_resp = malloc(sizeof(struct pam_response[num_msg]))) == NULL) return PAM_CONV_ERR; while (num_msg--) { switch ((*msg)[num_msg].msg_style) { case PAM_PROMPT_ECHO_ON: temp_resp[num_msg].resp = strdup(data->name); break; case PAM_PROMPT_ECHO_OFF: temp_resp[num_msg].resp = strdup(data->pw); break; default: temp_resp[num_msg].resp = NULL; } temp_resp[num_msg].resp_retcode = 0; } *resp = temp_resp; return PAM_SUCCESS; } #endif /* HAVE_PAM_START */ /* * check that `pass' is the correct password for `uid' * returns zero if no, nonzero if yes */ int validate_password(uid_t uid, const char *pass) { #ifdef HAVE_PAM_START struct pam_conv pc; struct appdata data; pam_handle_t *ph; int i; #else char *crypted_pwd; #ifdef HAVE_GETSPNAM struct spwd *sp; #endif #endif struct passwd *pw; int retval = 0; int flags = 0; #ifdef PAM_DATA_SILENT flags = PAM_DATA_SILENT; #else flags = 0; #endif /* PAM_DATA_SILENT */ if ((pw = getpwuid(uid)) == NULL) { return retval; } #ifdef HAVE_PAM_START pc.conv = conv; pc.appdata_ptr = &data; data.name = pw->pw_name; data.pw = pass; if (pam_start("citadel", pw->pw_name, &pc, &ph) != PAM_SUCCESS) return retval; if ((i = pam_authenticate(ph, flags)) == PAM_SUCCESS) if ((i = pam_acct_mgmt(ph, flags)) == PAM_SUCCESS) retval = -1; pam_end(ph, i | flags); #else crypted_pwd = pw->pw_passwd; #ifdef HAVE_GETSPNAM if ((sp = getspnam(pw->pw_name)) != NULL) crypted_pwd = sp->sp_pwdp; #endif if (!strcmp(crypt(pass, crypted_pwd), crypted_pwd)) retval = -1; #endif /* HAVE_PAM_START */ return retval; } citadel-9.01/room_ops.h0000644000000000000000000000177012507024051013555 0ustar rootrootint is_known (struct ctdlroom *roombuf, int roomnum, struct ctdluser *userbuf); int has_newmsgs (struct ctdlroom *roombuf, int roomnum, struct ctdluser *userbuf); int is_zapped (struct ctdlroom *roombuf, int roomnum, struct ctdluser *userbuf); void b_putroom(struct ctdlroom *qrbuf, char *room_name); void b_deleteroom(char *); void lgetfloor (struct floor *flbuf, int floor_num); void lputfloor (struct floor *flbuf, int floor_num); int sort_msglist (long int *listptrs, int oldcount); void list_roomname(struct ctdlroom *qrbuf, int ra, int current_view, int default_view); void convert_room_name_macros(char *towhere, size_t maxlen); typedef enum _POST_TYPE{ POST_LOGGED_IN, POST_EXTERNAL, CHECK_EXISTANCE, POST_LMTP }PostType; int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf, size_t n, const char* RemoteIdentifier, PostType PostPublic, int is_reply ); int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void); int CtdlDoIHavePermissionToReadMessagesInThisRoom(void); citadel-9.01/domain.h0000644000000000000000000000152412507024051013164 0ustar rootroot/* * Copyright (c) 1987-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ struct mx { int pref; char host[1024]; }; int getmx(char *mxbuf, char *dest); int get_hosts(char *mxbuf, char *rectype); /* HP/UX has old include files...these are from arpa/nameser.h */ #include "typesize.h" #ifndef HFIXEDSZ #define HFIXEDSZ 12 /* I hope! */ #endif #ifndef INT16SZ #define INT16SZ sizeof(cit_int16_t) #endif #ifndef INT32SZ #define INT32SZ sizeof(cit_int32_t) #endif citadel-9.01/journaling.h0000644000000000000000000000054712507024051014071 0ustar rootrootstruct jnlq { struct jnlq *next; recptypes recps; char *from; char *node; char *rfca; char *subj; char *msgn; char *rfc822; }; void JournalBackgroundSubmit(struct CtdlMessage *msg, StrBuf *saved_rfc822_version, recptypes *recps); void JournalRunQueueMsg(struct jnlq *jmsg); void JournalRunQueue(void); citadel-9.01/aclocal.m40000644000000000000000000001032212507024060013400 0ustar rootroot# generated automatically by aclocal 1.11.1 -*- Autoconf -*- # Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, # 2005, 2006, 2007, 2008, 2009 Free Software Foundation, Inc. # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. # AM_AUX_DIR_EXPAND -*- Autoconf -*- # Copyright (C) 2001, 2003, 2005 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # For projects using AC_CONFIG_AUX_DIR([foo]), Autoconf sets # $ac_aux_dir to `$srcdir/foo'. In other projects, it is set to # `$srcdir', `$srcdir/..', or `$srcdir/../..'. # # Of course, Automake must honor this variable whenever it calls a # tool from the auxiliary directory. The problem is that $srcdir (and # therefore $ac_aux_dir as well) can be either absolute or relative, # depending on how configure is run. This is pretty annoying, since # it makes $ac_aux_dir quite unusable in subdirectories: in the top # source directory, any form will work fine, but in subdirectories a # relative path needs to be adjusted first. # # $ac_aux_dir/missing # fails when called from a subdirectory if $ac_aux_dir is relative # $top_srcdir/$ac_aux_dir/missing # fails if $ac_aux_dir is absolute, # fails when called from a subdirectory in a VPATH build with # a relative $ac_aux_dir # # The reason of the latter failure is that $top_srcdir and $ac_aux_dir # are both prefixed by $srcdir. In an in-source build this is usually # harmless because $srcdir is `.', but things will broke when you # start a VPATH build or use an absolute $srcdir. # # So we could use something similar to $top_srcdir/$ac_aux_dir/missing, # iff we strip the leading $srcdir from $ac_aux_dir. That would be: # am_aux_dir='\$(top_srcdir)/'`expr "$ac_aux_dir" : "$srcdir//*\(.*\)"` # and then we would define $MISSING as # MISSING="\${SHELL} $am_aux_dir/missing" # This will work as long as MISSING is not called from configure, because # unfortunately $(top_srcdir) has no meaning in configure. # However there are other variables, like CC, which are often used in # configure, and could therefore not use this "fixed" $ac_aux_dir. # # Another solution, used here, is to always expand $ac_aux_dir to an # absolute PATH. The drawback is that using absolute paths prevent a # configured tree to be moved without reconfiguration. AC_DEFUN([AM_AUX_DIR_EXPAND], [dnl Rely on autoconf to set up CDPATH properly. AC_PREREQ([2.50])dnl # expand $ac_aux_dir to an absolute path am_aux_dir=`cd $ac_aux_dir && pwd` ]) # Fake the existence of programs that GNU maintainers use. -*- Autoconf -*- # Copyright (C) 1997, 1999, 2000, 2001, 2003, 2004, 2005, 2008 # Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # serial 6 # AM_MISSING_PROG(NAME, PROGRAM) # ------------------------------ AC_DEFUN([AM_MISSING_PROG], [AC_REQUIRE([AM_MISSING_HAS_RUN]) $1=${$1-"${am_missing_run}$2"} AC_SUBST($1)]) # AM_MISSING_HAS_RUN # ------------------ # Define MISSING if not defined so far and test if it supports --run. # If it does, set am_missing_run to use it, otherwise, to nothing. AC_DEFUN([AM_MISSING_HAS_RUN], [AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl AC_REQUIRE_AUX_FILE([missing])dnl if test x"${MISSING+set}" != xset; then case $am_aux_dir in *\ * | *\ *) MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;; *) MISSING="\${SHELL} $am_aux_dir/missing" ;; esac fi # Use eval to expand $SHELL if eval "$MISSING --run true"; then am_missing_run="$MISSING --run " else am_missing_run= AC_MSG_WARN([`missing' script is too old or missing]) fi ]) m4_include([m4/ucread.m4]) m4_include([acinclude.m4]) citadel-9.01/room_ops.c0000644000000000000000000010176312507024051013553 0ustar rootroot/* * Server functions which perform operations on room objects. * * Copyright (c) 1987-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include "citserver.h" #include "ctdl_module.h" #include "config.h" #include "control.h" #include "user_ops.h" #include "room_ops.h" struct floor *floorcache[MAXFLOORS]; /* * Determine whether the currently logged in session has permission to read * messages in the current room. */ int CtdlDoIHavePermissionToReadMessagesInThisRoom(void) { if ( (!(CC->logged_in)) && (!(CC->internal_pgm)) && (!config.c_guest_logins) ) { return(om_not_logged_in); } return(om_ok); } /* * Check to see whether we have permission to post a message in the current * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or * returns 0 on success. */ int CtdlDoIHavePermissionToPostInThisRoom( char *errmsgbuf, size_t n, const char* RemoteIdentifier, PostType PostPublic, int is_reply ) { int ra; if (!(CC->logged_in) && (PostPublic == POST_LOGGED_IN)) { snprintf(errmsgbuf, n, "Not logged in."); return (ERROR + NOT_LOGGED_IN); } else if (PostPublic == CHECK_EXISTANCE) { return (0); // We're Evaling whether a recipient exists } else if (!(CC->logged_in)) { if ((CC->room.QRflags & QR_READONLY)) { snprintf(errmsgbuf, n, "Not logged in."); return (ERROR + NOT_LOGGED_IN); } if (CC->room.QRflags2 & QR2_MODERATED) { snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!"); return (ERROR + NOT_LOGGED_IN); } if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) { return CtdlNetconfigCheckRoomaccess(errmsgbuf, n, RemoteIdentifier); } return (0); } if ((CC->user.axlevel < AxProbU) && ((CC->room.QRflags & QR_MAILBOX) == 0)) { snprintf(errmsgbuf, n, "Need to be validated to enter (except in %s> to sysop)", MAILROOM); return (ERROR + HIGHER_ACCESS_REQUIRED); } CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL); if (ra & UA_POSTALLOWED) { strcpy(errmsgbuf, "OK to post or reply here"); return(0); } if ( (ra & UA_REPLYALLOWED) && (is_reply) ) { /* * To be thorough, we ought to check to see if the message they are * replying to is actually a valid one in this room, but unless this * actually becomes a problem we'll go with high performance instead. */ strcpy(errmsgbuf, "OK to reply here"); return(0); } if ( (ra & UA_REPLYALLOWED) && (!is_reply) ) { /* Clarify what happened with a better error message */ snprintf(errmsgbuf, n, "You may only reply to existing messages here."); return (ERROR + HIGHER_ACCESS_REQUIRED); } snprintf(errmsgbuf, n, "Higher access is required to post in this room."); return (ERROR + HIGHER_ACCESS_REQUIRED); } /* * Check whether the current user has permission to delete messages from * the current room (returns 1 for yes, 0 for no) */ int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) { int ra; CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL); if (ra & UA_DELETEALLOWED) return(1); return(0); } /* * Retrieve access control information for any user/room pair */ void CtdlRoomAccess(struct ctdlroom *roombuf, struct ctdluser *userbuf, int *result, int *view) { int retval = 0; visit vbuf; int is_me = 0; int is_guest = 0; if (userbuf == &CC->user) { is_me = 1; } if ((is_me) && (config.c_guest_logins) && (!CC->logged_in)) { is_guest = 1; } /* for internal programs, always do everything */ if (((CC->internal_pgm)) && (roombuf->QRflags & QR_INUSE)) { retval = (UA_KNOWN | UA_GOTOALLOWED | UA_POSTALLOWED | UA_DELETEALLOWED | UA_REPLYALLOWED); vbuf.v_view = 0; goto SKIP_EVERYTHING; } /* If guest mode is enabled, always grant access to the Lobby */ if ((is_guest) && (!strcasecmp(roombuf->QRname, BASEROOM))) { retval = (UA_KNOWN | UA_GOTOALLOWED); vbuf.v_view = 0; goto SKIP_EVERYTHING; } /* Locate any applicable user/room relationships */ if (is_guest) { memset(&vbuf, 0, sizeof vbuf); } else { CtdlGetRelationship(&vbuf, userbuf, roombuf); } /* Force the properties of the Aide room */ if (!strcasecmp(roombuf->QRname, config.c_aideroom)) { if (userbuf->axlevel >= AxAideU) { retval = UA_KNOWN | UA_GOTOALLOWED | UA_POSTALLOWED | UA_DELETEALLOWED | UA_REPLYALLOWED; } else { retval = 0; } goto NEWMSG; } /* If this is a public room, it's accessible... */ if ( ((roombuf->QRflags & QR_PRIVATE) == 0) && ((roombuf->QRflags & QR_MAILBOX) == 0) ) { retval = retval | UA_KNOWN | UA_GOTOALLOWED; } /* If this is a preferred users only room, check access level */ if (roombuf->QRflags & QR_PREFONLY) { if (userbuf->axlevel < AxPrefU) { retval = retval & ~UA_KNOWN & ~UA_GOTOALLOWED; } } /* For private rooms, check the generation number matchups */ if ( (roombuf->QRflags & QR_PRIVATE) && ((roombuf->QRflags & QR_MAILBOX) == 0) ) { /* An explicit match means the user belongs in this room */ if (vbuf.v_flags & V_ACCESS) { retval = retval | UA_KNOWN | UA_GOTOALLOWED; } /* Otherwise, check if this is a guess-name or passworded * room. If it is, a goto may at least be attempted */ else if ( (roombuf->QRflags & QR_PRIVATE) || (roombuf->QRflags & QR_PASSWORDED) ) { retval = retval & ~UA_KNOWN; retval = retval | UA_GOTOALLOWED; } } /* For mailbox rooms, also check the namespace */ /* Also, mailbox owners can delete their messages */ if ( (roombuf->QRflags & QR_MAILBOX) && (atol(roombuf->QRname) != 0)) { if (userbuf->usernum == atol(roombuf->QRname)) { retval = retval | UA_KNOWN | UA_GOTOALLOWED | UA_POSTALLOWED | UA_DELETEALLOWED | UA_REPLYALLOWED; } /* An explicit match means the user belongs in this room */ if (vbuf.v_flags & V_ACCESS) { retval = retval | UA_KNOWN | UA_GOTOALLOWED | UA_POSTALLOWED | UA_DELETEALLOWED | UA_REPLYALLOWED; } } /* For non-mailbox rooms... */ else { /* User is allowed to post in the room unless: * - User is not validated * - User has no net privileges and it is a shared network room * - It is a read-only room * - It is a blog room (in which case we only allow replies to existing messages) */ int post_allowed = 1; int reply_allowed = 1; if (userbuf->axlevel < AxProbU) { post_allowed = 0; reply_allowed = 0; } if ((userbuf->axlevel < AxNetU) && (roombuf->QRflags & QR_NETWORK)) { post_allowed = 0; reply_allowed = 0; } if (roombuf->QRflags & QR_READONLY) { post_allowed = 0; reply_allowed = 0; } if (roombuf->QRdefaultview == VIEW_BLOG) { post_allowed = 0; } if (post_allowed) { retval = retval | UA_POSTALLOWED | UA_REPLYALLOWED; } if (reply_allowed) { retval = retval | UA_REPLYALLOWED; } /* If "collaborative deletion" is active for this room, any user who can post * is also allowed to delete */ if (roombuf->QRflags2 & QR2_COLLABDEL) { if (retval & UA_POSTALLOWED) { retval = retval | UA_DELETEALLOWED; } } } /* Check to see if the user has forgotten this room */ if (vbuf.v_flags & V_FORGET) { retval = retval & ~UA_KNOWN; if ( ( ((roombuf->QRflags & QR_PRIVATE) == 0) && ((roombuf->QRflags & QR_MAILBOX) == 0) ) || ( (roombuf->QRflags & QR_MAILBOX) && (atol(roombuf->QRname) == CC->user.usernum)) ) { retval = retval | UA_ZAPPED; } } /* If user is explicitly locked out of this room, deny everything */ if (vbuf.v_flags & V_LOCKOUT) { retval = retval & ~UA_KNOWN & ~UA_GOTOALLOWED & ~UA_POSTALLOWED & ~UA_REPLYALLOWED; } /* Aides get access to all private rooms */ if ( (userbuf->axlevel >= AxAideU) && ((roombuf->QRflags & QR_MAILBOX) == 0) ) { if (vbuf.v_flags & V_FORGET) { retval = retval | UA_GOTOALLOWED | UA_POSTALLOWED | UA_REPLYALLOWED; } else { retval = retval | UA_KNOWN | UA_GOTOALLOWED | UA_POSTALLOWED | UA_REPLYALLOWED; } } /* Aides can gain access to mailboxes as well, but they don't show * by default. */ if ( (userbuf->axlevel >= AxAideU) && (roombuf->QRflags & QR_MAILBOX) ) { retval = retval | UA_GOTOALLOWED | UA_POSTALLOWED | UA_REPLYALLOWED; } /* Aides and Room Aides have admin privileges */ if ( (userbuf->axlevel >= AxAideU) || (userbuf->usernum == roombuf->QRroomaide) ) { retval = retval | UA_ADMINALLOWED | UA_DELETEALLOWED | UA_POSTALLOWED | UA_REPLYALLOWED; } NEWMSG: /* By the way, we also check for the presence of new messages */ if (is_msg_in_sequence_set(vbuf.v_seen, roombuf->QRhighest) == 0) { retval = retval | UA_HASNEWMSGS; } /* System rooms never show up in the list. */ if (roombuf->QRflags2 & QR2_SYSTEM) { retval = retval & ~UA_KNOWN; } SKIP_EVERYTHING: /* Now give the caller the information it wants. */ if (result != NULL) *result = retval; if (view != NULL) *view = vbuf.v_view; } /* * Self-checking stuff for a room record read into memory */ void room_sanity_check(struct ctdlroom *qrbuf) { /* Mailbox rooms are always on the lowest floor */ if (qrbuf->QRflags & QR_MAILBOX) { qrbuf->QRfloor = 0; } /* Listing order of 0 is illegal except for base rooms */ if (qrbuf->QRorder == 0) if (!(qrbuf->QRflags & QR_MAILBOX) && strncasecmp(qrbuf->QRname, config.c_baseroom, ROOMNAMELEN) && strncasecmp(qrbuf->QRname, config.c_aideroom, ROOMNAMELEN)) qrbuf->QRorder = 64; } /* * CtdlGetRoom() - retrieve room data from disk */ int CtdlGetRoom(struct ctdlroom *qrbuf, const char *room_name) { struct cdbdata *cdbqr; char lowercase_name[ROOMNAMELEN]; char personal_lowercase_name[ROOMNAMELEN]; const char *sptr; char *dptr, *eptr; dptr = lowercase_name; sptr = room_name; eptr = (dptr + (sizeof lowercase_name - 1)); while (!IsEmptyStr(sptr) && (dptr < eptr)){ *dptr = tolower(*sptr); sptr++; dptr++; } *dptr = '\0'; memset(qrbuf, 0, sizeof(struct ctdlroom)); /* First, try the public namespace */ cdbqr = cdb_fetch(CDB_ROOMS, lowercase_name, strlen(lowercase_name)); /* If that didn't work, try the user's personal namespace */ if (cdbqr == NULL) { snprintf(personal_lowercase_name, sizeof personal_lowercase_name, "%010ld.%s", CC->user.usernum, lowercase_name); cdbqr = cdb_fetch(CDB_ROOMS, personal_lowercase_name, strlen(personal_lowercase_name)); } if (cdbqr != NULL) { memcpy(qrbuf, cdbqr->ptr, ((cdbqr->len > sizeof(struct ctdlroom)) ? sizeof(struct ctdlroom) : cdbqr->len)); cdb_free(cdbqr); room_sanity_check(qrbuf); return (0); } else { return (1); } } /* * CtdlGetRoomLock() - same as getroom() but locks the record (if supported) */ int CtdlGetRoomLock(struct ctdlroom *qrbuf, char *room_name) { register int retval; retval = CtdlGetRoom(qrbuf, room_name); if (retval == 0) begin_critical_section(S_ROOMS); return(retval); } /* * b_putroom() - back end to putroom() and b_deleteroom() * (if the supplied buffer is NULL, delete the room record) */ void b_putroom(struct ctdlroom *qrbuf, char *room_name) { char lowercase_name[ROOMNAMELEN]; char *aptr, *bptr; long len; aptr = room_name; bptr = lowercase_name; while (!IsEmptyStr(aptr)) { *bptr = tolower(*aptr); aptr++; bptr++; } *bptr='\0'; len = bptr - lowercase_name; if (qrbuf == NULL) { cdb_delete(CDB_ROOMS, lowercase_name, len); } else { time(&qrbuf->QRmtime); cdb_store(CDB_ROOMS, lowercase_name, len, qrbuf, sizeof(struct ctdlroom)); } } /* * CtdlPutRoom() - store room data to disk */ void CtdlPutRoom(struct ctdlroom *qrbuf) { b_putroom(qrbuf, qrbuf->QRname); } /* * b_deleteroom() - delete a room record from disk */ void b_deleteroom(char *room_name) { b_putroom(NULL, room_name); } /* * CtdlPutRoomLock() - same as CtdlPutRoom() but unlocks the record (if supported) */ void CtdlPutRoomLock(struct ctdlroom *qrbuf) { CtdlPutRoom(qrbuf); end_critical_section(S_ROOMS); } /* * CtdlGetFloorByName() - retrieve the number of the named floor * return < 0 if not found else return floor number */ int CtdlGetFloorByName(const char *floor_name) { int a; struct floor *flbuf = NULL; for (a = 0; a < MAXFLOORS; ++a) { flbuf = CtdlGetCachedFloor(a); /* check to see if it already exists */ if ((!strcasecmp(flbuf->f_name, floor_name)) && (flbuf->f_flags & F_INUSE)) { return a; } } return -1; } /* * CtdlGetFloorByNameLock() - retrieve floor number for given floor and lock the floor list. */ int CtdlGetFloorByNameLock(const char *floor_name) { begin_critical_section(S_FLOORTAB); return CtdlGetFloorByName(floor_name); } /* * CtdlGetAvailableFloor() - Return number of first unused floor * return < 0 if none available */ int CtdlGetAvailableFloor(void) { int a; struct floor *flbuf = NULL; for (a = 0; a < MAXFLOORS; a++) { flbuf = CtdlGetCachedFloor(a); /* check to see if it already exists */ if ((flbuf->f_flags & F_INUSE) == 0) { return a; } } return -1; } /* * CtdlGetFloor() - retrieve floor data from disk */ void CtdlGetFloor(struct floor *flbuf, int floor_num) { struct cdbdata *cdbfl; memset(flbuf, 0, sizeof(struct floor)); cdbfl = cdb_fetch(CDB_FLOORTAB, &floor_num, sizeof(int)); if (cdbfl != NULL) { memcpy(flbuf, cdbfl->ptr, ((cdbfl->len > sizeof(struct floor)) ? sizeof(struct floor) : cdbfl->len)); cdb_free(cdbfl); } else { if (floor_num == 0) { safestrncpy(flbuf->f_name, "Main Floor", sizeof flbuf->f_name); flbuf->f_flags = F_INUSE; flbuf->f_ref_count = 3; } } } /* * lgetfloor() - same as CtdlGetFloor() but locks the record (if supported) */ void lgetfloor(struct floor *flbuf, int floor_num) { begin_critical_section(S_FLOORTAB); CtdlGetFloor(flbuf, floor_num); } /* * CtdlGetCachedFloor() - Get floor record from *cache* (loads from disk if needed) * * This is strictly a performance hack. */ struct floor *CtdlGetCachedFloor(int floor_num) { static int initialized = 0; int i; int fetch_new = 0; struct floor *fl = NULL; begin_critical_section(S_FLOORCACHE); if (initialized == 0) { for (i=0; iptr, ((cdbqr->len > sizeof(struct ctdlroom)) ? sizeof(struct ctdlroom) : cdbqr->len) ); cdb_free(cdbqr); room_sanity_check(&qrbuf); if (qrbuf.QRflags & QR_INUSE) { CB(&qrbuf, in_data); } } } /* * Traverse the room file... */ void CtdlForEachNetCfgRoom(ForEachRoomNetCfgCallBack CB, void *in_data, RoomNetCfg filter) { struct ctdlroom qrbuf; struct cdbdata *cdbqr; cdb_rewind(CDB_ROOMS); while (cdbqr = cdb_next_item(CDB_ROOMS), cdbqr != NULL) { memset(&qrbuf, 0, sizeof(struct ctdlroom)); memcpy(&qrbuf, cdbqr->ptr, ((cdbqr->len > sizeof(struct ctdlroom)) ? sizeof(struct ctdlroom) : cdbqr->len) ); cdb_free(cdbqr); room_sanity_check(&qrbuf); if (qrbuf.QRflags & QR_INUSE) { OneRoomNetCfg* RNCfg; RNCfg = CtdlGetNetCfgForRoom(qrbuf.QRnumber); if ((RNCfg != NULL) && ((filter == maxRoomNetCfg) || (RNCfg->NetConfigs[filter] != NULL))) { CB(&qrbuf, in_data, RNCfg); } } } } /* * delete_msglist() - delete room message pointers */ void delete_msglist(struct ctdlroom *whichroom) { struct cdbdata *cdbml; /* Make sure the msglist we're deleting actually exists, otherwise * libdb will complain when we try to delete an invalid record */ cdbml = cdb_fetch(CDB_MSGLISTS, &whichroom->QRnumber, sizeof(long)); if (cdbml != NULL) { cdb_free(cdbml); /* Go ahead and delete it */ cdb_delete(CDB_MSGLISTS, &whichroom->QRnumber, sizeof(long)); } } /* * Message pointer compare function for sort_msglist() */ int sort_msglist_cmp(const void *m1, const void *m2) { if ((*(const long *)m1) > (*(const long *)m2)) return(1); if ((*(const long *)m1) < (*(const long *)m2)) return(-1); return(0); } /* * sort message pointers * (returns new msg count) */ int sort_msglist(long listptrs[], int oldcount) { int numitems; int i = 0; numitems = oldcount; if (numitems < 2) { return (oldcount); } /* do the sort */ qsort(listptrs, numitems, sizeof(long), sort_msglist_cmp); /* and yank any nulls */ while ((i < numitems) && (listptrs[i] == 0L)) i++; if (i > 0) { memmove(&listptrs[0], &listptrs[i], (sizeof(long) * (numitems - i))); numitems-=i; } return (numitems); } /* * Determine whether a given room is non-editable. */ int CtdlIsNonEditable(struct ctdlroom *qrbuf) { /* Mail> rooms are non-editable */ if ( (qrbuf->QRflags & QR_MAILBOX) && (!strcasecmp(&qrbuf->QRname[11], MAILROOM)) ) return (1); /* Everything else is editable */ return (0); } /* * Make the specified room the current room for this session. No validation * or access control is done here -- the caller should make sure that the * specified room exists and is ok to access. */ void CtdlUserGoto(char *where, int display_result, int transiently, int *retmsgs, int *retnew, long *retoldest, long *retnewest) { struct CitContext *CCC = CC; int a; int new_messages = 0; int old_messages = 0; int total_messages = 0; long oldest_message = 0; long newest_message = 0; int info = 0; int rmailflag; int raideflag; int newmailcount = 0; visit vbuf; char truncated_roomname[ROOMNAMELEN]; struct cdbdata *cdbfr; long *msglist = NULL; int num_msgs = 0; unsigned int original_v_flags; int num_sets; int s; char setstr[128], lostr[64], histr[64]; long lo, hi; int is_trash = 0; /* If the supplied room name is NULL, the caller wants us to know that * it has already copied the room record into CC->room, so * we can skip the extra database fetch. */ if (where != NULL) { safestrncpy(CCC->room.QRname, where, sizeof CCC->room.QRname); CtdlGetRoom(&CCC->room, where); } /* Take care of all the formalities. */ begin_critical_section(S_USERS); CtdlGetRelationship(&vbuf, &CCC->user, &CCC->room); original_v_flags = vbuf.v_flags; /* Know the room ... but not if it's the page log room, or if the * caller specified that we're only entering this room transiently. */ if ((strcasecmp(CCC->room.QRname, config.c_logpages)) && (transiently == 0) ) { vbuf.v_flags = vbuf.v_flags & ~V_FORGET & ~V_LOCKOUT; vbuf.v_flags = vbuf.v_flags | V_ACCESS; } /* Only rewrite the database record if we changed something */ if (vbuf.v_flags != original_v_flags) { CtdlSetRelationship(&vbuf, &CCC->user, &CCC->room); } end_critical_section(S_USERS); /* Check for new mail */ newmailcount = NewMailCount(); /* set info to 1 if the user needs to read the room's info file */ if (CCC->room.QRinfo > vbuf.v_lastseen) { info = 1; } cdbfr = cdb_fetch(CDB_MSGLISTS, &CCC->room.QRnumber, sizeof(long)); if (cdbfr != NULL) { msglist = (long *) cdbfr->ptr; cdbfr->ptr = NULL; /* CtdlUserGoto() now owns this memory */ num_msgs = cdbfr->len / sizeof(long); cdb_free(cdbfr); } total_messages = 0; for (a=0; a 0L) ++total_messages; } if (total_messages > 0) { oldest_message = msglist[0]; newest_message = msglist[num_msgs - 1]; } num_sets = num_tokens(vbuf.v_seen, ','); for (s=0; s= 2) { extract_token(histr, setstr, 1, ':', sizeof histr); if (!strcmp(histr, "*")) { snprintf(histr, sizeof histr, "%ld", LONG_MAX); } } else { strcpy(histr, lostr); } lo = atol(lostr); hi = atol(histr); for (a=0; a 0L) { if ((msglist[a] >= lo) && (msglist[a] <= hi)) { ++old_messages; msglist[a] = 0L; } } } new_messages = total_messages - old_messages; if (msglist != NULL) free(msglist); if (CCC->room.QRflags & QR_MAILBOX) rmailflag = 1; else rmailflag = 0; if ((CCC->room.QRroomaide == CCC->user.usernum) || (CCC->user.axlevel >= AxAideU)) raideflag = 1; else raideflag = 0; safestrncpy(truncated_roomname, CCC->room.QRname, sizeof truncated_roomname); if ( (CCC->room.QRflags & QR_MAILBOX) && (atol(CCC->room.QRname) == CCC->user.usernum) ) { safestrncpy(truncated_roomname, &truncated_roomname[11], sizeof truncated_roomname); } if (!strcasecmp(truncated_roomname, USERTRASHROOM)) { is_trash = 1; } if (retmsgs != NULL) *retmsgs = total_messages; if (retnew != NULL) *retnew = new_messages; if (retoldest != NULL) *retoldest = oldest_message; if (retnewest != NULL) *retnewest = newest_message; MSG_syslog(LOG_INFO, "<%s> %d new of %d total messages, oldest=%ld, newest=%ld\n", CCC->room.QRname, new_messages, total_messages, oldest_message, newest_message ); CCC->curr_view = (int)vbuf.v_view; if (display_result) { cprintf("%d%c%s|%d|%d|%d|%d|%ld|%ld|%d|%d|%d|%d|%d|%d|%d|%d|%ld|\n", CIT_OK, CtdlCheckExpress(), truncated_roomname, (int)new_messages, (int)total_messages, (int)info, (int)CCC->room.QRflags, (long)CCC->room.QRhighest, (long)vbuf.v_lastseen, (int)rmailflag, (int)raideflag, (int)newmailcount, (int)CCC->room.QRfloor, (int)vbuf.v_view, (int)CCC->room.QRdefaultview, (int)is_trash, (int)CCC->room.QRflags2, (long)CCC->room.QRmtime ); } } /* * Handle some of the macro named rooms */ void convert_room_name_macros(char *towhere, size_t maxlen) { if (!strcasecmp(towhere, "_BASEROOM_")) { safestrncpy(towhere, config.c_baseroom, maxlen); } else if (!strcasecmp(towhere, "_MAIL_")) { safestrncpy(towhere, MAILROOM, maxlen); } else if (!strcasecmp(towhere, "_TRASH_")) { safestrncpy(towhere, USERTRASHROOM, maxlen); } else if (!strcasecmp(towhere, "_DRAFTS_")) { safestrncpy(towhere, USERDRAFTROOM, maxlen); } else if (!strcasecmp(towhere, "_BITBUCKET_")) { safestrncpy(towhere, config.c_twitroom, maxlen); } else if (!strcasecmp(towhere, "_CALENDAR_")) { safestrncpy(towhere, USERCALENDARROOM, maxlen); } else if (!strcasecmp(towhere, "_TASKS_")) { safestrncpy(towhere, USERTASKSROOM, maxlen); } else if (!strcasecmp(towhere, "_CONTACTS_")) { safestrncpy(towhere, USERCONTACTSROOM, maxlen); } else if (!strcasecmp(towhere, "_NOTES_")) { safestrncpy(towhere, USERNOTESROOM, maxlen); } } /* * Back end function to rename a room. * You can also specify which floor to move the room to, or specify -1 to * keep the room on the same floor it was on. * * If you are renaming a mailbox room, you must supply the namespace prefix * in *at least* the old name! */ int CtdlRenameRoom(char *old_name, char *new_name, int new_floor) { int old_floor = 0; struct ctdlroom qrbuf; struct ctdlroom qrtmp; int ret = 0; struct floor *fl; struct floor flbuf; long owner = 0L; char actual_old_name[ROOMNAMELEN]; syslog(LOG_DEBUG, "CtdlRenameRoom(%s, %s, %d)\n", old_name, new_name, new_floor); if (new_floor >= 0) { fl = CtdlGetCachedFloor(new_floor); if ((fl->f_flags & F_INUSE) == 0) { return(crr_invalid_floor); } } begin_critical_section(S_ROOMS); if ( (CtdlGetRoom(&qrtmp, new_name) == 0) && (strcasecmp(new_name, old_name)) ) { ret = crr_already_exists; } else if (CtdlGetRoom(&qrbuf, old_name) != 0) { ret = crr_room_not_found; } else if ( (CC->user.axlevel < AxAideU) && (!CC->internal_pgm) && (CC->user.usernum != qrbuf.QRroomaide) && ( (((qrbuf.QRflags & QR_MAILBOX) == 0) || (atol(qrbuf.QRname) != CC->user.usernum))) ) { ret = crr_access_denied; } else if (CtdlIsNonEditable(&qrbuf)) { ret = crr_noneditable; } else { /* Rename it */ safestrncpy(actual_old_name, qrbuf.QRname, sizeof actual_old_name); if (qrbuf.QRflags & QR_MAILBOX) { owner = atol(qrbuf.QRname); } if ( (owner > 0L) && (atol(new_name) == 0L) ) { snprintf(qrbuf.QRname, sizeof(qrbuf.QRname), "%010ld.%s", owner, new_name); } else { safestrncpy(qrbuf.QRname, new_name, sizeof(qrbuf.QRname)); } /* Reject change of floor for baseroom/aideroom */ if (!strncasecmp(old_name, config.c_baseroom, ROOMNAMELEN) || !strncasecmp(old_name, config.c_aideroom, ROOMNAMELEN)) { new_floor = 0; } /* Take care of floor stuff */ old_floor = qrbuf.QRfloor; if (new_floor < 0) { new_floor = old_floor; } qrbuf.QRfloor = new_floor; CtdlPutRoom(&qrbuf); begin_critical_section(S_CONFIG); /* If baseroom/aideroom name changes, update config */ if (!strncasecmp(old_name, config.c_baseroom, ROOMNAMELEN)) { safestrncpy(config.c_baseroom, new_name, ROOMNAMELEN); put_config(); } if (!strncasecmp(old_name, config.c_aideroom, ROOMNAMELEN)) { safestrncpy(config.c_aideroom, new_name, ROOMNAMELEN); put_config(); } end_critical_section(S_CONFIG); /* If the room name changed, then there are now two room * records, so we have to delete the old one. */ if (strcasecmp(new_name, old_name)) { b_deleteroom(actual_old_name); } ret = crr_ok; } end_critical_section(S_ROOMS); /* Adjust the floor reference counts if necessary */ if (new_floor != old_floor) { lgetfloor(&flbuf, old_floor); --flbuf.f_ref_count; lputfloor(&flbuf, old_floor); syslog(LOG_DEBUG, "Reference count for floor %d is now %d\n", old_floor, flbuf.f_ref_count); lgetfloor(&flbuf, new_floor); ++flbuf.f_ref_count; lputfloor(&flbuf, new_floor); syslog(LOG_DEBUG, "Reference count for floor %d is now %d\n", new_floor, flbuf.f_ref_count); } /* ...and everybody say "YATTA!" */ return(ret); } /* * Asynchronously schedule a room for deletion. The room will appear * deleted to the user(s), but it won't actually get purged from the * database until THE DREADED AUTO-PURGER makes its next run. */ void CtdlScheduleRoomForDeletion(struct ctdlroom *qrbuf) { char old_name[ROOMNAMELEN]; static int seq = 0; syslog(LOG_NOTICE, "Scheduling room <%s> for deletion\n", qrbuf->QRname); safestrncpy(old_name, qrbuf->QRname, sizeof old_name); CtdlGetRoom(qrbuf, qrbuf->QRname); /* Turn the room into a private mailbox owned by a user who doesn't * exist. This will immediately make the room invisible to everyone, * and qualify the room for purging. */ snprintf(qrbuf->QRname, sizeof qrbuf->QRname, "9999999999.%08lx.%04d.%s", time(NULL), ++seq, old_name ); qrbuf->QRflags |= QR_MAILBOX; time(&qrbuf->QRgen); /* Use a timestamp as the new generation number */ CtdlPutRoom(qrbuf); b_deleteroom(old_name); } /* * Back end processing to delete a room and everything associated with it * (This one is synchronous and should only get called by THE DREADED * AUTO-PURGER in serv_expire.c. All user-facing code should call * the asynchronous schedule_room_for_deletion() instead.) */ void CtdlDeleteRoom(struct ctdlroom *qrbuf) { struct floor flbuf; char filename[100]; /* TODO: filename magic? does this realy work? */ syslog(LOG_NOTICE, "Deleting room <%s>\n", qrbuf->QRname); /* Delete the info file */ assoc_file_name(filename, sizeof filename, qrbuf, ctdl_info_dir); unlink(filename); /* Delete the image file */ assoc_file_name(filename, sizeof filename, qrbuf, ctdl_image_dir); unlink(filename); /* Delete the room's network config file */ assoc_file_name(filename, sizeof filename, qrbuf, ctdl_netcfg_dir); unlink(filename); /* Delete the messages in the room * (Careful: this opens an S_ROOMS critical section!) */ CtdlDeleteMessages(qrbuf->QRname, NULL, 0, ""); /* Flag the room record as not in use */ CtdlGetRoomLock(qrbuf, qrbuf->QRname); qrbuf->QRflags = 0; CtdlPutRoomLock(qrbuf); /* then decrement the reference count for the floor */ lgetfloor(&flbuf, (int) (qrbuf->QRfloor)); flbuf.f_ref_count = flbuf.f_ref_count - 1; lputfloor(&flbuf, (int) (qrbuf->QRfloor)); /* Delete the room record from the database! */ b_deleteroom(qrbuf->QRname); } /* * Check access control for deleting a room */ int CtdlDoIHavePermissionToDeleteThisRoom(struct ctdlroom *qr) { if ((!(CC->logged_in)) && (!(CC->internal_pgm))) { return(0); } if (CtdlIsNonEditable(qr)) { return(0); } /* * For mailboxes, check stuff */ if (qr->QRflags & QR_MAILBOX) { if (strlen(qr->QRname) < 12) return(0); /* bad name */ if (atol(qr->QRname) != CC->user.usernum) { return(0); /* not my room */ } /* Can't delete your Mail> room */ if (!strcasecmp(&qr->QRname[11], MAILROOM)) return(0); /* Otherwise it's ok */ return(1); } /* * For normal rooms, just check for admin or room admin status. */ return(is_room_aide()); } /* * Internal code to create a new room (returns room flags) * * Room types: 0=public, 1=hidden, 2=passworded, 3=invitation-only, * 4=mailbox, 5=mailbox, but caller supplies namespace */ unsigned CtdlCreateRoom(char *new_room_name, int new_room_type, char *new_room_pass, int new_room_floor, int really_create, int avoid_access, int new_room_view) { struct ctdlroom qrbuf; struct floor flbuf; visit vbuf; syslog(LOG_DEBUG, "CtdlCreateRoom(name=%s, type=%d, view=%d)\n", new_room_name, new_room_type, new_room_view); if (CtdlGetRoom(&qrbuf, new_room_name) == 0) { syslog(LOG_DEBUG, "%s already exists.\n", new_room_name); return(0); } memset(&qrbuf, 0, sizeof(struct ctdlroom)); safestrncpy(qrbuf.QRpasswd, new_room_pass, sizeof qrbuf.QRpasswd); qrbuf.QRflags = QR_INUSE; if (new_room_type > 0) qrbuf.QRflags = (qrbuf.QRflags | QR_PRIVATE); if (new_room_type == 1) qrbuf.QRflags = (qrbuf.QRflags | QR_GUESSNAME); if (new_room_type == 2) qrbuf.QRflags = (qrbuf.QRflags | QR_PASSWORDED); if ( (new_room_type == 4) || (new_room_type == 5) ) { qrbuf.QRflags = (qrbuf.QRflags | QR_MAILBOX); /* qrbuf.QRflags2 |= QR2_SUBJECTREQ; */ } /* If the user is requesting a personal room, set up the room * name accordingly (prepend the user number) */ if (new_room_type == 4) { CtdlMailboxName(qrbuf.QRname, sizeof qrbuf.QRname, &CC->user, new_room_name); } else { safestrncpy(qrbuf.QRname, new_room_name, sizeof qrbuf.QRname); } /* If the room is private, and the system administrator has elected * to automatically grant room admin privileges, do so now. */ if ((qrbuf.QRflags & QR_PRIVATE) && (CREATAIDE == 1)) { qrbuf.QRroomaide = CC->user.usernum; } /* Blog owners automatically become room admins of their blogs. * (In the future we will offer a site-wide configuration setting to suppress this behavior.) */ else if (new_room_view == VIEW_BLOG) { qrbuf.QRroomaide = CC->user.usernum; } /* Otherwise, set the room admin to undefined. */ else { qrbuf.QRroomaide = (-1L); } /* * If the caller is only interested in testing whether this will work, * return now without creating the room. */ if (!really_create) return (qrbuf.QRflags); qrbuf.QRnumber = get_new_room_number(); qrbuf.QRhighest = 0L; /* No messages in this room yet */ time(&qrbuf.QRgen); /* Use a timestamp as the generation number */ qrbuf.QRfloor = new_room_floor; qrbuf.QRdefaultview = new_room_view; /* save what we just did... */ CtdlPutRoom(&qrbuf); /* bump the reference count on whatever floor the room is on */ lgetfloor(&flbuf, (int) qrbuf.QRfloor); flbuf.f_ref_count = flbuf.f_ref_count + 1; lputfloor(&flbuf, (int) qrbuf.QRfloor); /* Grant the creator access to the room unless the avoid_access * parameter was specified. */ if ( (CC->logged_in) && (avoid_access == 0) ) { CtdlGetRelationship(&vbuf, &CC->user, &qrbuf); vbuf.v_flags = vbuf.v_flags & ~V_FORGET & ~V_LOCKOUT; vbuf.v_flags = vbuf.v_flags | V_ACCESS; CtdlSetRelationship(&vbuf, &CC->user, &qrbuf); } /* resume our happy day */ return (qrbuf.QRflags); } citadel-9.01/clientsocket.h0000644000000000000000000000226112507024051014403 0ustar rootroot/* * Header file for TCP client socket library * * Copyright (c) 1987-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ int sock_connect(char *host, char *service); int sock_write(int *sock, const char *buf, int nbytes); int sock_write_timeout(int *sock, const char *buf, int nbytes, int timeout); int ml_sock_gets(int *sock, char *buf, int nSec); int sock_getln(int *sock, char *buf, int bufsize); int CtdlSockGetLine(int *sock, StrBuf *Target, int nSec); int sock_puts(int *sock, char *buf); int socket_read_blob(int *Socket, StrBuf * Target, int bytes, int timeout); /* * This looks dumb, but it's being done for future portability */ #define sock_close(sock) close(sock) #define sock_shutdown(sock, how) shutdown(sock, how) /* * Default timeout for client sessions */ #define CLIENT_TIMEOUT 600 citadel-9.01/citserver.h0000644000000000000000000000315212507024051013722 0ustar rootroot/* * Copyright (c) 1987-2012 by the citadel.org team * * This program is open source software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "serv_extensions.h" #include "context.h" #include "ctdl_module.h" /* Simple linked list structures ... used in a bunch of different places. */ typedef struct RoomProcList RoomProcList; struct RoomProcList { struct RoomProcList *next; OneRoomNetCfg *OneRNCfg; char name[ROOMNAMELEN]; char lcname[ROOMNAMELEN]; long namelen; long lastsent; long key; long QRNum; }; struct UserProcList { struct UserProcList *next; char user[64]; }; #define CTDLUSERIP (IsEmptyStr(CC->cs_addr) ? CC->cs_clientinfo: CC->cs_addr) void cit_backtrace(void); void cit_oneline_backtrace(void); void cit_panic_backtrace(int SigNum); void master_startup (void); void master_cleanup (int exitcode); void set_wtmpsupp (char *newtext); void set_wtmpsupp_to_current_room(void); void do_command_loop(void); void do_async_loop(void); void begin_session(struct CitContext *con); void citproto_begin_session(void); void citproto_begin_admin_session(void); void help_subst (char *strbuf, char *source, char *dest); extern int panic_fd; char CtdlCheckExpress(void); int CtdlIsPublicClient(void); extern time_t server_startup_time; extern int openid_level_supported; citadel-9.01/axdefs.h0000644000000000000000000000056712507024051013175 0ustar rootroot#ifndef AXDEFS char *axdefs[]={ "Deleted", "New User", "Problem User", "Local User", "Network User", "Preferred User", "Admin", "Admin" }; #define AXDEFS 1 #else extern char *axdefs[]; #endif #ifndef VIEWDEFS char *viewdefs[]={ "Messages", "Summary", "Address book", "Calendar", "Tasks" }; #define VIEWDEFS 1 #else extern char *viewdefs[]; #endif citadel-9.01/po/0000755000000000000000000000000012507024051012160 5ustar rootrootcitadel-9.01/po/citadel-setup/0000755000000000000000000000000012507024051014723 5ustar rootrootcitadel-9.01/po/citadel-setup/de.po0000644000000000000000000004467112507024051015667 0ustar rootroot# Translation of citadel debconf templates to German # Copyright (C) Helge Kreutzmann , 2008, 2009. # This file is distributed under the same license as the citadel package. # msgid "" msgstr "" "Project-Id-Version: citadel 7.63-1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2010-09-28 00:22+0200\n" "PO-Revision-Date: 2010-10-22 14:33+0000\n" "Last-Translator: Helge Kreutzmann \n" "Language-Team: de \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=ISO-8859-15\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2010-11-03 04:54+0000\n" "X-Generator: Launchpad (build Unknown)\n" "Language: \n" #: ../utils/setup.c:119 msgid "Citadel Home Directory" msgstr "Citadel Basis Verzeichnis" #: ../utils/setup.c:122 msgid "" "Enter the full pathname of the directory in which the Citadel\n" "installation you are creating or updating resides. If you\n" "specify a directory other than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" msgstr "" "Geben Sie den vollen Pfad Ihrer Citadel Installation / Update an.\n" "Wenn Sie ein anderes Verzeichnis anstelle des Default-Werts\n" "angeben, starten Sie den Citadel-Server mit der Option '-h'.\n" #: ../utils/setup.c:128 msgid "" "Enter the subdirectory name for an alternate installation of Citadel. To do " "a default installation just leave it blank.If you specify a directory other " "than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" "note that it may not have a leading /" msgstr "" "Geben Sie einen Unterordner für eine alternative Installation von Citadel " "an. Um den Default-Wert zu verwenden, lassen Sie das Feld leer.\n" "Wenn Sie ein anderes Verzeichnis als das Default angeben, müssen Sie den " "Server mit der Option '-h' starten.\n" "Den Pfad bitte ohne führendes '/' angeben." #: ../utils/setup.c:135 msgid "Citadel administrator username:" msgstr "Benutzername des Citadel-Administrators:" #: ../utils/setup.c:137 msgid "" "Please enter the name of the Citadel user account that should be granted " "administrative privileges once created. If using internal authentication " "this user account will be created if it does not exist. For external " "authentication this user account has to exist." msgstr "" "Bitte geben Sie den Namen des Citadel-Benutzerkontos an, dem nach der " "Erstellung administrative Privilegien erteilt werden sollen. Falls interne " "Authentifizierung verwandt wird und das Benutzerkonto noch nicht existiert, " "wird es erstellt. Für externe Authentifizierung muss das Benutzerkonto " "existieren." #: ../utils/setup.c:143 msgid "Administrator password:" msgstr "Administrator Passwort:" #: ../utils/setup.c:145 msgid "" "Enter a password for the system administrator. When setup\n" "completes it will attempt to create the administrator user\n" "and set the password specified here.\n" msgstr "" "Bitte geben Sie ein Passwort für den System-Administrator an.\n" "Wennn das Setup abgeschlossen ist, wird der System-Administrator\n" "mit diesem Passwort angelegt.\n" # #: ../utils/setup.c:149 msgid "Citadel User ID:" msgstr "Citadel Benutzer Kennung" #: ../utils/setup.c:151 msgid "" "Citadel needs to run under its own user ID. This would\n" "typically be called \"citadel\", but if you are running Citadel\n" "as a public BBS, you might also call it \"bbs\" or \"guest\".\n" "The server will run under this user ID. Please specify that\n" "user ID here. You may specify either a user name or a numeric\n" "UID.\n" msgstr "" "Citadel muss unter einer eigenen Benutzer-ID laufen. Üblicherweise\n" "lautet diese 'citadel', wenn Sie Citadel aber als öffentlicher BBS laufen " "lassen,\n" "nennen Sie die ID 'bbs' oder 'guest'. Der Server wird unter dieser ID " "ausgeführt.\n" "Geben Sie nun den Benutzernamen oder UID an.\n" #: ../utils/setup.c:158 msgid "Listening address for the Citadel server:" msgstr "Adresse, auf der Citadel auf Anfragen wartet:" #: ../utils/setup.c:160 msgid "" "Please specify the IP address which the server should be listening to. You " "can name a specific IPv4 or IPv6 address, or you can specify\n" "'*' for 'any address', '::' for 'any IPv6 address', or '0.0.0.0'\n" "for 'any IPv4 address'. If you leave this blank, Citadel will\n" "listen on all addresses. This can usually be left to the default unless " "multiple instances of Citadel are running on the same computer." msgstr "" "Bitte geben Sie die IP-Adresse, die der Server sein sollte zuhören. Sie " "können den Namen einer bestimmten IPv4 oder IPv6-Adresse, oder Sie können " "festlegen,\n" "'*' Für 'eine beliebige Adresse', ':' für 'alle IPv6-Adresse \"oder '0 " ".0.0.0'\n" "für \"alle IPv4-Adresse\". Wenn Sie dieses Feld leer lassen, wird Citadel\n" "hören auf alle Adressen. Diese können in der Regel auf den Standardwert " "belassen werden, es sei denn mehrere Instanzen Zitadelle auf dem gleichen " "Computer ausgeführt werden." # #: ../utils/setup.c:168 msgid "Server port number:" msgstr "LDAP-Portnummer:" #: ../utils/setup.c:170 msgid "" "Specify the TCP port number on which your server will run.\n" "Normally, this will be port 504, which is the official port\n" "assigned by the IANA for Citadel servers. You will only need\n" "to specify a different port number if you run multiple instances\n" "of Citadel on the same computer and there is something else\n" "already using port 504.\n" msgstr "" "Geben Sie den TCP-Port-Nummer auf dem Server ausgeführt wird.\n" "Normalerweise wird dieser Port 504, die die offizielle Schnittstelle ist\n" "zugewiesen von der IANA für Citadel-Servern. Sie müssen nur\n" "Um einen anderen Port-Nummer, wenn Sie mehrere Instanzen laufen\n" "der Zitadelle auf dem gleichen Computer, und es ist etwas anderes\n" "bereits über Port 504 auf.\n" #: ../utils/setup.c:177 msgid "Authentication method to use:" msgstr "Zu verwendene Authentifizierungsmethode:" #: ../utils/setup.c:179 msgid "" "Please choose the user authentication mode. By default Citadel will use its " "own internal user accounts database. If you choose Host, Citadel users will " "have accounts on the host system, authenticated via /etc/passwd or a PAM " "source. LDAP chooses an RFC 2307 compliant directory server, the last option " "chooses the nonstandard MS Active Directory LDAP scheme.\n" "Do not change this option unless you are sure it is required, since changing " "back requires a full reinstall of Citadel.\n" " 0. Self contained authentication\n" " 1. Host system integrated authentication\n" " 2. External LDAP - RFC 2307 compliant directory\n" " 3. External LDAP - nonstandard MS Active Directory\n" "\n" "For help: http://www.citadel.org/doku.php/faq:installation:authmodes\n" "\n" "ANSWER \"0\" UNLESS YOU COMPLETELY UNDERSTAND THIS OPTION.\n" msgstr "" "Bitte wählen Sie die Benutzer-Authentifizierung-Modus. Standardmäßig Citadel " "wird seine eigenen internen Benutzerkonten-Datenbank. Wenn Sie Host wählen, " "wird Citadel Benutzer haben Accounts auf dem Host-System, über / etc / " "passwd oder eine PAM Quelle authentifiziert. LDAP wählt eine RFC 2307 " "kompatiblen Verzeichnisserver, wählt die letzte Option der Nicht-Standard-MS " "Active Directory LDAP Schema.\n" "Ändern Sie diese Option, wenn Sie sicher, dass es erforderlich ist, sind " "seit dem Wechsel zurück erfordert ein voller Citadel neu zu installieren.\n" "0. Eigenständiges Authentifizierung\n" "1. Host-System integrierte Authentifizierung\n" "2. Externe LDAP - RFC 2307 konforme Verzeichnisdienste\n" "3. Externe LDAP - Nicht-Standard-MS Active Directory\n" "\n" "Für Hilfe: http://www.citadel.org/doku.php/faq:installation:authmodes\n" #: ../utils/setup.c:197 msgid "LDAP host:" msgstr "LDAP-Host:" # #: ../utils/setup.c:199 msgid "Please enter the host name or IP address of your LDAP server.\n" msgstr "" "Bitte geben Sie den Rechnernamen oder die IP-Adresse Ihres LDAP-Servers an.\n" #: ../utils/setup.c:201 msgid "LDAP port number:" msgstr "LDAP-Portnummer:" # #: ../utils/setup.c:203 msgid "Please enter the port number of the LDAP service (usually 389).\n" msgstr "" "Bitte geben Sie die Portnummer Ihres LDAP-Dienstes an (normalerweise 389).\n" #: ../utils/setup.c:205 msgid "LDAP base DN:" msgstr "LDAP-base-DN:" #: ../utils/setup.c:207 msgid "" "Please enter the Base DN to search for authentication\n" "(for example: dc=example,dc=com)\n" msgstr "" "Bitte geben Sie die »Base DN«, die zur Authentifizierung durchsucht werden " "soll (beispielsweise dc=example,dc=com), an.\n" #: ../utils/setup.c:210 msgid "LDAP bind DN:" msgstr "LDAP-bind-DN:" #: ../utils/setup.c:212 msgid "" "Please enter the DN of an account to use for binding to the LDAP server for " "performing queries. The account does not require any other privileges. If " "your LDAP server allows anonymous queries, you can leave this blank.Please " "enter the DN of an account to use for binding to the LDAP server\n" "for performing queries. The account does not require any other\n" "privileges. If your LDAP server allows anonymous queries, you can\n" "leave this blank.\n" msgstr "" "Bitte geben Sie die DN eines Kontos an, das zum Binden an den LDAP-Server " "zur Durchführung von Abfragen verwandt wird. Das Konto benötigt keine " "weiteren Privilegien. Falls Ihr LDAP-Server anonyme Abfragen erlaubt, können " "Sie dies leer lassen.\n" #: ../utils/setup.c:220 msgid "LDAP bind password:" msgstr "LDAP-bind-Passwort:" #: ../utils/setup.c:222 msgid "" "If you entered a Bind DN in the previous question, you must now enter\n" "the password associated with that account. Otherwise, you can leave this\n" "blank.\n" msgstr "" "Falls Sie in der vorherigen Frage eine Bind-DN eingegeben haben, müssen Sie " "\n" "jetzt das zum Konto zugehörige Passwort eingeben. Lassen Sie dies \n" "andernfalls leer.\n" #: ../utils/setup.c:299 msgid "Yes/No" msgstr "Ja/Nein" #: ../utils/setup.c:300 msgid "Yes" msgstr "Erlauben" #: ../utils/setup.c:300 msgid "No" msgstr "Keine" #: ../utils/setup.c:346 msgid "Press return to continue..." msgstr "Drücken Sie die Eingabetaste, um fortzufahren" #: ../utils/setup.c:364 msgid "Important Message" msgstr "Wichtige Nachricht" #: ../utils/setup.c:379 msgid "Error" msgstr "Fehler" #: ../utils/setup.c:459 msgid "Adding service entry..." msgstr "Füge Serviceeintrag hinzu" #. Other errors might mean something really did go wrong. #. #: ../utils/setup.c:463 ../utils/setup.c:510 ../utils/setup.c:518 msgid "Cannot open" msgstr "Kann nicht öffnen" #: ../utils/setup.c:569 msgid "" "Citadel already appears to be configured to start at boot.\n" "Would you like to keep your boot configuration as is?\n" msgstr "Übersetzung überprüfen\n" #: ../utils/setup.c:577 msgid "Would you like to automatically start Citadel at boot?\n" msgstr "Soll Citadel automatisch beim Systemstart gestartet werden?\n" #: ../utils/setup.c:583 msgid "Cannot create" msgstr "Erstellen fehlgeschlagen" #: ../utils/setup.c:682 #, c-format msgid "" "Setup can configure the \"xinetd\" service to automatically\n" "connect incoming telnet sessions to Citadel, bypassing the\n" "host system login: prompt. Would you like to do this?\n" msgstr "" "Setup kann den \"xinetd\" Dämon so konfigurieren,\n" "dass einkommende telnet-Verbindungen an dem\n" "Host System login vorbei zu Citadel durchgereicht werden.\n" #: ../utils/setup.c:740 msgid "You appear to have the " msgstr "zur Zeit nicht übersetzt " #: ../utils/setup.c:742 msgid "" " email program\n" "running on your system. If you want Citadel mail\n" "connected with " msgstr "" " Email Programm\n" "läuft auf Ihrem System. Wenn Sie möchten das Citdael Mail\n" "sich zu %s " #: ../utils/setup.c:746 msgid "" " you will have to manually integrate\n" "them. It is preferable to disable " msgstr "" " verbindet, muss dies per Hand eingerichtet werden.\n" "Es wird empfohlen %s zu deaktivieren " #: ../utils/setup.c:749 msgid "" ", and use Citadel's\n" "SMTP, POP3, and IMAP services.\n" "\n" "May we disable " msgstr "" "und Citadel's\n" "SMTP, POP3 und IMAP Dämon zu nutzen.\n" "\n" "Soll nun %s abgeschaltet werden? " #: ../utils/setup.c:753 msgid "" "so that Citadel has access to ports\n" "25, 110, and 143?\n" msgstr "" "hat Citadel Zugriff auf Port\n" "25, 110 and 143?\n" #: ../utils/setup.c:863 msgid "This is currently set to:" msgstr "Dies ist momentan eingestellt auf:" #: ../utils/setup.c:864 msgid "Enter new value or press return to leave unchanged:" msgstr "Geben Sie einen neuen Wert ein oder drücken sie Enter:" #: ../utils/setup.c:1067 ../utils/setup.c:1072 ../utils/setup.c:1384 msgid "setup: cannot open" msgstr "Setup: Kann nicht geöffnet werden." #: ../utils/setup.c:1175 #, c-format msgid "" "\n" "/etc/nsswitch.conf is configured to use the 'db' module for\n" "one or more services. This is not necessary on most systems,\n" "and it is known to crash the Citadel server when delivering\n" "mail to the Internet.\n" "\n" "Do you want this module to be automatically disabled?\n" "\n" msgstr "" "\n" "/etc/nsswitch.conf ist so konfiguriert, dass das Modul 'db' für\n" "ein oder mehrere Dämonen zu nutzen. Dies ist auf den meisten Systemen\n" "nicht notwendig und kann zu einem Absturz des Citadel Servers führen,\n" "wenn dieser Emails in das Internet versendet.\n" "\n" "Möchten Sie dieses Modul automatisch deaktivieren?\n" "\n" #: ../utils/setup.c:1236 ../utils/setup.c:1252 msgid "Setup finished" msgstr "Installation beendet." #: ../utils/setup.c:1237 msgid "" "Setup of the Citadel server is complete.\n" "If you will be using WebCit, please run its\n" "setup program now; otherwise, run './citadel'\n" "to log in.\n" msgstr "" "Die Installation von Citadel-Server ist abgeschlossen.\n" "Wenn Sie die WebCit Oberfläche nutzen wollen,\n" "starten Sie nun die Installation.\n" "Anderenfalls starten Sie './citadel' und loggen Sie sich ein.\n" #: ../utils/setup.c:1243 msgid "Setup failed" msgstr "Installation fehlgeschlagen" #: ../utils/setup.c:1244 msgid "" "Setup is finished, but the Citadel server failed to start.\n" "Go back and check your configuration.\n" msgstr "" "Installation beendet konnte aber Citadel server nicht starten.\n" "Bitte überprüfen sie ihre Einstellungen.\n" #: ../utils/setup.c:1253 msgid "Setup is finished. You may now start the server." msgstr "Installation beendet. Sie können den Server jetzt starten." #: ../utils/setup.c:1279 msgid "My System" msgstr "Mein System" #: ../utils/setup.c:1282 msgid "US 800 555 1212" msgstr "US 800 555 1212" #: ../utils/setup.c:1368 ../utils/setup.c:1373 msgid "setup: cannot append" msgstr "Installation: kann nicht fortfahren" #: ../utils/setup.c:1450 ../utils/setup.c:1457 ../utils/setup.c:1472 #: ../utils/setup.c:1512 msgid "Citadel Setup" msgstr "Citadel Setup" #: ../utils/setup.c:1459 msgid "The directory you specified does not exist" msgstr "Das Verzeichniss existiert nicht." #: ../utils/setup.c:1473 msgid "" "The Citadel service is still running.\n" "Please stop the service manually and run setup again." msgstr "" "Der Citadel Dämon läuft bereits.\n" "Bitte stoppen Sie in per Hand und starten das Setup erneut." #: ../utils/setup.c:1485 msgid "Citadel setup program" msgstr "Citadel Setup Programm" #: ../utils/setup.c:1513 msgid "This Citadel installation is too old to be upgraded." msgstr "Diese Citadelinstallation ist zu alt um upgegradet zu werden." #: ../utils/setup.c:1552 ../utils/setup.c:1554 ../utils/setup.c:1556 msgid "Setting file permissions" msgstr "Setze Datei Zugriffsrechte" #~ msgid "" #~ "Please specify the IP address which the server should be listening to. If " #~ "you specify 0.0.0.0, the server will listen on all addresses." #~ msgstr "" #~ "Bitte geben Sie die IP-Adressen an, auf der der Server auf Anfragen warten " #~ "soll. Falls Sie 0.0.0.0 angeben, wird der Server auf allen Adressen auf " #~ "Anfragen warten." #~ msgid "" #~ "This can usually be left to the default unless multiple instances of Citadel " #~ "are running on the same computer." #~ msgstr "" #~ "Normalerweise können Sie die Vorgabe beibehalten, falls Sie nicht mehrere " #~ "Instanzen von Citadel auf dem gleichen Computer betreiben." #~ msgid "Internal" #~ msgstr "Intern" #~ msgid "Host" #~ msgstr "Host" #~ msgid "LDAP" #~ msgstr "LDAP" #~ msgid "" #~ "Do not change this option unless you are sure it is required, since changing " #~ "back requires a full reinstall of Citadel." #~ msgstr "" #~ "Ändern Sie diese Option nur, falls Sie sich sicher sind, dass Sie sie " #~ "benötigen. Das Zurücksetzen dieser Option benötigt eine komplette " #~ "Neuinstallation von Citadel." #~ msgid "" #~ "While not mandatory, it is highly recommended that you set a password for " #~ "the administrator user." #~ msgstr "" #~ "Es ist zwar nicht zwingend notwendig, wird aber nachdrücklich empfohlen, " #~ "dass Sie für den administrativen Benutzer ein Passwort setzen." #~ msgid "Internal, Host, LDAP, Active Directory" #~ msgstr "Intern, Host, LDAP, Active Directory" #~ msgid "Enable external authentication mode?" #~ msgstr "Externen Authentifizierungsmodus aktivieren?" #~ msgid "" #~ "Please enter the name of the Citadel user account that should be granted " #~ "administrative privileges once created." #~ msgstr "" #~ "Geben Sie den Namen des Citadel-Benutzerkontos ein, dem nach der Erstellung " #~ "administrative Privilegien gewährt werden sollen." #~ msgid "" #~ "Specify the way authentication is done, either host based or Citadel " #~ "internal. Host based authentication could be /etc/passwd or an LDAP " #~ "directory. WARNING: do not change this setting once your system is " #~ "installed. Answer \"no\" unless you completely understand this option." #~ msgstr "" #~ "Geben Sie an, wie die Authentifizierung läuft, entweder Rechner-basiert oder " #~ "Citadel-intern. Rechner-basierte Authentifizierung könnte /etc/passwd oder " #~ "ein LDAP-Verzeichnis sein. WARNUNG: Ändern Sie diese Option nicht, nachdem " #~ "Ihr System installiert ist. Antworten Sie »Nein« falls Sie diese Option " #~ "nicht komplett verstehen." #~ msgid "" #~ "For post configuring your Citadel Server, use citadel-webcit with your " #~ "browser, log in as the user you specified as the Administrator, and review " #~ "the Points under the Administration menu. If you have further questions " #~ "review www.citadel.org, especially the FAQ and Documentation section." #~ msgstr "" #~ "Für die weitere Konfiguration Ihres Citadel-Servers verwenden Sie bitte " #~ "citadel-webcit in Ihrem Browser. Melden Sie sich dort als den Benutzer an, " #~ "den Sie eben als Administrator angegeben haben und schauen Sie dann die " #~ "Punkte im Administrations-Menü durch. Falls Sie weitere Fragen haben, lesen " #~ "Sie www.citadel.org, insbesondere die FAQ und den Dokumentations-Abschnitt." citadel-9.01/po/citadel-setup/ja.po0000644000000000000000000002737312507024051015671 0ustar rootroot# Copyright (C) 2009 Debian Citadel Team # This file is distributed under the same license as citadel package. # Hideki Yamane (Debian-JP) , 2009. # msgid "" msgstr "" "Project-Id-Version: citadel 7.66-1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2010-09-28 00:22+0200\n" "PO-Revision-Date: 2010-10-24 16:01+0000\n" "Last-Translator: Hideki Yamane (Debian-JP) \n" "Language-Team: Japanese \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2010-10-25 04:35+0000\n" "X-Generator: Launchpad (build Unknown)\n" "Language: ja\n" #: ../utils/setup.c:119 msgid "Citadel Home Directory" msgstr "" #: ../utils/setup.c:122 msgid "" "Enter the full pathname of the directory in which the Citadel\n" "installation you are creating or updating resides. If you\n" "specify a directory other than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" msgstr "" #: ../utils/setup.c:128 msgid "" "Enter the subdirectory name for an alternate installation of Citadel. To do " "a default installation just leave it blank.If you specify a directory other " "than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" "note that it may not have a leading /" msgstr "" #: ../utils/setup.c:135 msgid "Citadel administrator username:" msgstr "Citadel ã®ç®¡ç†è€…ユーザå:" #: ../utils/setup.c:137 msgid "" "Please enter the name of the Citadel user account that should be granted " "administrative privileges once created. If using internal authentication " "this user account will be created if it does not exist. For external " "authentication this user account has to exist." msgstr "" "作æˆå¾Œã«ç®¡ç†è€…特権を与ãˆã‚‹ Citadel " "ã®ãƒ¦ãƒ¼ã‚¶ã‚¢ã‚«ã‚¦ãƒ³ãƒˆåを入力ã—ã¦ãã ã•ã„。内部アカウントã§ã®èªè¨¼ã‚’使ã†å ´åˆã€ã“ã®ãƒ¦ãƒ¼ã‚¶ã‚¢ã‚«ã‚¦ãƒ³ãƒˆãŒå­˜åœ¨ã—ãªã„ã®ã§ã‚れã°ä½œæˆã•れã¾ã™ã€‚外部èªè¨¼ã®å ´åˆã¯ã€ã“ã®ãƒ¦" "ーザアカウントãŒå­˜åœ¨ã—ã¦ã„ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚" #: ../utils/setup.c:143 msgid "Administrator password:" msgstr "管ç†è€…パスワード:" #: ../utils/setup.c:145 msgid "" "Enter a password for the system administrator. When setup\n" "completes it will attempt to create the administrator user\n" "and set the password specified here.\n" msgstr "" #: ../utils/setup.c:149 msgid "Citadel User ID:" msgstr "" #: ../utils/setup.c:151 msgid "" "Citadel needs to run under its own user ID. This would\n" "typically be called \"citadel\", but if you are running Citadel\n" "as a public BBS, you might also call it \"bbs\" or \"guest\".\n" "The server will run under this user ID. Please specify that\n" "user ID here. You may specify either a user name or a numeric\n" "UID.\n" msgstr "" #: ../utils/setup.c:158 msgid "Listening address for the Citadel server:" msgstr "Citadel サーãƒã®å¾…ã¡å—ã‘アドレス:" #: ../utils/setup.c:160 msgid "" "Please specify the IP address which the server should be listening to. You " "can name a specific IPv4 or IPv6 address, or you can specify\n" "'*' for 'any address', '::' for 'any IPv6 address', or '0.0.0.0'\n" "for 'any IPv4 address'. If you leave this blank, Citadel will\n" "listen on all addresses. This can usually be left to the default unless " "multiple instances of Citadel are running on the same computer." msgstr "" #: ../utils/setup.c:168 msgid "Server port number:" msgstr "" #: ../utils/setup.c:170 msgid "" "Specify the TCP port number on which your server will run.\n" "Normally, this will be port 504, which is the official port\n" "assigned by the IANA for Citadel servers. You will only need\n" "to specify a different port number if you run multiple instances\n" "of Citadel on the same computer and there is something else\n" "already using port 504.\n" msgstr "" #: ../utils/setup.c:177 msgid "Authentication method to use:" msgstr "利用ã™ã‚‹èªè¨¼æ–¹å¼:" #: ../utils/setup.c:179 msgid "" "Please choose the user authentication mode. By default Citadel will use its " "own internal user accounts database. If you choose Host, Citadel users will " "have accounts on the host system, authenticated via /etc/passwd or a PAM " "source. LDAP chooses an RFC 2307 compliant directory server, the last option " "chooses the nonstandard MS Active Directory LDAP scheme.\n" "Do not change this option unless you are sure it is required, since changing " "back requires a full reinstall of Citadel.\n" " 0. Self contained authentication\n" " 1. Host system integrated authentication\n" " 2. External LDAP - RFC 2307 compliant directory\n" " 3. External LDAP - nonstandard MS Active Directory\n" "\n" "For help: http://www.citadel.org/doku.php/faq:installation:authmodes\n" "\n" "ANSWER \"0\" UNLESS YOU COMPLETELY UNDERSTAND THIS OPTION.\n" msgstr "" #: ../utils/setup.c:197 msgid "LDAP host:" msgstr "LDAP ホスト:" #: ../utils/setup.c:199 msgid "Please enter the host name or IP address of your LDAP server.\n" msgstr "LDAP サーãƒã®ãƒ›ã‚¹ãƒˆåã€ã‚ã‚‹ã„㯠IP アドレスを入力ã—ã¦ãã ã•ã„。\n" #: ../utils/setup.c:201 msgid "LDAP port number:" msgstr "LDAP ã®ãƒãƒ¼ãƒˆç•ªå·:" #: ../utils/setup.c:203 msgid "Please enter the port number of the LDAP service (usually 389).\n" msgstr "LDAP サービスã®ãƒãƒ¼ãƒˆç•ªå·ã‚’入力ã—ã¦ãã ã•ã„ (通常㯠389 ã§ã™)。\n" #: ../utils/setup.c:205 msgid "LDAP base DN:" msgstr "LDAP ベース DN:" #: ../utils/setup.c:207 msgid "" "Please enter the Base DN to search for authentication\n" "(for example: dc=example,dc=com)\n" msgstr "" "èªè¨¼ã®éš›ã€æ¤œç´¢ã«ä½¿ã†ãƒ™ãƒ¼ã‚¹ DN を入力ã—ã¦ãã ã•ã„\n" " (例:dc=example,dc=com)。\n" #: ../utils/setup.c:210 msgid "LDAP bind DN:" msgstr "LDAP ã¸ã®ãƒã‚¤ãƒ³ãƒ‰ã«ä½¿ã† DN:" #: ../utils/setup.c:212 msgid "" "Please enter the DN of an account to use for binding to the LDAP server for " "performing queries. The account does not require any other privileges. If " "your LDAP server allows anonymous queries, you can leave this blank.Please " "enter the DN of an account to use for binding to the LDAP server\n" "for performing queries. The account does not require any other\n" "privileges. If your LDAP server allows anonymous queries, you can\n" "leave this blank.\n" msgstr "" #: ../utils/setup.c:220 msgid "LDAP bind password:" msgstr "LDAP ã¸ã®ãƒã‚¤ãƒ³ãƒ‰ã«ä½¿ã†ãƒ‘スワード:" #: ../utils/setup.c:222 msgid "" "If you entered a Bind DN in the previous question, you must now enter\n" "the password associated with that account. Otherwise, you can leave this\n" "blank.\n" msgstr "" #: ../utils/setup.c:299 msgid "Yes/No" msgstr "" #: ../utils/setup.c:300 msgid "Yes" msgstr "" #: ../utils/setup.c:300 msgid "No" msgstr "" #: ../utils/setup.c:346 msgid "Press return to continue..." msgstr "" #: ../utils/setup.c:364 msgid "Important Message" msgstr "" #: ../utils/setup.c:379 msgid "Error" msgstr "" #: ../utils/setup.c:459 msgid "Adding service entry..." msgstr "" #. Other errors might mean something really did go wrong. #. #: ../utils/setup.c:463 ../utils/setup.c:510 ../utils/setup.c:518 msgid "Cannot open" msgstr "" #: ../utils/setup.c:569 msgid "" "Citadel already appears to be configured to start at boot.\n" "Would you like to keep your boot configuration as is?\n" msgstr "" #: ../utils/setup.c:577 msgid "Would you like to automatically start Citadel at boot?\n" msgstr "" #: ../utils/setup.c:583 msgid "Cannot create" msgstr "" #: ../utils/setup.c:682 #, c-format msgid "" "Setup can configure the \"xinetd\" service to automatically\n" "connect incoming telnet sessions to Citadel, bypassing the\n" "host system login: prompt. Would you like to do this?\n" msgstr "" #: ../utils/setup.c:740 msgid "You appear to have the " msgstr "" #: ../utils/setup.c:742 msgid "" " email program\n" "running on your system. If you want Citadel mail\n" "connected with " msgstr "" #: ../utils/setup.c:746 msgid "" " you will have to manually integrate\n" "them. It is preferable to disable " msgstr "" #: ../utils/setup.c:749 msgid "" ", and use Citadel's\n" "SMTP, POP3, and IMAP services.\n" "\n" "May we disable " msgstr "" #: ../utils/setup.c:753 msgid "" "so that Citadel has access to ports\n" "25, 110, and 143?\n" msgstr "" #: ../utils/setup.c:863 msgid "This is currently set to:" msgstr "" #: ../utils/setup.c:864 msgid "Enter new value or press return to leave unchanged:" msgstr "" #: ../utils/setup.c:1067 ../utils/setup.c:1072 ../utils/setup.c:1384 msgid "setup: cannot open" msgstr "" #: ../utils/setup.c:1175 #, c-format msgid "" "\n" "/etc/nsswitch.conf is configured to use the 'db' module for\n" "one or more services. This is not necessary on most systems,\n" "and it is known to crash the Citadel server when delivering\n" "mail to the Internet.\n" "\n" "Do you want this module to be automatically disabled?\n" "\n" msgstr "" #: ../utils/setup.c:1236 ../utils/setup.c:1252 msgid "Setup finished" msgstr "" #: ../utils/setup.c:1237 msgid "" "Setup of the Citadel server is complete.\n" "If you will be using WebCit, please run its\n" "setup program now; otherwise, run './citadel'\n" "to log in.\n" msgstr "" #: ../utils/setup.c:1243 msgid "Setup failed" msgstr "" #: ../utils/setup.c:1244 msgid "" "Setup is finished, but the Citadel server failed to start.\n" "Go back and check your configuration.\n" msgstr "" #: ../utils/setup.c:1253 msgid "Setup is finished. You may now start the server." msgstr "" #: ../utils/setup.c:1279 msgid "My System" msgstr "" #: ../utils/setup.c:1282 msgid "US 800 555 1212" msgstr "" #: ../utils/setup.c:1368 ../utils/setup.c:1373 msgid "setup: cannot append" msgstr "" #: ../utils/setup.c:1450 ../utils/setup.c:1457 ../utils/setup.c:1472 #: ../utils/setup.c:1512 msgid "Citadel Setup" msgstr "" #: ../utils/setup.c:1459 msgid "The directory you specified does not exist" msgstr "" #: ../utils/setup.c:1473 msgid "" "The Citadel service is still running.\n" "Please stop the service manually and run setup again." msgstr "" #: ../utils/setup.c:1485 msgid "Citadel setup program" msgstr "" #: ../utils/setup.c:1513 msgid "This Citadel installation is too old to be upgraded." msgstr "" #: ../utils/setup.c:1552 ../utils/setup.c:1554 ../utils/setup.c:1556 msgid "Setting file permissions" msgstr "" #~ msgid "" #~ "Please specify the IP address which the server should be listening to. If " #~ "you specify 0.0.0.0, the server will listen on all addresses." #~ msgstr "サーãƒãŒå¾…ã¡å—ã‘ã‚’ã™ã‚‹ IP アドレスを指定ã—ã¦ãã ã•ã„。0.0.0.0 を指定ã—ãŸå ´åˆã€ã‚µãƒ¼ãƒã¯ã™ã¹ã¦ã®ã‚¢ãƒ‰ãƒ¬ã‚¹ã§å¾…ã¡å—ã‘ã¾ã™ã€‚" #~ msgid "" #~ "This can usually be left to the default unless multiple instances of Citadel " #~ "are running on the same computer." #~ msgstr "åŒã˜ã‚³ãƒ³ãƒ”ュータ上ã§è¤‡æ•°ã® Citadel インスタンスãŒå‹•作ã—ã¦ã„ãªã‘れã°ã€ã“れã¯é€šå¸¸ãƒ‡ãƒ•ォルトã®ã¾ã¾ã§æ§‹ã„ã¾ã›ã‚“。" #~ msgid "Internal" #~ msgstr "内部アカウント" #~ msgid "Host" #~ msgstr "ホスト上" #~ msgid "LDAP" #~ msgstr "LDAP" #~ msgid "" #~ "Do not change this option unless you are sure it is required, since changing " #~ "back requires a full reinstall of Citadel." #~ msgstr "" #~ "何ãŒå¿…è¦ã‹åˆ†ã‹ã£ã¦ã„ãªã„å ´åˆã¯ã€ã“ã®ã‚ªãƒ—ションを変更ã—ãªã„ã§ãã ã•ã„ã€‚å¤‰æ›´ã‚’å…ƒã«æˆ»ã™ã«ã¯ Citadel をゼロã‹ã‚‰å†ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã™ã‚‹ã“ã¨ãŒå¿…è¦ã§ã™ã€‚" #~ msgid "" #~ "While not mandatory, it is highly recommended that you set a password for " #~ "the administrator user." #~ msgstr "å¿…é ˆã§ã¯ãªã„ã‚‚ã®ã®ã€ç®¡ç†è€…ユーザã«ãƒ‘スワードを設定ã™ã‚‹ã“ã¨ã‚’å¼·ããŠå‹§ã‚ã—ã¾ã™ã€‚" #~ msgid "Internal, Host, LDAP, Active Directory" #~ msgstr "内部アカウント, ホスト上, LDAP, Active Directory" citadel-9.01/po/citadel-setup/ar.po0000644000000000000000000003471212507024051015674 0ustar rootroot# translation of ar.po to Arabic # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # # Ossama M. Khayat , 2008, 2010. msgid "" msgstr "" "Project-Id-Version: ar\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2010-09-28 00:22+0200\n" "PO-Revision-Date: 2010-10-22 14:27+0000\n" "Last-Translator: Ossama M. Khayat \n" "Language-Team: Arabic \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2010-10-23 04:47+0000\n" "X-Generator: Launchpad (build Unknown)\n" "Language: ar\n" #: ../utils/setup.c:119 msgid "Citadel Home Directory" msgstr "" #: ../utils/setup.c:122 msgid "" "Enter the full pathname of the directory in which the Citadel\n" "installation you are creating or updating resides. If you\n" "specify a directory other than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" msgstr "" #: ../utils/setup.c:128 msgid "" "Enter the subdirectory name for an alternate installation of Citadel. To do " "a default installation just leave it blank.If you specify a directory other " "than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" "note that it may not have a leading /" msgstr "" #: ../utils/setup.c:135 msgid "Citadel administrator username:" msgstr "كلمة مرور مدير Citadel:" #: ../utils/setup.c:137 msgid "" "Please enter the name of the Citadel user account that should be granted " "administrative privileges once created. If using internal authentication " "this user account will be created if it does not exist. For external " "authentication this user account has to exist." msgstr "" "رجاء أدخل اسم حساب مستخدم Citadel الذي يجب أن يحصل على الصلاحيات الإدارية " "عند إنشاءه. إن كنت تستخدم المصادقة الداخلية ÙØ³ÙŠØªÙ… إنشاء هذا الحساب إن لم يكن " "موجوداً. أما ÙÙŠ حالة المصادقة الخارجية، Ùيجب أن يكون حساب المستخدم موجود " "مسبقاً." #: ../utils/setup.c:143 msgid "Administrator password:" msgstr "كلمة مرور المدير:" #: ../utils/setup.c:145 msgid "" "Enter a password for the system administrator. When setup\n" "completes it will attempt to create the administrator user\n" "and set the password specified here.\n" msgstr "" #: ../utils/setup.c:149 msgid "Citadel User ID:" msgstr "" #: ../utils/setup.c:151 msgid "" "Citadel needs to run under its own user ID. This would\n" "typically be called \"citadel\", but if you are running Citadel\n" "as a public BBS, you might also call it \"bbs\" or \"guest\".\n" "The server will run under this user ID. Please specify that\n" "user ID here. You may specify either a user name or a numeric\n" "UID.\n" msgstr "" #: ../utils/setup.c:158 msgid "Listening address for the Citadel server:" msgstr "عنوان الإصغاء لخادم Citadel:" #: ../utils/setup.c:160 msgid "" "Please specify the IP address which the server should be listening to. You " "can name a specific IPv4 or IPv6 address, or you can specify\n" "'*' for 'any address', '::' for 'any IPv6 address', or '0.0.0.0'\n" "for 'any IPv4 address'. If you leave this blank, Citadel will\n" "listen on all addresses. This can usually be left to the default unless " "multiple instances of Citadel are running on the same computer." msgstr "" #: ../utils/setup.c:168 msgid "Server port number:" msgstr "" #: ../utils/setup.c:170 msgid "" "Specify the TCP port number on which your server will run.\n" "Normally, this will be port 504, which is the official port\n" "assigned by the IANA for Citadel servers. You will only need\n" "to specify a different port number if you run multiple instances\n" "of Citadel on the same computer and there is something else\n" "already using port 504.\n" msgstr "" #: ../utils/setup.c:177 msgid "Authentication method to use:" msgstr "طريقة المصادقة المطلوب استخدامها:" #: ../utils/setup.c:179 msgid "" "Please choose the user authentication mode. By default Citadel will use its " "own internal user accounts database. If you choose Host, Citadel users will " "have accounts on the host system, authenticated via /etc/passwd or a PAM " "source. LDAP chooses an RFC 2307 compliant directory server, the last option " "chooses the nonstandard MS Active Directory LDAP scheme.\n" "Do not change this option unless you are sure it is required, since changing " "back requires a full reinstall of Citadel.\n" " 0. Self contained authentication\n" " 1. Host system integrated authentication\n" " 2. External LDAP - RFC 2307 compliant directory\n" " 3. External LDAP - nonstandard MS Active Directory\n" "\n" "For help: http://www.citadel.org/doku.php/faq:installation:authmodes\n" "\n" "ANSWER \"0\" UNLESS YOU COMPLETELY UNDERSTAND THIS OPTION.\n" msgstr "" #: ../utils/setup.c:197 msgid "LDAP host:" msgstr "مضي٠LDAP:" #: ../utils/setup.c:199 #, fuzzy msgid "Please enter the host name or IP address of your LDAP server.\n" msgstr "رجاء أدخل اسم المضي٠أو عنوان IP لخادم LDAP الخاص بك." #: ../utils/setup.c:201 msgid "LDAP port number:" msgstr "رقم Ù…Ù†ÙØ° LDAP:" #: ../utils/setup.c:203 #, fuzzy msgid "Please enter the port number of the LDAP service (usually 389).\n" msgstr "الرجاء إدخال رقم Ø§Ù„Ù…Ù†ÙØ° لخدمة LDAP (عادة 389)" #: ../utils/setup.c:205 msgid "LDAP base DN:" msgstr "LDAP base DN:" #: ../utils/setup.c:207 #, fuzzy msgid "" "Please enter the Base DN to search for authentication\n" "(for example: dc=example,dc=com)\n" msgstr "رجاء أدخل قيمة Base DN للبحث عن المصادقة (مثال: dc=example,dc=com)." #: ../utils/setup.c:210 msgid "LDAP bind DN:" msgstr "LDAP bind DN:" #: ../utils/setup.c:212 #, fuzzy msgid "" "Please enter the DN of an account to use for binding to the LDAP server for " "performing queries. The account does not require any other privileges. If " "your LDAP server allows anonymous queries, you can leave this blank.Please " "enter the DN of an account to use for binding to the LDAP server\n" "for performing queries. The account does not require any other\n" "privileges. If your LDAP server allows anonymous queries, you can\n" "leave this blank.\n" msgstr "" "رجاء اكتب قيمة DN للحساب المطلوب استخدامه للربط بخادم LDAP لتنÙيذ " "الاستعلامات. لا يتطلب هذا الحساب أي صلاحيات أخرى. إن كان خادم LDAP الخاص بك " "يسمح بالاستعلامات من قبل أي مستخدم، Ùيمكنك ترك هذه القيمة ÙØ§Ø±ØºØ©." #: ../utils/setup.c:220 msgid "LDAP bind password:" msgstr "كلمة مرور LDAP bind:" #: ../utils/setup.c:222 #, fuzzy msgid "" "If you entered a Bind DN in the previous question, you must now enter\n" "the password associated with that account. Otherwise, you can leave this\n" "blank.\n" msgstr "" "إن أدخلت قيمة Bind DN ÙÙŠ السؤال السابق، Ùيجب الآن أن تكتب كلمة المرور " "المرتبطة بذلك الحساب. وإلا، يمكنك ترك هذه ÙØ§Ø±ØºØ©." #: ../utils/setup.c:299 msgid "Yes/No" msgstr "" #: ../utils/setup.c:300 msgid "Yes" msgstr "" #: ../utils/setup.c:300 msgid "No" msgstr "" #: ../utils/setup.c:346 msgid "Press return to continue..." msgstr "" #: ../utils/setup.c:364 msgid "Important Message" msgstr "" #: ../utils/setup.c:379 msgid "Error" msgstr "" #: ../utils/setup.c:459 msgid "Adding service entry..." msgstr "" #. Other errors might mean something really did go wrong. #. #: ../utils/setup.c:463 ../utils/setup.c:510 ../utils/setup.c:518 msgid "Cannot open" msgstr "" #: ../utils/setup.c:569 msgid "" "Citadel already appears to be configured to start at boot.\n" "Would you like to keep your boot configuration as is?\n" msgstr "" #: ../utils/setup.c:577 msgid "Would you like to automatically start Citadel at boot?\n" msgstr "" #: ../utils/setup.c:583 msgid "Cannot create" msgstr "" #: ../utils/setup.c:682 #, c-format msgid "" "Setup can configure the \"xinetd\" service to automatically\n" "connect incoming telnet sessions to Citadel, bypassing the\n" "host system login: prompt. Would you like to do this?\n" msgstr "" #: ../utils/setup.c:740 msgid "You appear to have the " msgstr "" #: ../utils/setup.c:742 msgid "" " email program\n" "running on your system. If you want Citadel mail\n" "connected with " msgstr "" #: ../utils/setup.c:746 msgid "" " you will have to manually integrate\n" "them. It is preferable to disable " msgstr "" #: ../utils/setup.c:749 msgid "" ", and use Citadel's\n" "SMTP, POP3, and IMAP services.\n" "\n" "May we disable " msgstr "" #: ../utils/setup.c:753 msgid "" "so that Citadel has access to ports\n" "25, 110, and 143?\n" msgstr "" #: ../utils/setup.c:863 msgid "This is currently set to:" msgstr "" #: ../utils/setup.c:864 msgid "Enter new value or press return to leave unchanged:" msgstr "" #: ../utils/setup.c:1067 ../utils/setup.c:1072 ../utils/setup.c:1384 msgid "setup: cannot open" msgstr "" #: ../utils/setup.c:1175 #, c-format msgid "" "\n" "/etc/nsswitch.conf is configured to use the 'db' module for\n" "one or more services. This is not necessary on most systems,\n" "and it is known to crash the Citadel server when delivering\n" "mail to the Internet.\n" "\n" "Do you want this module to be automatically disabled?\n" "\n" msgstr "" #: ../utils/setup.c:1236 ../utils/setup.c:1252 msgid "Setup finished" msgstr "" #: ../utils/setup.c:1237 msgid "" "Setup of the Citadel server is complete.\n" "If you will be using WebCit, please run its\n" "setup program now; otherwise, run './citadel'\n" "to log in.\n" msgstr "" #: ../utils/setup.c:1243 msgid "Setup failed" msgstr "" #: ../utils/setup.c:1244 msgid "" "Setup is finished, but the Citadel server failed to start.\n" "Go back and check your configuration.\n" msgstr "" #: ../utils/setup.c:1253 msgid "Setup is finished. You may now start the server." msgstr "" #: ../utils/setup.c:1279 msgid "My System" msgstr "" #: ../utils/setup.c:1282 msgid "US 800 555 1212" msgstr "" #: ../utils/setup.c:1368 ../utils/setup.c:1373 msgid "setup: cannot append" msgstr "" #: ../utils/setup.c:1450 ../utils/setup.c:1457 ../utils/setup.c:1472 #: ../utils/setup.c:1512 msgid "Citadel Setup" msgstr "" #: ../utils/setup.c:1459 msgid "The directory you specified does not exist" msgstr "" #: ../utils/setup.c:1473 msgid "" "The Citadel service is still running.\n" "Please stop the service manually and run setup again." msgstr "" #: ../utils/setup.c:1485 msgid "Citadel setup program" msgstr "" #: ../utils/setup.c:1513 msgid "This Citadel installation is too old to be upgraded." msgstr "" #: ../utils/setup.c:1552 ../utils/setup.c:1554 ../utils/setup.c:1556 msgid "Setting file permissions" msgstr "" #~ msgid "" #~ "Please specify the IP address which the server should be listening to. If " #~ "you specify 0.0.0.0, the server will listen on all addresses." #~ msgstr "" #~ "الرجاء تحديد عنوان IP الذي يجب أن يصغي إليه الخادم. إن استخدمت العنوان " #~ "0.0.0.0ØŒ ÙØ³ÙŠØµØºÙŠ Ø§Ù„Ø®Ø§Ø¯Ù… لجميع العناوين." #~ msgid "" #~ "This can usually be left to the default unless multiple instances of Citadel " #~ "are running on the same computer." #~ msgstr "" #~ "يمكن ترك هذه القيمة Ø§Ù„Ø§ÙØªØ±Ø§Ø¶ÙŠØ© إلا إن كان هناك أكثر من نسخة Citadel تعمل على " #~ "Ù†ÙØ³ الجهاز." #~ msgid "Internal" #~ msgstr "داخلي" #~ msgid "Host" #~ msgstr "مضيÙ" #~ msgid "LDAP" #~ msgstr "LDAP" #~ msgid "" #~ "Do not change this option unless you are sure it is required, since " #~ "changing back requires a full reinstall of Citadel." #~ msgstr "" #~ "لا تغير هذا الخيار ما لم تكن متأكداً من أنه مطلوب، حيث أن استعادة قيمته " #~ "للحالية يتطلب إعادة تثبيت Citadel بالكامل." #~ msgid "" #~ "While not mandatory, it is highly recommended that you set a password for " #~ "the administrator user." #~ msgstr "" #~ "مع أنه ليس إلزامياً، ولكن من المستحسن جداً تعيين كلمة مرور للمستخدم المدير." #~ msgid "Enable external authentication mode?" #~ msgstr "تريد تمكين وضع المصادقة الخارجية؟" #, fuzzy #~ msgid "" #~ "Enter the name of the Citadel administrator (which is probably you). " #~ "When an account is created with this name, it will automatically be " #~ "given administrator-level access." #~ msgstr "" #~ msgid "" #~ "Please enter the name of the Citadel user account that should be granted " #~ "administrative privileges once created." #~ msgstr "" #~ "أدخل اسم مدير Citadel (والذي هو انت غالباً). عندما يتم إنشاء حساب المستخدم " #~ "بهذا الاسم، Ø³ÙŠÙØ¹Ø·Ù‰ صلاحيات وصول المدير تلقائياً." #~ msgid "" #~ "Specify the way authentication is done, either host based or Citadel " #~ "internal. Host based authentication could be /etc/passwd or an LDAP " #~ "directory. WARNING: do not change this setting once your system is " #~ "installed. Answer \"no\" unless you completely understand this option." #~ msgstr "" #~ "حدد الطريقة التي تتم بها المصادقة، إما عبر اسم المضي٠أو طريقة Citadel " #~ "الخاصة. تكون مصادقة المضي٠عبر /etc/passwd أو دليل LDAP. تنبيه: لا ØªÙØºÙŠÙ‘ر " #~ "هذا الخيار عقب تثبيت نظامك. أجب بالنÙÙŠ ما لم تكن تÙهم هذا الخيار تماماً." #~ msgid "" #~ "For post configuring your Citadel Server, use citadel-webcit with your " #~ "browser, log in as the user you specified as the Administrator, and " #~ "review the Points under the Administration menu. If you have further " #~ "questions review www.citadel.org, especially the FAQ and Documentation " #~ "section." #~ msgstr "" #~ "كي تقوم بتهيئة خادم Citadel لاحقاً، استخدم citadel-webcit من خلال Ù…ØªØµÙØ­ÙƒØŒ " #~ "ادخل باسم المستخدم الذي حددته كمدير، واستعرض النقاط ضمن قائمة الإدارة. إن " #~ "كانت لديك أسئلة أخرى رجاءً Ø²ÙØ± الموقع www.citadel.orgØŒ وبالتحديد قسم " #~ "الأسئلة المتكررة والتوثيق." citadel-9.01/po/citadel-setup/pl.po0000644000000000000000000002250012507024051015675 0ustar rootroot# Polish translation for citadel # Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013 # This file is distributed under the same license as the citadel package. # FIRST AUTHOR , 2013. # msgid "" msgstr "" "Project-Id-Version: citadel\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2010-09-28 00:22+0200\n" "PO-Revision-Date: 2013-11-27 07:09+0000\n" "Last-Translator: FULL NAME \n" "Language-Team: Polish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2013-11-28 05:10+0000\n" "X-Generator: Launchpad (build 16847)\n" #: ../utils/setup.c:119 msgid "Citadel Home Directory" msgstr "" #: ../utils/setup.c:122 msgid "" "Enter the full pathname of the directory in which the Citadel\n" "installation you are creating or updating resides. If you\n" "specify a directory other than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" msgstr "" #: ../utils/setup.c:128 msgid "" "Enter the subdirectory name for an alternate installation of Citadel. To do " "a default installation just leave it blank.If you specify a directory other " "than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" "note that it may not have a leading /" msgstr "" #: ../utils/setup.c:135 msgid "Citadel administrator username:" msgstr "" #: ../utils/setup.c:137 msgid "" "Please enter the name of the Citadel user account that should be granted " "administrative privileges once created. If using internal authentication " "this user account will be created if it does not exist. For external " "authentication this user account has to exist." msgstr "" #: ../utils/setup.c:143 msgid "Administrator password:" msgstr "" #: ../utils/setup.c:145 msgid "" "Enter a password for the system administrator. When setup\n" "completes it will attempt to create the administrator user\n" "and set the password specified here.\n" msgstr "" #: ../utils/setup.c:149 msgid "Citadel User ID:" msgstr "" #: ../utils/setup.c:151 msgid "" "Citadel needs to run under its own user ID. This would\n" "typically be called \"citadel\", but if you are running Citadel\n" "as a public BBS, you might also call it \"bbs\" or \"guest\".\n" "The server will run under this user ID. Please specify that\n" "user ID here. You may specify either a user name or a numeric\n" "UID.\n" msgstr "" #: ../utils/setup.c:158 msgid "Listening address for the Citadel server:" msgstr "" #: ../utils/setup.c:160 msgid "" "Please specify the IP address which the server should be listening to. You " "can name a specific IPv4 or IPv6 address, or you can specify\n" "'*' for 'any address', '::' for 'any IPv6 address', or '0.0.0.0'\n" "for 'any IPv4 address'. If you leave this blank, Citadel will\n" "listen on all addresses. This can usually be left to the default unless " "multiple instances of Citadel are running on the same computer." msgstr "" #: ../utils/setup.c:168 msgid "Server port number:" msgstr "" #: ../utils/setup.c:170 msgid "" "Specify the TCP port number on which your server will run.\n" "Normally, this will be port 504, which is the official port\n" "assigned by the IANA for Citadel servers. You will only need\n" "to specify a different port number if you run multiple instances\n" "of Citadel on the same computer and there is something else\n" "already using port 504.\n" msgstr "" #: ../utils/setup.c:177 msgid "Authentication method to use:" msgstr "" #: ../utils/setup.c:179 msgid "" "Please choose the user authentication mode. By default Citadel will use its " "own internal user accounts database. If you choose Host, Citadel users will " "have accounts on the host system, authenticated via /etc/passwd or a PAM " "source. LDAP chooses an RFC 2307 compliant directory server, the last option " "chooses the nonstandard MS Active Directory LDAP scheme.\n" "Do not change this option unless you are sure it is required, since changing " "back requires a full reinstall of Citadel.\n" " 0. Self contained authentication\n" " 1. Host system integrated authentication\n" " 2. External LDAP - RFC 2307 compliant directory\n" " 3. External LDAP - nonstandard MS Active Directory\n" "\n" "For help: http://www.citadel.org/doku.php/faq:installation:authmodes\n" "\n" "ANSWER \"0\" UNLESS YOU COMPLETELY UNDERSTAND THIS OPTION.\n" msgstr "" #: ../utils/setup.c:197 msgid "LDAP host:" msgstr "" #: ../utils/setup.c:199 msgid "Please enter the host name or IP address of your LDAP server.\n" msgstr "" #: ../utils/setup.c:201 msgid "LDAP port number:" msgstr "" #: ../utils/setup.c:203 msgid "Please enter the port number of the LDAP service (usually 389).\n" msgstr "" #: ../utils/setup.c:205 msgid "LDAP base DN:" msgstr "" #: ../utils/setup.c:207 msgid "" "Please enter the Base DN to search for authentication\n" "(for example: dc=example,dc=com)\n" msgstr "" #: ../utils/setup.c:210 msgid "LDAP bind DN:" msgstr "" #: ../utils/setup.c:212 msgid "" "Please enter the DN of an account to use for binding to the LDAP server for " "performing queries. The account does not require any other privileges. If " "your LDAP server allows anonymous queries, you can leave this blank.Please " "enter the DN of an account to use for binding to the LDAP server\n" "for performing queries. The account does not require any other\n" "privileges. If your LDAP server allows anonymous queries, you can\n" "leave this blank.\n" msgstr "" #: ../utils/setup.c:220 msgid "LDAP bind password:" msgstr "" #: ../utils/setup.c:222 msgid "" "If you entered a Bind DN in the previous question, you must now enter\n" "the password associated with that account. Otherwise, you can leave this\n" "blank.\n" msgstr "" #: ../utils/setup.c:299 msgid "Yes/No" msgstr "" #: ../utils/setup.c:300 msgid "Yes" msgstr "" #: ../utils/setup.c:300 msgid "No" msgstr "" #: ../utils/setup.c:346 msgid "Press return to continue..." msgstr "" #: ../utils/setup.c:364 msgid "Important Message" msgstr "" #: ../utils/setup.c:379 msgid "Error" msgstr "" #: ../utils/setup.c:459 msgid "Adding service entry..." msgstr "" #. Other errors might mean something really did go wrong. #. #: ../utils/setup.c:463 ../utils/setup.c:510 ../utils/setup.c:518 msgid "Cannot open" msgstr "" #: ../utils/setup.c:569 msgid "" "Citadel already appears to be configured to start at boot.\n" "Would you like to keep your boot configuration as is?\n" msgstr "" #: ../utils/setup.c:577 msgid "Would you like to automatically start Citadel at boot?\n" msgstr "" #: ../utils/setup.c:583 msgid "Cannot create" msgstr "" #: ../utils/setup.c:682 #, c-format msgid "" "Setup can configure the \"xinetd\" service to automatically\n" "connect incoming telnet sessions to Citadel, bypassing the\n" "host system login: prompt. Would you like to do this?\n" msgstr "" #: ../utils/setup.c:740 msgid "You appear to have the " msgstr "" #: ../utils/setup.c:742 msgid "" " email program\n" "running on your system. If you want Citadel mail\n" "connected with " msgstr "" #: ../utils/setup.c:746 msgid "" " you will have to manually integrate\n" "them. It is preferable to disable " msgstr "" #: ../utils/setup.c:749 msgid "" ", and use Citadel's\n" "SMTP, POP3, and IMAP services.\n" "\n" "May we disable " msgstr "" #: ../utils/setup.c:753 msgid "" "so that Citadel has access to ports\n" "25, 110, and 143?\n" msgstr "" #: ../utils/setup.c:863 msgid "This is currently set to:" msgstr "" #: ../utils/setup.c:864 msgid "Enter new value or press return to leave unchanged:" msgstr "" #: ../utils/setup.c:1067 ../utils/setup.c:1072 ../utils/setup.c:1384 msgid "setup: cannot open" msgstr "" #: ../utils/setup.c:1175 #, c-format msgid "" "\n" "/etc/nsswitch.conf is configured to use the 'db' module for\n" "one or more services. This is not necessary on most systems,\n" "and it is known to crash the Citadel server when delivering\n" "mail to the Internet.\n" "\n" "Do you want this module to be automatically disabled?\n" "\n" msgstr "" #: ../utils/setup.c:1236 ../utils/setup.c:1252 msgid "Setup finished" msgstr "" #: ../utils/setup.c:1237 msgid "" "Setup of the Citadel server is complete.\n" "If you will be using WebCit, please run its\n" "setup program now; otherwise, run './citadel'\n" "to log in.\n" msgstr "" #: ../utils/setup.c:1243 msgid "Setup failed" msgstr "" #: ../utils/setup.c:1244 msgid "" "Setup is finished, but the Citadel server failed to start.\n" "Go back and check your configuration.\n" msgstr "" #: ../utils/setup.c:1253 msgid "Setup is finished. You may now start the server." msgstr "" #: ../utils/setup.c:1279 msgid "My System" msgstr "" #: ../utils/setup.c:1282 msgid "US 800 555 1212" msgstr "" #: ../utils/setup.c:1368 ../utils/setup.c:1373 msgid "setup: cannot append" msgstr "" #: ../utils/setup.c:1450 ../utils/setup.c:1457 ../utils/setup.c:1472 #: ../utils/setup.c:1512 msgid "Citadel Setup" msgstr "" #: ../utils/setup.c:1459 msgid "The directory you specified does not exist" msgstr "" #: ../utils/setup.c:1473 msgid "" "The Citadel service is still running.\n" "Please stop the service manually and run setup again." msgstr "" #: ../utils/setup.c:1485 msgid "Citadel setup program" msgstr "" #: ../utils/setup.c:1513 msgid "This Citadel installation is too old to be upgraded." msgstr "" #: ../utils/setup.c:1552 ../utils/setup.c:1554 ../utils/setup.c:1556 msgid "Setting file permissions" msgstr "" citadel-9.01/po/citadel-setup/fr.po0000644000000000000000000004357512507024051015710 0ustar rootroot# Translation of citadel debconf templates to French # Copyright (C) 2005-2009 Debian French l10n team # This file is distributed under the same license as the citadel package. # # Translators: # Jean-Luc Coulon (f5ibh) , 2008. # Christian Perrier , 2008, 2009. # Nicolas Delvaux msgid "" msgstr "" "Project-Id-Version: fr-new\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2010-09-28 00:22+0200\n" "PO-Revision-Date: 2012-04-29 19:54+0100\n" "Last-Translator: François LANKAR \n" "Language-Team: French \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2012-06-06 04:33+0000\n" "X-Generator: Launchpad (build 15353)\n" "Language: fr\n" #: ../utils/setup.c:119 msgid "Citadel Home Directory" msgstr "Répertoire principal Citadel" #: ../utils/setup.c:122 msgid "" "Enter the full pathname of the directory in which the Citadel\n" "installation you are creating or updating resides. If you\n" "specify a directory other than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" msgstr "" "Entrez le chemin complet du répertoire dans lequel se trouve\n" "l'installation de Citadel que vous créez ou mettez à jour. Si vous\n" "spécifiez un répertoire différent de celui par défaut, vous devrez\n" "indiquer le paramètre -f au serveur lorsque vous le démarrerez.\n" #: ../utils/setup.c:128 msgid "" "Enter the subdirectory name for an alternate installation of Citadel. To do " "a default installation just leave it blank.If you specify a directory other " "than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" "note that it may not have a leading /" msgstr "" "Entrez le chemin complet du répertoire dans lequel se trouve\n" "l'installation de Citadel que vous créez ou mettez à jour. Si vous\n" "spécifiez un répertoire autre que celui par défaut, vous devrez\n" "indiquer le paramètre -f au serveur lorsque vous le démarrerez." #: ../utils/setup.c:135 msgid "Citadel administrator username:" msgstr "Identifiant de l'administrateur de Citadel:" #: ../utils/setup.c:137 msgid "" "Please enter the name of the Citadel user account that should be granted " "administrative privileges once created. If using internal authentication " "this user account will be created if it does not exist. For external " "authentication this user account has to exist." msgstr "" "Veuillez indiquer l'identifiant Citadel qui disposera des privilèges " "d'administration après création. Si le système interne d'authentification " "est utilisé, ce compte sera créé s'il n'existe déjà. Si un système externe " "d'authentification est utilisé, ce compte doit déjà y exister." #: ../utils/setup.c:143 msgid "Administrator password:" msgstr "Mot de passe de l'administrateur:" #: ../utils/setup.c:145 msgid "" "Enter a password for the system administrator. When setup\n" "completes it will attempt to create the administrator user\n" "and set the password specified here.\n" msgstr "" "Entrer le mot de passe de l'administrateur du système. Lors de la\n" "finalisation de l'installation un compte administrateur sera créé\n" "avec le mot de passe défini ici.\n" #: ../utils/setup.c:149 msgid "Citadel User ID:" msgstr "ID utilisateur Citadel :" #: ../utils/setup.c:151 msgid "" "Citadel needs to run under its own user ID. This would\n" "typically be called \"citadel\", but if you are running Citadel\n" "as a public BBS, you might also call it \"bbs\" or \"guest\".\n" "The server will run under this user ID. Please specify that\n" "user ID here. You may specify either a user name or a numeric\n" "UID.\n" msgstr "" "Citadel doit s'exécuter avec son propre identifiant utilisateur " "généralement\n" "appelé \"citadel\", mais si vous lancez Citadel en tant que BBS publique, " "vous\n" "pouvez aussi l'appeler \"bbs\" ou \"guest\". Le serveur utilisera cet " "identifiant.\n" "Renseignez ici cet identifiant utilisateur. Vous pouvez renseigner soit le " "nom\n" "d'utilisateur soit son UID numérique.\n" #: ../utils/setup.c:158 msgid "Listening address for the Citadel server:" msgstr "Adresse IP où Citadel sera à l'écoute :" #: ../utils/setup.c:160 msgid "" "Please specify the IP address which the server should be listening to. You " "can name a specific IPv4 or IPv6 address, or you can specify\n" "'*' for 'any address', '::' for 'any IPv6 address', or '0.0.0.0'\n" "for 'any IPv4 address'. If you leave this blank, Citadel will\n" "listen on all addresses. This can usually be left to the default unless " "multiple instances of Citadel are running on the same computer." msgstr "" "Définissez l'adresse IP sur laquelle le serveur sera en écoute. Vous pourrez " "renseigner une adresse IPv4 ou IPv6 spécifique, ou renseigner\n" "'*' pour 'n'importe quelle adresse', '::' pour 'n'importe quelle adresse " "IPv6', ou '0.0.0.0'\n" "pour 'n'importe quelle adresse IPv4'. Si vous laissez vide, Citadel sera en " "écoute\n" "sur toutes les adresses. Ce champ est généralement laissé à sa valeur par " "défaut à moins d'exécuter plusieurs instances de Citadel sur le même server." #: ../utils/setup.c:168 msgid "Server port number:" msgstr "Numéro de port du serveur :" #: ../utils/setup.c:170 msgid "" "Specify the TCP port number on which your server will run.\n" "Normally, this will be port 504, which is the official port\n" "assigned by the IANA for Citadel servers. You will only need\n" "to specify a different port number if you run multiple instances\n" "of Citadel on the same computer and there is something else\n" "already using port 504.\n" msgstr "" "Définissez le port TCP sur lequel le serveur sera en écoute.\n" "Généralement, ce port est le 504, correspondant au numéro\n" "de port officiel attribué aux serveurs Citadel par le IANA.\n" "Vous n'aurez besoin de définir un autre numéro de port que\n" "si vous lancez plusieurs instances de Citadel sur la même machine\n" "ou qu'une autre application utilise déjà le port 504.\n" #: ../utils/setup.c:177 msgid "Authentication method to use:" msgstr "Méthode d'authentification à utiliser :" #: ../utils/setup.c:179 msgid "" "Please choose the user authentication mode. By default Citadel will use its " "own internal user accounts database. If you choose Host, Citadel users will " "have accounts on the host system, authenticated via /etc/passwd or a PAM " "source. LDAP chooses an RFC 2307 compliant directory server, the last option " "chooses the nonstandard MS Active Directory LDAP scheme.\n" "Do not change this option unless you are sure it is required, since changing " "back requires a full reinstall of Citadel.\n" " 0. Self contained authentication\n" " 1. Host system integrated authentication\n" " 2. External LDAP - RFC 2307 compliant directory\n" " 3. External LDAP - nonstandard MS Active Directory\n" "\n" "For help: http://www.citadel.org/doku.php/faq:installation:authmodes\n" "\n" "ANSWER \"0\" UNLESS YOU COMPLETELY UNDERSTAND THIS OPTION.\n" msgstr "" "Sélectionnez le mode d'authentification utilisateur. Par défaut Citadel " "utilise ses propres comptes utilisateur interne de base de données. Si vous " "choisissez Authentification Interne, les utilisateurs Citadel auront leurs " "comptes sur le système hôte, authentifié par /etc/passwd ou par une source " "PAM. Faire le choix LDAP pour un serveur-répertoire compatible avec la RFC " "2307, et en dernier choix le schéma LDAP non-standard de MS Active " "Directory.\n" "Ne changez cette option que si vous êtes sûr que c'est nécessaire, car le " "retour arrière nécessite une réinstallation complète de Citadel.\n" " 0. Auto-authentification\n" " 1. Authentification interne au système\n" " 2. LDAP externe - répertoire compatible RFC 2307\n" " 3. LDAP externe - non-standard MS Active Directory\n" "\n" "Aide à : http://www.citadel.org/doku.php/faq:installation:authmodes\n" "\n" "CHOISISSEZ \"0\" SI VOUS NE MAITRISEZ PAS COMPLETEMENT CETTE OPTION.\n" #: ../utils/setup.c:197 msgid "LDAP host:" msgstr "Serveur LDAP :" #: ../utils/setup.c:199 msgid "Please enter the host name or IP address of your LDAP server.\n" msgstr "Veuillez indiquer le nom d'hôte ou l'adresse IP du serveur LDAP.\n" #: ../utils/setup.c:201 msgid "LDAP port number:" msgstr "Port du serveur LDAP:" #: ../utils/setup.c:203 msgid "Please enter the port number of the LDAP service (usually 389).\n" msgstr "" "Veuillez indiquer le numéro du port d'écoute pour le serveur LDAP (en " "général 389).\n" #: ../utils/setup.c:205 msgid "LDAP base DN:" msgstr "DN de base du serveur LDAP:" #: ../utils/setup.c:207 msgid "" "Please enter the Base DN to search for authentication\n" "(for example: dc=example,dc=com)\n" msgstr "" "Veuillez indiquer la base de recherche pour l'authentification LDAP " "(p. ex. dc=example,dc=com).\n" #: ../utils/setup.c:210 msgid "LDAP bind DN:" msgstr "Compte de connexion LDAP:" #: ../utils/setup.c:212 msgid "" "Please enter the DN of an account to use for binding to the LDAP server for " "performing queries. The account does not require any other privileges. If " "your LDAP server allows anonymous queries, you can leave this blank.Please " "enter the DN of an account to use for binding to the LDAP server\n" "for performing queries. The account does not require any other\n" "privileges. If your LDAP server allows anonymous queries, you can\n" "leave this blank.\n" msgstr "" "Veuillez entrer l'identifiant unique d'un compte à utiliser pour la liaison " "avec le serveur LDAP pour l'exécution des requêtes. Le compte ne nécessite " "pas d'autres privilèges. Si votre serveur LDAP autorise les requêtes " "anonymes, vous pouvez laisser ce champ vide.\n" #: ../utils/setup.c:220 msgid "LDAP bind password:" msgstr "Mot de passe de connexion LDAP:" #: ../utils/setup.c:222 msgid "" "If you entered a Bind DN in the previous question, you must now enter\n" "the password associated with that account. Otherwise, you can leave this\n" "blank.\n" msgstr "" "Si vous avez entré un identifiant Bind à la question précédente, vous devez " "maintenant entrer \n" "le mot de passe associé à ce compte. Sinon, vous pouvez laisser ce \n" "champ vide.\n" #: ../utils/setup.c:299 msgid "Yes/No" msgstr "Oui/Non" #: ../utils/setup.c:300 msgid "Yes" msgstr "Oui" #: ../utils/setup.c:300 msgid "No" msgstr "Non" #: ../utils/setup.c:346 msgid "Press return to continue..." msgstr "Appuyer sur entrée pour continuer…" #: ../utils/setup.c:364 msgid "Important Message" msgstr "Message important" #: ../utils/setup.c:379 msgid "Error" msgstr "Erreur" #: ../utils/setup.c:459 msgid "Adding service entry..." msgstr "Ajouter un service" #. Other errors might mean something really did go wrong. #. #: ../utils/setup.c:463 ../utils/setup.c:510 ../utils/setup.c:518 msgid "Cannot open" msgstr "Ne peut être ouvert" #: ../utils/setup.c:569 msgid "" "Citadel already appears to be configured to start at boot.\n" "Would you like to keep your boot configuration as is?\n" msgstr "" "Citadel est déjà configuré pour se lancer au démarrage.\n" "Voulez-vous garder cette configuration de démarrage ?\n" #: ../utils/setup.c:577 msgid "Would you like to automatically start Citadel at boot?\n" msgstr "Voulez-vous lancer Citadel au démarrage ?\n" #: ../utils/setup.c:583 msgid "Cannot create" msgstr "Ne peut être créé" #: ../utils/setup.c:682 #, c-format msgid "" "Setup can configure the \"xinetd\" service to automatically\n" "connect incoming telnet sessions to Citadel, bypassing the\n" "host system login: prompt. Would you like to do this?\n" msgstr "" "Le programme d'installation peut configurer le service \"xinetd\" pour se " "connecter\n" "automatiquement à des sessions telnet entrantes vers Citadel, outrepassant " "l'invite \n" "de connexion au système hôte. Voulez-vous faire cela ?\n" #: ../utils/setup.c:740 msgid "You appear to have the " msgstr "Vous semblez avoir le " #: ../utils/setup.c:742 msgid "" " email program\n" "running on your system. If you want Citadel mail\n" "connected with " msgstr "" " programme de messagerie \n" "en cours d'exécution sur votre système. Si vous voulez que la messagerie de " "Citadel y soit \n" "connecté " #: ../utils/setup.c:746 msgid "" " you will have to manually integrate\n" "them. It is preferable to disable " msgstr "" " vous devrez les intégrer manuellement.\n" "Il est préférable de désactiver " #: ../utils/setup.c:749 msgid "" ", and use Citadel's\n" "SMTP, POP3, and IMAP services.\n" "\n" "May we disable " msgstr "" ", et utilise les services\n" "SMTP, POP3 et IMAP de Citadel.\n" "\n" "Nous pourrions désactiver " #: ../utils/setup.c:753 msgid "" "so that Citadel has access to ports\n" "25, 110, and 143?\n" msgstr "" "de telle sorte que Citadel ait accès aux ports \n" "25, 110 et 143 ?\n" #: ../utils/setup.c:863 msgid "This is currently set to:" msgstr "Actuellement défini à :" #: ../utils/setup.c:864 msgid "Enter new value or press return to leave unchanged:" msgstr "" "Entrez une nouvelle valeur ou appuyez sur entrée pour laisser la valeur " "actuelle :" #: ../utils/setup.c:1067 ../utils/setup.c:1072 ../utils/setup.c:1384 msgid "setup: cannot open" msgstr "setup : ne peut être ouvert" #: ../utils/setup.c:1175 #, c-format msgid "" "\n" "/etc/nsswitch.conf is configured to use the 'db' module for\n" "one or more services. This is not necessary on most systems,\n" "and it is known to crash the Citadel server when delivering\n" "mail to the Internet.\n" "\n" "Do you want this module to be automatically disabled?\n" "\n" msgstr "" "\n" "/etc/nsswitch.conf est configuré pour utiliser le module « db » pour \n" "un ou plusieurs services. Ce n'est pas nécessaire sur la plupart des " "systèmes, \n" "et il est connu pour faire planter le serveur Citadel lorsqu'il envoie \n" "un courrier sur Internet. \n" "\n" "Voulez-vous que ce module soit désactivé automatiquement ?\n" "\n" #: ../utils/setup.c:1236 ../utils/setup.c:1252 msgid "Setup finished" msgstr "Installation terminée" #: ../utils/setup.c:1237 msgid "" "Setup of the Citadel server is complete.\n" "If you will be using WebCit, please run its\n" "setup program now; otherwise, run './citadel'\n" "to log in.\n" msgstr "" "L'installation du serveur Citadel est terminée.\n" "Si vous voulez utiliser WebCit, veuillez lancer\n" "maintenant son programme d'installation ;\n" "sinon, lancez « ./citadel » pour vous identifier.\n" #: ../utils/setup.c:1243 msgid "Setup failed" msgstr "Installation échouée" #: ../utils/setup.c:1244 msgid "" "Setup is finished, but the Citadel server failed to start.\n" "Go back and check your configuration.\n" msgstr "" "L'installation est terminée mais le serveur Citadel n'a pu être lancé.\n" "Retournez en arrière pour vérifier votre configuration.\n" #: ../utils/setup.c:1253 msgid "Setup is finished. You may now start the server." msgstr "" "L'installation est terminée. Vous pouvez maintenant lancer le serveur." #: ../utils/setup.c:1279 msgid "My System" msgstr "Mon Système" #: ../utils/setup.c:1282 msgid "US 800 555 1212" msgstr "US 800 555 1212" #: ../utils/setup.c:1368 ../utils/setup.c:1373 msgid "setup: cannot append" msgstr "Programme d'installation : ne peut pas ajouter" #: ../utils/setup.c:1450 ../utils/setup.c:1457 ../utils/setup.c:1472 #: ../utils/setup.c:1512 msgid "Citadel Setup" msgstr "Installation de Citadel" #: ../utils/setup.c:1459 msgid "The directory you specified does not exist" msgstr "Le répertoire indiqué n'existe pas." #: ../utils/setup.c:1473 msgid "" "The Citadel service is still running.\n" "Please stop the service manually and run setup again." msgstr "" "Le service Citadel est toujours en cours d'exécution.\n" "Arrêtez le service manuellement et relancer l'installation." #: ../utils/setup.c:1485 msgid "Citadel setup program" msgstr "Programme d'installation de Citadel" #: ../utils/setup.c:1513 msgid "This Citadel installation is too old to be upgraded." msgstr "" "Cette installation de Citadel est trop ancienne pour être mise à jour." #: ../utils/setup.c:1552 ../utils/setup.c:1554 ../utils/setup.c:1556 msgid "Setting file permissions" msgstr "Définition des autorisations de fichiers" #~ msgid "" #~ "Please specify the IP address which the server should be listening to. If " #~ "you specify 0.0.0.0, the server will listen on all addresses." #~ msgstr "" #~ "Veuillez indiquer l'adresse IP sur laquelle le serveur sera actif. Si " #~ "vous indiquez 0.0.0.0, Citadel sera à l'écoute de toutes les adresses." #~ msgid "" #~ "This can usually be left to the default unless multiple instances of " #~ "Citadel are running on the same computer." #~ msgstr "" #~ "Vous pouvez normalement sauter cette étape à moins que plusieurs " #~ "instances de Citadel ne tournent sur le même ordinateur." #~ msgid "Internal" #~ msgstr "Interne" #~ msgid "Host" #~ msgstr "Hôte" #~ msgid "LDAP" #~ msgstr "LDAP" #~ msgid "" #~ "Do not change this option unless you are sure it is required, since " #~ "changing back requires a full reinstall of Citadel." #~ msgstr "" #~ "Ne modifiez cette option que si elle est indispensable car il n'est pas " #~ "possible de la changer sans entièrement réinstaller Citadel." #~ msgid "" #~ "While not mandatory, it is highly recommended that you set a password for " #~ "the administrator user." #~ msgstr "" #~ "Bien que cela ne soit pas indispensable, il est fortement conseillé de " #~ "choisir un mot de passe pour l'utilisateur qui disposera des privilèges " #~ "d'administration." #~ msgid "Internal, Host, LDAP, Active Directory" #~ msgstr "Interne, Hôte, LDAP, Active Directory" #~ msgid "Enable external authentication mode?" #~ msgstr "Faut-il activer le mode d'authentification ?" #~ msgid "" #~ "Please enter the name of the Citadel user account that should be granted " #~ "administrative privileges once created." #~ msgstr "" #~ "Veuillez indiquer l'identifiant qui bénéficiera des privilèges " #~ "d'administration de Citadel." citadel-9.01/po/citadel-setup/sv.po0000644000000000000000000002745412507024051015727 0ustar rootroot# translation of citadel.po to Swedish # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the Citadel package. # # Martin Bagge , 2008. msgid "" msgstr "" "Project-Id-Version: citadel\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2010-09-28 00:22+0200\n" "PO-Revision-Date: 2010-10-24 15:58+0000\n" "Last-Translator: brother \n" "Language-Team: swedish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2010-10-25 04:36+0000\n" "X-Generator: Launchpad (build Unknown)\n" "X-Poedit-Country: Sweden\n" "Language: sv\n" "X-Poedit-Language: Swedish\n" #: ../utils/setup.c:119 msgid "Citadel Home Directory" msgstr "" #: ../utils/setup.c:122 msgid "" "Enter the full pathname of the directory in which the Citadel\n" "installation you are creating or updating resides. If you\n" "specify a directory other than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" msgstr "" #: ../utils/setup.c:128 msgid "" "Enter the subdirectory name for an alternate installation of Citadel. To do " "a default installation just leave it blank.If you specify a directory other " "than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" "note that it may not have a leading /" msgstr "" #: ../utils/setup.c:135 msgid "Citadel administrator username:" msgstr "Användarnamn för Citadel-administratören:" #: ../utils/setup.c:137 msgid "" "Please enter the name of the Citadel user account that should be granted " "administrative privileges once created. If using internal authentication " "this user account will be created if it does not exist. For external " "authentication this user account has to exist." msgstr "" "Ange namnet pÃ¥ det användarkonto i Citadel som ska ha administrativa " "rättigheter när det skapas. Om intern autentisering används kommer detta " "användarkonto att skapas om det inte existerar. För extern autentisering " "mÃ¥ste detta användarkonto existera frÃ¥n början." #: ../utils/setup.c:143 msgid "Administrator password:" msgstr "Administratörens lösenord:" #: ../utils/setup.c:145 msgid "" "Enter a password for the system administrator. When setup\n" "completes it will attempt to create the administrator user\n" "and set the password specified here.\n" msgstr "" #: ../utils/setup.c:149 msgid "Citadel User ID:" msgstr "" #: ../utils/setup.c:151 msgid "" "Citadel needs to run under its own user ID. This would\n" "typically be called \"citadel\", but if you are running Citadel\n" "as a public BBS, you might also call it \"bbs\" or \"guest\".\n" "The server will run under this user ID. Please specify that\n" "user ID here. You may specify either a user name or a numeric\n" "UID.\n" msgstr "" #: ../utils/setup.c:158 msgid "Listening address for the Citadel server:" msgstr "Adress som Citadel-servern ska lyssna pÃ¥:" #: ../utils/setup.c:160 msgid "" "Please specify the IP address which the server should be listening to. You " "can name a specific IPv4 or IPv6 address, or you can specify\n" "'*' for 'any address', '::' for 'any IPv6 address', or '0.0.0.0'\n" "for 'any IPv4 address'. If you leave this blank, Citadel will\n" "listen on all addresses. This can usually be left to the default unless " "multiple instances of Citadel are running on the same computer." msgstr "" #: ../utils/setup.c:168 msgid "Server port number:" msgstr "" #: ../utils/setup.c:170 msgid "" "Specify the TCP port number on which your server will run.\n" "Normally, this will be port 504, which is the official port\n" "assigned by the IANA for Citadel servers. You will only need\n" "to specify a different port number if you run multiple instances\n" "of Citadel on the same computer and there is something else\n" "already using port 504.\n" msgstr "" #: ../utils/setup.c:177 msgid "Authentication method to use:" msgstr "Identifieringsmetod som ska användas:" #: ../utils/setup.c:179 msgid "" "Please choose the user authentication mode. By default Citadel will use its " "own internal user accounts database. If you choose Host, Citadel users will " "have accounts on the host system, authenticated via /etc/passwd or a PAM " "source. LDAP chooses an RFC 2307 compliant directory server, the last option " "chooses the nonstandard MS Active Directory LDAP scheme.\n" "Do not change this option unless you are sure it is required, since changing " "back requires a full reinstall of Citadel.\n" " 0. Self contained authentication\n" " 1. Host system integrated authentication\n" " 2. External LDAP - RFC 2307 compliant directory\n" " 3. External LDAP - nonstandard MS Active Directory\n" "\n" "For help: http://www.citadel.org/doku.php/faq:installation:authmodes\n" "\n" "ANSWER \"0\" UNLESS YOU COMPLETELY UNDERSTAND THIS OPTION.\n" msgstr "" #: ../utils/setup.c:197 msgid "LDAP host:" msgstr "LDAP-värd:" #: ../utils/setup.c:199 msgid "Please enter the host name or IP address of your LDAP server.\n" msgstr "Ange värdnamnet eller IP-adressen för din LDAP-server.\n" #: ../utils/setup.c:201 msgid "LDAP port number:" msgstr "Portnummer för LDAP:" #: ../utils/setup.c:203 msgid "Please enter the port number of the LDAP service (usually 389).\n" msgstr "Ange portnummer för din LDAP-server (vanligen 389).\n" #: ../utils/setup.c:205 msgid "LDAP base DN:" msgstr "Bas-DN för LDAP:" #: ../utils/setup.c:207 msgid "" "Please enter the Base DN to search for authentication\n" "(for example: dc=example,dc=com)\n" msgstr "" "Ange det Bas-DN som ska användas vid sökning för identifiering av användare " "\n" "(ex. dc=example,dc=com).\n" #: ../utils/setup.c:210 msgid "LDAP bind DN:" msgstr "DN för förfrÃ¥gningar till LDAP:" #: ../utils/setup.c:212 msgid "" "Please enter the DN of an account to use for binding to the LDAP server for " "performing queries. The account does not require any other privileges. If " "your LDAP server allows anonymous queries, you can leave this blank.Please " "enter the DN of an account to use for binding to the LDAP server\n" "for performing queries. The account does not require any other\n" "privileges. If your LDAP server allows anonymous queries, you can\n" "leave this blank.\n" msgstr "" #: ../utils/setup.c:220 msgid "LDAP bind password:" msgstr "Lösenord för förfrÃ¥gnings-DN:" #: ../utils/setup.c:222 msgid "" "If you entered a Bind DN in the previous question, you must now enter\n" "the password associated with that account. Otherwise, you can leave this\n" "blank.\n" msgstr "" #: ../utils/setup.c:299 msgid "Yes/No" msgstr "" #: ../utils/setup.c:300 msgid "Yes" msgstr "" #: ../utils/setup.c:300 msgid "No" msgstr "" #: ../utils/setup.c:346 msgid "Press return to continue..." msgstr "" #: ../utils/setup.c:364 msgid "Important Message" msgstr "" #: ../utils/setup.c:379 msgid "Error" msgstr "" #: ../utils/setup.c:459 msgid "Adding service entry..." msgstr "" #. Other errors might mean something really did go wrong. #. #: ../utils/setup.c:463 ../utils/setup.c:510 ../utils/setup.c:518 msgid "Cannot open" msgstr "" #: ../utils/setup.c:569 msgid "" "Citadel already appears to be configured to start at boot.\n" "Would you like to keep your boot configuration as is?\n" msgstr "" #: ../utils/setup.c:577 msgid "Would you like to automatically start Citadel at boot?\n" msgstr "" #: ../utils/setup.c:583 msgid "Cannot create" msgstr "" #: ../utils/setup.c:682 #, c-format msgid "" "Setup can configure the \"xinetd\" service to automatically\n" "connect incoming telnet sessions to Citadel, bypassing the\n" "host system login: prompt. Would you like to do this?\n" msgstr "" #: ../utils/setup.c:740 msgid "You appear to have the " msgstr "" #: ../utils/setup.c:742 msgid "" " email program\n" "running on your system. If you want Citadel mail\n" "connected with " msgstr "" #: ../utils/setup.c:746 msgid "" " you will have to manually integrate\n" "them. It is preferable to disable " msgstr "" #: ../utils/setup.c:749 msgid "" ", and use Citadel's\n" "SMTP, POP3, and IMAP services.\n" "\n" "May we disable " msgstr "" #: ../utils/setup.c:753 msgid "" "so that Citadel has access to ports\n" "25, 110, and 143?\n" msgstr "" #: ../utils/setup.c:863 msgid "This is currently set to:" msgstr "" #: ../utils/setup.c:864 msgid "Enter new value or press return to leave unchanged:" msgstr "" #: ../utils/setup.c:1067 ../utils/setup.c:1072 ../utils/setup.c:1384 msgid "setup: cannot open" msgstr "" #: ../utils/setup.c:1175 #, c-format msgid "" "\n" "/etc/nsswitch.conf is configured to use the 'db' module for\n" "one or more services. This is not necessary on most systems,\n" "and it is known to crash the Citadel server when delivering\n" "mail to the Internet.\n" "\n" "Do you want this module to be automatically disabled?\n" "\n" msgstr "" #: ../utils/setup.c:1236 ../utils/setup.c:1252 msgid "Setup finished" msgstr "" #: ../utils/setup.c:1237 msgid "" "Setup of the Citadel server is complete.\n" "If you will be using WebCit, please run its\n" "setup program now; otherwise, run './citadel'\n" "to log in.\n" msgstr "" #: ../utils/setup.c:1243 msgid "Setup failed" msgstr "" #: ../utils/setup.c:1244 msgid "" "Setup is finished, but the Citadel server failed to start.\n" "Go back and check your configuration.\n" msgstr "" #: ../utils/setup.c:1253 msgid "Setup is finished. You may now start the server." msgstr "" #: ../utils/setup.c:1279 msgid "My System" msgstr "" #: ../utils/setup.c:1282 msgid "US 800 555 1212" msgstr "" #: ../utils/setup.c:1368 ../utils/setup.c:1373 msgid "setup: cannot append" msgstr "" #: ../utils/setup.c:1450 ../utils/setup.c:1457 ../utils/setup.c:1472 #: ../utils/setup.c:1512 msgid "Citadel Setup" msgstr "" #: ../utils/setup.c:1459 msgid "The directory you specified does not exist" msgstr "" #: ../utils/setup.c:1473 msgid "" "The Citadel service is still running.\n" "Please stop the service manually and run setup again." msgstr "" #: ../utils/setup.c:1485 msgid "Citadel setup program" msgstr "" #: ../utils/setup.c:1513 msgid "This Citadel installation is too old to be upgraded." msgstr "" #: ../utils/setup.c:1552 ../utils/setup.c:1554 ../utils/setup.c:1556 msgid "Setting file permissions" msgstr "" #~ msgid "" #~ "Please specify the IP address which the server should be listening to. If " #~ "you specify 0.0.0.0, the server will listen on all addresses." #~ msgstr "" #~ "Ange IP-adressen som servern ska lyssna pÃ¥. Om du anger 0.0.0.0 kommer " #~ "servern att lyssna pÃ¥ alla adresser." #~ msgid "" #~ "This can usually be left to the default unless multiple instances of Citadel " #~ "are running on the same computer." #~ msgstr "" #~ "Standardvalet fungerar utmärkt för alla som endast kör en instans av Citadel " #~ "pÃ¥ samma dator." #~ msgid "Internal" #~ msgstr "Internt" #~ msgid "Host" #~ msgstr "Värd" #~ msgid "LDAP" #~ msgstr "LDAP" #~ msgid "" #~ "Do not change this option unless you are sure it is required, since changing " #~ "back requires a full reinstall of Citadel." #~ msgstr "" #~ "Ändra inte denna inställning om du inte är helt säker pÃ¥ att det behövs. För " #~ "att Ã¥terställa mÃ¥ste hela Citadel installeras om." #~ msgid "" #~ "While not mandatory, it is highly recommended that you set a password for " #~ "the administrator user." #~ msgstr "" #~ "Det är förvisso inget krav men det är en stark rekomendation att du anger " #~ "ett lösenord för den administrativa användaren." #~ msgid "Internal, Host, LDAP, Active Directory" #~ msgstr "Internt, Värd, LDAP, Active Directory" #~ msgid "Enable external authentication mode?" #~ msgstr "Aktivera det externa identifieringsläget?" #~ msgid "" #~ "Please enter the name of the Citadel user account that should be granted " #~ "administrative privileges once created." #~ msgstr "" #~ "Ange namnet pÃ¥ det Citadel-konto som ska agera adminstratör när det skapats." citadel-9.01/po/citadel-setup/citadel-setup.pot0000644000000000000000000002235012507024051020214 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR The Citadel Project - http://www.citadel.org # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2010-09-28 00:22+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" #: ../utils/setup.c:119 msgid "Citadel Home Directory" msgstr "" #: ../utils/setup.c:122 msgid "" "Enter the full pathname of the directory in which the Citadel\n" "installation you are creating or updating resides. If you\n" "specify a directory other than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" msgstr "" #: ../utils/setup.c:128 msgid "" "Enter the subdirectory name for an alternate installation of Citadel. To do " "a default installation just leave it blank.If you specify a directory other " "than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" "note that it may not have a leading /" msgstr "" #: ../utils/setup.c:135 msgid "Citadel administrator username:" msgstr "" #: ../utils/setup.c:137 msgid "" "Please enter the name of the Citadel user account that should be granted " "administrative privileges once created. If using internal authentication " "this user account will be created if it does not exist. For external " "authentication this user account has to exist." msgstr "" #: ../utils/setup.c:143 msgid "Administrator password:" msgstr "" #: ../utils/setup.c:145 msgid "" "Enter a password for the system administrator. When setup\n" "completes it will attempt to create the administrator user\n" "and set the password specified here.\n" msgstr "" #: ../utils/setup.c:149 msgid "Citadel User ID:" msgstr "" #: ../utils/setup.c:151 msgid "" "Citadel needs to run under its own user ID. This would\n" "typically be called \"citadel\", but if you are running Citadel\n" "as a public BBS, you might also call it \"bbs\" or \"guest\".\n" "The server will run under this user ID. Please specify that\n" "user ID here. You may specify either a user name or a numeric\n" "UID.\n" msgstr "" #: ../utils/setup.c:158 msgid "Listening address for the Citadel server:" msgstr "" #: ../utils/setup.c:160 msgid "" "Please specify the IP address which the server should be listening to. You " "can name a specific IPv4 or IPv6 address, or you can specify\n" "'*' for 'any address', '::' for 'any IPv6 address', or '0.0.0.0'\n" "for 'any IPv4 address'. If you leave this blank, Citadel will\n" "listen on all addresses. This can usually be left to the default unless " "multiple instances of Citadel are running on the same computer." msgstr "" #: ../utils/setup.c:168 msgid "Server port number:" msgstr "" #: ../utils/setup.c:170 msgid "" "Specify the TCP port number on which your server will run.\n" "Normally, this will be port 504, which is the official port\n" "assigned by the IANA for Citadel servers. You will only need\n" "to specify a different port number if you run multiple instances\n" "of Citadel on the same computer and there is something else\n" "already using port 504.\n" msgstr "" #: ../utils/setup.c:177 msgid "Authentication method to use:" msgstr "" #: ../utils/setup.c:179 msgid "" "Please choose the user authentication mode. By default Citadel will use its " "own internal user accounts database. If you choose Host, Citadel users will " "have accounts on the host system, authenticated via /etc/passwd or a PAM " "source. LDAP chooses an RFC 2307 compliant directory server, the last option " "chooses the nonstandard MS Active Directory LDAP scheme.\n" "Do not change this option unless you are sure it is required, since changing " "back requires a full reinstall of Citadel.\n" " 0. Self contained authentication\n" " 1. Host system integrated authentication\n" " 2. External LDAP - RFC 2307 compliant directory\n" " 3. External LDAP - nonstandard MS Active Directory\n" "\n" "For help: http://www.citadel.org/doku.php/faq:installation:authmodes\n" "\n" "ANSWER \"0\" UNLESS YOU COMPLETELY UNDERSTAND THIS OPTION.\n" msgstr "" #: ../utils/setup.c:197 msgid "LDAP host:" msgstr "" #: ../utils/setup.c:199 msgid "Please enter the host name or IP address of your LDAP server.\n" msgstr "" #: ../utils/setup.c:201 msgid "LDAP port number:" msgstr "" #: ../utils/setup.c:203 msgid "Please enter the port number of the LDAP service (usually 389).\n" msgstr "" #: ../utils/setup.c:205 msgid "LDAP base DN:" msgstr "" #: ../utils/setup.c:207 msgid "" "Please enter the Base DN to search for authentication\n" "(for example: dc=example,dc=com)\n" msgstr "" #: ../utils/setup.c:210 msgid "LDAP bind DN:" msgstr "" #: ../utils/setup.c:212 msgid "" "Please enter the DN of an account to use for binding to the LDAP server for " "performing queries. The account does not require any other privileges. If " "your LDAP server allows anonymous queries, you can leave this blank.Please " "enter the DN of an account to use for binding to the LDAP server\n" "for performing queries. The account does not require any other\n" "privileges. If your LDAP server allows anonymous queries, you can\n" "leave this blank.\n" msgstr "" #: ../utils/setup.c:220 msgid "LDAP bind password:" msgstr "" #: ../utils/setup.c:222 msgid "" "If you entered a Bind DN in the previous question, you must now enter\n" "the password associated with that account. Otherwise, you can leave this\n" "blank.\n" msgstr "" #: ../utils/setup.c:299 msgid "Yes/No" msgstr "" #: ../utils/setup.c:300 msgid "Yes" msgstr "" #: ../utils/setup.c:300 msgid "No" msgstr "" #: ../utils/setup.c:346 msgid "Press return to continue..." msgstr "" #: ../utils/setup.c:364 msgid "Important Message" msgstr "" #: ../utils/setup.c:379 msgid "Error" msgstr "" #: ../utils/setup.c:459 msgid "Adding service entry..." msgstr "" #. Other errors might mean something really did go wrong. #. #: ../utils/setup.c:463 ../utils/setup.c:510 ../utils/setup.c:518 msgid "Cannot open" msgstr "" #: ../utils/setup.c:569 msgid "" "Citadel already appears to be configured to start at boot.\n" "Would you like to keep your boot configuration as is?\n" msgstr "" #: ../utils/setup.c:577 msgid "Would you like to automatically start Citadel at boot?\n" msgstr "" #: ../utils/setup.c:583 msgid "Cannot create" msgstr "" #: ../utils/setup.c:682 #, c-format msgid "" "Setup can configure the \"xinetd\" service to automatically\n" "connect incoming telnet sessions to Citadel, bypassing the\n" "host system login: prompt. Would you like to do this?\n" msgstr "" #: ../utils/setup.c:740 msgid "You appear to have the " msgstr "" #: ../utils/setup.c:742 msgid "" " email program\n" "running on your system. If you want Citadel mail\n" "connected with " msgstr "" #: ../utils/setup.c:746 msgid "" " you will have to manually integrate\n" "them. It is preferable to disable " msgstr "" #: ../utils/setup.c:749 msgid "" ", and use Citadel's\n" "SMTP, POP3, and IMAP services.\n" "\n" "May we disable " msgstr "" #: ../utils/setup.c:753 msgid "" "so that Citadel has access to ports\n" "25, 110, and 143?\n" msgstr "" #: ../utils/setup.c:863 msgid "This is currently set to:" msgstr "" #: ../utils/setup.c:864 msgid "Enter new value or press return to leave unchanged:" msgstr "" #: ../utils/setup.c:1067 ../utils/setup.c:1072 ../utils/setup.c:1384 msgid "setup: cannot open" msgstr "" #: ../utils/setup.c:1175 #, c-format msgid "" "\n" "/etc/nsswitch.conf is configured to use the 'db' module for\n" "one or more services. This is not necessary on most systems,\n" "and it is known to crash the Citadel server when delivering\n" "mail to the Internet.\n" "\n" "Do you want this module to be automatically disabled?\n" "\n" msgstr "" #: ../utils/setup.c:1236 ../utils/setup.c:1252 msgid "Setup finished" msgstr "" #: ../utils/setup.c:1237 msgid "" "Setup of the Citadel server is complete.\n" "If you will be using WebCit, please run its\n" "setup program now; otherwise, run './citadel'\n" "to log in.\n" msgstr "" #: ../utils/setup.c:1243 msgid "Setup failed" msgstr "" #: ../utils/setup.c:1244 msgid "" "Setup is finished, but the Citadel server failed to start.\n" "Go back and check your configuration.\n" msgstr "" #: ../utils/setup.c:1253 msgid "Setup is finished. You may now start the server." msgstr "" #: ../utils/setup.c:1279 msgid "My System" msgstr "" #: ../utils/setup.c:1282 msgid "US 800 555 1212" msgstr "" #: ../utils/setup.c:1368 ../utils/setup.c:1373 msgid "setup: cannot append" msgstr "" #: ../utils/setup.c:1450 ../utils/setup.c:1457 ../utils/setup.c:1472 #: ../utils/setup.c:1512 msgid "Citadel Setup" msgstr "" #: ../utils/setup.c:1459 msgid "The directory you specified does not exist" msgstr "" #: ../utils/setup.c:1473 msgid "" "The Citadel service is still running.\n" "Please stop the service manually and run setup again." msgstr "" #: ../utils/setup.c:1485 msgid "Citadel setup program" msgstr "" #: ../utils/setup.c:1513 msgid "This Citadel installation is too old to be upgraded." msgstr "" #: ../utils/setup.c:1552 ../utils/setup.c:1554 ../utils/setup.c:1556 msgid "Setting file permissions" msgstr "" citadel-9.01/po/citadel-setup/pt_BR.po0000644000000000000000000003430212507024051016273 0ustar rootroot# Brazilian Portuguese translation for citadel # Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011 # This file is distributed under the same license as the citadel package. # FIRST AUTHOR , 2011. # msgid "" msgstr "" "Project-Id-Version: citadel\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2010-09-28 00:22+0200\n" "PO-Revision-Date: 2012-03-10 19:41+0000\n" "Last-Translator: Darlildo Lima \n" "Language-Team: Brazilian Portuguese \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2012-03-11 04:33+0000\n" "X-Generator: Launchpad (build 14914)\n" #: ../utils/setup.c:119 msgid "Citadel Home Directory" msgstr "Diretório Principal do Citadel" #: ../utils/setup.c:122 msgid "" "Enter the full pathname of the directory in which the Citadel\n" "installation you are creating or updating resides. If you\n" "specify a directory other than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" msgstr "" "Insira a localização completa do diretório em que a instalação Citadel\n" "que estas criando ou atualizando reside. Se escolheres um diretório " "diferente do padrão, terás\n" "que especificar a etiqueta -h para o servidor quando iniciares o programa.\n" #: ../utils/setup.c:128 msgid "" "Enter the subdirectory name for an alternate installation of Citadel. To do " "a default installation just leave it blank.If you specify a directory other " "than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" "note that it may not have a leading /" msgstr "" "Insira o nome do subdiretório para uma instalação alternativa de Citadel. " "Para instalar no local padrão deixe em branco. Se escolheres um diretório " "diferente do padrão, terás\n" "que especificar a etiqueta -h para o servidor quando iniciares o programa.\n" "Note que talvez não tenha / antecedendo" #: ../utils/setup.c:135 msgid "Citadel administrator username:" msgstr "Nome de usuário administrador Citadel:" #: ../utils/setup.c:137 msgid "" "Please enter the name of the Citadel user account that should be granted " "administrative privileges once created. If using internal authentication " "this user account will be created if it does not exist. For external " "authentication this user account has to exist." msgstr "" "Favor inserir o nome do usuário da conta Citadel que deverá ter opções " "administrativas quando criada. Se estiveres usando autenticação interna, " "está conta será criada caso não exista. Para autenticação externa, a conta " "já deve existir." #: ../utils/setup.c:143 msgid "Administrator password:" msgstr "Senha do administrador:" #: ../utils/setup.c:145 msgid "" "Enter a password for the system administrator. When setup\n" "completes it will attempt to create the administrator user\n" "and set the password specified here.\n" msgstr "" "Crie uma senha para o administrador do sistema. Quando a instalação estiver " "completa, \n" "o sistema tentará criar o usuário administrador \n" "usando a senha que especificares aqui.\n" #: ../utils/setup.c:149 msgid "Citadel User ID:" msgstr "ID de usuário Citadel:" #: ../utils/setup.c:151 msgid "" "Citadel needs to run under its own user ID. This would\n" "typically be called \"citadel\", but if you are running Citadel\n" "as a public BBS, you might also call it \"bbs\" or \"guest\".\n" "The server will run under this user ID. Please specify that\n" "user ID here. You may specify either a user name or a numeric\n" "UID.\n" msgstr "" "Citadel precisa executar sob seu proprio ID de usuario. Isto tipicamente\n" "seria chamado \"citadel\", mas se estiveres executando Citadel \n" "como um BBS público, poderás também o chamar de \"bbs\" ou \"hóspede\".\n" "O servidor executará usando esta ID. Por favor especifique esta ID aqui. " "Poderás especificar em forma escrita ou numérica.\n" "UID.\n" #: ../utils/setup.c:158 msgid "Listening address for the Citadel server:" msgstr "Acessando endereço do servidor Citadel:" #: ../utils/setup.c:160 msgid "" "Please specify the IP address which the server should be listening to. You " "can name a specific IPv4 or IPv6 address, or you can specify\n" "'*' for 'any address', '::' for 'any IPv6 address', or '0.0.0.0'\n" "for 'any IPv4 address'. If you leave this blank, Citadel will\n" "listen on all addresses. This can usually be left to the default unless " "multiple instances of Citadel are running on the same computer." msgstr "" "Por favor especifique o endereço de IP que o servidor deverá tentar " "conecção. Podes nomear um endreço específico de IPv4 ou IPv6, ou podes " "especificar\n" "'*' para 'qualquer endereço', '::' para 'qualquer endereço IPv6', ou " "'0.0.0.0'\n" "para 'qualquer endereço IPv4'. Se deixares isto em branco, Citadel irá\n" "tentar conecção em todos os endereços. Isto normalmente pode ser deixado no " "padrão normal, exceto quando tiveres várias instâncias do Citadel no mesmo " "computador." #: ../utils/setup.c:168 msgid "Server port number:" msgstr "Número da porta do servidor:" #: ../utils/setup.c:170 msgid "" "Specify the TCP port number on which your server will run.\n" "Normally, this will be port 504, which is the official port\n" "assigned by the IANA for Citadel servers. You will only need\n" "to specify a different port number if you run multiple instances\n" "of Citadel on the same computer and there is something else\n" "already using port 504.\n" msgstr "" "Especifique o número da porta TCP em que o servidor irá executar.\n" "Normalmente, será a porta 504, que é a porta oficial\n" "assinada pelo IANA para servidores Citadel. Você só precisa\n" "especificar um número de porta diferente se você executar\n" "várias instâncias do Citadel no mesmo computador ou outra\n" "coisa que utilize a porta 504.\n" #: ../utils/setup.c:177 msgid "Authentication method to use:" msgstr "Método de autenticação a ser usado:" #: ../utils/setup.c:179 msgid "" "Please choose the user authentication mode. By default Citadel will use its " "own internal user accounts database. If you choose Host, Citadel users will " "have accounts on the host system, authenticated via /etc/passwd or a PAM " "source. LDAP chooses an RFC 2307 compliant directory server, the last option " "chooses the nonstandard MS Active Directory LDAP scheme.\n" "Do not change this option unless you are sure it is required, since changing " "back requires a full reinstall of Citadel.\n" " 0. Self contained authentication\n" " 1. Host system integrated authentication\n" " 2. External LDAP - RFC 2307 compliant directory\n" " 3. External LDAP - nonstandard MS Active Directory\n" "\n" "For help: http://www.citadel.org/doku.php/faq:installation:authmodes\n" "\n" "ANSWER \"0\" UNLESS YOU COMPLETELY UNDERSTAND THIS OPTION.\n" msgstr "" "Por favor escolha o mode de autenticação do usuário. Por padrão, Citadel " "usará a sua própria database interna de usuários. Se escolheres Hóspede, " "usuários Citadel terão suas contas no sistema hóspede, autenticados\n" "via /etc/passwd ou uma fonte PAM. LDAP escolhe um diretório compatível com " "RFC 2307, a última opção escolhe o esquema não-padrão a MS Active Directory " "LDAP.\n" "Não mude esta opção exceto se estiveres certo que a mudança e necessária, " "pois voltar para a configuração padrão requer a reinstalação completa do " "Citadel.\n" " 0. Autenticação independente\n" " 1. Autenticação integrada ao sistema hóspede.\n" " 2. Diretório externo LDAP - compatível com RFC 2307\n" " 3. Diretório externo LDAP - não-padrão MS Active Directory\n" "\n" "Para ajuda visite " "http://www.citadel.org/doku.php/faq:installation:authmodes\n" "\n" "RESPONDA \"0\" SE NÃO ENTEDERES ESTA OPÇÃO COMPLETAMENTE.\n" #: ../utils/setup.c:197 msgid "LDAP host:" msgstr "Hóspede LDAP" #: ../utils/setup.c:199 msgid "Please enter the host name or IP address of your LDAP server.\n" msgstr "" "Por favor informe o nome do hóspede ou o endereço de IP de seu servidor " "LDAP.\n" #: ../utils/setup.c:201 msgid "LDAP port number:" msgstr "Número da porta do LDAP:" #: ../utils/setup.c:203 msgid "Please enter the port number of the LDAP service (usually 389).\n" msgstr "" "Por favor, indique o número da porta do serviço LDAP (normalmente 389).\n" #: ../utils/setup.c:205 msgid "LDAP base DN:" msgstr "Base DN do LDAP" #: ../utils/setup.c:207 msgid "" "Please enter the Base DN to search for authentication\n" "(for example: dc=example,dc=com)\n" msgstr "" #: ../utils/setup.c:210 msgid "LDAP bind DN:" msgstr "" #: ../utils/setup.c:212 msgid "" "Please enter the DN of an account to use for binding to the LDAP server for " "performing queries. The account does not require any other privileges. If " "your LDAP server allows anonymous queries, you can leave this blank.Please " "enter the DN of an account to use for binding to the LDAP server\n" "for performing queries. The account does not require any other\n" "privileges. If your LDAP server allows anonymous queries, you can\n" "leave this blank.\n" msgstr "" #: ../utils/setup.c:220 msgid "LDAP bind password:" msgstr "" #: ../utils/setup.c:222 msgid "" "If you entered a Bind DN in the previous question, you must now enter\n" "the password associated with that account. Otherwise, you can leave this\n" "blank.\n" msgstr "" #: ../utils/setup.c:299 msgid "Yes/No" msgstr "Sim/Não" #: ../utils/setup.c:300 msgid "Yes" msgstr "Sim" #: ../utils/setup.c:300 msgid "No" msgstr "Não" #: ../utils/setup.c:346 msgid "Press return to continue..." msgstr "Pressione enter para continuar..." #: ../utils/setup.c:364 msgid "Important Message" msgstr "Mensagem Importante" #: ../utils/setup.c:379 msgid "Error" msgstr "Erro" #: ../utils/setup.c:459 msgid "Adding service entry..." msgstr "Adicionando entrada de serviço..." #. Other errors might mean something really did go wrong. #. #: ../utils/setup.c:463 ../utils/setup.c:510 ../utils/setup.c:518 msgid "Cannot open" msgstr "Não foi possível abrir" #: ../utils/setup.c:569 msgid "" "Citadel already appears to be configured to start at boot.\n" "Would you like to keep your boot configuration as is?\n" msgstr "" "Citadel parece já estar configurado para iniciar no boot (inicialização).\n" "Gostaria de manter a configuração de inicialização como está?\n" #: ../utils/setup.c:577 msgid "Would you like to automatically start Citadel at boot?\n" msgstr "Gostaria de iniciar o Citadel com o boot (inicialização)?\n" #: ../utils/setup.c:583 msgid "Cannot create" msgstr "Não é possível criar" #: ../utils/setup.c:682 #, c-format msgid "" "Setup can configure the \"xinetd\" service to automatically\n" "connect incoming telnet sessions to Citadel, bypassing the\n" "host system login: prompt. Would you like to do this?\n" msgstr "" #: ../utils/setup.c:740 msgid "You appear to have the " msgstr "Aparentemente você tem o " #: ../utils/setup.c:742 msgid "" " email program\n" "running on your system. If you want Citadel mail\n" "connected with " msgstr "" " programa de email\n" "executando em seu sistema. Se você deseja o Citadel mail\n" "conectado com " #: ../utils/setup.c:746 msgid "" " you will have to manually integrate\n" "them. It is preferable to disable " msgstr "" " você deve integrá-los manualmente.\n" "é preferível desabilitar " #: ../utils/setup.c:749 msgid "" ", and use Citadel's\n" "SMTP, POP3, and IMAP services.\n" "\n" "May we disable " msgstr "" ", e use os serviços\n" "SMTP, POP3, e IMAP do Citadel.\n" "\n" "Para desabilitarmos " #: ../utils/setup.c:753 msgid "" "so that Citadel has access to ports\n" "25, 110, and 143?\n" msgstr "" "e então o Citadel tenha acesso as\n" "portas 25, 110, e 143?\n" #: ../utils/setup.c:863 msgid "This is currently set to:" msgstr "Isto está definido como:" #: ../utils/setup.c:864 msgid "Enter new value or press return to leave unchanged:" msgstr "Digite um novo valor ou pressione Enter para deixar inalterada:" #: ../utils/setup.c:1067 ../utils/setup.c:1072 ../utils/setup.c:1384 msgid "setup: cannot open" msgstr "Instalação: Não foi possível abrir" #: ../utils/setup.c:1175 #, c-format msgid "" "\n" "/etc/nsswitch.conf is configured to use the 'db' module for\n" "one or more services. This is not necessary on most systems,\n" "and it is known to crash the Citadel server when delivering\n" "mail to the Internet.\n" "\n" "Do you want this module to be automatically disabled?\n" "\n" msgstr "" #: ../utils/setup.c:1236 ../utils/setup.c:1252 msgid "Setup finished" msgstr "Instalação concluída" #: ../utils/setup.c:1237 msgid "" "Setup of the Citadel server is complete.\n" "If you will be using WebCit, please run its\n" "setup program now; otherwise, run './citadel'\n" "to log in.\n" msgstr "" #: ../utils/setup.c:1243 msgid "Setup failed" msgstr "A instalação falhou" #: ../utils/setup.c:1244 msgid "" "Setup is finished, but the Citadel server failed to start.\n" "Go back and check your configuration.\n" msgstr "" #: ../utils/setup.c:1253 msgid "Setup is finished. You may now start the server." msgstr "A instalação foi concluída. Você deve agora iniciar o servidor." #: ../utils/setup.c:1279 msgid "My System" msgstr "Meu sistema" #: ../utils/setup.c:1282 msgid "US 800 555 1212" msgstr "US 800 555 1212" #: ../utils/setup.c:1368 ../utils/setup.c:1373 msgid "setup: cannot append" msgstr "" #: ../utils/setup.c:1450 ../utils/setup.c:1457 ../utils/setup.c:1472 #: ../utils/setup.c:1512 msgid "Citadel Setup" msgstr "Instalação do Citadel" #: ../utils/setup.c:1459 msgid "The directory you specified does not exist" msgstr "" #: ../utils/setup.c:1473 msgid "" "The Citadel service is still running.\n" "Please stop the service manually and run setup again." msgstr "" "O serviço do Citadel ainda está em execução.\n" "Por favor, pare o serviço manualmente e execute a instalação novamente." #: ../utils/setup.c:1485 msgid "Citadel setup program" msgstr "Programa de instalação do Citadel" #: ../utils/setup.c:1513 msgid "This Citadel installation is too old to be upgraded." msgstr "Esta instalação do Citadel é muito antiga e não pode ser atualizada." #: ../utils/setup.c:1552 ../utils/setup.c:1554 ../utils/setup.c:1556 msgid "Setting file permissions" msgstr "Configurando permisões de arquivo" citadel-9.01/po/citadel-setup/create-pot.sh0000755000000000000000000000145012507024051017325 0ustar rootroot#!/bin/bash echo Updating citadel.pot from strings in the source code ... xgettext \ --copyright-holder='The Citadel Project - http://www.citadel.org' \ --from-code='utf-8' \ -k_ \ -o citadel-setup.pot \ --add-comments \ ../../utils/setup.c #xgettext \ # --copyright-holder='The Citadel Project - http://www.citadel.org' \ # --from-code='utf-8' \ # -k_ \ # -o citadel-server.pot \ # --add-comments \ # ../*.c \ # `cat ../Make_sources | sed -e "s;.*+= ;;" ` # # #xgettext \ # --copyright-holder='The Citadel Project - http://www.citadel.org' \ # --from-code='utf-8' \ # -k_ \ # -o citadel-client.pot \ # --add-comments \ # ../*.c \ # ../textclient/*.c for x in *.po do echo Merging citadel-setup.pot into $x ... msgmerge $x citadel-setup.pot -o $x done citadel-9.01/po/citadel-setup/kk.po0000644000000000000000000002250212507024051015671 0ustar rootroot# Kazakh translation for citadel # Copyright (c) 2010 Rosetta Contributors and Canonical Ltd 2010 # This file is distributed under the same license as the citadel package. # FIRST AUTHOR , 2010. # msgid "" msgstr "" "Project-Id-Version: citadel\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2010-09-28 00:22+0200\n" "PO-Revision-Date: 2010-12-26 10:06+0000\n" "Last-Translator: FULL NAME \n" "Language-Team: Kazakh \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2010-12-27 04:35+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: ../utils/setup.c:119 msgid "Citadel Home Directory" msgstr "" #: ../utils/setup.c:122 msgid "" "Enter the full pathname of the directory in which the Citadel\n" "installation you are creating or updating resides. If you\n" "specify a directory other than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" msgstr "" #: ../utils/setup.c:128 msgid "" "Enter the subdirectory name for an alternate installation of Citadel. To do " "a default installation just leave it blank.If you specify a directory other " "than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" "note that it may not have a leading /" msgstr "" #: ../utils/setup.c:135 msgid "Citadel administrator username:" msgstr "" #: ../utils/setup.c:137 msgid "" "Please enter the name of the Citadel user account that should be granted " "administrative privileges once created. If using internal authentication " "this user account will be created if it does not exist. For external " "authentication this user account has to exist." msgstr "" #: ../utils/setup.c:143 msgid "Administrator password:" msgstr "" #: ../utils/setup.c:145 msgid "" "Enter a password for the system administrator. When setup\n" "completes it will attempt to create the administrator user\n" "and set the password specified here.\n" msgstr "" #: ../utils/setup.c:149 msgid "Citadel User ID:" msgstr "" #: ../utils/setup.c:151 msgid "" "Citadel needs to run under its own user ID. This would\n" "typically be called \"citadel\", but if you are running Citadel\n" "as a public BBS, you might also call it \"bbs\" or \"guest\".\n" "The server will run under this user ID. Please specify that\n" "user ID here. You may specify either a user name or a numeric\n" "UID.\n" msgstr "" #: ../utils/setup.c:158 msgid "Listening address for the Citadel server:" msgstr "" #: ../utils/setup.c:160 msgid "" "Please specify the IP address which the server should be listening to. You " "can name a specific IPv4 or IPv6 address, or you can specify\n" "'*' for 'any address', '::' for 'any IPv6 address', or '0.0.0.0'\n" "for 'any IPv4 address'. If you leave this blank, Citadel will\n" "listen on all addresses. This can usually be left to the default unless " "multiple instances of Citadel are running on the same computer." msgstr "" #: ../utils/setup.c:168 msgid "Server port number:" msgstr "" #: ../utils/setup.c:170 msgid "" "Specify the TCP port number on which your server will run.\n" "Normally, this will be port 504, which is the official port\n" "assigned by the IANA for Citadel servers. You will only need\n" "to specify a different port number if you run multiple instances\n" "of Citadel on the same computer and there is something else\n" "already using port 504.\n" msgstr "" #: ../utils/setup.c:177 msgid "Authentication method to use:" msgstr "" #: ../utils/setup.c:179 msgid "" "Please choose the user authentication mode. By default Citadel will use its " "own internal user accounts database. If you choose Host, Citadel users will " "have accounts on the host system, authenticated via /etc/passwd or a PAM " "source. LDAP chooses an RFC 2307 compliant directory server, the last option " "chooses the nonstandard MS Active Directory LDAP scheme.\n" "Do not change this option unless you are sure it is required, since changing " "back requires a full reinstall of Citadel.\n" " 0. Self contained authentication\n" " 1. Host system integrated authentication\n" " 2. External LDAP - RFC 2307 compliant directory\n" " 3. External LDAP - nonstandard MS Active Directory\n" "\n" "For help: http://www.citadel.org/doku.php/faq:installation:authmodes\n" "\n" "ANSWER \"0\" UNLESS YOU COMPLETELY UNDERSTAND THIS OPTION.\n" msgstr "" #: ../utils/setup.c:197 msgid "LDAP host:" msgstr "" #: ../utils/setup.c:199 msgid "Please enter the host name or IP address of your LDAP server.\n" msgstr "" #: ../utils/setup.c:201 msgid "LDAP port number:" msgstr "" #: ../utils/setup.c:203 msgid "Please enter the port number of the LDAP service (usually 389).\n" msgstr "" #: ../utils/setup.c:205 msgid "LDAP base DN:" msgstr "" #: ../utils/setup.c:207 msgid "" "Please enter the Base DN to search for authentication\n" "(for example: dc=example,dc=com)\n" msgstr "" #: ../utils/setup.c:210 msgid "LDAP bind DN:" msgstr "" #: ../utils/setup.c:212 msgid "" "Please enter the DN of an account to use for binding to the LDAP server for " "performing queries. The account does not require any other privileges. If " "your LDAP server allows anonymous queries, you can leave this blank.Please " "enter the DN of an account to use for binding to the LDAP server\n" "for performing queries. The account does not require any other\n" "privileges. If your LDAP server allows anonymous queries, you can\n" "leave this blank.\n" msgstr "" #: ../utils/setup.c:220 msgid "LDAP bind password:" msgstr "" #: ../utils/setup.c:222 msgid "" "If you entered a Bind DN in the previous question, you must now enter\n" "the password associated with that account. Otherwise, you can leave this\n" "blank.\n" msgstr "" #: ../utils/setup.c:299 msgid "Yes/No" msgstr "" #: ../utils/setup.c:300 msgid "Yes" msgstr "" #: ../utils/setup.c:300 msgid "No" msgstr "" #: ../utils/setup.c:346 msgid "Press return to continue..." msgstr "" #: ../utils/setup.c:364 msgid "Important Message" msgstr "" #: ../utils/setup.c:379 msgid "Error" msgstr "" #: ../utils/setup.c:459 msgid "Adding service entry..." msgstr "" #. Other errors might mean something really did go wrong. #. #: ../utils/setup.c:463 ../utils/setup.c:510 ../utils/setup.c:518 msgid "Cannot open" msgstr "" #: ../utils/setup.c:569 msgid "" "Citadel already appears to be configured to start at boot.\n" "Would you like to keep your boot configuration as is?\n" msgstr "" #: ../utils/setup.c:577 msgid "Would you like to automatically start Citadel at boot?\n" msgstr "" #: ../utils/setup.c:583 msgid "Cannot create" msgstr "" #: ../utils/setup.c:682 #, c-format msgid "" "Setup can configure the \"xinetd\" service to automatically\n" "connect incoming telnet sessions to Citadel, bypassing the\n" "host system login: prompt. Would you like to do this?\n" msgstr "" #: ../utils/setup.c:740 msgid "You appear to have the " msgstr "" #: ../utils/setup.c:742 msgid "" " email program\n" "running on your system. If you want Citadel mail\n" "connected with " msgstr "" #: ../utils/setup.c:746 msgid "" " you will have to manually integrate\n" "them. It is preferable to disable " msgstr "" #: ../utils/setup.c:749 msgid "" ", and use Citadel's\n" "SMTP, POP3, and IMAP services.\n" "\n" "May we disable " msgstr "" #: ../utils/setup.c:753 msgid "" "so that Citadel has access to ports\n" "25, 110, and 143?\n" msgstr "" #: ../utils/setup.c:863 msgid "This is currently set to:" msgstr "" #: ../utils/setup.c:864 msgid "Enter new value or press return to leave unchanged:" msgstr "" #: ../utils/setup.c:1067 ../utils/setup.c:1072 ../utils/setup.c:1384 msgid "setup: cannot open" msgstr "" #: ../utils/setup.c:1175 #, c-format msgid "" "\n" "/etc/nsswitch.conf is configured to use the 'db' module for\n" "one or more services. This is not necessary on most systems,\n" "and it is known to crash the Citadel server when delivering\n" "mail to the Internet.\n" "\n" "Do you want this module to be automatically disabled?\n" "\n" msgstr "" #: ../utils/setup.c:1236 ../utils/setup.c:1252 msgid "Setup finished" msgstr "" #: ../utils/setup.c:1237 msgid "" "Setup of the Citadel server is complete.\n" "If you will be using WebCit, please run its\n" "setup program now; otherwise, run './citadel'\n" "to log in.\n" msgstr "" #: ../utils/setup.c:1243 msgid "Setup failed" msgstr "" #: ../utils/setup.c:1244 msgid "" "Setup is finished, but the Citadel server failed to start.\n" "Go back and check your configuration.\n" msgstr "" #: ../utils/setup.c:1253 msgid "Setup is finished. You may now start the server." msgstr "" #: ../utils/setup.c:1279 msgid "My System" msgstr "" #: ../utils/setup.c:1282 msgid "US 800 555 1212" msgstr "" #: ../utils/setup.c:1368 ../utils/setup.c:1373 msgid "setup: cannot append" msgstr "" #: ../utils/setup.c:1450 ../utils/setup.c:1457 ../utils/setup.c:1472 #: ../utils/setup.c:1512 msgid "Citadel Setup" msgstr "" #: ../utils/setup.c:1459 msgid "The directory you specified does not exist" msgstr "" #: ../utils/setup.c:1473 msgid "" "The Citadel service is still running.\n" "Please stop the service manually and run setup again." msgstr "" #: ../utils/setup.c:1485 msgid "Citadel setup program" msgstr "" #: ../utils/setup.c:1513 msgid "This Citadel installation is too old to be upgraded." msgstr "" #: ../utils/setup.c:1552 ../utils/setup.c:1554 ../utils/setup.c:1556 msgid "Setting file permissions" msgstr "" citadel-9.01/po/citadel-setup/Makefile.in0000644000000000000000000000127012507024051016770 0ustar rootrootinclude ../../Make_sources SRCS:= $(wildcard *.po) OBJS:= $(patsubst %.po, \ ../../locale/%/LC_MESSAGES/citadel-setup.mo, \ $(SRCS)) # ../../locale/%/LC_MESSAGES/citadel_client.mo, \ # ../../locale/%/LC_MESSAGES/citadel_server.mo, \ .SUFFIXES: .po .mo .PHONY: all all: $(OBJS) clean: rm -r ../../locale/* ../../locale/%/LC_MESSAGES/citadel-setup.mo: %.po mkdir -p $(patsubst %.po, ../../locale/%/LC_MESSAGES, $<) msgfmt -o $@ $< #../locale/%/LC_MESSAGES/citadel_client.mo: %.po # mkdir -p $(patsubst %.po, ../locale/%/LC_MESSAGES, $<) # msgfmt -o $@ $< # #../locale/%/LC_MESSAGES/citadel_server.mo: %.po # mkdir -p $(patsubst %.po, ../locale/%/LC_MESSAGES, $<) # msgfmt -o $@ $< # citadel-9.01/po/citadel-setup/cs.po0000644000000000000000000004523712507024051015703 0ustar rootroot# Czech translation of citadel debconf messages. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the citadel package. # Miroslav Kure , 2008, 2009 # msgid "" msgstr "" "Project-Id-Version: citadel\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2010-09-28 00:22+0200\n" "PO-Revision-Date: 2010-10-22 14:32+0000\n" "Last-Translator: Miroslav Kure \n" "Language-Team: Czech \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2010-10-23 04:47+0000\n" "X-Generator: Launchpad (build Unknown)\n" "Language: cs\n" #: ../utils/setup.c:119 msgid "Citadel Home Directory" msgstr "Hlavní adresář Citadely" #: ../utils/setup.c:122 msgid "" "Enter the full pathname of the directory in which the Citadel\n" "installation you are creating or updating resides. If you\n" "specify a directory other than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" msgstr "" "NapiÅ¡te prosím celou cestu k adresáři, ve kterém chcete instalaci\n" "Citadely vytvoÅ™it nebo aktualizovat. Pokud nastavíte jiný adresář,\n" "než je výchozí, musíte nastavit i flag -h na serveru, kde budete vÅ¡e " "spouÅ¡tÄ›t.\n" #: ../utils/setup.c:128 msgid "" "Enter the subdirectory name for an alternate installation of Citadel. To do " "a default installation just leave it blank.If you specify a directory other " "than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" "note that it may not have a leading /" msgstr "" "Zadejte podadresář pro alternativní instalaci Citadely. Pro porvedení " "výchozí instalace, toto pole nevyplňujte. Pokud zvolíte jiný adresář než " "výchozí, budete muset\n" "zadat volbu -h pÅ™i spouÅ¡tÄ›ní serveru.\n" "Toto pole nemusí obsahovat úvodní lomítko" #: ../utils/setup.c:135 msgid "Citadel administrator username:" msgstr "Uživatelské jméno správce Citadely:" #: ../utils/setup.c:137 msgid "" "Please enter the name of the Citadel user account that should be granted " "administrative privileges once created. If using internal authentication " "this user account will be created if it does not exist. For external " "authentication this user account has to exist." msgstr "" "Zadejte jméno uživatelského úÄtu Citadely, který bude mít správcovská " "oprávnÄ›ní. Používáte-li interní autentizaci Citadely, bude uživatelský úÄet " "v případÄ› potÅ™eby vytvoÅ™en. Pokud používáte externí autentizaci, musí již " "uživatelský úÄet existovat." #: ../utils/setup.c:143 msgid "Administrator password:" msgstr "Správcovské heslo:" #: ../utils/setup.c:145 msgid "" "Enter a password for the system administrator. When setup\n" "completes it will attempt to create the administrator user\n" "and set the password specified here.\n" msgstr "" "Zadejte heslo pro systémového administrátora. Až bude\n" "instalace hotová, Citadela vytvoří úÄet správce\n" "a pÅ™idÄ›lí mu heslo zde zadané.\n" #: ../utils/setup.c:149 msgid "Citadel User ID:" msgstr "ID uživatele Citadely:" #: ../utils/setup.c:151 msgid "" "Citadel needs to run under its own user ID. This would\n" "typically be called \"citadel\", but if you are running Citadel\n" "as a public BBS, you might also call it \"bbs\" or \"guest\".\n" "The server will run under this user ID. Please specify that\n" "user ID here. You may specify either a user name or a numeric\n" "UID.\n" msgstr "" "Citadela musí běžet pod svým vlastním uživatelským ID. To je\n" "obvykle nazváno \"citadel\", avÅ¡ak provozujete-li Citadelu\n" "jako veÅ™ejné BBS, můžete jej nazvat také \"bbs\" nebo \"guest\".\n" "Server bude běžet pod tímto ID. UpÅ™esnÄ›te zde prosím toto\n" "uživatelské ID. Můžete napsat uživatelské jméno nebo použít\n" "Äíselné UID.\n" #: ../utils/setup.c:158 msgid "Listening address for the Citadel server:" msgstr "IP adresa, na které má Citadela naslouchat:" #: ../utils/setup.c:160 msgid "" "Please specify the IP address which the server should be listening to. You " "can name a specific IPv4 or IPv6 address, or you can specify\n" "'*' for 'any address', '::' for 'any IPv6 address', or '0.0.0.0'\n" "for 'any IPv4 address'. If you leave this blank, Citadel will\n" "listen on all addresses. This can usually be left to the default unless " "multiple instances of Citadel are running on the same computer." msgstr "" "UpÅ™esnÄ›te prosím adresu, na které by mÄ›l server naslouchat. Můžete zadat " "IPv4 nebo IPv6 adresu, nebo můžete\n" "použít '*' pro jakoukoli adresu, '::' pro libovolnou IPv6 adresu, nebo " "'0.0.0.0'\n" "pro libovolnou IPv4 adresu. Pokud toto pole zanecháte prázdné, bude " "Citadela\n" "naslouchat na vÅ¡ech adresách. Toto pole může být obvykle pÅ™eskoÄeno, ledaže " "by na stejném poÄítaÄi běželo více instancí Citadely." #: ../utils/setup.c:168 msgid "Server port number:" msgstr "Serverové Äíslo portu:" #: ../utils/setup.c:170 msgid "" "Specify the TCP port number on which your server will run.\n" "Normally, this will be port 504, which is the official port\n" "assigned by the IANA for Citadel servers. You will only need\n" "to specify a different port number if you run multiple instances\n" "of Citadel on the same computer and there is something else\n" "already using port 504.\n" msgstr "" "UpÅ™esnÄ›te Äíslo TCP portu, na kterém server poběží.\n" "Obvykle je jím port 504, který IANA oficiálnÄ› pÅ™idÄ›lila\n" "Citadelím serverům. Jiný port budete muset zadat\n" "pouze, pokud na stejném poÄítaÄi běží více instancí\n" "Citadely a nÄ›který z nich již port 504 používá.\n" #: ../utils/setup.c:177 msgid "Authentication method to use:" msgstr "Použitá metoda ověření:" #: ../utils/setup.c:179 msgid "" "Please choose the user authentication mode. By default Citadel will use its " "own internal user accounts database. If you choose Host, Citadel users will " "have accounts on the host system, authenticated via /etc/passwd or a PAM " "source. LDAP chooses an RFC 2307 compliant directory server, the last option " "chooses the nonstandard MS Active Directory LDAP scheme.\n" "Do not change this option unless you are sure it is required, since changing " "back requires a full reinstall of Citadel.\n" " 0. Self contained authentication\n" " 1. Host system integrated authentication\n" " 2. External LDAP - RFC 2307 compliant directory\n" " 3. External LDAP - nonstandard MS Active Directory\n" "\n" "For help: http://www.citadel.org/doku.php/faq:installation:authmodes\n" "\n" "ANSWER \"0\" UNLESS YOU COMPLETELY UNDERSTAND THIS OPTION.\n" msgstr "" "Zvolte prosím způsob ověření uživatele. ObyÄejnÄ› Citadela používá vlastní " "vnitří databázi uživatelských úÄtů. Pokud zvolíte Hostitele, uživatelé " "Citadely budou mít úÄet na hostitelském systému, ověřováni pÅ™es /etc/passwd " "nebo zdroj PAM. LDAP vybírá RFC 2307 kompatibilní server, poslední volba " "vybírá nestandardní MS Active Directory LDAP.\n" "Neměňte tuto volbu, nejste-li si jisti, že je to nutné, jelikož zpÄ›tná zmÄ›na " "vyžaduje pÅ™einstalování Citadely.\n" " 0. Vlastní ověřování\n" " 1. Ověřování u hostitele\n" " 2. VnÄ›jší LDAP (RFC 2307)\n" " 3. VnÄ›jší LDAM (nestandardní MS Active Directory)\n" "\n" "NápovÄ›du naleznete na " "http://www.citadel.org/doku.php/faq:installation:authmodes\n" "\n" "OdpovÄ›zte \"0\", ledaže otázce zcela rozumíte\n" #: ../utils/setup.c:197 msgid "LDAP host:" msgstr "LDAP server:" #: ../utils/setup.c:199 msgid "Please enter the host name or IP address of your LDAP server.\n" msgstr "Vložte prosím jméno hostitele nebo IP adresu VaÅ¡eho LDAP serveru.\n" #: ../utils/setup.c:201 msgid "LDAP port number:" msgstr "Číslo LDAP portu:" #: ../utils/setup.c:203 msgid "Please enter the port number of the LDAP service (usually 389).\n" msgstr "Zadejte prosím Äíslo portu LDAP (obvykle 389)\n" #: ../utils/setup.c:205 msgid "LDAP base DN:" msgstr "Základní LDAP DN:" #: ../utils/setup.c:207 msgid "" "Please enter the Base DN to search for authentication\n" "(for example: dc=example,dc=com)\n" msgstr "" "Zadejte prosím Základní DN (Base DN) k vyhledávání ověření\n" "(NapÅ™.: dc=example,dc=com)\n" #: ../utils/setup.c:210 msgid "LDAP bind DN:" msgstr "DN úÄtu pro LDAP dotazy:" #: ../utils/setup.c:212 msgid "" "Please enter the DN of an account to use for binding to the LDAP server for " "performing queries. The account does not require any other privileges. If " "your LDAP server allows anonymous queries, you can leave this blank.Please " "enter the DN of an account to use for binding to the LDAP server\n" "for performing queries. The account does not require any other\n" "privileges. If your LDAP server allows anonymous queries, you can\n" "leave this blank.\n" msgstr "" "Zadejte prosím DN úÄtu k pÅ™ipojení k LDAP serveru pÅ™i zasílání dotazů. Tento " "úÄet nemusí mít žádná jiná práva. Pokud Váš LDAP server povoluje anonymní " "dotazy, můžete nechat toto pole nevyplnÄ›né.\n" #: ../utils/setup.c:220 msgid "LDAP bind password:" msgstr "Heslo úÄtu pro LDAP dotazy:" #: ../utils/setup.c:222 msgid "" "If you entered a Bind DN in the previous question, you must now enter\n" "the password associated with that account. Otherwise, you can leave this\n" "blank.\n" msgstr "" "Pokud jste v pÅ™edchozí otázce zadali DN úÄtu, musíte nyní zadat\n" "heslo k tomuto úÄtu. V opaÄném případÄ› můžete necat toto pole\n" "prázdné.\n" #: ../utils/setup.c:299 msgid "Yes/No" msgstr "Ano/Ne" #: ../utils/setup.c:300 msgid "Yes" msgstr "Ano" #: ../utils/setup.c:300 msgid "No" msgstr "Ne" #: ../utils/setup.c:346 msgid "Press return to continue..." msgstr "Pro pokraÄování stisknÄ›te Enter..." #: ../utils/setup.c:364 msgid "Important Message" msgstr "Důležitá zpráva" #: ../utils/setup.c:379 msgid "Error" msgstr "Chyba" #: ../utils/setup.c:459 msgid "Adding service entry..." msgstr "PÅ™idávám zápis služby..." #. Other errors might mean something really did go wrong. #. #: ../utils/setup.c:463 ../utils/setup.c:510 ../utils/setup.c:518 msgid "Cannot open" msgstr "Nelze otevřít" #: ../utils/setup.c:569 msgid "" "Citadel already appears to be configured to start at boot.\n" "Would you like to keep your boot configuration as is?\n" msgstr "" "Zdá se, že Citadela je již nastavena, aby se pÅ™i naÄtení systému spustila.\n" "PÅ™ejete si ponechat toto nastavení nezmÄ›nÄ›no?\n" #: ../utils/setup.c:577 msgid "Would you like to automatically start Citadel at boot?\n" msgstr "PÅ™ejete si spouÅ¡tÄ›t Citadelu automaticky pÅ™i spuÅ¡tÄ›ní systému?\n" #: ../utils/setup.c:583 msgid "Cannot create" msgstr "Nelze vytvoÅ™it" #: ../utils/setup.c:682 #, c-format msgid "" "Setup can configure the \"xinetd\" service to automatically\n" "connect incoming telnet sessions to Citadel, bypassing the\n" "host system login: prompt. Would you like to do this?\n" msgstr "" "Instalátor může nastavit službu \"xinetd\", aby automaticky\n" "spojovala příchozí spojení telnetu s Citadelou bez výzvy\n" "k pÅ™ihlášení na hostitelském systému. PÅ™ejete si to provést?\n" #: ../utils/setup.c:740 msgid "You appear to have the " msgstr "Zdá se, že emailový program " #: ../utils/setup.c:742 msgid "" " email program\n" "running on your system. If you want Citadel mail\n" "connected with " msgstr "" " běží\n" "na vaÅ¡em systému. Chcete-li mail Citadely\n" "spojit s " #: ../utils/setup.c:746 msgid "" " you will have to manually integrate\n" "them. It is preferable to disable " msgstr "" " , budete to muset provést\n" "ruÄnÄ›. VýhodnÄ›jší je zakázat " #: ../utils/setup.c:749 msgid "" ", and use Citadel's\n" "SMTP, POP3, and IMAP services.\n" "\n" "May we disable " msgstr "" "a použít Citadelí\n" "SMTP, POP3 a IMAP služby.\n" "\n" "Má Citadela zakázat " #: ../utils/setup.c:753 msgid "" "so that Citadel has access to ports\n" "25, 110, and 143?\n" msgstr "" ", aby mohla pÅ™istupovat k portům\n" "25, 110 a 143?\n" #: ../utils/setup.c:863 msgid "This is currently set to:" msgstr "Toto je nyní nastaveno na:" #: ../utils/setup.c:864 msgid "Enter new value or press return to leave unchanged:" msgstr "Zadejte novou hodnotu, nebo stisknÄ›te Enter (a nic neměňte):" #: ../utils/setup.c:1067 ../utils/setup.c:1072 ../utils/setup.c:1384 msgid "setup: cannot open" msgstr "instalace: nelze otevřít" #: ../utils/setup.c:1175 #, c-format msgid "" "\n" "/etc/nsswitch.conf is configured to use the 'db' module for\n" "one or more services. This is not necessary on most systems,\n" "and it is known to crash the Citadel server when delivering\n" "mail to the Internet.\n" "\n" "Do you want this module to be automatically disabled?\n" "\n" msgstr "" "\n" "/etc/nsswitch.conf je nastaven, aby používal modul 'db' pro\n" "jednu nebo více služeb. To není na vÄ›tÅ¡inÄ› systémů potÅ™eba\n" "a je známo, že Citadela spadne pÅ™i doruÄování\n" "mailů na internet\n" "\n" "Chcete tento modul zakázat?\n" "\n" #: ../utils/setup.c:1236 ../utils/setup.c:1252 msgid "Setup finished" msgstr "Instalace dokonÄena" #: ../utils/setup.c:1237 msgid "" "Setup of the Citadel server is complete.\n" "If you will be using WebCit, please run its\n" "setup program now; otherwise, run './citadel'\n" "to log in.\n" msgstr "" "Instalace Citadelího serveru je hotova.\n" "Pokud budete používat WebCit, spusÅ¥te\n" "jeho nastavení nyní, v opaÄném případÄ›\n" "spusÅ¥te ./citadel k pÅ™ihlášení.\n" #: ../utils/setup.c:1243 msgid "Setup failed" msgstr "Instalace selhala" #: ../utils/setup.c:1244 msgid "" "Setup is finished, but the Citadel server failed to start.\n" "Go back and check your configuration.\n" msgstr "" "Instalace je dokonÄena, ale nepodaÅ™ilo se spustit Citadelí server.\n" "JdÄ›te zpÄ›t a zkontrolujte svou konfiguraci.\n" #: ../utils/setup.c:1253 msgid "Setup is finished. You may now start the server." msgstr "Instalace je dokonÄena. Nyní můžete spustit Citadelí server" #: ../utils/setup.c:1279 msgid "My System" msgstr "Můj Systém" #: ../utils/setup.c:1282 msgid "US 800 555 1212" msgstr "US 800 555 1212" #: ../utils/setup.c:1368 ../utils/setup.c:1373 msgid "setup: cannot append" msgstr "instalace: nelze pÅ™ipojit" #: ../utils/setup.c:1450 ../utils/setup.c:1457 ../utils/setup.c:1472 #: ../utils/setup.c:1512 msgid "Citadel Setup" msgstr "Instalace Citadely" #: ../utils/setup.c:1459 msgid "The directory you specified does not exist" msgstr "Zadaný adresář neexistuje" #: ../utils/setup.c:1473 msgid "" "The Citadel service is still running.\n" "Please stop the service manually and run setup again." msgstr "" "Citadelí server stále běží.\n" "Zastavte jej prosím ruÄnÄ› a spusÅ¥te instalaci znovu." #: ../utils/setup.c:1485 msgid "Citadel setup program" msgstr "Program pro instalaci Citadely" #: ../utils/setup.c:1513 msgid "This Citadel installation is too old to be upgraded." msgstr "Tato instalace Citadely je příliÅ¡ stará na povýšení." #: ../utils/setup.c:1552 ../utils/setup.c:1554 ../utils/setup.c:1556 msgid "Setting file permissions" msgstr "Nastavuji oprávnÄ›ní souborů" #~ msgid "" #~ "Please specify the IP address which the server should be listening to. If " #~ "you specify 0.0.0.0, the server will listen on all addresses." #~ msgstr "" #~ "Zadejte IP adresu, na které má server naslouchat. Zadáte-li 0.0.0.0, bude " #~ "server naslouchat na vÅ¡ech adresách." #~ msgid "" #~ "This can usually be left to the default unless multiple instances of Citadel " #~ "are running on the same computer." #~ msgstr "" #~ "Pokud na tomto poÄítaÄi nemáte více instancí Citadel, můžete nejspíš " #~ "ponechat výchozí hodnotu." #~ msgid "Internal" #~ msgstr "vlastní" #~ msgid "Host" #~ msgstr "hostitel" #~ msgid "LDAP" #~ msgstr "LDAP" #~ msgid "" #~ "Do not change this option unless you are sure it is required, since changing " #~ "back requires a full reinstall of Citadel." #~ msgstr "" #~ "Pokud si nejste jisti, že tuto možnost opravdu chcete, radÄ›ji ji neměňte, " #~ "protože návrat zpÄ›t znamená kompletní reinstalaci Citadel." #~ msgid "" #~ "While not mandatory, it is highly recommended that you set a password for " #~ "the administrator user." #~ msgstr "" #~ "I když není heslo pro správce povinné, důraznÄ› doporuÄujeme jej nastavit." #~ msgid "Internal, Host, LDAP, Active Directory" #~ msgstr "vlastní, hostitel, LDAP, Active Directory" #~ msgid "Enable external authentication mode?" #~ msgstr "Povolit externí autentizaci?" #~ msgid "" #~ "Specify the IP address on which your server will run. If you specify " #~ "0.0.0.0, Citadel will listen on all addresses. You can usually skip this " #~ "unless you are running multiple instances of Citadel on the same computer." #~ msgstr "" #~ "Zadejte IP adresu, na které má server naslouchat. Zadáte-li 0.0.0.0, bude " #~ "Citadel naslouchat na vÅ¡ech adresách. Pokud na tomto poÄítaÄi nemáte více " #~ "instancí Citadel, nejspíš můžete otázku pÅ™eskoÄit." #~ msgid "" #~ "Specify the way authentication is done, either host based or Citadel " #~ "internal. Host based accounting could be /etc/passwd or an LDAP directory. " #~ "WARNING: do not change this setting once your system is installed. Answer " #~ "\"no\" unless you completely understand this option." #~ msgstr "" #~ "Zadejte způsob autentizace - buÄ založený na mechanismech hostitelského " #~ "poÄítaÄe, nebo na interní správÄ› přímo v Citadel. Ověřováním podle " #~ "hostitelského poÄítaÄe se myslí /etc/passwd nebo LDAP adresář. VAROVÃNÃ: po " #~ "instalaci systému již toto nastavení neměňte. Pokud si nejste zcela jisti, " #~ "zda otázce rozumíte, odpovÄ›zte „ne“." #~ msgid "" #~ "Enter the name of the Citadel administrator (which is probably you). When an " #~ "account is created with this name, it will automatically be given " #~ "administrator-level access." #~ msgstr "" #~ "Zadejte jméno správce Citadel (což budete pravdÄ›podobnÄ› vy). PÅ™i vytvoÅ™ení " #~ "úÄtu se zadaným jménem mu budou automaticky pÅ™idána správcovská oprávnÄ›ní." #~ msgid "" #~ "For post configuring your Citadel Server, use citadel-webcit with your " #~ "browser, log in as the user you specified as the Administrator, and review " #~ "the Points under the Administration menu. If you have further questions " #~ "review www.citadel.org, specialy the FAQ and Documentation section." #~ msgstr "" #~ "Pro poinstalaÄní nastavení Citadel serveru si nainstalujte balík citadel-" #~ "webcit. Ve webovém prohlížeÄi se pÅ™ihlaste pod uživatelem, kterého jste " #~ "zvolili za správce a projdÄ›te si menu Administration. Máte-li další otázky, " #~ "podívejte se na http://www.citadel.org do Äástí Dokumentace a FAQ." citadel-9.01/po/citadel-setup/pt.po0000644000000000000000000003231112507024051015706 0ustar rootroot# Portuguese translation of citadel's debconf messages. # Copyright (C) 2008, Pedro Ribeiro # This file is distributed under the same license as the citadel package. # Pedro Ribeiro , 2008-2010. # msgid "" msgstr "" "Project-Id-Version: citadel-7.72-1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2010-09-28 00:22+0200\n" "PO-Revision-Date: 2010-10-22 14:42+0000\n" "Last-Translator: Pedro Ribeiro \n" "Language-Team: Portuguese \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2010-10-25 04:35+0000\n" "X-Generator: Launchpad (build Unknown)\n" "Language: pt\n" #: ../utils/setup.c:119 msgid "Citadel Home Directory" msgstr "" #: ../utils/setup.c:122 msgid "" "Enter the full pathname of the directory in which the Citadel\n" "installation you are creating or updating resides. If you\n" "specify a directory other than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" msgstr "" #: ../utils/setup.c:128 msgid "" "Enter the subdirectory name for an alternate installation of Citadel. To do " "a default installation just leave it blank.If you specify a directory other " "than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" "note that it may not have a leading /" msgstr "" #: ../utils/setup.c:135 msgid "Citadel administrator username:" msgstr "Nome de utilizador do administrador Citadel:" #: ../utils/setup.c:137 msgid "" "Please enter the name of the Citadel user account that should be granted " "administrative privileges once created. If using internal authentication " "this user account will be created if it does not exist. For external " "authentication this user account has to exist." msgstr "" "Indique, por favor, o nome da conta de utilizador do Citadel à qual serão " "concedidos privilégios de administração aquando da sua criação. Se usar a " "autenticação interna, a conta será criada se não existir. Se usar " "autenticação externa, a conta tem que existir." #: ../utils/setup.c:143 msgid "Administrator password:" msgstr "Palavra-passe de administrador:" #: ../utils/setup.c:145 msgid "" "Enter a password for the system administrator. When setup\n" "completes it will attempt to create the administrator user\n" "and set the password specified here.\n" msgstr "" #: ../utils/setup.c:149 msgid "Citadel User ID:" msgstr "" #: ../utils/setup.c:151 msgid "" "Citadel needs to run under its own user ID. This would\n" "typically be called \"citadel\", but if you are running Citadel\n" "as a public BBS, you might also call it \"bbs\" or \"guest\".\n" "The server will run under this user ID. Please specify that\n" "user ID here. You may specify either a user name or a numeric\n" "UID.\n" msgstr "" #: ../utils/setup.c:158 msgid "Listening address for the Citadel server:" msgstr "Endereço de escuta para o Citadel:" #: ../utils/setup.c:160 msgid "" "Please specify the IP address which the server should be listening to. You " "can name a specific IPv4 or IPv6 address, or you can specify\n" "'*' for 'any address', '::' for 'any IPv6 address', or '0.0.0.0'\n" "for 'any IPv4 address'. If you leave this blank, Citadel will\n" "listen on all addresses. This can usually be left to the default unless " "multiple instances of Citadel are running on the same computer." msgstr "" #: ../utils/setup.c:168 msgid "Server port number:" msgstr "Número de porto LDAP:" #: ../utils/setup.c:170 msgid "" "Specify the TCP port number on which your server will run.\n" "Normally, this will be port 504, which is the official port\n" "assigned by the IANA for Citadel servers. You will only need\n" "to specify a different port number if you run multiple instances\n" "of Citadel on the same computer and there is something else\n" "already using port 504.\n" msgstr "" #: ../utils/setup.c:177 msgid "Authentication method to use:" msgstr "Método de autenticação a usar:" #: ../utils/setup.c:179 msgid "" "Please choose the user authentication mode. By default Citadel will use its " "own internal user accounts database. If you choose Host, Citadel users will " "have accounts on the host system, authenticated via /etc/passwd or a PAM " "source. LDAP chooses an RFC 2307 compliant directory server, the last option " "chooses the nonstandard MS Active Directory LDAP scheme.\n" "Do not change this option unless you are sure it is required, since changing " "back requires a full reinstall of Citadel.\n" " 0. Self contained authentication\n" " 1. Host system integrated authentication\n" " 2. External LDAP - RFC 2307 compliant directory\n" " 3. External LDAP - nonstandard MS Active Directory\n" "\n" "For help: http://www.citadel.org/doku.php/faq:installation:authmodes\n" "\n" "ANSWER \"0\" UNLESS YOU COMPLETELY UNDERSTAND THIS OPTION.\n" msgstr "" #: ../utils/setup.c:197 msgid "LDAP host:" msgstr "Servidor LDAP:" #: ../utils/setup.c:199 msgid "Please enter the host name or IP address of your LDAP server.\n" msgstr "Indique por favor o nome ou endereço IP do servidor LDAP.\n" #: ../utils/setup.c:201 msgid "LDAP port number:" msgstr "Número de porto LDAP:" #: ../utils/setup.c:203 msgid "Please enter the port number of the LDAP service (usually 389).\n" msgstr "" "Indique por favor o número de porto do serviço LDAP (normalmente 389).\n" #: ../utils/setup.c:205 msgid "LDAP base DN:" msgstr "DN de base LDAP:" #: ../utils/setup.c:207 msgid "" "Please enter the Base DN to search for authentication\n" "(for example: dc=example,dc=com)\n" msgstr "" "Indique por favor o DN de Base para procurar por autenticação \n" "(por exemplo: dc=exemplo,dc=com).\n" #: ../utils/setup.c:210 msgid "LDAP bind DN:" msgstr "DN ligação LDAP:" #: ../utils/setup.c:212 msgid "" "Please enter the DN of an account to use for binding to the LDAP server for " "performing queries. The account does not require any other privileges. If " "your LDAP server allows anonymous queries, you can leave this blank.Please " "enter the DN of an account to use for binding to the LDAP server\n" "for performing queries. The account does not require any other\n" "privileges. If your LDAP server allows anonymous queries, you can\n" "leave this blank.\n" msgstr "" #: ../utils/setup.c:220 msgid "LDAP bind password:" msgstr "Palavra-passe de ligação:" #: ../utils/setup.c:222 msgid "" "If you entered a Bind DN in the previous question, you must now enter\n" "the password associated with that account. Otherwise, you can leave this\n" "blank.\n" msgstr "" #: ../utils/setup.c:299 msgid "Yes/No" msgstr "" #: ../utils/setup.c:300 msgid "Yes" msgstr "" #: ../utils/setup.c:300 msgid "No" msgstr "" #: ../utils/setup.c:346 msgid "Press return to continue..." msgstr "" #: ../utils/setup.c:364 msgid "Important Message" msgstr "" #: ../utils/setup.c:379 msgid "Error" msgstr "" #: ../utils/setup.c:459 msgid "Adding service entry..." msgstr "" #. Other errors might mean something really did go wrong. #. #: ../utils/setup.c:463 ../utils/setup.c:510 ../utils/setup.c:518 msgid "Cannot open" msgstr "" #: ../utils/setup.c:569 msgid "" "Citadel already appears to be configured to start at boot.\n" "Would you like to keep your boot configuration as is?\n" msgstr "" #: ../utils/setup.c:577 msgid "Would you like to automatically start Citadel at boot?\n" msgstr "" #: ../utils/setup.c:583 msgid "Cannot create" msgstr "" #: ../utils/setup.c:682 #, c-format msgid "" "Setup can configure the \"xinetd\" service to automatically\n" "connect incoming telnet sessions to Citadel, bypassing the\n" "host system login: prompt. Would you like to do this?\n" msgstr "" #: ../utils/setup.c:740 msgid "You appear to have the " msgstr "" #: ../utils/setup.c:742 msgid "" " email program\n" "running on your system. If you want Citadel mail\n" "connected with " msgstr "" #: ../utils/setup.c:746 msgid "" " you will have to manually integrate\n" "them. It is preferable to disable " msgstr "" #: ../utils/setup.c:749 msgid "" ", and use Citadel's\n" "SMTP, POP3, and IMAP services.\n" "\n" "May we disable " msgstr "" #: ../utils/setup.c:753 msgid "" "so that Citadel has access to ports\n" "25, 110, and 143?\n" msgstr "" #: ../utils/setup.c:863 msgid "This is currently set to:" msgstr "" #: ../utils/setup.c:864 msgid "Enter new value or press return to leave unchanged:" msgstr "" #: ../utils/setup.c:1067 ../utils/setup.c:1072 ../utils/setup.c:1384 msgid "setup: cannot open" msgstr "" #: ../utils/setup.c:1175 #, c-format msgid "" "\n" "/etc/nsswitch.conf is configured to use the 'db' module for\n" "one or more services. This is not necessary on most systems,\n" "and it is known to crash the Citadel server when delivering\n" "mail to the Internet.\n" "\n" "Do you want this module to be automatically disabled?\n" "\n" msgstr "" #: ../utils/setup.c:1236 ../utils/setup.c:1252 msgid "Setup finished" msgstr "" #: ../utils/setup.c:1237 msgid "" "Setup of the Citadel server is complete.\n" "If you will be using WebCit, please run its\n" "setup program now; otherwise, run './citadel'\n" "to log in.\n" msgstr "" #: ../utils/setup.c:1243 msgid "Setup failed" msgstr "" #: ../utils/setup.c:1244 msgid "" "Setup is finished, but the Citadel server failed to start.\n" "Go back and check your configuration.\n" msgstr "" #: ../utils/setup.c:1253 msgid "Setup is finished. You may now start the server." msgstr "" #: ../utils/setup.c:1279 msgid "My System" msgstr "" #: ../utils/setup.c:1282 msgid "US 800 555 1212" msgstr "" #: ../utils/setup.c:1368 ../utils/setup.c:1373 msgid "setup: cannot append" msgstr "" #: ../utils/setup.c:1450 ../utils/setup.c:1457 ../utils/setup.c:1472 #: ../utils/setup.c:1512 msgid "Citadel Setup" msgstr "" #: ../utils/setup.c:1459 msgid "The directory you specified does not exist" msgstr "" #: ../utils/setup.c:1473 msgid "" "The Citadel service is still running.\n" "Please stop the service manually and run setup again." msgstr "" #: ../utils/setup.c:1485 msgid "Citadel setup program" msgstr "" #: ../utils/setup.c:1513 msgid "This Citadel installation is too old to be upgraded." msgstr "" #: ../utils/setup.c:1552 ../utils/setup.c:1554 ../utils/setup.c:1556 msgid "Setting file permissions" msgstr "" #~ msgid "" #~ "Please specify the IP address which the server should be listening to. If " #~ "you specify 0.0.0.0, the server will listen on all addresses." #~ msgstr "" #~ "Por favor, indique o endereço IP no qual o servidor irá estar à escuta. Se " #~ "indicar 0.0.0.0 o servidor irá escutar em todos os endereços." #~ msgid "" #~ "This can usually be left to the default unless multiple instances of Citadel " #~ "are running on the same computer." #~ msgstr "" #~ "Pode normalmente deixar o valor pré-definido a não ser que execute várias " #~ "instâncias do Citadel no mesmo computador." #~ msgid "Internal" #~ msgstr "Interna" #~ msgid "Host" #~ msgstr "Host" #~ msgid "LDAP" #~ msgstr "LDAP" #~ msgid "" #~ "Do not change this option unless you are sure it is required, since changing " #~ "back requires a full reinstall of Citadel." #~ msgstr "" #~ "Não altere esta opção a não ser que esteja seguro da sua necessidade, uma " #~ "vez que mudá-la posteriormente necessita de uma reinstalação do Citadel." #~ msgid "" #~ "While not mandatory, it is highly recommended that you set a password for " #~ "the administrator user." #~ msgstr "" #~ "Embora não sendo obrigatória, é altamente recomendado que defina uma palavra-" #~ "passe para o administrador." #~ msgid "Internal, Host, LDAP, Active Directory" #~ msgstr "Interna, Host, LDAP, Active Directory" #~ msgid "Enable external authentication mode?" #~ msgstr "Habilitar modo de autenticação externa?" #~ msgid "" #~ "Please enter the name of the Citadel user account that should be granted " #~ "administrative privileges once created." #~ msgstr "" #~ "Indique o nome da conta de utilizador do Citadel à qual serão " #~ "automaticamente atribuídos privilégios administrativos aquando da sua " #~ "criação." #~ msgid "" #~ "Specify the way authentication is done, either host based or Citadel " #~ "internal. Host based authentication could be /etc/passwd or an LDAP " #~ "directory. WARNING: do not change this setting once your system is " #~ "installed. Answer \"no\" unless you completely understand this option." #~ msgstr "" #~ "Indique o modo como a autenticação é efectuada, ou pela máquina ou " #~ "internamente pelo Citadel. A autenticação pela máquina pode ser através " #~ "de/etc/passwd ou dum directório LDAP. ATENÇÃO: não mude esta opção após o " #~ "seusistema estar instalado. Responda \"no\" a não ser que entenda " #~ "completamente esta opção." #~ msgid "" #~ "For post configuring your Citadel Server, use citadel-webcit with your " #~ "browser, log in as the user you specified as the Administrator, and review " #~ "the Points under the Administration menu. If you have further questions " #~ "review www.citadel.org, especially the FAQ and Documentation section." #~ msgstr "" #~ "Para configuração posterior do seu servidor Citadel, use o citadel-webcit " #~ "com o seu browser, entre com o nome de utilazdor designado como " #~ "Administrator, e reveja 'Points' no menu 'Administration'. Se tem mais " #~ "dúvidas reveja www.citadel.org, especialmente o FAQ e a secção de " #~ "Documentação." citadel-9.01/po/citadel-setup/it.po0000644000000000000000000002266312507024051015710 0ustar rootroot# Italian translation for citadel # Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011 # This file is distributed under the same license as the citadel package. # FIRST AUTHOR , 2011. # msgid "" msgstr "" "Project-Id-Version: citadel\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2010-09-28 00:22+0200\n" "PO-Revision-Date: 2011-08-27 14:10+0000\n" "Last-Translator: Guybrush88 \n" "Language-Team: Italian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2011-08-28 04:30+0000\n" "X-Generator: Launchpad (build 13794)\n" #: ../utils/setup.c:119 msgid "Citadel Home Directory" msgstr "Cartella principale di Citadel" #: ../utils/setup.c:122 msgid "" "Enter the full pathname of the directory in which the Citadel\n" "installation you are creating or updating resides. If you\n" "specify a directory other than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" msgstr "" #: ../utils/setup.c:128 msgid "" "Enter the subdirectory name for an alternate installation of Citadel. To do " "a default installation just leave it blank.If you specify a directory other " "than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" "note that it may not have a leading /" msgstr "" #: ../utils/setup.c:135 msgid "Citadel administrator username:" msgstr "Nome utente dell'amministratore di Citadel:" #: ../utils/setup.c:137 msgid "" "Please enter the name of the Citadel user account that should be granted " "administrative privileges once created. If using internal authentication " "this user account will be created if it does not exist. For external " "authentication this user account has to exist." msgstr "" #: ../utils/setup.c:143 msgid "Administrator password:" msgstr "Password amministratore:" #: ../utils/setup.c:145 msgid "" "Enter a password for the system administrator. When setup\n" "completes it will attempt to create the administrator user\n" "and set the password specified here.\n" msgstr "" #: ../utils/setup.c:149 msgid "Citadel User ID:" msgstr "ID utente di Citadel:" #: ../utils/setup.c:151 msgid "" "Citadel needs to run under its own user ID. This would\n" "typically be called \"citadel\", but if you are running Citadel\n" "as a public BBS, you might also call it \"bbs\" or \"guest\".\n" "The server will run under this user ID. Please specify that\n" "user ID here. You may specify either a user name or a numeric\n" "UID.\n" msgstr "" #: ../utils/setup.c:158 msgid "Listening address for the Citadel server:" msgstr "" #: ../utils/setup.c:160 msgid "" "Please specify the IP address which the server should be listening to. You " "can name a specific IPv4 or IPv6 address, or you can specify\n" "'*' for 'any address', '::' for 'any IPv6 address', or '0.0.0.0'\n" "for 'any IPv4 address'. If you leave this blank, Citadel will\n" "listen on all addresses. This can usually be left to the default unless " "multiple instances of Citadel are running on the same computer." msgstr "" #: ../utils/setup.c:168 msgid "Server port number:" msgstr "" #: ../utils/setup.c:170 msgid "" "Specify the TCP port number on which your server will run.\n" "Normally, this will be port 504, which is the official port\n" "assigned by the IANA for Citadel servers. You will only need\n" "to specify a different port number if you run multiple instances\n" "of Citadel on the same computer and there is something else\n" "already using port 504.\n" msgstr "" #: ../utils/setup.c:177 msgid "Authentication method to use:" msgstr "" #: ../utils/setup.c:179 msgid "" "Please choose the user authentication mode. By default Citadel will use its " "own internal user accounts database. If you choose Host, Citadel users will " "have accounts on the host system, authenticated via /etc/passwd or a PAM " "source. LDAP chooses an RFC 2307 compliant directory server, the last option " "chooses the nonstandard MS Active Directory LDAP scheme.\n" "Do not change this option unless you are sure it is required, since changing " "back requires a full reinstall of Citadel.\n" " 0. Self contained authentication\n" " 1. Host system integrated authentication\n" " 2. External LDAP - RFC 2307 compliant directory\n" " 3. External LDAP - nonstandard MS Active Directory\n" "\n" "For help: http://www.citadel.org/doku.php/faq:installation:authmodes\n" "\n" "ANSWER \"0\" UNLESS YOU COMPLETELY UNDERSTAND THIS OPTION.\n" msgstr "" #: ../utils/setup.c:197 msgid "LDAP host:" msgstr "" #: ../utils/setup.c:199 msgid "Please enter the host name or IP address of your LDAP server.\n" msgstr "" #: ../utils/setup.c:201 msgid "LDAP port number:" msgstr "" #: ../utils/setup.c:203 msgid "Please enter the port number of the LDAP service (usually 389).\n" msgstr "" #: ../utils/setup.c:205 msgid "LDAP base DN:" msgstr "" #: ../utils/setup.c:207 msgid "" "Please enter the Base DN to search for authentication\n" "(for example: dc=example,dc=com)\n" msgstr "" #: ../utils/setup.c:210 msgid "LDAP bind DN:" msgstr "" #: ../utils/setup.c:212 msgid "" "Please enter the DN of an account to use for binding to the LDAP server for " "performing queries. The account does not require any other privileges. If " "your LDAP server allows anonymous queries, you can leave this blank.Please " "enter the DN of an account to use for binding to the LDAP server\n" "for performing queries. The account does not require any other\n" "privileges. If your LDAP server allows anonymous queries, you can\n" "leave this blank.\n" msgstr "" #: ../utils/setup.c:220 msgid "LDAP bind password:" msgstr "" #: ../utils/setup.c:222 msgid "" "If you entered a Bind DN in the previous question, you must now enter\n" "the password associated with that account. Otherwise, you can leave this\n" "blank.\n" msgstr "" #: ../utils/setup.c:299 msgid "Yes/No" msgstr "" #: ../utils/setup.c:300 msgid "Yes" msgstr "" #: ../utils/setup.c:300 msgid "No" msgstr "" #: ../utils/setup.c:346 msgid "Press return to continue..." msgstr "" #: ../utils/setup.c:364 msgid "Important Message" msgstr "" #: ../utils/setup.c:379 msgid "Error" msgstr "" #: ../utils/setup.c:459 msgid "Adding service entry..." msgstr "" #. Other errors might mean something really did go wrong. #. #: ../utils/setup.c:463 ../utils/setup.c:510 ../utils/setup.c:518 msgid "Cannot open" msgstr "" #: ../utils/setup.c:569 msgid "" "Citadel already appears to be configured to start at boot.\n" "Would you like to keep your boot configuration as is?\n" msgstr "" #: ../utils/setup.c:577 msgid "Would you like to automatically start Citadel at boot?\n" msgstr "" #: ../utils/setup.c:583 msgid "Cannot create" msgstr "" #: ../utils/setup.c:682 #, c-format msgid "" "Setup can configure the \"xinetd\" service to automatically\n" "connect incoming telnet sessions to Citadel, bypassing the\n" "host system login: prompt. Would you like to do this?\n" msgstr "" #: ../utils/setup.c:740 msgid "You appear to have the " msgstr "" #: ../utils/setup.c:742 msgid "" " email program\n" "running on your system. If you want Citadel mail\n" "connected with " msgstr "" #: ../utils/setup.c:746 msgid "" " you will have to manually integrate\n" "them. It is preferable to disable " msgstr "" #: ../utils/setup.c:749 msgid "" ", and use Citadel's\n" "SMTP, POP3, and IMAP services.\n" "\n" "May we disable " msgstr "" #: ../utils/setup.c:753 msgid "" "so that Citadel has access to ports\n" "25, 110, and 143?\n" msgstr "" #: ../utils/setup.c:863 msgid "This is currently set to:" msgstr "" #: ../utils/setup.c:864 msgid "Enter new value or press return to leave unchanged:" msgstr "" #: ../utils/setup.c:1067 ../utils/setup.c:1072 ../utils/setup.c:1384 msgid "setup: cannot open" msgstr "" #: ../utils/setup.c:1175 #, c-format msgid "" "\n" "/etc/nsswitch.conf is configured to use the 'db' module for\n" "one or more services. This is not necessary on most systems,\n" "and it is known to crash the Citadel server when delivering\n" "mail to the Internet.\n" "\n" "Do you want this module to be automatically disabled?\n" "\n" msgstr "" #: ../utils/setup.c:1236 ../utils/setup.c:1252 msgid "Setup finished" msgstr "" #: ../utils/setup.c:1237 msgid "" "Setup of the Citadel server is complete.\n" "If you will be using WebCit, please run its\n" "setup program now; otherwise, run './citadel'\n" "to log in.\n" msgstr "" #: ../utils/setup.c:1243 msgid "Setup failed" msgstr "" #: ../utils/setup.c:1244 msgid "" "Setup is finished, but the Citadel server failed to start.\n" "Go back and check your configuration.\n" msgstr "" #: ../utils/setup.c:1253 msgid "Setup is finished. You may now start the server." msgstr "" #: ../utils/setup.c:1279 msgid "My System" msgstr "" #: ../utils/setup.c:1282 msgid "US 800 555 1212" msgstr "" #: ../utils/setup.c:1368 ../utils/setup.c:1373 msgid "setup: cannot append" msgstr "" #: ../utils/setup.c:1450 ../utils/setup.c:1457 ../utils/setup.c:1472 #: ../utils/setup.c:1512 msgid "Citadel Setup" msgstr "" #: ../utils/setup.c:1459 msgid "The directory you specified does not exist" msgstr "" #: ../utils/setup.c:1473 msgid "" "The Citadel service is still running.\n" "Please stop the service manually and run setup again." msgstr "" #: ../utils/setup.c:1485 msgid "Citadel setup program" msgstr "" #: ../utils/setup.c:1513 msgid "This Citadel installation is too old to be upgraded." msgstr "" #: ../utils/setup.c:1552 ../utils/setup.c:1554 ../utils/setup.c:1556 msgid "Setting file permissions" msgstr "" citadel-9.01/po/citadel-setup/es.po0000644000000000000000000004311212507024051015673 0ustar rootroot# citadel po-debconf translation to Spanish # Copyright (C) 2009 Software in the Public Interest # This file is distributed under the same license as the citadel package. # # Changes: # - Initial translation # Shukoh , 2009 # # # Traductores, si no conoce el formato PO, merece la pena leer la # documentación de gettext, especialmente las secciones dedicadas a este # formato, por ejemplo ejecutando: # info -n '(gettext)PO Files' # info -n '(gettext)Header Entry' # # Equipo de traducción al español, por favor lean antes de traducir # los siguientes documentos: # # - El proyecto de traducción de Debian al español # http://www.debian.org/intl/spanish/coordinacion # especialmente las notas de traducción en # http://www.debian.org/intl/spanish/notas # # - La guía de traducción de po's de debconf: # /usr/share/doc/po-debconf/README-trans # o http://www.debian.org/intl/l10n/po-debconf/README-trans # msgid "" msgstr "" "Project-Id-Version: citadel-7.66-1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2010-09-28 00:22+0200\n" "PO-Revision-Date: 2012-05-28 16:11+0000\n" "Last-Translator: Enrique D A \n" "Language-Team: Debian Spanish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2012-05-29 04:33+0000\n" "X-Generator: Launchpad (build 15316)\n" "Language: es\n" #: ../utils/setup.c:119 msgid "Citadel Home Directory" msgstr "Carpeta personal Citadel" #: ../utils/setup.c:122 msgid "" "Enter the full pathname of the directory in which the Citadel\n" "installation you are creating or updating resides. If you\n" "specify a directory other than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" msgstr "" "Introduzca la ruta completa del directorio en el que\n" "se encuentra la instalación de Citadel. Si especifica\n" "un directorio diferente al predeterminado, deberá\n" "especificar la opción -h en el servidor cuando lo inicie.\n" #: ../utils/setup.c:128 msgid "" "Enter the subdirectory name for an alternate installation of Citadel. To do " "a default installation just leave it blank.If you specify a directory other " "than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" "note that it may not have a leading /" msgstr "" "Ingrese el nombre de subdirectorio para una instalación alternativa de " "Citadel. Para una instalación predeterminada solo dejelo vacío. Si " "especifica un directorio diferente al predeterminado,\n" "necesitará especificar la bandera -h al servidor cuando lo inicie.\n" "observe que el nombre no termina con /" #: ../utils/setup.c:135 msgid "Citadel administrator username:" msgstr "Nombre de usuario del administrador de Citadel:" #: ../utils/setup.c:137 msgid "" "Please enter the name of the Citadel user account that should be granted " "administrative privileges once created. If using internal authentication " "this user account will be created if it does not exist. For external " "authentication this user account has to exist." msgstr "" "Por favor, introduzca el nombre de la cuenta de usuario de Citadel a la que " "se le deben conceder privilegios de administrador una vez creada. Si emplea " "autenticación interna esta cuenta de usuario se creará, si aún no existe. " "Para autenticación externa esta cuenta tiene que existir." #: ../utils/setup.c:143 msgid "Administrator password:" msgstr "Contraseña del administrador:" #: ../utils/setup.c:145 msgid "" "Enter a password for the system administrator. When setup\n" "completes it will attempt to create the administrator user\n" "and set the password specified here.\n" msgstr "" "Introduzca una contraseña para la administración del sistema.\n" "Cuando finalice la configuración se intentará crear un usuario\n" "administrador con la contraseña indicada.\n" #: ../utils/setup.c:149 msgid "Citadel User ID:" msgstr "ID de usuario de Citadel:" #: ../utils/setup.c:151 msgid "" "Citadel needs to run under its own user ID. This would\n" "typically be called \"citadel\", but if you are running Citadel\n" "as a public BBS, you might also call it \"bbs\" or \"guest\".\n" "The server will run under this user ID. Please specify that\n" "user ID here. You may specify either a user name or a numeric\n" "UID.\n" msgstr "" "Citadel necesita correr bajo su propio ID de usuario. Este puede\n" "llamarse \"citadel\", pero si está corriendo Citadel\n" "como un BBS público, podría llamarlo \"bbs\" o \"invitado\".\n" "El servidor correrá bajo este ID de usuario. Por favor indique el\n" "ID de usuario aquí. Puede especificar tanto un nombre de usuario o un \n" "UID númerico.\n" #: ../utils/setup.c:158 msgid "Listening address for the Citadel server:" msgstr "Dirección de escucha para el servidor Citadel:" #: ../utils/setup.c:160 msgid "" "Please specify the IP address which the server should be listening to. You " "can name a specific IPv4 or IPv6 address, or you can specify\n" "'*' for 'any address', '::' for 'any IPv6 address', or '0.0.0.0'\n" "for 'any IPv4 address'. If you leave this blank, Citadel will\n" "listen on all addresses. This can usually be left to the default unless " "multiple instances of Citadel are running on the same computer." msgstr "" "Por favor especifique la dirección IP que el servidor deberá escuchar. Puede " "nombrar una dirección especifica IPv4 o IPv6, o puede\n" "especificar '*' para 'cualquier dirección', '::' para 'cualquier dirección " "IPv6' o '0.0.0.0'\n" "para 'cualquier dirección IPv4'. Si lo deja en blanco, Citadel escuchará\n" "en todas las direcciones. Puede dejar el valor predeterminado a menos que " "varias instancias de Citadel esten corriendo en la misma computadora." #: ../utils/setup.c:168 msgid "Server port number:" msgstr "Número de puerto del servidor:" #: ../utils/setup.c:170 msgid "" "Specify the TCP port number on which your server will run.\n" "Normally, this will be port 504, which is the official port\n" "assigned by the IANA for Citadel servers. You will only need\n" "to specify a different port number if you run multiple instances\n" "of Citadel on the same computer and there is something else\n" "already using port 504.\n" msgstr "" "Especifica el número del puerto TCP con el que su sistema se ejecutará.\n" "Normalmente, este será el puerto 504, que es el puerto oficial\n" "asignado por la IANA para los servidores de Citadel. Sólos tendrá\n" "que especificar un número de puerto diferente si ejecutas múltiples " "instancias\n" "de Citadel en el mismo ordenador y hay algo más usando el puerto 504.\n" #: ../utils/setup.c:177 msgid "Authentication method to use:" msgstr "Método de autenticación a utilizar:" #: ../utils/setup.c:179 msgid "" "Please choose the user authentication mode. By default Citadel will use its " "own internal user accounts database. If you choose Host, Citadel users will " "have accounts on the host system, authenticated via /etc/passwd or a PAM " "source. LDAP chooses an RFC 2307 compliant directory server, the last option " "chooses the nonstandard MS Active Directory LDAP scheme.\n" "Do not change this option unless you are sure it is required, since changing " "back requires a full reinstall of Citadel.\n" " 0. Self contained authentication\n" " 1. Host system integrated authentication\n" " 2. External LDAP - RFC 2307 compliant directory\n" " 3. External LDAP - nonstandard MS Active Directory\n" "\n" "For help: http://www.citadel.org/doku.php/faq:installation:authmodes\n" "\n" "ANSWER \"0\" UNLESS YOU COMPLETELY UNDERSTAND THIS OPTION.\n" msgstr "" "Por favor seleccione el modo de autenticación del usuario. Citadel puede " "usar su propia base de datos de cuentas de usuario internas. Si elige " "anfitrión (Host), Los usuarios de Citadel deberán tener cuentas en el equipo " "anfitrión, autenticados por medio de /etc/passwd o una fuente PAM. LDAP " "selecciona un servidor de directorio que cumple con RFC 2307, la última " "opción elige el esquema no estándar de Directorio Activo de MS.\n" "No cambie esta opción a menos que este seguro que es requerida, ya que " "cambiarla de nuevo requiere una reinstalación completa de Citadel.\n" "0. Autenticación interna\n" "1. Autenticación integrada en el sistema anfitrión\n" "3. LDAP Externo - Directorio compatible RFC 2307\n" "3. LDAP Externo - Directorio Activo MS no estándar\n" #: ../utils/setup.c:197 msgid "LDAP host:" msgstr "Equipo de LDAP:" #: ../utils/setup.c:199 msgid "Please enter the host name or IP address of your LDAP server.\n" msgstr "" "Por favor, introduzca el nombre del equipo o la dirección IP de su servidor " "LDAP.\n" #: ../utils/setup.c:201 msgid "LDAP port number:" msgstr "Número del puerto de LDAP:" #: ../utils/setup.c:203 msgid "Please enter the port number of the LDAP service (usually 389).\n" msgstr "" "Por favor, introduzca el número del puerto del servicio LDAP (generalmente " "389).\n" #: ../utils/setup.c:205 msgid "LDAP base DN:" msgstr "DN base de LDAP:" #: ../utils/setup.c:207 msgid "" "Please enter the Base DN to search for authentication\n" "(for example: dc=example,dc=com)\n" msgstr "" "Por favor, introduzca el DN base para buscar la autenticación (por ejemplo:\n" "dc=ejemplo, dc=com).\n" #: ../utils/setup.c:210 msgid "LDAP bind DN:" msgstr "DN de enlace de LDAP:" #: ../utils/setup.c:212 msgid "" "Please enter the DN of an account to use for binding to the LDAP server for " "performing queries. The account does not require any other privileges. If " "your LDAP server allows anonymous queries, you can leave this blank.Please " "enter the DN of an account to use for binding to the LDAP server\n" "for performing queries. The account does not require any other\n" "privileges. If your LDAP server allows anonymous queries, you can\n" "leave this blank.\n" msgstr "" "Por favor introduzca el DN de una cuenta a usar para ligar al servidor LDAP\n" "para realizar consultas. La cuenta no requiere ningún otro\n" "privilegio. Si el servidor LDAP permite consultas anónimas, puede\n" "dejarlo en blanco.\n" #: ../utils/setup.c:220 msgid "LDAP bind password:" msgstr "Contraseña para el enlace de LDAP:" #: ../utils/setup.c:222 msgid "" "If you entered a Bind DN in the previous question, you must now enter\n" "the password associated with that account. Otherwise, you can leave this\n" "blank.\n" msgstr "" "Si introdujo un Bind DN en la pregunta anterior, ahora debe introducir\n" "la contraseña asociada con esa cuenta. Si no, puede dejar este\n" "en blanco.\n" #: ../utils/setup.c:299 msgid "Yes/No" msgstr "Sí/No" #: ../utils/setup.c:300 msgid "Yes" msgstr "Sí" #: ../utils/setup.c:300 msgid "No" msgstr "No" #: ../utils/setup.c:346 msgid "Press return to continue..." msgstr "Pulse Intro para continuar..." #: ../utils/setup.c:364 msgid "Important Message" msgstr "Mensaje importante" #: ../utils/setup.c:379 msgid "Error" msgstr "Error" #: ../utils/setup.c:459 msgid "Adding service entry..." msgstr "Adición de entrada de servicio" #. Other errors might mean something really did go wrong. #. #: ../utils/setup.c:463 ../utils/setup.c:510 ../utils/setup.c:518 msgid "Cannot open" msgstr "No se pudo abrir" #: ../utils/setup.c:569 msgid "" "Citadel already appears to be configured to start at boot.\n" "Would you like to keep your boot configuration as is?\n" msgstr "" "Citadel parece estar configurado para iniciarse en el arranque.\n" "¿Quiere mantener su configuración de arranque, como está?\n" #: ../utils/setup.c:577 msgid "Would you like to automatically start Citadel at boot?\n" msgstr "¿Te gustaría que Citadel se iniciase automáticamente al arrancar?\n" #: ../utils/setup.c:583 msgid "Cannot create" msgstr "No se pudo crear" #: ../utils/setup.c:682 #, c-format msgid "" "Setup can configure the \"xinetd\" service to automatically\n" "connect incoming telnet sessions to Citadel, bypassing the\n" "host system login: prompt. Would you like to do this?\n" msgstr "" "El programa de instalación puede configurar el servicio \"xinetd\" para " "automáticamente\n" "conectar las sesiones entrantes de telnet al Citadel, sin pasar por el\n" "inicio de sesión del sistema anfitrión. ¿Quiere hacer esto?\n" #: ../utils/setup.c:740 msgid "You appear to have the " msgstr "Parece que tiene el " #: ../utils/setup.c:742 msgid "" " email program\n" "running on your system. If you want Citadel mail\n" "connected with " msgstr "" " programa de correo\n" "corriendo en su sistema. Si desea que Citadel mal\n" "se conecte con " #: ../utils/setup.c:746 msgid "" " you will have to manually integrate\n" "them. It is preferable to disable " msgstr "" " tendrá que integrarlos manualmente.\n" "Es preferible desactivarlo " #: ../utils/setup.c:749 msgid "" ", and use Citadel's\n" "SMTP, POP3, and IMAP services.\n" "\n" "May we disable " msgstr "" ", y usar los servicios de Citadel\n" "SMTP, POP3 e IMAP\n" "Podemos desactivarlos " #: ../utils/setup.c:753 msgid "" "so that Citadel has access to ports\n" "25, 110, and 143?\n" msgstr "" "para que Citadel tenga acceso a los puertos\n" "25, 110 y 143?\n" #: ../utils/setup.c:863 msgid "This is currently set to:" msgstr "Esto está actualmente establecido a:" #: ../utils/setup.c:864 msgid "Enter new value or press return to leave unchanged:" msgstr "Entre un nuevo valor o presione retorno para dejarlo sin cambio:" #: ../utils/setup.c:1067 ../utils/setup.c:1072 ../utils/setup.c:1384 msgid "setup: cannot open" msgstr "instalación: no puede abrir" #: ../utils/setup.c:1175 #, c-format msgid "" "\n" "/etc/nsswitch.conf is configured to use the 'db' module for\n" "one or more services. This is not necessary on most systems,\n" "and it is known to crash the Citadel server when delivering\n" "mail to the Internet.\n" "\n" "Do you want this module to be automatically disabled?\n" "\n" msgstr "" "\n" "/etc/nsswitch.conf está configurador para usar el modulo 'db' para\n" "uno o más servicios. Esto no es necesario en la mayoría de los sistemas,\n" "y se sabe que bloquea el servidor Citadel al entregar el correo\n" "a Internet.\n" "\n" "¿Quieres que este módulo se desactive automáticamente?\n" "\n" #: ../utils/setup.c:1236 ../utils/setup.c:1252 msgid "Setup finished" msgstr "Configuración terminada" #: ../utils/setup.c:1237 msgid "" "Setup of the Citadel server is complete.\n" "If you will be using WebCit, please run its\n" "setup program now; otherwise, run './citadel'\n" "to log in.\n" msgstr "" "La configuración del servidor Citadel está completa.\n" "Si usará Webcit, por favor ejecute su\n" "programa de configuración ahora, de lo contrario, corra './citadel'\n" "para entrar.\n" #: ../utils/setup.c:1243 msgid "Setup failed" msgstr "La configuración fallo." #: ../utils/setup.c:1244 msgid "" "Setup is finished, but the Citadel server failed to start.\n" "Go back and check your configuration.\n" msgstr "" "La configuración ha concluido, pero el servidor Citadel fallo al iniciar.\n" "Regrese y revise su configuración.\n" #: ../utils/setup.c:1253 msgid "Setup is finished. You may now start the server." msgstr "La configuración ha terminado. Ahora puede iniciar el servidor." #: ../utils/setup.c:1279 msgid "My System" msgstr "Mi Sistema" #: ../utils/setup.c:1282 msgid "US 800 555 1212" msgstr "US 800 555 1212" #: ../utils/setup.c:1368 ../utils/setup.c:1373 msgid "setup: cannot append" msgstr "configuración: no puede anexar" #: ../utils/setup.c:1450 ../utils/setup.c:1457 ../utils/setup.c:1472 #: ../utils/setup.c:1512 msgid "Citadel Setup" msgstr "Instalar Citadel" #: ../utils/setup.c:1459 msgid "The directory you specified does not exist" msgstr "El directorio especificado no existe" #: ../utils/setup.c:1473 msgid "" "The Citadel service is still running.\n" "Please stop the service manually and run setup again." msgstr "" "El servicio Citadel aún está corriendo.\n" "Por favor detenga el servicio manualmente y corra la configuración otra vez." #: ../utils/setup.c:1485 msgid "Citadel setup program" msgstr "Programa de configuración de Citadel" #: ../utils/setup.c:1513 msgid "This Citadel installation is too old to be upgraded." msgstr "Esta instalación de Citadel es demasiado vieja para ser actualizada." #: ../utils/setup.c:1552 ../utils/setup.c:1554 ../utils/setup.c:1556 msgid "Setting file permissions" msgstr "Configurar permisos de archivo" #~ msgid "" #~ "Please specify the IP address which the server should be listening to. If " #~ "you specify 0.0.0.0, the server will listen on all addresses." #~ msgstr "" #~ "Por favor, indique la dirección IP en la que el servidor debería estar " #~ "escuchando. Si indica la 0.0.0.0, el servidor escuchará en todas las " #~ "direcciones." #~ msgid "" #~ "This can usually be left to the default unless multiple instances of Citadel " #~ "are running on the same computer." #~ msgstr "" #~ "Normalmente se puede dejar el valor predeterminado, a no ser que tenga " #~ "varias instancias de Citadel ejecutándose en el mismo equipo." #~ msgid "Internal" #~ msgstr "Interna" #~ msgid "Host" #~ msgstr "Host" #~ msgid "LDAP" #~ msgstr "LDAP" #~ msgid "" #~ "Do not change this option unless you are sure it is required, since changing " #~ "back requires a full reinstall of Citadel." #~ msgstr "" #~ "No cambie esta opción a no ser que esté seguro de que es necesario, ya que " #~ "si lo cambia se deberá reinstalar completamente Citadel para volver al " #~ "estado anterior." #~ msgid "" #~ "While not mandatory, it is highly recommended that you set a password for " #~ "the administrator user." #~ msgstr "" #~ "A pesar de que no es obligatorio, es bastante recomendable establecer una " #~ "contraseña para el administrador." #~ msgid "Internal, Host, LDAP, Active Directory" #~ msgstr "Interna, Host, LDAP, Active Directory" citadel-9.01/po/citadel-setup/fi.po0000644000000000000000000002672212507024051015672 0ustar rootroot# Esko Arajärvi , 2009. msgid "" msgstr "" "Project-Id-Version: citadel\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2010-09-28 00:22+0200\n" "PO-Revision-Date: 2010-10-22 14:35+0000\n" "Last-Translator: Esko Arajärvi \n" "Language-Team: Finnish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2010-11-03 04:54+0000\n" "X-Generator: Launchpad (build Unknown)\n" "X-Poedit-Country: FINLAND\n" "Language: fi\n" #: ../utils/setup.c:119 msgid "Citadel Home Directory" msgstr "" #: ../utils/setup.c:122 msgid "" "Enter the full pathname of the directory in which the Citadel\n" "installation you are creating or updating resides. If you\n" "specify a directory other than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" msgstr "" #: ../utils/setup.c:128 msgid "" "Enter the subdirectory name for an alternate installation of Citadel. To do " "a default installation just leave it blank.If you specify a directory other " "than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" "note that it may not have a leading /" msgstr "" #: ../utils/setup.c:135 msgid "Citadel administrator username:" msgstr "Citadel-ylläpitäjän käyttäjätunnus:" #: ../utils/setup.c:137 msgid "" "Please enter the name of the Citadel user account that should be granted " "administrative privileges once created. If using internal authentication " "this user account will be created if it does not exist. For external " "authentication this user account has to exist." msgstr "" "Anna sen Citadel-käyttäjätunnuksen nimi, jolle annetaan luotaessa " "ylläpitäjän oikeudet. Käytettäessä sisäistä tunnistautumista, tämä " "käyttäjätunnus luodaan, jos sitä ei ole. Ulkoista tunnistautumista " "käytettäessä tämän käyttäjätunnuksen täytyy olla olemassa." #: ../utils/setup.c:143 msgid "Administrator password:" msgstr "Ylläpitäjän salasana:" #: ../utils/setup.c:145 msgid "" "Enter a password for the system administrator. When setup\n" "completes it will attempt to create the administrator user\n" "and set the password specified here.\n" msgstr "" #: ../utils/setup.c:149 msgid "Citadel User ID:" msgstr "" #: ../utils/setup.c:151 msgid "" "Citadel needs to run under its own user ID. This would\n" "typically be called \"citadel\", but if you are running Citadel\n" "as a public BBS, you might also call it \"bbs\" or \"guest\".\n" "The server will run under this user ID. Please specify that\n" "user ID here. You may specify either a user name or a numeric\n" "UID.\n" msgstr "" #: ../utils/setup.c:158 msgid "Listening address for the Citadel server:" msgstr "Osoite, jota Citadel-palvelin kuuntelee:" #: ../utils/setup.c:160 msgid "" "Please specify the IP address which the server should be listening to. You " "can name a specific IPv4 or IPv6 address, or you can specify\n" "'*' for 'any address', '::' for 'any IPv6 address', or '0.0.0.0'\n" "for 'any IPv4 address'. If you leave this blank, Citadel will\n" "listen on all addresses. This can usually be left to the default unless " "multiple instances of Citadel are running on the same computer." msgstr "" #: ../utils/setup.c:168 msgid "Server port number:" msgstr "" #: ../utils/setup.c:170 msgid "" "Specify the TCP port number on which your server will run.\n" "Normally, this will be port 504, which is the official port\n" "assigned by the IANA for Citadel servers. You will only need\n" "to specify a different port number if you run multiple instances\n" "of Citadel on the same computer and there is something else\n" "already using port 504.\n" msgstr "" #: ../utils/setup.c:177 msgid "Authentication method to use:" msgstr "" #: ../utils/setup.c:179 msgid "" "Please choose the user authentication mode. By default Citadel will use its " "own internal user accounts database. If you choose Host, Citadel users will " "have accounts on the host system, authenticated via /etc/passwd or a PAM " "source. LDAP chooses an RFC 2307 compliant directory server, the last option " "chooses the nonstandard MS Active Directory LDAP scheme.\n" "Do not change this option unless you are sure it is required, since changing " "back requires a full reinstall of Citadel.\n" " 0. Self contained authentication\n" " 1. Host system integrated authentication\n" " 2. External LDAP - RFC 2307 compliant directory\n" " 3. External LDAP - nonstandard MS Active Directory\n" "\n" "For help: http://www.citadel.org/doku.php/faq:installation:authmodes\n" "\n" "ANSWER \"0\" UNLESS YOU COMPLETELY UNDERSTAND THIS OPTION.\n" msgstr "" #: ../utils/setup.c:197 msgid "LDAP host:" msgstr "LDAP-kone:" #: ../utils/setup.c:199 msgid "Please enter the host name or IP address of your LDAP server.\n" msgstr "Anna LDAP-palvelimen verkkonimi tai IP-osoite.\n" #: ../utils/setup.c:201 msgid "LDAP port number:" msgstr "LDAP-portin numero:" #: ../utils/setup.c:203 msgid "Please enter the port number of the LDAP service (usually 389).\n" msgstr "Anna LDAP-palvelun porttinumero (yleensä 389).\n" #: ../utils/setup.c:205 msgid "LDAP base DN:" msgstr "LDAPin kanta-DN (erittelevä nimi):" #: ../utils/setup.c:207 msgid "" "Please enter the Base DN to search for authentication\n" "(for example: dc=example,dc=com)\n" msgstr "" "Anna erittelevä nimi, josta kirjautumistietoja etsitään (esimerkiksi: " "dc=example,dc=com).\n" #: ../utils/setup.c:210 msgid "LDAP bind DN:" msgstr "LDAPin yhteys-DN (yhteyden erittelevä nimi):" #: ../utils/setup.c:212 msgid "" "Please enter the DN of an account to use for binding to the LDAP server for " "performing queries. The account does not require any other privileges. If " "your LDAP server allows anonymous queries, you can leave this blank.Please " "enter the DN of an account to use for binding to the LDAP server\n" "for performing queries. The account does not require any other\n" "privileges. If your LDAP server allows anonymous queries, you can\n" "leave this blank.\n" msgstr "" #: ../utils/setup.c:220 msgid "LDAP bind password:" msgstr "LDAP-yhteyden salasana:" #: ../utils/setup.c:222 msgid "" "If you entered a Bind DN in the previous question, you must now enter\n" "the password associated with that account. Otherwise, you can leave this\n" "blank.\n" msgstr "" "Jos määrittelit yhteys-DN:n aiemmassa kysymyksessä, syötä tähän tunnuksen\n" "salasana. Voi muussa tapauksessa jättää kentän tyhjäksi.\n" #: ../utils/setup.c:299 msgid "Yes/No" msgstr "" #: ../utils/setup.c:300 msgid "Yes" msgstr "" #: ../utils/setup.c:300 msgid "No" msgstr "" #: ../utils/setup.c:346 msgid "Press return to continue..." msgstr "" #: ../utils/setup.c:364 msgid "Important Message" msgstr "" #: ../utils/setup.c:379 msgid "Error" msgstr "" #: ../utils/setup.c:459 msgid "Adding service entry..." msgstr "" #. Other errors might mean something really did go wrong. #. #: ../utils/setup.c:463 ../utils/setup.c:510 ../utils/setup.c:518 msgid "Cannot open" msgstr "" #: ../utils/setup.c:569 msgid "" "Citadel already appears to be configured to start at boot.\n" "Would you like to keep your boot configuration as is?\n" msgstr "" #: ../utils/setup.c:577 msgid "Would you like to automatically start Citadel at boot?\n" msgstr "" #: ../utils/setup.c:583 msgid "Cannot create" msgstr "" #: ../utils/setup.c:682 #, c-format msgid "" "Setup can configure the \"xinetd\" service to automatically\n" "connect incoming telnet sessions to Citadel, bypassing the\n" "host system login: prompt. Would you like to do this?\n" msgstr "" #: ../utils/setup.c:740 msgid "You appear to have the " msgstr "" #: ../utils/setup.c:742 msgid "" " email program\n" "running on your system. If you want Citadel mail\n" "connected with " msgstr "" #: ../utils/setup.c:746 msgid "" " you will have to manually integrate\n" "them. It is preferable to disable " msgstr "" #: ../utils/setup.c:749 msgid "" ", and use Citadel's\n" "SMTP, POP3, and IMAP services.\n" "\n" "May we disable " msgstr "" #: ../utils/setup.c:753 msgid "" "so that Citadel has access to ports\n" "25, 110, and 143?\n" msgstr "" #: ../utils/setup.c:863 msgid "This is currently set to:" msgstr "" #: ../utils/setup.c:864 msgid "Enter new value or press return to leave unchanged:" msgstr "" #: ../utils/setup.c:1067 ../utils/setup.c:1072 ../utils/setup.c:1384 msgid "setup: cannot open" msgstr "" #: ../utils/setup.c:1175 #, c-format msgid "" "\n" "/etc/nsswitch.conf is configured to use the 'db' module for\n" "one or more services. This is not necessary on most systems,\n" "and it is known to crash the Citadel server when delivering\n" "mail to the Internet.\n" "\n" "Do you want this module to be automatically disabled?\n" "\n" msgstr "" #: ../utils/setup.c:1236 ../utils/setup.c:1252 msgid "Setup finished" msgstr "" #: ../utils/setup.c:1237 msgid "" "Setup of the Citadel server is complete.\n" "If you will be using WebCit, please run its\n" "setup program now; otherwise, run './citadel'\n" "to log in.\n" msgstr "" #: ../utils/setup.c:1243 msgid "Setup failed" msgstr "" #: ../utils/setup.c:1244 msgid "" "Setup is finished, but the Citadel server failed to start.\n" "Go back and check your configuration.\n" msgstr "" #: ../utils/setup.c:1253 msgid "Setup is finished. You may now start the server." msgstr "" #: ../utils/setup.c:1279 msgid "My System" msgstr "" #: ../utils/setup.c:1282 msgid "US 800 555 1212" msgstr "" #: ../utils/setup.c:1368 ../utils/setup.c:1373 msgid "setup: cannot append" msgstr "" #: ../utils/setup.c:1450 ../utils/setup.c:1457 ../utils/setup.c:1472 #: ../utils/setup.c:1512 msgid "Citadel Setup" msgstr "" #: ../utils/setup.c:1459 msgid "The directory you specified does not exist" msgstr "" #: ../utils/setup.c:1473 msgid "" "The Citadel service is still running.\n" "Please stop the service manually and run setup again." msgstr "" #: ../utils/setup.c:1485 msgid "Citadel setup program" msgstr "" #: ../utils/setup.c:1513 msgid "This Citadel installation is too old to be upgraded." msgstr "" #: ../utils/setup.c:1552 ../utils/setup.c:1554 ../utils/setup.c:1556 msgid "Setting file permissions" msgstr "" #~ msgid "" #~ "Please specify the IP address which the server should be listening to. If " #~ "you specify 0.0.0.0, the server will listen on all addresses." #~ msgstr "" #~ "Anna IP-osoite, jota palvelin kuuntelee. Jos syötät 0.0.0.0 palvelin " #~ "kuuntelee kaikkia osoitteita." #~ msgid "" #~ "This can usually be left to the default unless multiple instances of Citadel " #~ "are running on the same computer." #~ msgstr "" #~ "Oletusarvoa voidaan yleensä käyttää, ellei samalla koneella ajeta useampia " #~ "Citadel-instansseja." #~ msgid "Internal" #~ msgstr "Sisäinen" #~ msgid "Host" #~ msgstr "Isäntä" #~ msgid "LDAP" #~ msgstr "LDAP" #~ msgid "" #~ "Do not change this option unless you are sure it is required, since changing " #~ "back requires a full reinstall of Citadel." #~ msgstr "" #~ "Älä vaihda tätä valintaa, ellet ole varma, että se on tarpeen, koska " #~ "takaisin vaihtaminen vaatii, että Citadel asennetaan täysin uudelleen." #~ msgid "" #~ "While not mandatory, it is highly recommended that you set a password for " #~ "the administrator user." #~ msgstr "" #~ "On erittäin suositeltavaa asettaa ylläpitäjälle salasana, vaikka se ei " #~ "olekaan pakollista." #~ msgid "Internal, Host, LDAP, Active Directory" #~ msgstr "Sisäinen, Isäntä, LDAP, Active Directory" #~ msgid "Enable external authentication mode?" #~ msgstr "Otetaanko ulkoinen tunnistautumistapa käyttöön?" citadel-9.01/po/citadel-setup/en_GB.po0000644000000000000000000003611412507024051016242 0ustar rootroot# English (United Kingdom) translation for citadel # Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 # This file is distributed under the same license as the citadel package. # FIRST AUTHOR , 2012. # msgid "" msgstr "" "Project-Id-Version: citadel\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2010-09-28 00:22+0200\n" "PO-Revision-Date: 2012-09-19 12:46+0000\n" "Last-Translator: Biffaboy \n" "Language-Team: English (United Kingdom) \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2012-09-20 04:31+0000\n" "X-Generator: Launchpad (build 15985)\n" #: ../utils/setup.c:119 msgid "Citadel Home Directory" msgstr "Citadel Home Directory" #: ../utils/setup.c:122 msgid "" "Enter the full pathname of the directory in which the Citadel\n" "installation you are creating or updating resides. If you\n" "specify a directory other than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" msgstr "" "Enter the full pathname of the directory in which the Citadel\n" "installation you are creating or updating resides. If you\n" "specify a directory other than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" #: ../utils/setup.c:128 msgid "" "Enter the subdirectory name for an alternate installation of Citadel. To do " "a default installation just leave it blank.If you specify a directory other " "than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" "note that it may not have a leading /" msgstr "" "Enter the subdirectory name for an alternate installation of Citadel. To do " "a default installation just leave it blank.If you specify a directory other " "than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" "note that it may not have a leading /" #: ../utils/setup.c:135 msgid "Citadel administrator username:" msgstr "Citadel administrator username:" #: ../utils/setup.c:137 msgid "" "Please enter the name of the Citadel user account that should be granted " "administrative privileges once created. If using internal authentication " "this user account will be created if it does not exist. For external " "authentication this user account has to exist." msgstr "" "Please enter the name of the Citadel user account that should be granted " "administrative privileges once created. If using internal authentication " "this user account will be created if it does not exist. For external " "authentication this user account has to exist." #: ../utils/setup.c:143 msgid "Administrator password:" msgstr "Administrator password:" #: ../utils/setup.c:145 msgid "" "Enter a password for the system administrator. When setup\n" "completes it will attempt to create the administrator user\n" "and set the password specified here.\n" msgstr "" "Enter a password for the system administrator. When setup\n" "completes it will attempt to create the administrator user\n" "and set the password specified here.\n" #: ../utils/setup.c:149 msgid "Citadel User ID:" msgstr "Citadel User ID:" #: ../utils/setup.c:151 msgid "" "Citadel needs to run under its own user ID. This would\n" "typically be called \"citadel\", but if you are running Citadel\n" "as a public BBS, you might also call it \"bbs\" or \"guest\".\n" "The server will run under this user ID. Please specify that\n" "user ID here. You may specify either a user name or a numeric\n" "UID.\n" msgstr "" "Citadel needs to run under its own user ID. This would\n" "typically be called \"citadel\", but if you are running Citadel\n" "as a public BBS, you might also call it \"bbs\" or \"guest\".\n" "The server will run under this user ID. Please specify that\n" "user ID here. You may specify either a user name or a numeric\n" "UID.\n" #: ../utils/setup.c:158 msgid "Listening address for the Citadel server:" msgstr "Listening address for the Citadel server:" #: ../utils/setup.c:160 msgid "" "Please specify the IP address which the server should be listening to. You " "can name a specific IPv4 or IPv6 address, or you can specify\n" "'*' for 'any address', '::' for 'any IPv6 address', or '0.0.0.0'\n" "for 'any IPv4 address'. If you leave this blank, Citadel will\n" "listen on all addresses. This can usually be left to the default unless " "multiple instances of Citadel are running on the same computer." msgstr "" "Please specify the IP address which the server should be listening to. You " "can name a specific IPv4 or IPv6 address, or you can specify\n" "'*' for 'any address', '::' for 'any IPv6 address', or '0.0.0.0'\n" "for 'any IPv4 address'. If you leave this blank, Citadel will\n" "listen on all addresses. This can usually be left to the default unless " "multiple instances of Citadel are running on the same computer." #: ../utils/setup.c:168 msgid "Server port number:" msgstr "Server port number:" #: ../utils/setup.c:170 msgid "" "Specify the TCP port number on which your server will run.\n" "Normally, this will be port 504, which is the official port\n" "assigned by the IANA for Citadel servers. You will only need\n" "to specify a different port number if you run multiple instances\n" "of Citadel on the same computer and there is something else\n" "already using port 504.\n" msgstr "" "Specify the TCP port number on which your server will run.\n" "Normally, this will be port 504, which is the official port\n" "assigned by the IANA for Citadel servers. You will only need\n" "to specify a different port number if you run multiple instances\n" "of Citadel on the same computer and there is something else\n" "already using port 504.\n" #: ../utils/setup.c:177 msgid "Authentication method to use:" msgstr "Authentication method to use:" #: ../utils/setup.c:179 msgid "" "Please choose the user authentication mode. By default Citadel will use its " "own internal user accounts database. If you choose Host, Citadel users will " "have accounts on the host system, authenticated via /etc/passwd or a PAM " "source. LDAP chooses an RFC 2307 compliant directory server, the last option " "chooses the nonstandard MS Active Directory LDAP scheme.\n" "Do not change this option unless you are sure it is required, since changing " "back requires a full reinstall of Citadel.\n" " 0. Self contained authentication\n" " 1. Host system integrated authentication\n" " 2. External LDAP - RFC 2307 compliant directory\n" " 3. External LDAP - nonstandard MS Active Directory\n" "\n" "For help: http://www.citadel.org/doku.php/faq:installation:authmodes\n" "\n" "ANSWER \"0\" UNLESS YOU COMPLETELY UNDERSTAND THIS OPTION.\n" msgstr "" "Please choose the user authentication mode. By default Citadel will use its " "own internal user accounts database. If you choose Host, Citadel users will " "have accounts on the host system, authenticated via /etc/passwd or a PAM " "source. LDAP chooses an RFC 2307 compliant directory server, the last option " "chooses the nonstandard MS Active Directory LDAP scheme.\n" "Do not change this option unless you are sure it is required, since changing " "back requires a full reinstall of Citadel.\n" " 0. Self contained authentication\n" " 1. Host system integrated authentication\n" " 2. External LDAP - RFC 2307 compliant directory\n" " 3. External LDAP - nonstandard MS Active Directory\n" "\n" "For help: http://www.citadel.org/doku.php/faq:installation:authmodes\n" "\n" "ANSWER \"0\" UNLESS YOU COMPLETELY UNDERSTAND THIS OPTION.\n" #: ../utils/setup.c:197 msgid "LDAP host:" msgstr "LDAP host:" #: ../utils/setup.c:199 msgid "Please enter the host name or IP address of your LDAP server.\n" msgstr "Please enter the host name or IP address of your LDAP server.\n" #: ../utils/setup.c:201 msgid "LDAP port number:" msgstr "LDAP port number:" #: ../utils/setup.c:203 msgid "Please enter the port number of the LDAP service (usually 389).\n" msgstr "Please enter the port number of the LDAP service (usually 389).\n" #: ../utils/setup.c:205 msgid "LDAP base DN:" msgstr "LDAP base DN:" #: ../utils/setup.c:207 msgid "" "Please enter the Base DN to search for authentication\n" "(for example: dc=example,dc=com)\n" msgstr "" "Please enter the Base DN to search for authentication\n" "(for example: dc=example,dc=com)\n" #: ../utils/setup.c:210 msgid "LDAP bind DN:" msgstr "LDAP bind DN:" #: ../utils/setup.c:212 msgid "" "Please enter the DN of an account to use for binding to the LDAP server for " "performing queries. The account does not require any other privileges. If " "your LDAP server allows anonymous queries, you can leave this blank.Please " "enter the DN of an account to use for binding to the LDAP server\n" "for performing queries. The account does not require any other\n" "privileges. If your LDAP server allows anonymous queries, you can\n" "leave this blank.\n" msgstr "" "Please enter the DN of an account to use for binding to the LDAP server for " "performing queries. The account does not require any other privileges. If " "your LDAP server allows anonymous queries, you can leave this blank.Please " "enter the DN of an account to use for binding to the LDAP server\n" "for performing queries. The account does not require any other\n" "privileges. If your LDAP server allows anonymous queries, you can\n" "leave this blank.\n" #: ../utils/setup.c:220 msgid "LDAP bind password:" msgstr "LDAP bind password:" #: ../utils/setup.c:222 msgid "" "If you entered a Bind DN in the previous question, you must now enter\n" "the password associated with that account. Otherwise, you can leave this\n" "blank.\n" msgstr "" "If you entered a Bind DN in the previous question, you must now enter\n" "the password associated with that account. Otherwise, you can leave this\n" "blank.\n" #: ../utils/setup.c:299 msgid "Yes/No" msgstr "Yes/No" #: ../utils/setup.c:300 msgid "Yes" msgstr "Yes" #: ../utils/setup.c:300 msgid "No" msgstr "No" #: ../utils/setup.c:346 msgid "Press return to continue..." msgstr "Press return to continue..." #: ../utils/setup.c:364 msgid "Important Message" msgstr "Important Message" #: ../utils/setup.c:379 msgid "Error" msgstr "Error" #: ../utils/setup.c:459 msgid "Adding service entry..." msgstr "Adding service entry..." #. Other errors might mean something really did go wrong. #. #: ../utils/setup.c:463 ../utils/setup.c:510 ../utils/setup.c:518 msgid "Cannot open" msgstr "Cannot open" #: ../utils/setup.c:569 msgid "" "Citadel already appears to be configured to start at boot.\n" "Would you like to keep your boot configuration as is?\n" msgstr "" "Citadel already appears to be configured to start at boot.\n" "Would you like to keep your boot configuration as is?\n" #: ../utils/setup.c:577 msgid "Would you like to automatically start Citadel at boot?\n" msgstr "Would you like to automatically start Citadel at boot?\n" #: ../utils/setup.c:583 msgid "Cannot create" msgstr "Cannot create" #: ../utils/setup.c:682 #, c-format msgid "" "Setup can configure the \"xinetd\" service to automatically\n" "connect incoming telnet sessions to Citadel, bypassing the\n" "host system login: prompt. Would you like to do this?\n" msgstr "" "Setup can configure the \"xinetd\" service to automatically\n" "connect incoming telnet sessions to Citadel, bypassing the\n" "host system login: prompt. Would you like to do this?\n" #: ../utils/setup.c:740 msgid "You appear to have the " msgstr "You appear to have the " #: ../utils/setup.c:742 msgid "" " email program\n" "running on your system. If you want Citadel mail\n" "connected with " msgstr "" " email program\n" "running on your system. If you want Citadel mail\n" "connected with " #: ../utils/setup.c:746 msgid "" " you will have to manually integrate\n" "them. It is preferable to disable " msgstr "" " you will have to manually integrate\n" "them. It is preferable to disable " #: ../utils/setup.c:749 msgid "" ", and use Citadel's\n" "SMTP, POP3, and IMAP services.\n" "\n" "May we disable " msgstr "" ", and use Citadel's\n" "SMTP, POP3, and IMAP services.\n" "\n" "May we disable " #: ../utils/setup.c:753 msgid "" "so that Citadel has access to ports\n" "25, 110, and 143?\n" msgstr "" "so that Citadel has access to ports\n" "25, 110, and 143?\n" #: ../utils/setup.c:863 msgid "This is currently set to:" msgstr "This is currently set to:" #: ../utils/setup.c:864 msgid "Enter new value or press return to leave unchanged:" msgstr "Enter new value or press return to leave unchanged:" #: ../utils/setup.c:1067 ../utils/setup.c:1072 ../utils/setup.c:1384 msgid "setup: cannot open" msgstr "setup: cannot open" #: ../utils/setup.c:1175 #, c-format msgid "" "\n" "/etc/nsswitch.conf is configured to use the 'db' module for\n" "one or more services. This is not necessary on most systems,\n" "and it is known to crash the Citadel server when delivering\n" "mail to the Internet.\n" "\n" "Do you want this module to be automatically disabled?\n" "\n" msgstr "" "\n" "/etc/nsswitch.conf is configured to use the 'db' module for\n" "one or more services. This is not necessary on most systems,\n" "and it is known to crash the Citadel server when delivering\n" "mail to the Internet.\n" "\n" "Do you want this module to be automatically disabled?\n" "\n" #: ../utils/setup.c:1236 ../utils/setup.c:1252 msgid "Setup finished" msgstr "Setup finished" #: ../utils/setup.c:1237 msgid "" "Setup of the Citadel server is complete.\n" "If you will be using WebCit, please run its\n" "setup program now; otherwise, run './citadel'\n" "to log in.\n" msgstr "" "Setup of the Citadel server is complete.\n" "If you will be using WebCit, please run its\n" "setup program now; otherwise, run './citadel'\n" "to log in.\n" #: ../utils/setup.c:1243 msgid "Setup failed" msgstr "Setup failed" #: ../utils/setup.c:1244 msgid "" "Setup is finished, but the Citadel server failed to start.\n" "Go back and check your configuration.\n" msgstr "" "Setup is finished, but the Citadel server failed to start.\n" "Go back and check your configuration.\n" #: ../utils/setup.c:1253 msgid "Setup is finished. You may now start the server." msgstr "Setup is finished. You may now start the server." #: ../utils/setup.c:1279 msgid "My System" msgstr "My System" #: ../utils/setup.c:1282 msgid "US 800 555 1212" msgstr "US 800 555 1212" #: ../utils/setup.c:1368 ../utils/setup.c:1373 msgid "setup: cannot append" msgstr "setup: cannot append" #: ../utils/setup.c:1450 ../utils/setup.c:1457 ../utils/setup.c:1472 #: ../utils/setup.c:1512 msgid "Citadel Setup" msgstr "Citadel Setup" #: ../utils/setup.c:1459 msgid "The directory you specified does not exist" msgstr "The directory you specified does not exist" #: ../utils/setup.c:1473 msgid "" "The Citadel service is still running.\n" "Please stop the service manually and run setup again." msgstr "" "The Citadel service is still running.\n" "Please stop the service manually and run setup again." #: ../utils/setup.c:1485 msgid "Citadel setup program" msgstr "Citadel setup program" #: ../utils/setup.c:1513 msgid "This Citadel installation is too old to be upgraded." msgstr "This Citadel installation is too old to be upgraded." #: ../utils/setup.c:1552 ../utils/setup.c:1554 ../utils/setup.c:1556 msgid "Setting file permissions" msgstr "Setting file permissions" citadel-9.01/po/citadel-setup/vi.po0000644000000000000000000002751512507024051015713 0ustar rootroot# Vietnamese translation for Citadel. # Copyright © 2009 Free Software Foundation, Inc. # Clytie Siddall , 2008-2009. # msgid "" msgstr "" "Project-Id-Version: citadel 7.61-1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2010-09-28 00:22+0200\n" "PO-Revision-Date: 2010-10-24 15:58+0000\n" "Last-Translator: Clytie Siddall \n" "Language-Team: Vietnamese \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: LocFactoryEditor 1.8\n" "X-Launchpad-Export-Date: 2010-10-23 04:47+0000\n" "X-Generator: Launchpad (build Unknown)\n" "Language: vi\n" #: ../utils/setup.c:119 msgid "Citadel Home Directory" msgstr "" #: ../utils/setup.c:122 msgid "" "Enter the full pathname of the directory in which the Citadel\n" "installation you are creating or updating resides. If you\n" "specify a directory other than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" msgstr "" #: ../utils/setup.c:128 msgid "" "Enter the subdirectory name for an alternate installation of Citadel. To do " "a default installation just leave it blank.If you specify a directory other " "than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" "note that it may not have a leading /" msgstr "" #: ../utils/setup.c:135 msgid "Citadel administrator username:" msgstr "Tên ngưá»i dùng quản trị Citadel:" #: ../utils/setup.c:137 msgid "" "Please enter the name of the Citadel user account that should be granted " "administrative privileges once created. If using internal authentication " "this user account will be created if it does not exist. For external " "authentication this user account has to exist." msgstr "" "Hãy gõ tên cá»§a tài khoản ngưá»i dùng Citadel nên có quyá»n quản trị. Nếu sá»­ " "dụng chức năng xác thá»±c ná»™i bá»™ thì tài khoản ngưá»i dùng này không tồn tại sẽ " "được tạo. Äối vá»›i xác thá»±c từ xa, tài khoản ngưá»i dùng này phải tồn tại." #: ../utils/setup.c:143 msgid "Administrator password:" msgstr "Mật khẩu quản trị:" #: ../utils/setup.c:145 msgid "" "Enter a password for the system administrator. When setup\n" "completes it will attempt to create the administrator user\n" "and set the password specified here.\n" msgstr "" #: ../utils/setup.c:149 msgid "Citadel User ID:" msgstr "" #: ../utils/setup.c:151 msgid "" "Citadel needs to run under its own user ID. This would\n" "typically be called \"citadel\", but if you are running Citadel\n" "as a public BBS, you might also call it \"bbs\" or \"guest\".\n" "The server will run under this user ID. Please specify that\n" "user ID here. You may specify either a user name or a numeric\n" "UID.\n" msgstr "" #: ../utils/setup.c:158 msgid "Listening address for the Citadel server:" msgstr "Äịa chỉ lắng nghe cho trình phục vụ Citadel:" #: ../utils/setup.c:160 msgid "" "Please specify the IP address which the server should be listening to. You " "can name a specific IPv4 or IPv6 address, or you can specify\n" "'*' for 'any address', '::' for 'any IPv6 address', or '0.0.0.0'\n" "for 'any IPv4 address'. If you leave this blank, Citadel will\n" "listen on all addresses. This can usually be left to the default unless " "multiple instances of Citadel are running on the same computer." msgstr "" #: ../utils/setup.c:168 msgid "Server port number:" msgstr "" #: ../utils/setup.c:170 msgid "" "Specify the TCP port number on which your server will run.\n" "Normally, this will be port 504, which is the official port\n" "assigned by the IANA for Citadel servers. You will only need\n" "to specify a different port number if you run multiple instances\n" "of Citadel on the same computer and there is something else\n" "already using port 504.\n" msgstr "" #: ../utils/setup.c:177 msgid "Authentication method to use:" msgstr "" #: ../utils/setup.c:179 msgid "" "Please choose the user authentication mode. By default Citadel will use its " "own internal user accounts database. If you choose Host, Citadel users will " "have accounts on the host system, authenticated via /etc/passwd or a PAM " "source. LDAP chooses an RFC 2307 compliant directory server, the last option " "chooses the nonstandard MS Active Directory LDAP scheme.\n" "Do not change this option unless you are sure it is required, since changing " "back requires a full reinstall of Citadel.\n" " 0. Self contained authentication\n" " 1. Host system integrated authentication\n" " 2. External LDAP - RFC 2307 compliant directory\n" " 3. External LDAP - nonstandard MS Active Directory\n" "\n" "For help: http://www.citadel.org/doku.php/faq:installation:authmodes\n" "\n" "ANSWER \"0\" UNLESS YOU COMPLETELY UNDERSTAND THIS OPTION.\n" msgstr "" #: ../utils/setup.c:197 msgid "LDAP host:" msgstr "Máy chá»§ LDAP:" #: ../utils/setup.c:199 msgid "Please enter the host name or IP address of your LDAP server.\n" msgstr "Hãy gõ tên máy hay địa chỉ IP cá»§a máy phục vụ LDAP nên dùng.\n" #: ../utils/setup.c:201 msgid "LDAP port number:" msgstr "Số thứ tá»± cổng LDAP:" #: ../utils/setup.c:203 msgid "Please enter the port number of the LDAP service (usually 389).\n" msgstr "Hãy gõ số thứ tá»± cổng cá»§a dịch vụ LDAP (thưá»ng là 389).\n" #: ../utils/setup.c:205 msgid "LDAP base DN:" msgstr "DN cÆ¡ bản LDAP:" #: ../utils/setup.c:207 msgid "" "Please enter the Base DN to search for authentication\n" "(for example: dc=example,dc=com)\n" msgstr "" #: ../utils/setup.c:210 msgid "LDAP bind DN:" msgstr "DN tổ hợp LDAP:" #: ../utils/setup.c:212 msgid "" "Please enter the DN of an account to use for binding to the LDAP server for " "performing queries. The account does not require any other privileges. If " "your LDAP server allows anonymous queries, you can leave this blank.Please " "enter the DN of an account to use for binding to the LDAP server\n" "for performing queries. The account does not require any other\n" "privileges. If your LDAP server allows anonymous queries, you can\n" "leave this blank.\n" msgstr "" #: ../utils/setup.c:220 msgid "LDAP bind password:" msgstr "Mật khẩu tổ hợp LDAP:" #: ../utils/setup.c:222 msgid "" "If you entered a Bind DN in the previous question, you must now enter\n" "the password associated with that account. Otherwise, you can leave this\n" "blank.\n" msgstr "" "Nếu bạn đã nhập má»™t tên phân biệt (DN) kiểu đóng kết khi đáp ứng câu há»i \n" "đằng trước, ở đây cÅ©ng cần gõ mật khẩu tương ứng vá»›i tài khoản đó. Không " "thì\n" "bá» trống trưá»ng này.\n" #: ../utils/setup.c:299 msgid "Yes/No" msgstr "" #: ../utils/setup.c:300 msgid "Yes" msgstr "" #: ../utils/setup.c:300 msgid "No" msgstr "" #: ../utils/setup.c:346 msgid "Press return to continue..." msgstr "" #: ../utils/setup.c:364 msgid "Important Message" msgstr "" #: ../utils/setup.c:379 msgid "Error" msgstr "" #: ../utils/setup.c:459 msgid "Adding service entry..." msgstr "" #. Other errors might mean something really did go wrong. #. #: ../utils/setup.c:463 ../utils/setup.c:510 ../utils/setup.c:518 msgid "Cannot open" msgstr "" #: ../utils/setup.c:569 msgid "" "Citadel already appears to be configured to start at boot.\n" "Would you like to keep your boot configuration as is?\n" msgstr "" #: ../utils/setup.c:577 msgid "Would you like to automatically start Citadel at boot?\n" msgstr "" #: ../utils/setup.c:583 msgid "Cannot create" msgstr "" #: ../utils/setup.c:682 #, c-format msgid "" "Setup can configure the \"xinetd\" service to automatically\n" "connect incoming telnet sessions to Citadel, bypassing the\n" "host system login: prompt. Would you like to do this?\n" msgstr "" #: ../utils/setup.c:740 msgid "You appear to have the " msgstr "" #: ../utils/setup.c:742 msgid "" " email program\n" "running on your system. If you want Citadel mail\n" "connected with " msgstr "" #: ../utils/setup.c:746 msgid "" " you will have to manually integrate\n" "them. It is preferable to disable " msgstr "" #: ../utils/setup.c:749 msgid "" ", and use Citadel's\n" "SMTP, POP3, and IMAP services.\n" "\n" "May we disable " msgstr "" #: ../utils/setup.c:753 msgid "" "so that Citadel has access to ports\n" "25, 110, and 143?\n" msgstr "" #: ../utils/setup.c:863 msgid "This is currently set to:" msgstr "" #: ../utils/setup.c:864 msgid "Enter new value or press return to leave unchanged:" msgstr "" #: ../utils/setup.c:1067 ../utils/setup.c:1072 ../utils/setup.c:1384 msgid "setup: cannot open" msgstr "" #: ../utils/setup.c:1175 #, c-format msgid "" "\n" "/etc/nsswitch.conf is configured to use the 'db' module for\n" "one or more services. This is not necessary on most systems,\n" "and it is known to crash the Citadel server when delivering\n" "mail to the Internet.\n" "\n" "Do you want this module to be automatically disabled?\n" "\n" msgstr "" #: ../utils/setup.c:1236 ../utils/setup.c:1252 msgid "Setup finished" msgstr "" #: ../utils/setup.c:1237 msgid "" "Setup of the Citadel server is complete.\n" "If you will be using WebCit, please run its\n" "setup program now; otherwise, run './citadel'\n" "to log in.\n" msgstr "" #: ../utils/setup.c:1243 msgid "Setup failed" msgstr "" #: ../utils/setup.c:1244 msgid "" "Setup is finished, but the Citadel server failed to start.\n" "Go back and check your configuration.\n" msgstr "" #: ../utils/setup.c:1253 msgid "Setup is finished. You may now start the server." msgstr "" #: ../utils/setup.c:1279 msgid "My System" msgstr "" #: ../utils/setup.c:1282 msgid "US 800 555 1212" msgstr "" #: ../utils/setup.c:1368 ../utils/setup.c:1373 msgid "setup: cannot append" msgstr "" #: ../utils/setup.c:1450 ../utils/setup.c:1457 ../utils/setup.c:1472 #: ../utils/setup.c:1512 msgid "Citadel Setup" msgstr "" #: ../utils/setup.c:1459 msgid "The directory you specified does not exist" msgstr "" #: ../utils/setup.c:1473 msgid "" "The Citadel service is still running.\n" "Please stop the service manually and run setup again." msgstr "" #: ../utils/setup.c:1485 msgid "Citadel setup program" msgstr "" #: ../utils/setup.c:1513 msgid "This Citadel installation is too old to be upgraded." msgstr "" #: ../utils/setup.c:1552 ../utils/setup.c:1554 ../utils/setup.c:1556 msgid "Setting file permissions" msgstr "" #~ msgid "" #~ "Please specify the IP address which the server should be listening to. If " #~ "you specify 0.0.0.0, the server will listen on all addresses." #~ msgstr "" #~ "Hãy chỉ định địa chỉ IP trên đó trình phục vụ nên lắng nghe. Äặt địa chỉ « " #~ "0.0.0.0 » thì trình phục vụ lắng nghe trên má»i địa chỉ." #~ msgid "" #~ "This can usually be left to the default unless multiple instances of Citadel " #~ "are running on the same computer." #~ msgstr "" #~ "Giá trị mặc định thưá»ng vẫn thích hợp nếu chỉ có má»™t tiến trình Citadel Ä‘ang " #~ "chạy trên máy này." #~ msgid "Internal" #~ msgstr "Ná»™i bá»™" #~ msgid "Host" #~ msgstr "Máy chá»§" #~ msgid "LDAP" #~ msgstr "LDAP" #~ msgid "" #~ "Do not change this option unless you are sure it is required, since changing " #~ "back requires a full reinstall of Citadel." #~ msgstr "" #~ "Chưa chắc thì không nên bật tuỳ chá»n này, vì việc hoàn nguyên tuỳ chá»n này " #~ "cần thiết cài đặt lại Citadel má»™t cách hoàn toàn." #~ msgid "" #~ "While not mandatory, it is highly recommended that you set a password for " #~ "the administrator user." #~ msgstr "" #~ "Äể bảo vệ máy tính, rất khuyên bạn đặt mật khẩu cho ngưá»i dùng quản trị." #~ msgid "Internal, Host, LDAP, Active Directory" #~ msgstr "Ná»™i bá»™, Máy chá»§, LDAP, Thư mục Hoạt động" #~ msgid "Enable external authentication mode?" #~ msgstr "Bật chế độ xác thá»±c bên ngoài không?" citadel-9.01/po/citadel-setup/ru.po0000644000000000000000000005013512507024051015715 0ustar rootroot# translation of citadel_7.63-1_ru.po to Russian # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # # Yuri Kozlov , 2008. # Max Kosmach , 2009. # Yuri Kozlov , 2009. msgid "" msgstr "" "Project-Id-Version: citadel 7.63-1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2010-09-28 00:22+0200\n" "PO-Revision-Date: 2012-01-01 22:15+0000\n" "Last-Translator: Alexander Vrublevskiy \n" "Language-Team: Russian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2012-01-02 04:55+0000\n" "X-Generator: Launchpad (build 14560)\n" "Language: ru\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" #: ../utils/setup.c:119 msgid "Citadel Home Directory" msgstr "Ð”Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ Citadel" #: ../utils/setup.c:122 msgid "" "Enter the full pathname of the directory in which the Citadel\n" "installation you are creating or updating resides. If you\n" "specify a directory other than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" msgstr "" "Введите полный путь к директории уÑтановки/Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Citadel.\n" "ЕÑли путь отличаетÑÑ Ð¾Ñ‚ пути по умолчанию, не забудьте указать\n" "его в значении параметра -h при запуÑке citserver\n" #: ../utils/setup.c:128 msgid "" "Enter the subdirectory name for an alternate installation of Citadel. To do " "a default installation just leave it blank.If you specify a directory other " "than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" "note that it may not have a leading /" msgstr "" "Ð”Ð»Ñ ÑƒÑтановки в директорию по умолчанию оÑтавьте Ñто поле пуÑтым.\n" "ЕÑли вам необходимо уÑтановить Citadel в какое-либо другое меÑто,\n" "введите здеÑÑŒ полный путь к нужной директории (без / в начале!).\n" "Имейте в виду, что Ñто значение нужно будет передать Ñ Ð¿Ð°Ñ€Ð°Ð¼ÐµÑ‚Ñ€Ð¾Ð¼ -h при " "запуÑке citserver" #: ../utils/setup.c:135 msgid "Citadel administrator username:" msgstr "ÐдминиÑтратор Citadel" #: ../utils/setup.c:137 msgid "" "Please enter the name of the Citadel user account that should be granted " "administrative privileges once created. If using internal authentication " "this user account will be created if it does not exist. For external " "authentication this user account has to exist." msgstr "" "Введите Ð¸Ð¼Ñ ÑƒÑ‡ÐµÑ‚Ð½Ð¾Ð¹ запиÑи админиÑтратора Citadel.\r\n" "ЕÑли иÑпользуетÑÑ Ð²Ð½ÐµÑˆÐ½ÑÑ Ð°ÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ, запиÑÑŒ должна ÑущеÑтвовать.\r\n" "ЕÑли внешнÑÑ Ð°ÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ Ð¿Ñ€Ð¾Ð¸Ñходит через LDAP/AD,\r\n" "в качеÑтве имени админиÑтратора укажите Display Name нужной запиÑи" #: ../utils/setup.c:143 msgid "Administrator password:" msgstr "Пароль админиÑтратора" #: ../utils/setup.c:145 msgid "" "Enter a password for the system administrator. When setup\n" "completes it will attempt to create the administrator user\n" "and set the password specified here.\n" msgstr "Введите пароль админиÑтратора\n" #: ../utils/setup.c:149 msgid "Citadel User ID:" msgstr "ID Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Citadel" #: ../utils/setup.c:151 msgid "" "Citadel needs to run under its own user ID. This would\n" "typically be called \"citadel\", but if you are running Citadel\n" "as a public BBS, you might also call it \"bbs\" or \"guest\".\n" "The server will run under this user ID. Please specify that\n" "user ID here. You may specify either a user name or a numeric\n" "UID.\n" msgstr "" "По умолчанию программа setup автоматичеÑки Ñоздает пользователÑ,\n" "от имени которого будет работать Ñервер - citadel.\n" "ЕÑли Ñто вам не подходит, вы можете указать в Ñтом поле Ð¸Ð¼Ñ Ð¸Ð»Ð¸ ID другого " "пользователÑ\n" #: ../utils/setup.c:158 msgid "Listening address for the Citadel server:" msgstr "IP Ð°Ð´Ñ€ÐµÑ Ð½Ð° котором Citadel ожидает запроÑÑ‹" #: ../utils/setup.c:160 msgid "" "Please specify the IP address which the server should be listening to. You " "can name a specific IPv4 or IPv6 address, or you can specify\n" "'*' for 'any address', '::' for 'any IPv6 address', or '0.0.0.0'\n" "for 'any IPv4 address'. If you leave this blank, Citadel will\n" "listen on all addresses. This can usually be left to the default unless " "multiple instances of Citadel are running on the same computer." msgstr "" "По умолчанию Ñервер Citadel ожидает запроÑÑ‹ на вÑех IP адреÑах ÑиÑтемы.\n" "Обычно Ñтого доÑтаточно, еÑли только вы не планируете запуÑкать\n" "неÑколько ÑкземплÑров Citadel. Тем не менее, здеÑÑŒ вы можете указать\n" "конкретный Ð°Ð´Ñ€ÐµÑ IPv4 или IPv6, на котором Citadel будет ожидать запроÑÑ‹" #: ../utils/setup.c:168 msgid "Server port number:" msgstr "Ðомер порта" #: ../utils/setup.c:170 msgid "" "Specify the TCP port number on which your server will run.\n" "Normally, this will be port 504, which is the official port\n" "assigned by the IANA for Citadel servers. You will only need\n" "to specify a different port number if you run multiple instances\n" "of Citadel on the same computer and there is something else\n" "already using port 504.\n" msgstr "" "По умолчанию Citadel работает на порту 504/tcp, который официально\n" "зарегиÑтрирован в IANA. Однако, еÑли вы хотите запуÑтить неÑколько\n" "ÑкземплÑров Citadel на одной машине, укажите здеÑÑŒ какое-либо иное значение\n" #: ../utils/setup.c:177 msgid "Authentication method to use:" msgstr "СпоÑоб аутентификации" #: ../utils/setup.c:179 msgid "" "Please choose the user authentication mode. By default Citadel will use its " "own internal user accounts database. If you choose Host, Citadel users will " "have accounts on the host system, authenticated via /etc/passwd or a PAM " "source. LDAP chooses an RFC 2307 compliant directory server, the last option " "chooses the nonstandard MS Active Directory LDAP scheme.\n" "Do not change this option unless you are sure it is required, since changing " "back requires a full reinstall of Citadel.\n" " 0. Self contained authentication\n" " 1. Host system integrated authentication\n" " 2. External LDAP - RFC 2307 compliant directory\n" " 3. External LDAP - nonstandard MS Active Directory\n" "\n" "For help: http://www.citadel.org/doku.php/faq:installation:authmodes\n" "\n" "ANSWER \"0\" UNLESS YOU COMPLETELY UNDERSTAND THIS OPTION.\n" msgstr "" "Укажите ÑпоÑоб аутентификации пользователей.\n" "По умолчанию Citadel иÑпользует ÑобÑтвенный механизм аутентификации.\n" "Однако, еÑть варианты ÑиÑтемной аутентификацией (через /etc/passwd или любой " "модуль PAM),\n" "а также аутентификации через LDAP (RFC2307) и Active Directory (выбирайте " "Ñтот вариант,\n" "еÑли у Ð²Ð°Ñ AD). Имейте в виду, что метод аутентификации в дальнейшем " "невозможно\n" "будет изменить без потери доÑтупа Ð´Ð»Ñ Ð²Ñех ÑущеÑтвующих пользователей!\n" "\n" "0) внутреннÑÑ Ð°ÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ (по умолчанию)\n" "1) ÑиÑÑ‚ÐµÐ¼Ð½Ð°Ñ Ð°ÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ (/etc/passwd)\n" "2) внешнÑÑ Ð°ÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ - LDAP RFC2307\n" "3) внешнÑÑ Ð°ÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ - M$ Active Directory\n" "\n" "Подробнее: http://www.citadel.org/doku.php/faq:installation:authmodes\n" "\n" "Ð’ÐИМÐÐИЕ!!! ЕÑли не уверены в Ñвоих дейÑтвиÑÑ…, выбирайте вариант \"0\"!\n" #: ../utils/setup.c:197 msgid "LDAP host:" msgstr "Сервер LDAP" #: ../utils/setup.c:199 msgid "Please enter the host name or IP address of your LDAP server.\n" msgstr "Введите Ð°Ð´Ñ€ÐµÑ Ñервера LDAP\n" #: ../utils/setup.c:201 msgid "LDAP port number:" msgstr "Порт LDAP" #: ../utils/setup.c:203 msgid "Please enter the port number of the LDAP service (usually 389).\n" msgstr "Введите номер порта LDAP (обычно 389)\n" #: ../utils/setup.c:205 msgid "LDAP base DN:" msgstr "ПоиÑк в LDAP" #: ../utils/setup.c:207 msgid "" "Please enter the Base DN to search for authentication\n" "(for example: dc=example,dc=com)\n" msgstr "" "Введите distinguished name отправной точки Ð´Ð»Ñ Ð¿Ð¾Ð¸Ñка учетных запиÑей в " "LDAP\n" "(например, CN=Users,DC=domain,DC=local)\n" #: ../utils/setup.c:210 msgid "LDAP bind DN:" msgstr "Пользователь LDAP" #: ../utils/setup.c:212 msgid "" "Please enter the DN of an account to use for binding to the LDAP server for " "performing queries. The account does not require any other privileges. If " "your LDAP server allows anonymous queries, you can leave this blank.Please " "enter the DN of an account to use for binding to the LDAP server\n" "for performing queries. The account does not require any other\n" "privileges. If your LDAP server allows anonymous queries, you can\n" "leave this blank.\n" msgstr "" "ЕÑли ваш Ñервер LDAP позволÑет выполнение анонимных запроÑов, можете " "оÑтавить Ñто поле пуÑтым.\n" "Ð’ противном Ñлучае, введите здеÑÑŒ distinguished name учетной запиÑи LDAP,\n" "у которой еÑть права на Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ð¸,\n" "например CN=User,OU=Office,DC=domain,DC=local.\n" "\n" "Ð’ÐЖÐО! Ð”Ð»Ñ Ð´Ð°Ð½Ð½Ð¾Ð¹ запиÑи не нужно никаких оÑобенных прав,\n" "например, в Ñлучае аутентификации в AD, лучше ограничить права Ñтой запиÑи " "только членÑтвом в группе Domain Guests\n" #: ../utils/setup.c:220 msgid "LDAP bind password:" msgstr "Пароль LDAP" #: ../utils/setup.c:222 msgid "" "If you entered a Bind DN in the previous question, you must now enter\n" "the password associated with that account. Otherwise, you can leave this\n" "blank.\n" msgstr "" "ЕÑли ваш Ñервер LDAP позволÑет выполнение анонимных запроÑов, можете " "оÑтавить Ñто поле пуÑтым.\n" "Ð’ противном Ñлучае, введите здеÑÑŒ пароль учетной запиÑи Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ LDAP из " "предыдущего пункта\n" #: ../utils/setup.c:299 msgid "Yes/No" msgstr "Да/Ðет" #: ../utils/setup.c:300 msgid "Yes" msgstr "Да" #: ../utils/setup.c:300 msgid "No" msgstr "Ðет" #: ../utils/setup.c:346 msgid "Press return to continue..." msgstr "Ðажмите Enter чтобы продолжить..." #: ../utils/setup.c:364 msgid "Important Message" msgstr "Важное Ñообщение" #: ../utils/setup.c:379 msgid "Error" msgstr "Ошибка" #: ../utils/setup.c:459 msgid "Adding service entry..." msgstr "ДобавлÑетÑÑ ÑÐ»ÑƒÐ¶ÐµÐ±Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ..." #. Other errors might mean something really did go wrong. #. #: ../utils/setup.c:463 ../utils/setup.c:510 ../utils/setup.c:518 msgid "Cannot open" msgstr "Ðевозможно открыть" #: ../utils/setup.c:569 msgid "" "Citadel already appears to be configured to start at boot.\n" "Would you like to keep your boot configuration as is?\n" msgstr "" "Похоже, что ÑиÑтема Ñконфигурирована автоматичеÑки запуÑкать Citadel при " "Ñтарте.\n" "Это то, что вам нужно (обычно да)?\n" #: ../utils/setup.c:577 msgid "Would you like to automatically start Citadel at boot?\n" msgstr "ЗапуÑкать Citadel автоматичеÑки при Ñтарте ÑиÑтемы?\n" #: ../utils/setup.c:583 msgid "Cannot create" msgstr "Ошибка ÑозданиÑ" #: ../utils/setup.c:682 #, c-format msgid "" "Setup can configure the \"xinetd\" service to automatically\n" "connect incoming telnet sessions to Citadel, bypassing the\n" "host system login: prompt. Would you like to do this?\n" msgstr "" "Программа setup может попытатьÑÑ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑки наÑтроить xinetd,\n" "чтобы при подключении по telnet к Ñерверу Citadel пользователи\n" "миновали приглашение зарегиÑтрироватьÑÑ Ð² ÑиÑтеме. ÐаÑтроить?\n" #: ../utils/setup.c:740 msgid "You appear to have the " msgstr "Похоже, у Ð²Ð°Ñ ÑƒÐ¶Ðµ уÑтановлена " #: ../utils/setup.c:742 msgid "" " email program\n" "running on your system. If you want Citadel mail\n" "connected with " msgstr "" " ÑÐ²Ð¾Ñ Ð¿Ð¾Ñ‡Ñ‚Ð¾Ð²Ð°Ñ ÑиÑтема. ЕÑли вы хотите,\n" "чтобы Citadel работала ÑовмеÑтно Ñ Ð½ÐµÐ¹, " #: ../utils/setup.c:746 msgid "" " you will have to manually integrate\n" "them. It is preferable to disable " msgstr "" " вам придетÑÑ Ñовмещать их работу ÑамоÑтоÑтельно.\n" "РекомендуетÑÑ Ð¾Ñ‚ÐºÐ»ÑŽÑ‡Ð¸Ñ‚ÑŒ " #: ../utils/setup.c:749 msgid "" ", and use Citadel's\n" "SMTP, POP3, and IMAP services.\n" "\n" "May we disable " msgstr "" "ÑущеÑтвующую ÑиÑтему и иÑпользовать Ñлужбы\n" "SMTP, POP3 и IMAP4, предоÑтавлÑемые Citadel.\n" "Позволить программе setup отключить вашу почтовую ÑиÑтему " #: ../utils/setup.c:753 msgid "" "so that Citadel has access to ports\n" "25, 110, and 143?\n" msgstr "" "таки образом, чтобы Citadel занÑла порты\n" "25, 110 и 143?\n" #: ../utils/setup.c:863 msgid "This is currently set to:" msgstr "Текущие наÑтройки" #: ../utils/setup.c:864 msgid "Enter new value or press return to leave unchanged:" msgstr "" "Введите новое значение или нажмите Enter, чтобы оÑтавить без изменений" #: ../utils/setup.c:1067 ../utils/setup.c:1072 ../utils/setup.c:1384 msgid "setup: cannot open" msgstr "Программа setup: ошибка при открытии" #: ../utils/setup.c:1175 #, c-format msgid "" "\n" "/etc/nsswitch.conf is configured to use the 'db' module for\n" "one or more services. This is not necessary on most systems,\n" "and it is known to crash the Citadel server when delivering\n" "mail to the Internet.\n" "\n" "Do you want this module to be automatically disabled?\n" "\n" msgstr "" "\n" "УÑÑ‚Ð°Ñ€ÐµÐ²ÑˆÐ°Ñ Ð½Ð°Ñтройка /etc/nsswitch.conf (модуль 'db')\n" "может помешать работе Citadel.\n" "Ð’ большинÑтве Ñлучаев она не нужна. Попробовать отключить ее?\n" "\n" #: ../utils/setup.c:1236 ../utils/setup.c:1252 msgid "Setup finished" msgstr "УÑтановка завершена" #: ../utils/setup.c:1237 msgid "" "Setup of the Citadel server is complete.\n" "If you will be using WebCit, please run its\n" "setup program now; otherwise, run './citadel'\n" "to log in.\n" msgstr "" "УÑтановка Citadel завершена.\n" "ЕÑли вы ÑобираетеÑÑŒ иÑпользовать Webcit, уÑтановите его.\n" "Либо запуÑтите ./citadel чтобы приÑтупить к работе прÑмо ÑейчаÑ.\n" #: ../utils/setup.c:1243 msgid "Setup failed" msgstr "Ошибка уÑтановки" #: ../utils/setup.c:1244 msgid "" "Setup is finished, but the Citadel server failed to start.\n" "Go back and check your configuration.\n" msgstr "" "УÑтановка завершена, но программе setup не удалоÑÑŒ запуÑтить Citadel.\n" "ПожалуйÑта, перепроверьте вÑе наÑтройки.\n" #: ../utils/setup.c:1253 msgid "Setup is finished. You may now start the server." msgstr "ÐаÑтройка завершена, можно запуÑкать Citadel." #: ../utils/setup.c:1279 msgid "My System" msgstr "ÐœÐ¾Ñ ÑиÑтема" #: ../utils/setup.c:1282 msgid "US 800 555 1212" msgstr "US 800 555 1212" #: ../utils/setup.c:1368 ../utils/setup.c:1373 msgid "setup: cannot append" msgstr "Программа setup: невозможно добавить" #: ../utils/setup.c:1450 ../utils/setup.c:1457 ../utils/setup.c:1472 #: ../utils/setup.c:1512 msgid "Citadel Setup" msgstr "Программа setup" #: ../utils/setup.c:1459 msgid "The directory you specified does not exist" msgstr "Ð£ÐºÐ°Ð·Ð°Ð½Ð½Ð°Ñ Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ Ð½Ðµ ÑущеÑтвует" #: ../utils/setup.c:1473 msgid "" "The Citadel service is still running.\n" "Please stop the service manually and run setup again." msgstr "" "Даемон Citadel в данный момент запущен.\n" "ОÑтановите его и перезапуÑтите программу setup." #: ../utils/setup.c:1485 msgid "Citadel setup program" msgstr "Программа setup" #: ../utils/setup.c:1513 msgid "This Citadel installation is too old to be upgraded." msgstr "Увы, ваша Citadel Ñлишком уÑтарела и не может быть обновлена" #: ../utils/setup.c:1552 ../utils/setup.c:1554 ../utils/setup.c:1556 msgid "Setting file permissions" msgstr "УÑтанавливаютÑÑ Ð¿Ñ€Ð°Ð²Ð° доÑтупа к файлам" #~ msgid "" #~ "Please specify the IP address which the server should be listening to. If " #~ "you specify 0.0.0.0, the server will listen on all addresses." #~ msgstr "" #~ "Введите IP-адреÑ, который будет иÑпользовать Ñервер Ð´Ð»Ñ Ð¾Ð¶Ð¸Ð´Ð°Ð½Ð¸Ñ " #~ "подключений. ЕÑли указать 0.0.0.0, то Citadel будет ожидать Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð½Ð° " #~ "адреÑах вÑех интерфейÑов." #~ msgid "" #~ "This can usually be left to the default unless multiple instances of Citadel " #~ "are running on the same computer." #~ msgstr "" #~ "ЕÑли вы не запуÑкаете неÑколько ÑкземплÑров Citadel на одном компьютере, то " #~ "можно оÑтавить значение по умолчанию." #~ msgid "Internal" #~ msgstr "ВнутреннÑÑ" #~ msgid "Host" #~ msgstr "МашиннаÑ" #~ msgid "LDAP" #~ msgstr "LDAP" #~ msgid "" #~ "Do not change this option unless you are sure it is required, since changing " #~ "back requires a full reinstall of Citadel." #~ msgstr "" #~ "Ðе выбирайте других пунктов кроме первого, еÑли не уверены, что Ñто " #~ "дейÑтвительно нужно, так как Ð´Ð»Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¿Ð¾Ñ‚Ñ€ÐµÐ±ÑƒÐµÑ‚ÑÑ Ð¿Ð¾Ð»Ð½Ð¾Ñтью " #~ "переуÑтановить Citadel." #~ msgid "" #~ "While not mandatory, it is highly recommended that you set a password for " #~ "the administrator user." #~ msgstr "" #~ "Ð¥Ð¾Ñ‚Ñ Ñто необÑзательно, наÑтоÑтельно рекомендуетÑÑ Ð·Ð°Ð´Ð°Ñ‚ÑŒ пароль Ð´Ð»Ñ " #~ "админиÑтративного пользователÑ." #~ msgid "Internal, Host, LDAP, Active Directory" #~ msgstr "ВнутреннÑÑ, МашиннаÑ, LDAP, Active Directory" citadel-9.01/po/citadel-setup/hu.po0000644000000000000000000002250612507024051015704 0ustar rootroot# Hungarian translation for citadel # Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011 # This file is distributed under the same license as the citadel package. # FIRST AUTHOR , 2011. # msgid "" msgstr "" "Project-Id-Version: citadel\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2010-09-28 00:22+0200\n" "PO-Revision-Date: 2011-06-07 16:32+0000\n" "Last-Translator: FULL NAME \n" "Language-Team: Hungarian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2011-06-08 04:32+0000\n" "X-Generator: Launchpad (build 12959)\n" #: ../utils/setup.c:119 msgid "Citadel Home Directory" msgstr "" #: ../utils/setup.c:122 msgid "" "Enter the full pathname of the directory in which the Citadel\n" "installation you are creating or updating resides. If you\n" "specify a directory other than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" msgstr "" #: ../utils/setup.c:128 msgid "" "Enter the subdirectory name for an alternate installation of Citadel. To do " "a default installation just leave it blank.If you specify a directory other " "than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" "note that it may not have a leading /" msgstr "" #: ../utils/setup.c:135 msgid "Citadel administrator username:" msgstr "" #: ../utils/setup.c:137 msgid "" "Please enter the name of the Citadel user account that should be granted " "administrative privileges once created. If using internal authentication " "this user account will be created if it does not exist. For external " "authentication this user account has to exist." msgstr "" #: ../utils/setup.c:143 msgid "Administrator password:" msgstr "" #: ../utils/setup.c:145 msgid "" "Enter a password for the system administrator. When setup\n" "completes it will attempt to create the administrator user\n" "and set the password specified here.\n" msgstr "" #: ../utils/setup.c:149 msgid "Citadel User ID:" msgstr "" #: ../utils/setup.c:151 msgid "" "Citadel needs to run under its own user ID. This would\n" "typically be called \"citadel\", but if you are running Citadel\n" "as a public BBS, you might also call it \"bbs\" or \"guest\".\n" "The server will run under this user ID. Please specify that\n" "user ID here. You may specify either a user name or a numeric\n" "UID.\n" msgstr "" #: ../utils/setup.c:158 msgid "Listening address for the Citadel server:" msgstr "" #: ../utils/setup.c:160 msgid "" "Please specify the IP address which the server should be listening to. You " "can name a specific IPv4 or IPv6 address, or you can specify\n" "'*' for 'any address', '::' for 'any IPv6 address', or '0.0.0.0'\n" "for 'any IPv4 address'. If you leave this blank, Citadel will\n" "listen on all addresses. This can usually be left to the default unless " "multiple instances of Citadel are running on the same computer." msgstr "" #: ../utils/setup.c:168 msgid "Server port number:" msgstr "" #: ../utils/setup.c:170 msgid "" "Specify the TCP port number on which your server will run.\n" "Normally, this will be port 504, which is the official port\n" "assigned by the IANA for Citadel servers. You will only need\n" "to specify a different port number if you run multiple instances\n" "of Citadel on the same computer and there is something else\n" "already using port 504.\n" msgstr "" #: ../utils/setup.c:177 msgid "Authentication method to use:" msgstr "" #: ../utils/setup.c:179 msgid "" "Please choose the user authentication mode. By default Citadel will use its " "own internal user accounts database. If you choose Host, Citadel users will " "have accounts on the host system, authenticated via /etc/passwd or a PAM " "source. LDAP chooses an RFC 2307 compliant directory server, the last option " "chooses the nonstandard MS Active Directory LDAP scheme.\n" "Do not change this option unless you are sure it is required, since changing " "back requires a full reinstall of Citadel.\n" " 0. Self contained authentication\n" " 1. Host system integrated authentication\n" " 2. External LDAP - RFC 2307 compliant directory\n" " 3. External LDAP - nonstandard MS Active Directory\n" "\n" "For help: http://www.citadel.org/doku.php/faq:installation:authmodes\n" "\n" "ANSWER \"0\" UNLESS YOU COMPLETELY UNDERSTAND THIS OPTION.\n" msgstr "" #: ../utils/setup.c:197 msgid "LDAP host:" msgstr "" #: ../utils/setup.c:199 msgid "Please enter the host name or IP address of your LDAP server.\n" msgstr "" #: ../utils/setup.c:201 msgid "LDAP port number:" msgstr "" #: ../utils/setup.c:203 msgid "Please enter the port number of the LDAP service (usually 389).\n" msgstr "" #: ../utils/setup.c:205 msgid "LDAP base DN:" msgstr "" #: ../utils/setup.c:207 msgid "" "Please enter the Base DN to search for authentication\n" "(for example: dc=example,dc=com)\n" msgstr "" #: ../utils/setup.c:210 msgid "LDAP bind DN:" msgstr "" #: ../utils/setup.c:212 msgid "" "Please enter the DN of an account to use for binding to the LDAP server for " "performing queries. The account does not require any other privileges. If " "your LDAP server allows anonymous queries, you can leave this blank.Please " "enter the DN of an account to use for binding to the LDAP server\n" "for performing queries. The account does not require any other\n" "privileges. If your LDAP server allows anonymous queries, you can\n" "leave this blank.\n" msgstr "" #: ../utils/setup.c:220 msgid "LDAP bind password:" msgstr "" #: ../utils/setup.c:222 msgid "" "If you entered a Bind DN in the previous question, you must now enter\n" "the password associated with that account. Otherwise, you can leave this\n" "blank.\n" msgstr "" #: ../utils/setup.c:299 msgid "Yes/No" msgstr "" #: ../utils/setup.c:300 msgid "Yes" msgstr "" #: ../utils/setup.c:300 msgid "No" msgstr "" #: ../utils/setup.c:346 msgid "Press return to continue..." msgstr "" #: ../utils/setup.c:364 msgid "Important Message" msgstr "" #: ../utils/setup.c:379 msgid "Error" msgstr "" #: ../utils/setup.c:459 msgid "Adding service entry..." msgstr "" #. Other errors might mean something really did go wrong. #. #: ../utils/setup.c:463 ../utils/setup.c:510 ../utils/setup.c:518 msgid "Cannot open" msgstr "" #: ../utils/setup.c:569 msgid "" "Citadel already appears to be configured to start at boot.\n" "Would you like to keep your boot configuration as is?\n" msgstr "" #: ../utils/setup.c:577 msgid "Would you like to automatically start Citadel at boot?\n" msgstr "" #: ../utils/setup.c:583 msgid "Cannot create" msgstr "" #: ../utils/setup.c:682 #, c-format msgid "" "Setup can configure the \"xinetd\" service to automatically\n" "connect incoming telnet sessions to Citadel, bypassing the\n" "host system login: prompt. Would you like to do this?\n" msgstr "" #: ../utils/setup.c:740 msgid "You appear to have the " msgstr "" #: ../utils/setup.c:742 msgid "" " email program\n" "running on your system. If you want Citadel mail\n" "connected with " msgstr "" #: ../utils/setup.c:746 msgid "" " you will have to manually integrate\n" "them. It is preferable to disable " msgstr "" #: ../utils/setup.c:749 msgid "" ", and use Citadel's\n" "SMTP, POP3, and IMAP services.\n" "\n" "May we disable " msgstr "" #: ../utils/setup.c:753 msgid "" "so that Citadel has access to ports\n" "25, 110, and 143?\n" msgstr "" #: ../utils/setup.c:863 msgid "This is currently set to:" msgstr "" #: ../utils/setup.c:864 msgid "Enter new value or press return to leave unchanged:" msgstr "" #: ../utils/setup.c:1067 ../utils/setup.c:1072 ../utils/setup.c:1384 msgid "setup: cannot open" msgstr "" #: ../utils/setup.c:1175 #, c-format msgid "" "\n" "/etc/nsswitch.conf is configured to use the 'db' module for\n" "one or more services. This is not necessary on most systems,\n" "and it is known to crash the Citadel server when delivering\n" "mail to the Internet.\n" "\n" "Do you want this module to be automatically disabled?\n" "\n" msgstr "" #: ../utils/setup.c:1236 ../utils/setup.c:1252 msgid "Setup finished" msgstr "" #: ../utils/setup.c:1237 msgid "" "Setup of the Citadel server is complete.\n" "If you will be using WebCit, please run its\n" "setup program now; otherwise, run './citadel'\n" "to log in.\n" msgstr "" #: ../utils/setup.c:1243 msgid "Setup failed" msgstr "" #: ../utils/setup.c:1244 msgid "" "Setup is finished, but the Citadel server failed to start.\n" "Go back and check your configuration.\n" msgstr "" #: ../utils/setup.c:1253 msgid "Setup is finished. You may now start the server." msgstr "" #: ../utils/setup.c:1279 msgid "My System" msgstr "" #: ../utils/setup.c:1282 msgid "US 800 555 1212" msgstr "" #: ../utils/setup.c:1368 ../utils/setup.c:1373 msgid "setup: cannot append" msgstr "" #: ../utils/setup.c:1450 ../utils/setup.c:1457 ../utils/setup.c:1472 #: ../utils/setup.c:1512 msgid "Citadel Setup" msgstr "" #: ../utils/setup.c:1459 msgid "The directory you specified does not exist" msgstr "" #: ../utils/setup.c:1473 msgid "" "The Citadel service is still running.\n" "Please stop the service manually and run setup again." msgstr "" #: ../utils/setup.c:1485 msgid "Citadel setup program" msgstr "" #: ../utils/setup.c:1513 msgid "This Citadel installation is too old to be upgraded." msgstr "" #: ../utils/setup.c:1552 ../utils/setup.c:1554 ../utils/setup.c:1556 msgid "Setting file permissions" msgstr "" citadel-9.01/po/citadel-setup/ko.po0000644000000000000000000002250012507024051015673 0ustar rootroot# Korean translation for citadel # Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013 # This file is distributed under the same license as the citadel package. # FIRST AUTHOR , 2013. # msgid "" msgstr "" "Project-Id-Version: citadel\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2010-09-28 00:22+0200\n" "PO-Revision-Date: 2013-11-20 21:46+0000\n" "Last-Translator: FULL NAME \n" "Language-Team: Korean \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2013-11-21 05:11+0000\n" "X-Generator: Launchpad (build 16831)\n" #: ../utils/setup.c:119 msgid "Citadel Home Directory" msgstr "" #: ../utils/setup.c:122 msgid "" "Enter the full pathname of the directory in which the Citadel\n" "installation you are creating or updating resides. If you\n" "specify a directory other than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" msgstr "" #: ../utils/setup.c:128 msgid "" "Enter the subdirectory name for an alternate installation of Citadel. To do " "a default installation just leave it blank.If you specify a directory other " "than the default, you will need to\n" "specify the -h flag to the server when you start it up.\n" "note that it may not have a leading /" msgstr "" #: ../utils/setup.c:135 msgid "Citadel administrator username:" msgstr "" #: ../utils/setup.c:137 msgid "" "Please enter the name of the Citadel user account that should be granted " "administrative privileges once created. If using internal authentication " "this user account will be created if it does not exist. For external " "authentication this user account has to exist." msgstr "" #: ../utils/setup.c:143 msgid "Administrator password:" msgstr "" #: ../utils/setup.c:145 msgid "" "Enter a password for the system administrator. When setup\n" "completes it will attempt to create the administrator user\n" "and set the password specified here.\n" msgstr "" #: ../utils/setup.c:149 msgid "Citadel User ID:" msgstr "" #: ../utils/setup.c:151 msgid "" "Citadel needs to run under its own user ID. This would\n" "typically be called \"citadel\", but if you are running Citadel\n" "as a public BBS, you might also call it \"bbs\" or \"guest\".\n" "The server will run under this user ID. Please specify that\n" "user ID here. You may specify either a user name or a numeric\n" "UID.\n" msgstr "" #: ../utils/setup.c:158 msgid "Listening address for the Citadel server:" msgstr "" #: ../utils/setup.c:160 msgid "" "Please specify the IP address which the server should be listening to. You " "can name a specific IPv4 or IPv6 address, or you can specify\n" "'*' for 'any address', '::' for 'any IPv6 address', or '0.0.0.0'\n" "for 'any IPv4 address'. If you leave this blank, Citadel will\n" "listen on all addresses. This can usually be left to the default unless " "multiple instances of Citadel are running on the same computer." msgstr "" #: ../utils/setup.c:168 msgid "Server port number:" msgstr "" #: ../utils/setup.c:170 msgid "" "Specify the TCP port number on which your server will run.\n" "Normally, this will be port 504, which is the official port\n" "assigned by the IANA for Citadel servers. You will only need\n" "to specify a different port number if you run multiple instances\n" "of Citadel on the same computer and there is something else\n" "already using port 504.\n" msgstr "" #: ../utils/setup.c:177 msgid "Authentication method to use:" msgstr "" #: ../utils/setup.c:179 msgid "" "Please choose the user authentication mode. By default Citadel will use its " "own internal user accounts database. If you choose Host, Citadel users will " "have accounts on the host system, authenticated via /etc/passwd or a PAM " "source. LDAP chooses an RFC 2307 compliant directory server, the last option " "chooses the nonstandard MS Active Directory LDAP scheme.\n" "Do not change this option unless you are sure it is required, since changing " "back requires a full reinstall of Citadel.\n" " 0. Self contained authentication\n" " 1. Host system integrated authentication\n" " 2. External LDAP - RFC 2307 compliant directory\n" " 3. External LDAP - nonstandard MS Active Directory\n" "\n" "For help: http://www.citadel.org/doku.php/faq:installation:authmodes\n" "\n" "ANSWER \"0\" UNLESS YOU COMPLETELY UNDERSTAND THIS OPTION.\n" msgstr "" #: ../utils/setup.c:197 msgid "LDAP host:" msgstr "" #: ../utils/setup.c:199 msgid "Please enter the host name or IP address of your LDAP server.\n" msgstr "" #: ../utils/setup.c:201 msgid "LDAP port number:" msgstr "" #: ../utils/setup.c:203 msgid "Please enter the port number of the LDAP service (usually 389).\n" msgstr "" #: ../utils/setup.c:205 msgid "LDAP base DN:" msgstr "" #: ../utils/setup.c:207 msgid "" "Please enter the Base DN to search for authentication\n" "(for example: dc=example,dc=com)\n" msgstr "" #: ../utils/setup.c:210 msgid "LDAP bind DN:" msgstr "" #: ../utils/setup.c:212 msgid "" "Please enter the DN of an account to use for binding to the LDAP server for " "performing queries. The account does not require any other privileges. If " "your LDAP server allows anonymous queries, you can leave this blank.Please " "enter the DN of an account to use for binding to the LDAP server\n" "for performing queries. The account does not require any other\n" "privileges. If your LDAP server allows anonymous queries, you can\n" "leave this blank.\n" msgstr "" #: ../utils/setup.c:220 msgid "LDAP bind password:" msgstr "" #: ../utils/setup.c:222 msgid "" "If you entered a Bind DN in the previous question, you must now enter\n" "the password associated with that account. Otherwise, you can leave this\n" "blank.\n" msgstr "" #: ../utils/setup.c:299 msgid "Yes/No" msgstr "" #: ../utils/setup.c:300 msgid "Yes" msgstr "" #: ../utils/setup.c:300 msgid "No" msgstr "" #: ../utils/setup.c:346 msgid "Press return to continue..." msgstr "" #: ../utils/setup.c:364 msgid "Important Message" msgstr "" #: ../utils/setup.c:379 msgid "Error" msgstr "" #: ../utils/setup.c:459 msgid "Adding service entry..." msgstr "" #. Other errors might mean something really did go wrong. #. #: ../utils/setup.c:463 ../utils/setup.c:510 ../utils/setup.c:518 msgid "Cannot open" msgstr "" #: ../utils/setup.c:569 msgid "" "Citadel already appears to be configured to start at boot.\n" "Would you like to keep your boot configuration as is?\n" msgstr "" #: ../utils/setup.c:577 msgid "Would you like to automatically start Citadel at boot?\n" msgstr "" #: ../utils/setup.c:583 msgid "Cannot create" msgstr "" #: ../utils/setup.c:682 #, c-format msgid "" "Setup can configure the \"xinetd\" service to automatically\n" "connect incoming telnet sessions to Citadel, bypassing the\n" "host system login: prompt. Would you like to do this?\n" msgstr "" #: ../utils/setup.c:740 msgid "You appear to have the " msgstr "" #: ../utils/setup.c:742 msgid "" " email program\n" "running on your system. If you want Citadel mail\n" "connected with " msgstr "" #: ../utils/setup.c:746 msgid "" " you will have to manually integrate\n" "them. It is preferable to disable " msgstr "" #: ../utils/setup.c:749 msgid "" ", and use Citadel's\n" "SMTP, POP3, and IMAP services.\n" "\n" "May we disable " msgstr "" #: ../utils/setup.c:753 msgid "" "so that Citadel has access to ports\n" "25, 110, and 143?\n" msgstr "" #: ../utils/setup.c:863 msgid "This is currently set to:" msgstr "" #: ../utils/setup.c:864 msgid "Enter new value or press return to leave unchanged:" msgstr "" #: ../utils/setup.c:1067 ../utils/setup.c:1072 ../utils/setup.c:1384 msgid "setup: cannot open" msgstr "" #: ../utils/setup.c:1175 #, c-format msgid "" "\n" "/etc/nsswitch.conf is configured to use the 'db' module for\n" "one or more services. This is not necessary on most systems,\n" "and it is known to crash the Citadel server when delivering\n" "mail to the Internet.\n" "\n" "Do you want this module to be automatically disabled?\n" "\n" msgstr "" #: ../utils/setup.c:1236 ../utils/setup.c:1252 msgid "Setup finished" msgstr "" #: ../utils/setup.c:1237 msgid "" "Setup of the Citadel server is complete.\n" "If you will be using WebCit, please run its\n" "setup program now; otherwise, run './citadel'\n" "to log in.\n" msgstr "" #: ../utils/setup.c:1243 msgid "Setup failed" msgstr "" #: ../utils/setup.c:1244 msgid "" "Setup is finished, but the Citadel server failed to start.\n" "Go back and check your configuration.\n" msgstr "" #: ../utils/setup.c:1253 msgid "Setup is finished. You may now start the server." msgstr "" #: ../utils/setup.c:1279 msgid "My System" msgstr "" #: ../utils/setup.c:1282 msgid "US 800 555 1212" msgstr "" #: ../utils/setup.c:1368 ../utils/setup.c:1373 msgid "setup: cannot append" msgstr "" #: ../utils/setup.c:1450 ../utils/setup.c:1457 ../utils/setup.c:1472 #: ../utils/setup.c:1512 msgid "Citadel Setup" msgstr "" #: ../utils/setup.c:1459 msgid "The directory you specified does not exist" msgstr "" #: ../utils/setup.c:1473 msgid "" "The Citadel service is still running.\n" "Please stop the service manually and run setup again." msgstr "" #: ../utils/setup.c:1485 msgid "Citadel setup program" msgstr "" #: ../utils/setup.c:1513 msgid "This Citadel installation is too old to be upgraded." msgstr "" #: ../utils/setup.c:1552 ../utils/setup.c:1554 ../utils/setup.c:1556 msgid "Setting file permissions" msgstr "" citadel-9.01/mkinstalldirs0000755000000000000000000000123412507024051014350 0ustar rootroot#! /bin/sh # mkinstalldirs --- make directory hierarchy # Author: Noah Friedman # Created: 1993-05-16 # Public domain errstatus=0 for file do set fnord `echo ":$file" | sed -ne 's/^:\//#/;s/^://;s/\// /g;s/^#/\//;p'` shift pathcomp= for d do pathcomp="$pathcomp$d" case "$pathcomp" in -* ) pathcomp=./$pathcomp ;; esac if test ! -d "$pathcomp"; then echo "mkdir $pathcomp" 1>&2 mkdir "$pathcomp" || lasterr=$? if test ! -d "$pathcomp"; then errstatus=$lasterr fi fi pathcomp="$pathcomp/" done done exit $errstatus # mkinstalldirs ends here citadel-9.01/scripts/0000755000000000000000000000000012507024051013231 5ustar rootrootcitadel-9.01/scripts/mk_svn_revision.sh0000755000000000000000000000260712507024051017010 0ustar rootroot#!/bin/sh # # Script to generate svn_revision.c # ECHO=/usr/bin/printf SCRIPT_DIR=`dirname $0` SRC_DIR=`dirname $SCRIPT_DIR` CUR_DIR=`pwd` C_FILE="$CUR_DIR/svn_revision.c" H_FILE="$CUR_DIR/svn_revision.h" # determine if this code base came from subversion. if test -d $SRC_DIR/.svn ; then echo "have subversion repository" SVNVERSION=`which svnversion` if test -x $SVNVERSION ; then echo "have svnversion at $SVNVERSION" BUILD=`svnversion -n .` echo "This code base svn-revision: $BUILD" CAN_BUILD_SVN_REVISION="yes" fi else if test -d $SRC_DIR/../.git ; then echo "have Git repository." BUILD=`/usr/bin/git log -1 --pretty=%h . ` echo "This code base git-revision: $BUILD" CAN_BUILD_SVN_REVISION="yes" else if test -f $C_FILE; then exit fi fi fi if [ "$CAN_BUILD_SVN_REVISION" = "yes" ] ; then cat < $C_FILE /* * Subversion / GIT revision functions * * Autogenerated at make/release time * * Do not modify this file * */ const char *svn_revision (void) { const char *SVN_Version = "$BUILD"; return SVN_Version; } EOF elif test ! -f $C_FILE ; then cat < $C_FILE /* * Subversion / GIT revision functions * * Autogenerated at make time * * There should have been one with your source distribution * * Do not modify this file * */ const char *svn_revision (void) { const char *SVN_Version = "(unknown)"; return SVN_Version; } EOF fi citadel-9.01/scripts/mk_module_init.sh0000755000000000000000000001211512507024051016567 0ustar rootroot#!/bin/sh # # Script to generate $C_FILE # ECHO=/usr/bin/printf SED=/bin/sed #MINUS_e=X`$ECHO -n -e` #if [ $MINUS_e != "X" ] ; then # MINUS_e="" #else # MINUS_e="-e" #fi #MINUS_E=X`$ECHO -n -E` #if [ $MINUS_E != "X" ] ; then # MINUS_E="" #else # MINUS_E="-E" #fi CUR_DIR=`pwd` C_FILE="$CUR_DIR/modules_init.c" H_FILE="$CUR_DIR/modules_init.h" MOD_FILE="$CUR_DIR/Make_modules" SRC_FILE="$CUR_DIR/Make_sources" U_FILE="$CUR_DIR/modules_upgrade.c" /usr/bin/printf "Scanning extension modules for entry points.\n" STATIC_FIRST_MODULES="control modules euidindex msgbase nttlist database" DYNAMIC_MODULES=`grep CTDL_MODULE_INIT modules/*/*.c |$SED 's;.*(\(.*\));\1;'` if test -d user_modules; then USER_MODULES=`grep CTDL_MODULE_INIT user_modules/*/*.c |$SED 's;.*(\(.*\));\1;'` else USER_MODULES= fi STATIC_LAST_MODULES="netconfig" ############################################################################### # start the c file # ############################################################################### cat <$C_FILE /* * $C_FILE * Auto generated by mk_modules_init.sh DO NOT EDIT THIS FILE */ #include "sysdep.h" #include #include #include #include #include #include #include #include "citadel.h" #include "modules_init.h" #include "sysdep_decls.h" #include "serv_extensions.h" void LogPrintMessages(long err); extern long DetailErrorFlags; void initialise_modules (int threading) { long filter; const char *pMod; if (threading) { MODM_syslog(LOG_DEBUG, "Initializing, CtdlThreads enabled.\n"); } else { MODM_syslog(LOG_INFO, "Initializing. CtdlThreads not yet enabled.\n"); } EOF for i in ${STATIC_FIRST_MODULES} ${DYNAMIC_MODULES} ${USER_MODULES} ${STATIC_LAST_MODULES}; do cat <> $C_FILE pMod = CTDL_INIT_CALL($i); MOD_syslog(LOG_DEBUG, "Loaded module: %s\n", pMod); EOF done cat <> $C_FILE for (filter = 1; filter != 0; filter = filter << 1) if ((filter & DetailErrorFlags) != 0) LogPrintMessages(filter); } EOF ############################################################################### # start the header file # ############################################################################### cat < $H_FILE /* * $H_FILE * Auto generated by mk_modules_init.sh DO NOT EDIT THIS FILE */ #ifndef MODULES_INIT_H #define MODULES_INIT_H #include "ctdl_module.h" extern size_t nSizErrmsg; void initialise_modules (int threading); void upgrade_modules(void); EOF for i in ${STATIC_FIRST_MODULES} ${DYNAMIC_MODULES} ${USER_MODULES} ${STATIC_LAST_MODULES}; do # Add this entry point to the .h file cat <> $H_FILE CTDL_MODULE_INIT($i); EOF done grep CTDL_MODULE_UPGRADE *.c modules/*/*.c |$SED 's;.*(\(.*\));\CTDL_MODULE_UPGRADE(\1)\;\n;' >> $H_FILE cat <> $H_FILE #endif /* MODULES_INIT_H */ EOF ############################################################################### # u start the Makefile included file for $SERV_MODULES # ############################################################################### cat <$MOD_FILE # # Make_modules # This file is to be included by Makefile to dynamically add modules to the build process # THIS FILE WAS AUTO GENERATED BY mk_modules_init.sh DO NOT EDIT THIS FILE # SERV_MODULES = \\ EOF echo modules/*/*.c | $SED -e "s;\.c ;.o \\\\\n;g" -e "s;\.c;.o;" >> $MOD_FILE echo >> $MOD_FILE ############################################################################### # start of the files which inturn removes any existing file # ############################################################################### # start the Makefile included file for $SOURCES cat <$SRC_FILE # # Make_sources # This file is to be included by Makefile to dynamically add modules to the build process # THIS FILE WAS AUTO GENERATED BY mk_modules_init.sh DO NOT EDIT THIS FILE # SOURCES = \\ EOF echo modules/*/*.c | $SED "s;\.c ;.c \\\\\n;g" >> $SRC_FILE echo >> $SRC_FILE ############################################################################### # start the upgrade file # ############################################################################### cat <$U_FILE /* * $U_FILE * Auto generated by mk_modules_init.sh DO NOT EDIT THIS FILE */ #include "sysdep.h" #include #include #include #include #include #include #include #include #include "citadel.h" #include "modules_init.h" #include "sysdep_decls.h" #include "serv_extensions.h" void upgrade_modules (void) { const char *pMod; MODM_syslog(LOG_INFO, "Upgrade modules.\n"); EOF # Add this entry point to the .c file grep CTDL_MODULE_UPGRADE *.c modules/*/*.c |$SED 's;.*(\(.*\));\tpMod = CTDL_UPGRADE_CALL(\1)\;\n\tMOD_syslog(LOG_INFO, "%s\\n", pMod)\;\n;' >> $U_FILE #close the upgrade file /usr/bin/printf "}\n" >> $U_FILE citadel-9.01/scripts/valgrind_suspressions.txt0000644000000000000000000000770412507024051020450 0ustar rootroot{ blargtest ion_name_here Memcheck:Cond obj:/lib/libc-2.11.2.so fun:ERR_load_ERR_strings fun:ERR_load_crypto_strings fun:SSL_load_error_strings fun:init_ssl fun:init_sysdep fun:main } { Memcheck:Value8 obj:/lib/libc-2.11.2.so fun:ERR_load_ERR_strings fun:ERR_load_crypto_strings fun:SSL_load_error_strings fun:init_ssl fun:init_sysdep fun:main } { Memcheck:Cond fun:__GI_strlen obj:/usr/lib/libcrypto.so.0.9.8 fun:BIO_gets fun:PEM_read_bio fun:PEM_bytes_read_bio fun:PEM_ASN1_read_bio fun:SSL_CTX_use_certificate_chain_file fun:init_ssl fun:init_sysdep fun:main } { Memcheck:Cond fun:PEM_read_bio fun:PEM_bytes_read_bio fun:PEM_ASN1_read_bio fun:SSL_CTX_use_certificate_chain_file fun:init_ssl fun:init_sysdep fun:main } { some openssl initializationshit Memcheck:Cond ... fun:SSL_load_error_strings fun:init_ssl fun:init_sysdep fun:main } { some openssl shit Memcheck:Value8 ... fun:SSL_load_error_strings fun:init_ssl fun:init_sysdep fun:main } # obj:/lib/libc-* { more openssl library shit Memcheck:Leak ... fun:SSL_library_init fun:init_ssl fun:init_sysdep fun:main } { more openssl library shit Memcheck:Cond ... fun:SSL_CTX_use_certificate_chain_file fun:init_ssl fun:init_sysdep fun:main } { more openssl library shit Memcheck:Cond ... fun:SSL_CTX_use_PrivateKey_file fun:init_ssl fun:init_sysdep fun:main } { more openssl library shit Memcheck:Value8 ... fun:SSL_CTX_use_PrivateKey_file fun:init_ssl fun:init_sysdep fun:main } { Memcheck:Value8 ... fun:SSL_CTX_use_certificate_chain_file fun:init_ssl fun:init_sysdep fun:main } { Memcheck:Cond ... fun:_dl_start obj:/lib/ld-2.11.2.so obj:* obj:* obj:* } { Memcheck:Cond ... fun:dl_main fun:_dl_sysdep_start fun:_dl_start obj:/lib/ld-2.11.2.so obj:* obj:* } { Memcheck:Addr8 ... fun:__env_open fun:open_databases fun:master_startup fun:main } { Memcheck:Cond ... fun:__env_open fun:open_databases fun:master_startup fun:main } { Memcheck:Value8 ... fun:__env_open fun:open_databases fun:master_startup fun:main } { Memcheck:Value8 ... fun:__env_open fun:open_databases fun:master_startup } { Memcheck:Cond ... fun:__db_open_pp fun:open_databases fun:master_startup fun:main } { Memcheck:Cond ... fun:__env_open fun:open_databases fun:master_startup } { Memcheck:Value8 ... fun:__db_open_pp fun:open_databases fun:master_startup fun:main } { Memcheck:Value8 ... fun:__db_open_pp fun:open_databases fun:master_startup fun:main } { Memcheck:Cond ... fun:__db_open_pp fun:open_databases } { Memcheck:Value8 ... fun:__db_open_pp fun:open_databases fun:master_startup } { Memcheck:Value8 ... fun:__db_open_pp fun:open_databases } { Memcheck:Value8 fun:CtdlRegisterServiceHook ... } { Memcheck:Cond fun:__GI_strlen fun:CtdlRegisterServiceHook ... } # close { Memcheck:Cond ... fun:close_databases fun:master_cleanup fun:main } citadel-9.01/scripts/dolcov.sh0000755000000000000000000000111612507024051015055 0ustar rootroot#!/bin/bash CITDIR=`pwd` OUTDIR=${CITDIR}/../../coverage/citadel ln -s parsedate.c y.tab.c # if we call citserver with ./citserver, we don't need these: #cd ${CITDIR}/utillib/; ln -s . utillib; cd .. #cd ${CITDIR}/modules #for i in *; do cd $CITDIR/modules/$i; ln -s . modules; ln -s . $i; ln -s ../../user_ops.h .; done cd ${CITDIR} mkdir -p ${OUTDIR} lcov --base-directory ${CITDIR} --directory . --capture --output-file ${OUTDIR}/citadel.info $@ genhtml --output-directory ${OUTDIR} ${OUTDIR}/citadel.info #exit #rm y.tab.c find -type l -exec rm {} \; rm -f .#user_ops.h.gcovcitadel-9.01/acinclude.m40000644000000000000000000000301212507024051013727 0ustar rootroot# CIT_STRUCT_TM # ------------------ # Figure out how to get the current GMT offset. If `struct tm' has a # `tm_gmtoff' member, define `HAVE_STRUCT_TM_TM_GMTOFF'. Otherwise, if the # external variable `timezone' is found, define `HAVE_TIMEZONE'. AC_DEFUN([CIT_STRUCT_TM], [AC_REQUIRE([AC_STRUCT_TM])dnl AC_CHECK_MEMBERS([struct tm.tm_gmtoff],,,[#include #include <$ac_cv_struct_tm> ]) if test "$ac_cv_member_struct_tm_tm_gmtoff" != yes; then AC_CACHE_CHECK(for timezone, ac_cv_var_timezone, [AC_TRY_LINK( [#include ], [printf("%ld", (long)timezone);], ac_cv_var_timezone=yes, ac_cv_var_timezone=no)]) if test $ac_cv_var_timezone = yes; then AC_DEFINE(HAVE_TIMEZONE, 1, [Define if you don't have `tm_gmtoff' but do have the external variable `timezone'.]) fi fi ])# CIT_STRUCT_TM AC_DEFUN([AC_CHECK_DB],[ for lib in $1 do AS_VAR_PUSHDEF([ac_tr_db], [ac_cv_db_lib_${lib}])dnl bogo_saved_LIBS="$LIBS" LIBS="$LIBS -l$lib" AC_CACHE_CHECK([for db_create in -l${lib}], ac_tr_db, [AC_TRY_LINK([#include ], [int foo=db_create((void *)0, (void *) 0, 0 )], [AS_VAR_SET(ac_tr_db, yes)], [AS_VAR_SET(ac_tr_db, no)]) ]) AS_IF([test AS_VAR_GET(ac_tr_db) = yes], [$2 LIBS="$bogo_saved_LIBS" SERVER_LIBS="$SERVER_LIBS -l$lib" db=yes], [LIBS="$bogo_saved_LIBS" db=no]) AS_VAR_POPDEF([ac_tr_db])dnl test "$db" = "yes" && break done if test "$db" = "no"; then $3 fi ])# AC_CHECK_DB citadel-9.01/auth.h0000644000000000000000000000006512507024051012655 0ustar rootroot int validate_password(uid_t uid, const char *pass); citadel-9.01/ecrash.h0000644000000000000000000001074412507024051013166 0ustar rootroot/* * eCrash.h * David Frascone * * eCrash types and prototypes. * * vim: ts=4 */ #ifndef _ECRASH_H_ #define _ECRASH_H_ #include #include typedef void (*sighandler_t)(int); typedef long BOOL; #define MAX_LINE_LEN 256 #ifndef TRUE #define TRUE 1 #define FALSE 0 #define BOOL int #endif #define ECRASH_DEFAULT_STACK_DEPTH 10 #define ECRASH_DEFAULT_BACKTRACE_SIGNAL SIGUSR2 #define ECRASH_DEFAULT_THREAD_WAIT_TIME 10 #define ECRASH_MAX_NUM_SIGNALS 30 /** \struct eCrashSymbol * \brief Function Name / Address pair * * This is used in conjunction with eCrashSymbolTable to * provide an alternative to backtrace_symbols. */ typedef struct { char *function; void *address; } eCrashSymbol; /** \struct eCrashSymbolTable * \brief Symbol table used to avoid backtrace_symbols() * * This structure is used to provide a alternative to * backtrace_symbols which is not async signal safe. * * The symbol table should be sorted by address (it will * be either binary or linearly searched) */ typedef struct { int numSymbols; eCrashSymbol *symbols; } eCrashSymbolTable; #define ECRASH_DEBUG_ENABLE /* undef to turn off debug */ #ifdef ECRASH_DEBUG_ENABLE # define ECRASH_DEBUG_VERY_VERBOSE 1 # define ECRASH_DEBUG_VERBOSE 2 # define ECRASH_DEBUG_INFO 3 # define ECRASH_DEBUG_WARN 4 # define ECRASH_DEBUG_ERROR 5 # define ECRASH_DEBUG_OFF 6 # define ECRASH_DEBUG_DEFAULT (ECRASH_DEBUG_ERROR) # define DPRINTF(level, fmt...) \ if (level >= gbl_params.debugLevel) { printf(fmt); fflush(stdout); } #else /* ECRASH_DEBUG_ENABLE */ # define DPRINTF(level, fmt...) #endif /* ECRASH_DEBUG_ENABLE */ /** \struct eCrashParameters * \brief eCrash Initialization Parameters * * This structure contains all the global initialization functions * for eCrash. * * @see eCrash_Init */ typedef struct { /* OUTPUT OPTIONS */ /** Filename to output to, or NULL */ char *filename; /** FILE * to output to or NULL */ FILE *filep; /** fd to output to or -1 */ int fd; int debugLevel; /** If true, all registered threads will * be dumped */ BOOL dumpAllThreads; /** How far to backtrace each stack */ unsigned int maxStackDepth; /** Default signal to use to tell a thread to drop it's * stack. */ int defaultBacktraceSignal; /** How long to wait for a threads * dump */ unsigned int threadWaitTime; /** If this is non-zero, the dangerous function, backtrace_symbols * will be used. That function does a malloc(), so is not async * signal safe, and could cause deadlocks. */ BOOL useBacktraceSymbols; /** To avoid the issues with backtrace_symbols (see comments above) * the caller can supply it's own symbol table, containing function * names and start addresses. This table can be created using * a script, or a static table. * * If this variable is not null, it will be used, instead of * backtrace_symbols, reguardless of the setting * of useBacktraceSymbols. */ eCrashSymbolTable *symbolTable; /** Array of crash signals to catch, * ending in 0 I would have left it a [] array, but I * do a static copy, so I needed a set size. */ int signals[ECRASH_MAX_NUM_SIGNALS]; } eCrashParameters; /*! * Initialize eCrash. * * This function must be called before calling any other eCrash * functions. It sets up the global behavior of the system. * * @param params Our input parameters. The passed in structure will be copied. * * @return Zero on success. */ int eCrash_Init(eCrashParameters *params); /*! * UnInitialize eCrash. * * This function may be called to de-activate eCrash, release the * signal handlers, and free any memory allocated by eCrash. * * @return Zero on success. */ int eCrash_Uninit( void ); /*! * Register a thread for backtracing on crash. * * This function must be called by any thread wanting it's stack * dumped in the event of a crash. The thread my specify what * signal should be used, or the default, SIGUSR1 will be used. * * @param name String used to refer to us in crash dumps * @param signo Signal to use to generate dump (default: SIGUSR1) * * @return Zero on success. */ int eCrash_RegisterThread(char *name, int signo); /*! * Un-register a thread for stack dumps. * * This function may be called to un-register any previously * registered thread. * * @return Zero on success. */ int eCrash_UnregisterThread( void ); #endif /* _E_CRASH_H_ */ citadel-9.01/configure0000755000000000000000000075750612507024061013476 0ustar rootroot#! /bin/sh # From configure.ac Revision: 5108 . # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.67 for Citadel 9.01. # # Report bugs to . # # # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, # 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software # Foundation, Inc. # # # This configure script is free software; the Free Software Foundation # gives unlimited permission to copy, distribute and modify it. ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi as_nl=' ' export as_nl # Printing a long string crashes Solaris 7 /usr/bin/printf. as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo # Prefer a ksh shell builtin over an external printf program on Solaris, # but without wasting forks for bash or zsh. if test -z "$BASH_VERSION$ZSH_VERSION" \ && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='print -r --' as_echo_n='print -rn --' elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='printf %s\n' as_echo_n='printf %s' else if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' as_echo_n='/usr/ucb/echo -n' else as_echo_body='eval expr "X$1" : "X\\(.*\\)"' as_echo_n_body='eval arg=$1; case $arg in #( *"$as_nl"*) expr "X$arg" : "X\\(.*\\)$as_nl"; arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; esac; expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" ' export as_echo_n_body as_echo_n='sh -c $as_echo_n_body as_echo' fi export as_echo_body as_echo='sh -c $as_echo_body as_echo' fi # The user is always right. if test "${PATH_SEPARATOR+set}" != set; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # IFS # We need space, tab and new line, in precisely that order. Quoting is # there to prevent editors from complaining about space-tab. # (If _AS_PATH_WALK were called with IFS unset, it would disable word # splitting by setting IFS to empty value.) IFS=" "" $as_nl" # Find who we are. Look in the path if we contain no directory separator. case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # Unset variables that we do not need and which cause bugs (e.g. in # pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" # suppresses any "Segmentation fault" message there. '((' could # trigger a bug in pdksh 5.2.14. for as_var in BASH_ENV ENV MAIL MAILPATH do eval test x\${$as_var+set} = xset \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done PS1='$ ' PS2='> ' PS4='+ ' # NLS nuisances. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # CDPATH. (unset CDPATH) >/dev/null 2>&1 && unset CDPATH if test "x$CONFIG_SHELL" = x; then as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which # is contrary to our usage. Disable this feature. alias -g '\${1+\"\$@\"}'='\"\$@\"' setopt NO_GLOB_SUBST else case \`(set -o) 2>/dev/null\` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi " as_required="as_fn_return () { (exit \$1); } as_fn_success () { as_fn_return 0; } as_fn_failure () { as_fn_return 1; } as_fn_ret_success () { return 0; } as_fn_ret_failure () { return 1; } exitcode=0 as_fn_success || { exitcode=1; echo as_fn_success failed.; } as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then : else exitcode=1; echo positional parameters were not saved. fi test x\$exitcode = x0 || exit 1" as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1 test \$(( 1 + 1 )) = 2 || exit 1" if (eval "$as_required") 2>/dev/null; then : as_have_required=yes else as_have_required=no fi if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then : else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_found=false for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. as_found=: case $as_dir in #( /*) for as_base in sh bash ksh sh5; do # Try only shells that exist, to save several forks. as_shell=$as_dir/$as_base if { test -f "$as_shell" || test -f "$as_shell.exe"; } && { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then : CONFIG_SHELL=$as_shell as_have_required=yes if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then : break 2 fi fi done;; esac as_found=false done $as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } && { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then : CONFIG_SHELL=$SHELL as_have_required=yes fi; } IFS=$as_save_IFS if test "x$CONFIG_SHELL" != x; then : # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also # works around shells that cannot unset nonexistent variables. BASH_ENV=/dev/null ENV=/dev/null (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV export CONFIG_SHELL exec "$CONFIG_SHELL" "$as_myself" ${1+"$@"} fi if test x$as_have_required = xno; then : $as_echo "$0: This script requires a shell more modern than all" $as_echo "$0: the shells that I found on your system." if test x${ZSH_VERSION+set} = xset ; then $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should" $as_echo "$0: be upgraded to zsh 4.3.4 or later." else $as_echo "$0: Please tell bug-autoconf@gnu.org and $0: http://www.citadel.org/ about your system, including $0: any error possibly output before this message. Then $0: install a modern shell, or manually run the script $0: under such a shell if you do have one." fi exit 1 fi fi fi SHELL=${CONFIG_SHELL-/bin/sh} export SHELL # Unset more variables known to interfere with behavior of common tools. CLICOLOR_FORCE= GREP_OPTIONS= unset CLICOLOR_FORCE GREP_OPTIONS ## --------------------- ## ## M4sh Shell Functions. ## ## --------------------- ## # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : eval 'as_fn_append () { eval $1+=\$2 }' else as_fn_append () { eval $1=\$$1\$2 } fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : eval 'as_fn_arith () { as_val=$(( $* )) }' else as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } fi # as_fn_arith # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi $as_echo "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || $as_echo X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits as_lineno_1=$LINENO as_lineno_1a=$LINENO as_lineno_2=$LINENO as_lineno_2a=$LINENO eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) sed -n ' p /[$]LINENO/= ' <$as_myself | sed ' s/[$]LINENO.*/&-/ t lineno b :lineno N :loop s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ t loop s/-\n.*// ' >$as_me.lineno && chmod +x "$as_me.lineno" || { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } # Don't try to exec as it changes $[0], causing all sort of problems # (the dirname of $[0] is not the place where we might find the # original and so on. Autoconf is especially sensitive to this). . "./$as_me.lineno" # Exit status is that of the last command. exit } ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. xy) ECHO_C='\c';; *) echo `echo ksh88 bug on AIX 6.1` > /dev/null ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. # In both cases, we have to default to `cp -p'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -p' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -p' fi else as_ln_s='cp -p' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi if test -x / >/dev/null 2>&1; then as_test_x='test -x' else if ls -dL / >/dev/null 2>&1; then as_ls_L_option=L else as_ls_L_option= fi as_test_x=' eval sh -c '\'' if test -d "$1"; then test -d "$1/."; else case $1 in #( -*)set "./$1";; esac; case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in #(( ???[sx]*):;;*)false;;esac;fi '\'' sh ' fi as_executable_p=$as_test_x # Sed expression to map a string onto a valid CPP name. as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" # Sed expression to map a string onto a valid variable name. as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" test -n "$DJDIR" || exec 7<&0 &1 # Name of the host. # hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, # so uname gets run too. ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` # # Initializations. # ac_default_prefix=/usr/local ac_clean_files= ac_config_libobj_dir=. LIBOBJS= cross_compiling=no subdirs= MFLAGS= MAKEFLAGS= # Identity of this package. PACKAGE_NAME='Citadel' PACKAGE_TARNAME='citadel' PACKAGE_VERSION='9.01' PACKAGE_STRING='Citadel 9.01' PACKAGE_BUGREPORT='http://www.citadel.org/' PACKAGE_URL='' ac_unique_file="citserver.c" ac_default_prefix=/usr/local/citadel # Factoring default headers for most tests. ac_includes_default="\ #include #ifdef HAVE_SYS_TYPES_H # include #endif #ifdef HAVE_SYS_STAT_H # include #endif #ifdef STDC_HEADERS # include # include #else # ifdef HAVE_STDLIB_H # include # endif #endif #ifdef HAVE_STRING_H # if !defined STDC_HEADERS && defined HAVE_MEMORY_H # include # endif # include #endif #ifdef HAVE_STRINGS_H # include #endif #ifdef HAVE_INTTYPES_H # include #endif #ifdef HAVE_STDINT_H # include #endif #ifdef HAVE_UNISTD_H # include #endif" ac_subst_vars='LTLIBOBJS LIBOBJS SETUP_LIBS SERVER_LIBS SERVER_LDFLAGS DATABASE TARGETS chkpwd_LIBS RESOLV ok_msgfmt ok_msgmerge ok_xgettext ACLOCAL AUTOCONF PATCH DIFF YFLAGS YACC INSTALL_DATA INSTALL_SCRIPT INSTALL_PROGRAM DEPEND_FLAG host_os host_vendor host_cpu host build_os build_vendor build_cpu build LOCALEDIR MAKE_DOC_DIR MAKE_RUN_DIR MAKE_UTILBIN_DIR MAKE_AUTO_ETC_DIR MAKE_ETC_DIR MAKE_SPOOL_DIR MAKE_SSL_DIR MAKE_STATICDATA_DIR MAKE_HELP_DIR MAKE_DATA_DIR EGREP GREP CPP OBJEXT EXEEXT ac_ct_CC CPPFLAGS LDFLAGS CFLAGS CC target_alias host_alias build_alias LIBS ECHO_T ECHO_N ECHO_C DEFS mandir localedir libdir psdir pdfdir dvidir htmldir infodir docdir oldincludedir includedir localstatedir sharedstatedir sysconfdir datadir datarootdir libexecdir sbindir bindir program_transform_name prefix exec_prefix PACKAGE_URL PACKAGE_BUGREPORT PACKAGE_STRING PACKAGE_VERSION PACKAGE_TARNAME PACKAGE_NAME PATH_SEPARATOR SHELL' ac_subst_files='' ac_user_opts=' enable_option_checking with_datadir with_helpdir with_staticdatadir with_ssldir with_spooldir with_sysconfdir with_autosysconfdir with_utility_bindir with_rundir with_egdpool with_docdir with_localedir enable_iconv enable_pie with_pam with_kthread with_db with_ssl with_with_ldap with_with_gc with_backtrace with_gprof ' ac_precious_vars='build_alias host_alias target_alias CC CFLAGS LDFLAGS LIBS CPPFLAGS CPP YACC YFLAGS' # Initialize some variables set by options. ac_init_help= ac_init_version=false ac_unrecognized_opts= ac_unrecognized_sep= # The variables have the same names as the options, with # dashes changed to underlines. cache_file=/dev/null exec_prefix=NONE no_create= no_recursion= prefix=NONE program_prefix=NONE program_suffix=NONE program_transform_name=s,x,x, silent= site= srcdir= verbose= x_includes=NONE x_libraries=NONE # Installation directory options. # These are left unexpanded so users can "make install exec_prefix=/foo" # and all the variables that are supposed to be based on exec_prefix # by default will actually change. # Use braces instead of parens because sh, perl, etc. also accept them. # (The list follows the same order as the GNU Coding Standards.) bindir='${exec_prefix}/bin' sbindir='${exec_prefix}/sbin' libexecdir='${exec_prefix}/libexec' datarootdir='${prefix}/share' datadir='${datarootdir}' sysconfdir='${prefix}/etc' sharedstatedir='${prefix}/com' localstatedir='${prefix}/var' includedir='${prefix}/include' oldincludedir='/usr/include' docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' infodir='${datarootdir}/info' htmldir='${docdir}' dvidir='${docdir}' pdfdir='${docdir}' psdir='${docdir}' libdir='${exec_prefix}/lib' localedir='${datarootdir}/locale' mandir='${datarootdir}/man' ac_prev= ac_dashdash= for ac_option do # If the previous option needs an argument, assign it. if test -n "$ac_prev"; then eval $ac_prev=\$ac_option ac_prev= continue fi case $ac_option in *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; *=) ac_optarg= ;; *) ac_optarg=yes ;; esac # Accept the important Cygnus configure options, so we can diagnose typos. case $ac_dashdash$ac_option in --) ac_dashdash=yes ;; -bindir | --bindir | --bindi | --bind | --bin | --bi) ac_prev=bindir ;; -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) bindir=$ac_optarg ;; -build | --build | --buil | --bui | --bu) ac_prev=build_alias ;; -build=* | --build=* | --buil=* | --bui=* | --bu=*) build_alias=$ac_optarg ;; -cache-file | --cache-file | --cache-fil | --cache-fi \ | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) ac_prev=cache_file ;; -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) cache_file=$ac_optarg ;; --config-cache | -C) cache_file=config.cache ;; -datadir | --datadir | --datadi | --datad) ac_prev=datadir ;; -datadir=* | --datadir=* | --datadi=* | --datad=*) datadir=$ac_optarg ;; -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ | --dataroo | --dataro | --datar) ac_prev=datarootdir ;; -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) datarootdir=$ac_optarg ;; -disable-* | --disable-*) ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid feature name: $ac_useropt" ac_useropt_orig=$ac_useropt ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval enable_$ac_useropt=no ;; -docdir | --docdir | --docdi | --doc | --do) ac_prev=docdir ;; -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) docdir=$ac_optarg ;; -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) ac_prev=dvidir ;; -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) dvidir=$ac_optarg ;; -enable-* | --enable-*) ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid feature name: $ac_useropt" ac_useropt_orig=$ac_useropt ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval enable_$ac_useropt=\$ac_optarg ;; -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ | --exec | --exe | --ex) ac_prev=exec_prefix ;; -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ | --exec=* | --exe=* | --ex=*) exec_prefix=$ac_optarg ;; -gas | --gas | --ga | --g) # Obsolete; use --with-gas. with_gas=yes ;; -help | --help | --hel | --he | -h) ac_init_help=long ;; -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) ac_init_help=recursive ;; -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) ac_init_help=short ;; -host | --host | --hos | --ho) ac_prev=host_alias ;; -host=* | --host=* | --hos=* | --ho=*) host_alias=$ac_optarg ;; -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) ac_prev=htmldir ;; -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ | --ht=*) htmldir=$ac_optarg ;; -includedir | --includedir | --includedi | --included | --include \ | --includ | --inclu | --incl | --inc) ac_prev=includedir ;; -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ | --includ=* | --inclu=* | --incl=* | --inc=*) includedir=$ac_optarg ;; -infodir | --infodir | --infodi | --infod | --info | --inf) ac_prev=infodir ;; -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) infodir=$ac_optarg ;; -libdir | --libdir | --libdi | --libd) ac_prev=libdir ;; -libdir=* | --libdir=* | --libdi=* | --libd=*) libdir=$ac_optarg ;; -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ | --libexe | --libex | --libe) ac_prev=libexecdir ;; -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ | --libexe=* | --libex=* | --libe=*) libexecdir=$ac_optarg ;; -localedir | --localedir | --localedi | --localed | --locale) ac_prev=localedir ;; -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) localedir=$ac_optarg ;; -localstatedir | --localstatedir | --localstatedi | --localstated \ | --localstate | --localstat | --localsta | --localst | --locals) ac_prev=localstatedir ;; -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) localstatedir=$ac_optarg ;; -mandir | --mandir | --mandi | --mand | --man | --ma | --m) ac_prev=mandir ;; -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) mandir=$ac_optarg ;; -nfp | --nfp | --nf) # Obsolete; use --without-fp. with_fp=no ;; -no-create | --no-create | --no-creat | --no-crea | --no-cre \ | --no-cr | --no-c | -n) no_create=yes ;; -no-recursion | --no-recursion | --no-recursio | --no-recursi \ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) no_recursion=yes ;; -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ | --oldin | --oldi | --old | --ol | --o) ac_prev=oldincludedir ;; -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) oldincludedir=$ac_optarg ;; -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) ac_prev=prefix ;; -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) prefix=$ac_optarg ;; -program-prefix | --program-prefix | --program-prefi | --program-pref \ | --program-pre | --program-pr | --program-p) ac_prev=program_prefix ;; -program-prefix=* | --program-prefix=* | --program-prefi=* \ | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) program_prefix=$ac_optarg ;; -program-suffix | --program-suffix | --program-suffi | --program-suff \ | --program-suf | --program-su | --program-s) ac_prev=program_suffix ;; -program-suffix=* | --program-suffix=* | --program-suffi=* \ | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) program_suffix=$ac_optarg ;; -program-transform-name | --program-transform-name \ | --program-transform-nam | --program-transform-na \ | --program-transform-n | --program-transform- \ | --program-transform | --program-transfor \ | --program-transfo | --program-transf \ | --program-trans | --program-tran \ | --progr-tra | --program-tr | --program-t) ac_prev=program_transform_name ;; -program-transform-name=* | --program-transform-name=* \ | --program-transform-nam=* | --program-transform-na=* \ | --program-transform-n=* | --program-transform-=* \ | --program-transform=* | --program-transfor=* \ | --program-transfo=* | --program-transf=* \ | --program-trans=* | --program-tran=* \ | --progr-tra=* | --program-tr=* | --program-t=*) program_transform_name=$ac_optarg ;; -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) ac_prev=pdfdir ;; -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) pdfdir=$ac_optarg ;; -psdir | --psdir | --psdi | --psd | --ps) ac_prev=psdir ;; -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) psdir=$ac_optarg ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) silent=yes ;; -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) ac_prev=sbindir ;; -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ | --sbi=* | --sb=*) sbindir=$ac_optarg ;; -sharedstatedir | --sharedstatedir | --sharedstatedi \ | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ | --sharedst | --shareds | --shared | --share | --shar \ | --sha | --sh) ac_prev=sharedstatedir ;; -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ | --sha=* | --sh=*) sharedstatedir=$ac_optarg ;; -site | --site | --sit) ac_prev=site ;; -site=* | --site=* | --sit=*) site=$ac_optarg ;; -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) ac_prev=srcdir ;; -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) srcdir=$ac_optarg ;; -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ | --syscon | --sysco | --sysc | --sys | --sy) ac_prev=sysconfdir ;; -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) sysconfdir=$ac_optarg ;; -target | --target | --targe | --targ | --tar | --ta | --t) ac_prev=target_alias ;; -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) target_alias=$ac_optarg ;; -v | -verbose | --verbose | --verbos | --verbo | --verb) verbose=yes ;; -version | --version | --versio | --versi | --vers | -V) ac_init_version=: ;; -with-* | --with-*) ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid package name: $ac_useropt" ac_useropt_orig=$ac_useropt ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval with_$ac_useropt=\$ac_optarg ;; -without-* | --without-*) ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid package name: $ac_useropt" ac_useropt_orig=$ac_useropt ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval with_$ac_useropt=no ;; --x) # Obsolete; use --with-x. with_x=yes ;; -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ | --x-incl | --x-inc | --x-in | --x-i) ac_prev=x_includes ;; -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) x_includes=$ac_optarg ;; -x-libraries | --x-libraries | --x-librarie | --x-librari \ | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) ac_prev=x_libraries ;; -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) x_libraries=$ac_optarg ;; -*) as_fn_error $? "unrecognized option: \`$ac_option' Try \`$0 --help' for more information" ;; *=*) ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` # Reject names that are not valid shell variable names. case $ac_envvar in #( '' | [0-9]* | *[!_$as_cr_alnum]* ) as_fn_error $? "invalid variable name: \`$ac_envvar'" ;; esac eval $ac_envvar=\$ac_optarg export $ac_envvar ;; *) # FIXME: should be removed in autoconf 3.0. $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2 expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2 : ${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option} ;; esac done if test -n "$ac_prev"; then ac_option=--`echo $ac_prev | sed 's/_/-/g'` as_fn_error $? "missing argument to $ac_option" fi if test -n "$ac_unrecognized_opts"; then case $enable_option_checking in no) ;; fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; *) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; esac fi # Check all directory arguments for consistency. for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ datadir sysconfdir sharedstatedir localstatedir includedir \ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ libdir localedir mandir do eval ac_val=\$$ac_var # Remove trailing slashes. case $ac_val in */ ) ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` eval $ac_var=\$ac_val;; esac # Be sure to have absolute directory names. case $ac_val in [\\/$]* | ?:[\\/]* ) continue;; NONE | '' ) case $ac_var in *prefix ) continue;; esac;; esac as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" done # There might be people who depend on the old broken behavior: `$host' # used to hold the argument of --host etc. # FIXME: To remove some day. build=$build_alias host=$host_alias target=$target_alias # FIXME: To remove some day. if test "x$host_alias" != x; then if test "x$build_alias" = x; then cross_compiling=maybe $as_echo "$as_me: WARNING: if you wanted to set the --build type, don't use --host. If a cross compiler is detected then cross compile mode will be used" >&2 elif test "x$build_alias" != "x$host_alias"; then cross_compiling=yes fi fi ac_tool_prefix= test -n "$host_alias" && ac_tool_prefix=$host_alias- test "$silent" = yes && exec 6>/dev/null ac_pwd=`pwd` && test -n "$ac_pwd" && ac_ls_di=`ls -di .` && ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || as_fn_error $? "working directory cannot be determined" test "X$ac_ls_di" = "X$ac_pwd_ls_di" || as_fn_error $? "pwd does not report name of working directory" # Find the source files, if location was not specified. if test -z "$srcdir"; then ac_srcdir_defaulted=yes # Try the directory containing this script, then the parent directory. ac_confdir=`$as_dirname -- "$as_myself" || $as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_myself" : 'X\(//\)[^/]' \| \ X"$as_myself" : 'X\(//\)$' \| \ X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$as_myself" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` srcdir=$ac_confdir if test ! -r "$srcdir/$ac_unique_file"; then srcdir=.. fi else ac_srcdir_defaulted=no fi if test ! -r "$srcdir/$ac_unique_file"; then test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" fi ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" ac_abs_confdir=`( cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" pwd)` # When building in place, set srcdir=. if test "$ac_abs_confdir" = "$ac_pwd"; then srcdir=. fi # Remove unnecessary trailing slashes from srcdir. # Double slashes in file names in object file debugging info # mess up M-x gdb in Emacs. case $srcdir in */) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; esac for ac_var in $ac_precious_vars; do eval ac_env_${ac_var}_set=\${${ac_var}+set} eval ac_env_${ac_var}_value=\$${ac_var} eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} eval ac_cv_env_${ac_var}_value=\$${ac_var} done # # Report the --help message. # if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF \`configure' configures Citadel 9.01 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... To assign environment variables (e.g., CC, CFLAGS...), specify them as VAR=VALUE. See below for descriptions of some of the useful variables. Defaults for the options are specified in brackets. Configuration: -h, --help display this help and exit --help=short display options specific to this package --help=recursive display the short help of all the included packages -V, --version display version information and exit -q, --quiet, --silent do not print \`checking ...' messages --cache-file=FILE cache test results in FILE [disabled] -C, --config-cache alias for \`--cache-file=config.cache' -n, --no-create do not create output files --srcdir=DIR find the sources in DIR [configure dir or \`..'] Installation directories: --prefix=PREFIX install architecture-independent files in PREFIX [$ac_default_prefix] --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX [PREFIX] By default, \`make install' will install all the files in \`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify an installation prefix other than \`$ac_default_prefix' using \`--prefix', for instance \`--prefix=\$HOME'. For better control, use the options below. Fine tuning of the installation directories: --bindir=DIR user executables [EPREFIX/bin] --sbindir=DIR system admin executables [EPREFIX/sbin] --libexecdir=DIR program executables [EPREFIX/libexec] --sysconfdir=DIR read-only single-machine data [PREFIX/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] --datadir=DIR read-only architecture-independent data [DATAROOTDIR] --infodir=DIR info documentation [DATAROOTDIR/info] --localedir=DIR locale-dependent data [DATAROOTDIR/locale] --mandir=DIR man documentation [DATAROOTDIR/man] --docdir=DIR documentation root [DATAROOTDIR/doc/citadel] --htmldir=DIR html documentation [DOCDIR] --dvidir=DIR dvi documentation [DOCDIR] --pdfdir=DIR pdf documentation [DOCDIR] --psdir=DIR ps documentation [DOCDIR] _ACEOF cat <<\_ACEOF System types: --build=BUILD configure for building on BUILD [guessed] --host=HOST cross-compile to build programs to run on HOST [BUILD] _ACEOF fi if test -n "$ac_init_help"; then case $ac_init_help in short | recursive ) echo "Configuration of Citadel 9.01:";; esac cat <<\_ACEOF Optional Features: --disable-option-checking ignore unrecognized --enable/--with options --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) --enable-FEATURE[=ARG] include FEATURE [ARG=yes] --disable-iconv do not use iconv charset conversion --enable-pie build position-independent executables Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) --with-datadir directory to store the databases under --with-helpdir directory to store the helpfiles under --with-staticdatadir directory to store citadels system messages under --with-ssldir directory to store the ssl certificates under --with-spooldir directory to keep queues under --with-sysconfdir directory to store the configs under --with-autosysconfdir directory to store the automaticaly maintained configs under --with-utility-bindir directory where to find helper binaries --with-rundir directory to place runtime files (UDS) to? --with-egdpool the socket from Pseudo Random Generator, defaults to /var/run/egd-pool --with-docdir where to install the documentation. default: /usr/local/citadel/ --with-localedir directory to put the locale files to --with-pam use PAM if present (see PAM.txt before you try this) --with-kthread use kernel threads (on FreeBSD) (not recommended yet) --with-db[=DIR] use Berkeley DB 3.x [DIR=/usr/local/BerkeleyDB.3.[123]] --with-ssl=PATH Specify path to OpenSSL installation --with-ldap use OpenLDAP client library --with-gc use the Boehm-Demers-Weiser garbage collection library --with-backtrace enable backtrace dumps in the syslog --with-gprof enable profiling Some influential environment variables: CC C compiler command CFLAGS C compiler flags LDFLAGS linker flags, e.g. -L if you have libraries in a nonstandard directory LIBS libraries to pass to the linker, e.g. -l CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if you have headers in a nonstandard directory CPP C preprocessor YACC The `Yet Another C Compiler' implementation to use. Defaults to the first program found out of: `bison -y', `byacc', `yacc'. YFLAGS The list of arguments that will be passed by default to $YACC. This script will default YFLAGS to the empty string to avoid a default value of `-d' given by some make applications. Use these variables to override the choices made by `configure' or to help it to find libraries and programs with nonstandard names/locations. Report bugs to . _ACEOF ac_status=$? fi if test "$ac_init_help" = "recursive"; then # If there are subdirs, report their specific --help. for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue test -d "$ac_dir" || { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || continue ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix cd "$ac_dir" || { ac_status=$?; continue; } # Check for guested configure. if test -f "$ac_srcdir/configure.gnu"; then echo && $SHELL "$ac_srcdir/configure.gnu" --help=recursive elif test -f "$ac_srcdir/configure"; then echo && $SHELL "$ac_srcdir/configure" --help=recursive else $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 fi || ac_status=$? cd "$ac_pwd" || { ac_status=$?; break; } done fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF Citadel configure 9.01 generated by GNU Autoconf 2.67 Copyright (C) 2010 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. _ACEOF exit fi ## ------------------------ ## ## Autoconf initialization. ## ## ------------------------ ## # ac_fn_c_try_compile LINENO # -------------------------- # Try to compile conftest.$ac_ext, and return whether this succeeded. ac_fn_c_try_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack rm -f conftest.$ac_objext if { { ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_c_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then : ac_retval=0 else $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 fi eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;} as_fn_set_status $ac_retval } # ac_fn_c_try_compile # ac_fn_c_try_cpp LINENO # ---------------------- # Try to preprocess conftest.$ac_ext, and return whether this succeeded. ac_fn_c_try_cpp () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack if { { ac_try="$ac_cpp conftest.$ac_ext" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } > conftest.i && { test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || test ! -s conftest.err }; then : ac_retval=0 else $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 fi eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;} as_fn_set_status $ac_retval } # ac_fn_c_try_cpp # ac_fn_c_check_header_mongrel LINENO HEADER VAR INCLUDES # ------------------------------------------------------- # Tests whether HEADER exists, giving a warning if it cannot be compiled using # the include files in INCLUDES and setting the cache variable VAR # accordingly. ac_fn_c_check_header_mongrel () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack if eval "test \"\${$3+set}\"" = set; then : { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 $as_echo_n "checking for $2... " >&6; } if eval "test \"\${$3+set}\"" = set; then : $as_echo_n "(cached) " >&6 fi eval ac_res=\$$3 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 $as_echo "$ac_res" >&6; } else # Is the header compilable? { $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 usability" >&5 $as_echo_n "checking $2 usability... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 #include <$2> _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_header_compiler=yes else ac_header_compiler=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_compiler" >&5 $as_echo "$ac_header_compiler" >&6; } # Is the header present? { $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 presence" >&5 $as_echo_n "checking $2 presence... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include <$2> _ACEOF if ac_fn_c_try_cpp "$LINENO"; then : ac_header_preproc=yes else ac_header_preproc=no fi rm -f conftest.err conftest.i conftest.$ac_ext { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_preproc" >&5 $as_echo "$ac_header_preproc" >&6; } # So? What about this header? case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in #(( yes:no: ) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&5 $as_echo "$as_me: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 $as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} ;; no:yes:* ) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: present but cannot be compiled" >&5 $as_echo "$as_me: WARNING: $2: present but cannot be compiled" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: check for missing prerequisite headers?" >&5 $as_echo "$as_me: WARNING: $2: check for missing prerequisite headers?" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: see the Autoconf documentation" >&5 $as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&5 $as_echo "$as_me: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 $as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} ( $as_echo "## -------------------------------------- ## ## Report this to http://www.citadel.org/ ## ## -------------------------------------- ##" ) | sed "s/^/$as_me: WARNING: /" >&2 ;; esac { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 $as_echo_n "checking for $2... " >&6; } if eval "test \"\${$3+set}\"" = set; then : $as_echo_n "(cached) " >&6 else eval "$3=\$ac_header_compiler" fi eval ac_res=\$$3 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 $as_echo "$ac_res" >&6; } fi eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;} } # ac_fn_c_check_header_mongrel # ac_fn_c_try_run LINENO # ---------------------- # Try to link conftest.$ac_ext, and return whether this succeeded. Assumes # that executables *can* be run. ac_fn_c_try_run () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { ac_try='./conftest$ac_exeext' { { case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_try") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; }; then : ac_retval=0 else $as_echo "$as_me: program exited with status $ac_status" >&5 $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=$ac_status fi rm -rf conftest.dSYM conftest_ipa8_conftest.oo eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;} as_fn_set_status $ac_retval } # ac_fn_c_try_run # ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES # ------------------------------------------------------- # Tests whether HEADER exists and can be compiled using the include files in # INCLUDES, setting the cache variable VAR accordingly. ac_fn_c_check_header_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 $as_echo_n "checking for $2... " >&6; } if eval "test \"\${$3+set}\"" = set; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 #include <$2> _ACEOF if ac_fn_c_try_compile "$LINENO"; then : eval "$3=yes" else eval "$3=no" fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi eval ac_res=\$$3 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 $as_echo "$ac_res" >&6; } eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;} } # ac_fn_c_check_header_compile # ac_fn_c_try_link LINENO # ----------------------- # Try to link conftest.$ac_ext, and return whether this succeeded. ac_fn_c_try_link () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack rm -f conftest.$ac_objext conftest$ac_exeext if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_link") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_c_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && { test "$cross_compiling" = yes || $as_test_x conftest$ac_exeext }; then : ac_retval=0 else $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 fi # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would # interfere with the next link command; also delete a directory that is # left behind by Apple's compiler. We do this before executing the actions. rm -rf conftest.dSYM conftest_ipa8_conftest.oo eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;} as_fn_set_status $ac_retval } # ac_fn_c_try_link # ac_fn_c_compute_int LINENO EXPR VAR INCLUDES # -------------------------------------------- # Tries to find the compile-time value of EXPR in a program that includes # INCLUDES, setting VAR accordingly. Returns whether the value could be # computed ac_fn_c_compute_int () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack if test "$cross_compiling" = yes; then # Depending upon the size, compute the lo and hi bounds. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 int main () { static int test_array [1 - 2 * !(($2) >= 0)]; test_array [0] = 0 ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_lo=0 ac_mid=0 while :; do cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 int main () { static int test_array [1 - 2 * !(($2) <= $ac_mid)]; test_array [0] = 0 ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_hi=$ac_mid; break else as_fn_arith $ac_mid + 1 && ac_lo=$as_val if test $ac_lo -le $ac_mid; then ac_lo= ac_hi= break fi as_fn_arith 2 '*' $ac_mid + 1 && ac_mid=$as_val fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext done else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 int main () { static int test_array [1 - 2 * !(($2) < 0)]; test_array [0] = 0 ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_hi=-1 ac_mid=-1 while :; do cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 int main () { static int test_array [1 - 2 * !(($2) >= $ac_mid)]; test_array [0] = 0 ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_lo=$ac_mid; break else as_fn_arith '(' $ac_mid ')' - 1 && ac_hi=$as_val if test $ac_mid -le $ac_hi; then ac_lo= ac_hi= break fi as_fn_arith 2 '*' $ac_mid && ac_mid=$as_val fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext done else ac_lo= ac_hi= fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext # Binary search between lo and hi bounds. while test "x$ac_lo" != "x$ac_hi"; do as_fn_arith '(' $ac_hi - $ac_lo ')' / 2 + $ac_lo && ac_mid=$as_val cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 int main () { static int test_array [1 - 2 * !(($2) <= $ac_mid)]; test_array [0] = 0 ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_hi=$ac_mid else as_fn_arith '(' $ac_mid ')' + 1 && ac_lo=$as_val fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext done case $ac_lo in #(( ?*) eval "$3=\$ac_lo"; ac_retval=0 ;; '') ac_retval=1 ;; esac else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 static long int longval () { return $2; } static unsigned long int ulongval () { return $2; } #include #include int main () { FILE *f = fopen ("conftest.val", "w"); if (! f) return 1; if (($2) < 0) { long int i = longval (); if (i != ($2)) return 1; fprintf (f, "%ld", i); } else { unsigned long int i = ulongval (); if (i != ($2)) return 1; fprintf (f, "%lu", i); } /* Do not output a trailing newline, as this causes \r\n confusion on some platforms. */ return ferror (f) || fclose (f) != 0; ; return 0; } _ACEOF if ac_fn_c_try_run "$LINENO"; then : echo >>conftest.val; read $3 &5 $as_echo_n "checking for $2... " >&6; } if eval "test \"\${$3+set}\"" = set; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Define $2 to an innocuous variant, in case declares $2. For example, HP-UX 11i declares gettimeofday. */ #define $2 innocuous_$2 /* System header to define __stub macros and hopefully few prototypes, which can conflict with char $2 (); below. Prefer to if __STDC__ is defined, since exists even on freestanding compilers. */ #ifdef __STDC__ # include #else # include #endif #undef $2 /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char $2 (); /* The GNU C library defines this for functions which it implements to always fail with ENOSYS. Some functions are actually named something starting with __ and the normal name is an alias. */ #if defined __stub_$2 || defined __stub___$2 choke me #endif int main () { return $2 (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : eval "$3=yes" else eval "$3=no" fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext fi eval ac_res=\$$3 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 $as_echo "$ac_res" >&6; } eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;} } # ac_fn_c_check_func # ac_fn_c_check_type LINENO TYPE VAR INCLUDES # ------------------------------------------- # Tests whether TYPE exists after having included INCLUDES, setting cache # variable VAR accordingly. ac_fn_c_check_type () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 $as_echo_n "checking for $2... " >&6; } if eval "test \"\${$3+set}\"" = set; then : $as_echo_n "(cached) " >&6 else eval "$3=no" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 int main () { if (sizeof ($2)) return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 int main () { if (sizeof (($2))) return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : else eval "$3=yes" fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi eval ac_res=\$$3 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 $as_echo "$ac_res" >&6; } eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;} } # ac_fn_c_check_type # ac_fn_c_check_member LINENO AGGR MEMBER VAR INCLUDES # ---------------------------------------------------- # Tries to find if the field MEMBER exists in type AGGR, after including # INCLUDES, setting cache variable VAR accordingly. ac_fn_c_check_member () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2.$3" >&5 $as_echo_n "checking for $2.$3... " >&6; } if eval "test \"\${$4+set}\"" = set; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $5 int main () { static $2 ac_aggr; if (ac_aggr.$3) return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : eval "$4=yes" else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $5 int main () { static $2 ac_aggr; if (sizeof ac_aggr.$3) return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : eval "$4=yes" else eval "$4=no" fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi eval ac_res=\$$4 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 $as_echo "$ac_res" >&6; } eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;} } # ac_fn_c_check_member cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. It was created by Citadel $as_me 9.01, which was generated by GNU Autoconf 2.67. Invocation command line was $ $0 $@ _ACEOF exec 5>>config.log { cat <<_ASUNAME ## --------- ## ## Platform. ## ## --------- ## hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` uname -m = `(uname -m) 2>/dev/null || echo unknown` uname -r = `(uname -r) 2>/dev/null || echo unknown` uname -s = `(uname -s) 2>/dev/null || echo unknown` uname -v = `(uname -v) 2>/dev/null || echo unknown` /usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` /bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` /bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` /usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` /usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` /usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` /bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` /usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` /bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` _ASUNAME as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. $as_echo "PATH: $as_dir" done IFS=$as_save_IFS } >&5 cat >&5 <<_ACEOF ## ----------- ## ## Core tests. ## ## ----------- ## _ACEOF # Keep a trace of the command line. # Strip out --no-create and --no-recursion so they do not pile up. # Strip out --silent because we don't want to record it for future runs. # Also quote any args containing shell meta-characters. # Make two passes to allow for proper duplicate-argument suppression. ac_configure_args= ac_configure_args0= ac_configure_args1= ac_must_keep_next=false for ac_pass in 1 2 do for ac_arg do case $ac_arg in -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) continue ;; *\'*) ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac case $ac_pass in 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; 2) as_fn_append ac_configure_args1 " '$ac_arg'" if test $ac_must_keep_next = true; then ac_must_keep_next=false # Got value, back to normal. else case $ac_arg in *=* | --config-cache | -C | -disable-* | --disable-* \ | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ | -with-* | --with-* | -without-* | --without-* | --x) case "$ac_configure_args0 " in "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; esac ;; -* ) ac_must_keep_next=true ;; esac fi as_fn_append ac_configure_args " '$ac_arg'" ;; esac done done { ac_configure_args0=; unset ac_configure_args0;} { ac_configure_args1=; unset ac_configure_args1;} # When interrupted or exit'd, cleanup temporary files, and complete # config.log. We remove comments because anyway the quotes in there # would cause problems or look ugly. # WARNING: Use '\'' to represent an apostrophe within the trap. # WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. trap 'exit_status=$? # Save into config.log some information that might help in debugging. { echo $as_echo "## ---------------- ## ## Cache variables. ## ## ---------------- ##" echo # The following way of writing the cache mishandles newlines in values, ( for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( *${as_nl}ac_space=\ *) sed -n \ "s/'\''/'\''\\\\'\'''\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" ;; #( *) sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) echo $as_echo "## ----------------- ## ## Output variables. ## ## ----------------- ##" echo for ac_var in $ac_subst_vars do eval ac_val=\$$ac_var case $ac_val in *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac $as_echo "$ac_var='\''$ac_val'\''" done | sort echo if test -n "$ac_subst_files"; then $as_echo "## ------------------- ## ## File substitutions. ## ## ------------------- ##" echo for ac_var in $ac_subst_files do eval ac_val=\$$ac_var case $ac_val in *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac $as_echo "$ac_var='\''$ac_val'\''" done | sort echo fi if test -s confdefs.h; then $as_echo "## ----------- ## ## confdefs.h. ## ## ----------- ##" echo cat confdefs.h echo fi test "$ac_signal" != 0 && $as_echo "$as_me: caught signal $ac_signal" $as_echo "$as_me: exit $exit_status" } >&5 rm -f core *.core core.conftest.* && rm -f -r conftest* confdefs* conf$$* $ac_clean_files && exit $exit_status ' 0 for ac_signal in 1 2 13 15; do trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal done ac_signal=0 # confdefs.h avoids OS command line length limits that DEFS can exceed. rm -f -r conftest* confdefs.h $as_echo "/* confdefs.h */" > confdefs.h # Predefined preprocessor variables. cat >>confdefs.h <<_ACEOF #define PACKAGE_NAME "$PACKAGE_NAME" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_TARNAME "$PACKAGE_TARNAME" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_VERSION "$PACKAGE_VERSION" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_STRING "$PACKAGE_STRING" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_URL "$PACKAGE_URL" _ACEOF # Let the site file select an alternate cache file if it wants to. # Prefer an explicitly selected file to automatically selected ones. ac_site_file1=NONE ac_site_file2=NONE if test -n "$CONFIG_SITE"; then # We do not want a PATH search for config.site. case $CONFIG_SITE in #(( -*) ac_site_file1=./$CONFIG_SITE;; */*) ac_site_file1=$CONFIG_SITE;; *) ac_site_file1=./$CONFIG_SITE;; esac elif test "x$prefix" != xNONE; then ac_site_file1=$prefix/share/config.site ac_site_file2=$prefix/etc/config.site else ac_site_file1=$ac_default_prefix/share/config.site ac_site_file2=$ac_default_prefix/etc/config.site fi for ac_site_file in "$ac_site_file1" "$ac_site_file2" do test "x$ac_site_file" = xNONE && continue if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 $as_echo "$as_me: loading site script $ac_site_file" >&6;} sed 's/^/| /' "$ac_site_file" >&5 . "$ac_site_file" \ || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "failed to load site script $ac_site_file See \`config.log' for more details" "$LINENO" 5 ; } fi done if test -r "$cache_file"; then # Some versions of bash will fail to source /dev/null (special files # actually), so we avoid doing that. DJGPP emulates it as a regular file. if test /dev/null != "$cache_file" && test -f "$cache_file"; then { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 $as_echo "$as_me: loading cache $cache_file" >&6;} case $cache_file in [\\/]* | ?:[\\/]* ) . "$cache_file";; *) . "./$cache_file";; esac fi else { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 $as_echo "$as_me: creating cache $cache_file" >&6;} >$cache_file fi # Check that the precious variables saved in the cache have kept the same # value. ac_cache_corrupted=false for ac_var in $ac_precious_vars; do eval ac_old_set=\$ac_cv_env_${ac_var}_set eval ac_new_set=\$ac_env_${ac_var}_set eval ac_old_val=\$ac_cv_env_${ac_var}_value eval ac_new_val=\$ac_env_${ac_var}_value case $ac_old_set,$ac_new_set in set,) { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 $as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} ac_cache_corrupted=: ;; ,set) { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 $as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} ac_cache_corrupted=: ;; ,);; *) if test "x$ac_old_val" != "x$ac_new_val"; then # differences in whitespace do not lead to failure. ac_old_val_w=`echo x $ac_old_val` ac_new_val_w=`echo x $ac_new_val` if test "$ac_old_val_w" != "$ac_new_val_w"; then { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 $as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} ac_cache_corrupted=: else { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 $as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} eval $ac_var=\$ac_old_val fi { $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 $as_echo "$as_me: former value: \`$ac_old_val'" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 $as_echo "$as_me: current value: \`$ac_new_val'" >&2;} fi;; esac # Pass precious variables to config.status. if test "$ac_new_set" = set; then case $ac_new_val in *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; *) ac_arg=$ac_var=$ac_new_val ;; esac case " $ac_configure_args " in *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. *) as_fn_append ac_configure_args " '$ac_arg'" ;; esac fi done if $ac_cache_corrupted; then { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 $as_echo "$as_me: error: changes in the environment can compromise the build" >&2;} as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5 fi ## -------------------- ## ## Main body of script. ## ## -------------------- ## ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_config_headers="$ac_config_headers sysdep.h" if test "$prefix" = NONE; then cat >>confdefs.h <<_ACEOF #define CTDLDIR "$ac_default_prefix" _ACEOF ssl_dir="$ac_default_prefix/keys" localedir=$ac_default_prefix else cat >>confdefs.h <<_ACEOF #define CTDLDIR "$prefix" _ACEOF ssl_dir="$prefix/keys" localedir=$prefix fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. set dummy ${ac_tool_prefix}gcc; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if test "${ac_cv_prog_CC+set}" = set; then : $as_echo_n "(cached) " >&6 else if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_prog_CC="${ac_tool_prefix}gcc" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 $as_echo "$CC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi if test -z "$ac_cv_prog_CC"; then ac_ct_CC=$CC # Extract the first word of "gcc", so it can be a program name with args. set dummy gcc; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if test "${ac_cv_prog_ac_ct_CC+set}" = set; then : $as_echo_n "(cached) " >&6 else if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_prog_ac_ct_CC="gcc" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 $as_echo "$ac_ct_CC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi else CC="$ac_cv_prog_CC" fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. set dummy ${ac_tool_prefix}cc; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if test "${ac_cv_prog_CC+set}" = set; then : $as_echo_n "(cached) " >&6 else if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_prog_CC="${ac_tool_prefix}cc" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 $as_echo "$CC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi fi if test -z "$CC"; then # Extract the first word of "cc", so it can be a program name with args. set dummy cc; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if test "${ac_cv_prog_CC+set}" = set; then : $as_echo_n "(cached) " >&6 else if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else ac_prog_rejected=no as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then ac_prog_rejected=yes continue fi ac_cv_prog_CC="cc" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS if test $ac_prog_rejected = yes; then # We found a bogon in the path, so make sure we never use it. set dummy $ac_cv_prog_CC shift if test $# != 0; then # We chose a different compiler from the bogus one. # However, it has the same basename, so the bogon will be chosen # first if we set CC to just the basename; use the full file name. shift ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@" fi fi fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 $as_echo "$CC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then for ac_prog in cl.exe do # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. set dummy $ac_tool_prefix$ac_prog; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if test "${ac_cv_prog_CC+set}" = set; then : $as_echo_n "(cached) " >&6 else if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_prog_CC="$ac_tool_prefix$ac_prog" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 $as_echo "$CC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi test -n "$CC" && break done fi if test -z "$CC"; then ac_ct_CC=$CC for ac_prog in cl.exe do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if test "${ac_cv_prog_ac_ct_CC+set}" = set; then : $as_echo_n "(cached) " >&6 else if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_prog_ac_ct_CC="$ac_prog" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 $as_echo "$ac_ct_CC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi test -n "$ac_ct_CC" && break done if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi fi fi test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "no acceptable C compiler found in \$PATH See \`config.log' for more details" "$LINENO" 5 ; } # Provide some information about the compiler. $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 set X $ac_compile ac_compiler=$2 for ac_option in --version -v -V -qversion; do { { ac_try="$ac_compiler $ac_option >&5" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_compiler $ac_option >&5") 2>conftest.err ac_status=$? if test -s conftest.err; then sed '10a\ ... rest of stderr output deleted ... 10q' conftest.err >conftest.er1 cat conftest.er1 >&5 fi rm -f conftest.er1 conftest.err $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } done cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" # Try to create an executable without -o first, disregard a.out. # It will help us diagnose broken compilers, and finding out an intuition # of exeext. { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 $as_echo_n "checking whether the C compiler works... " >&6; } ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'` # The possible output files: ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" ac_rmfiles= for ac_file in $ac_files do case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; * ) ac_rmfiles="$ac_rmfiles $ac_file";; esac done rm -f $ac_rmfiles if { { ac_try="$ac_link_default" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_link_default") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then : # Autoconf-2.13 could set the ac_cv_exeext variable to `no'. # So ignore a value of `no', otherwise this would lead to `EXEEXT = no' # in a Makefile. We should not override ac_cv_exeext if it was cached, # so that the user can short-circuit this test for compilers unknown to # Autoconf. for ac_file in $ac_files '' do test -f "$ac_file" || continue case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; [ab].out ) # We found the default executable, but exeext='' is most # certainly right. break;; *.* ) if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no; then :; else ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` fi # We set ac_cv_exeext here because the later test for it is not # safe: cross compilers may not add the suffix if given an `-o' # argument, so we may need to know it at that point already. # Even if this section looks crufty: it has the advantage of # actually working. break;; * ) break;; esac done test "$ac_cv_exeext" = no && ac_cv_exeext= else ac_file='' fi if test -z "$ac_file"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error 77 "C compiler cannot create executables See \`config.log' for more details" "$LINENO" 5 ; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 $as_echo_n "checking for C compiler default output file name... " >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 $as_echo "$ac_file" >&6; } ac_exeext=$ac_cv_exeext rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out ac_clean_files=$ac_clean_files_save { $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 $as_echo_n "checking for suffix of executables... " >&6; } if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then : # If both `conftest.exe' and `conftest' are `present' (well, observable) # catch `conftest.exe'. For instance with Cygwin, `ls conftest' will # work properly (i.e., refer to `conftest.exe'), while it won't with # `rm'. for ac_file in conftest.exe conftest conftest.*; do test -f "$ac_file" || continue case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` break;; * ) break;; esac done else { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of executables: cannot compile and link See \`config.log' for more details" "$LINENO" 5 ; } fi rm -f conftest conftest$ac_cv_exeext { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 $as_echo "$ac_cv_exeext" >&6; } rm -f conftest.$ac_ext EXEEXT=$ac_cv_exeext ac_exeext=$EXEEXT cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main () { FILE *f = fopen ("conftest.out", "w"); return ferror (f) || fclose (f) != 0; ; return 0; } _ACEOF ac_clean_files="$ac_clean_files conftest.out" # Check that the compiler produces executables we can run. If not, either # the compiler is broken, or we cross compile. { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 $as_echo_n "checking whether we are cross compiling... " >&6; } if test "$cross_compiling" != yes; then { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } if { ac_try='./conftest$ac_cv_exeext' { { case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_try") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; }; then cross_compiling=no else if test "$cross_compiling" = maybe; then cross_compiling=yes else { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot run C compiled programs. If you meant to cross compile, use \`--host'. See \`config.log' for more details" "$LINENO" 5 ; } fi fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 $as_echo "$cross_compiling" >&6; } rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out ac_clean_files=$ac_clean_files_save { $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 $as_echo_n "checking for suffix of object files... " >&6; } if test "${ac_cv_objext+set}" = set; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF rm -f conftest.o conftest.obj if { { ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then : for ac_file in conftest.o conftest.obj conftest.*; do test -f "$ac_file" || continue; case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;; *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` break;; esac done else $as_echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of object files: cannot compile See \`config.log' for more details" "$LINENO" 5 ; } fi rm -f conftest.$ac_cv_objext conftest.$ac_ext fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 $as_echo "$ac_cv_objext" >&6; } OBJEXT=$ac_cv_objext ac_objext=$OBJEXT { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5 $as_echo_n "checking whether we are using the GNU C compiler... " >&6; } if test "${ac_cv_c_compiler_gnu+set}" = set; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { #ifndef __GNUC__ choke me #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_compiler_gnu=yes else ac_compiler_gnu=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext ac_cv_c_compiler_gnu=$ac_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 $as_echo "$ac_cv_c_compiler_gnu" >&6; } if test $ac_compiler_gnu = yes; then GCC=yes else GCC= fi ac_test_CFLAGS=${CFLAGS+set} ac_save_CFLAGS=$CFLAGS { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 $as_echo_n "checking whether $CC accepts -g... " >&6; } if test "${ac_cv_prog_cc_g+set}" = set; then : $as_echo_n "(cached) " >&6 else ac_save_c_werror_flag=$ac_c_werror_flag ac_c_werror_flag=yes ac_cv_prog_cc_g=no CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_prog_cc_g=yes else CFLAGS="" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : else ac_c_werror_flag=$ac_save_c_werror_flag CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_prog_cc_g=yes fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext ac_c_werror_flag=$ac_save_c_werror_flag fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 $as_echo "$ac_cv_prog_cc_g" >&6; } if test "$ac_test_CFLAGS" = set; then CFLAGS=$ac_save_CFLAGS elif test $ac_cv_prog_cc_g = yes; then if test "$GCC" = yes; then CFLAGS="-g -O2" else CFLAGS="-g" fi else if test "$GCC" = yes; then CFLAGS="-O2" else CFLAGS= fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5 $as_echo_n "checking for $CC option to accept ISO C89... " >&6; } if test "${ac_cv_prog_cc_c89+set}" = set; then : $as_echo_n "(cached) " >&6 else ac_cv_prog_cc_c89=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include #include /* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ struct buf { int x; }; FILE * (*rcsopen) (struct buf *, struct stat *, int); static char *e (p, i) char **p; int i; { return p[i]; } static char *f (char * (*g) (char **, int), char **p, ...) { char *s; va_list v; va_start (v,p); s = g (p, va_arg (v,int)); va_end (v); return s; } /* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has function prototypes and stuff, but not '\xHH' hex character constants. These don't provoke an error unfortunately, instead are silently treated as 'x'. The following induces an error, until -std is added to get proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an array size at least. It's necessary to write '\x00'==0 to get something that's true only with -std. */ int osf4_cc_array ['\x00' == 0 ? 1 : -1]; /* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters inside strings and character constants. */ #define FOO(x) 'x' int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1]; int test (int i, double x); struct s1 {int (*f) (int a);}; struct s2 {int (*f) (double a);}; int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int); int argc; char **argv; int main () { return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]; ; return 0; } _ACEOF for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \ -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO"; then : ac_cv_prog_cc_c89=$ac_arg fi rm -f core conftest.err conftest.$ac_objext test "x$ac_cv_prog_cc_c89" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC fi # AC_CACHE_VAL case "x$ac_cv_prog_cc_c89" in x) { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 $as_echo "none needed" >&6; } ;; xno) { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 $as_echo "unsupported" >&6; } ;; *) CC="$CC $ac_cv_prog_cc_c89" { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 $as_echo "$ac_cv_prog_cc_c89" >&6; } ;; esac if test "x$ac_cv_prog_cc_c89" != xno; then : fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5 $as_echo_n "checking how to run the C preprocessor... " >&6; } # On Suns, sometimes $CPP names a directory. if test -n "$CPP" && test -d "$CPP"; then CPP= fi if test -z "$CPP"; then if test "${ac_cv_prog_CPP+set}" = set; then : $as_echo_n "(cached) " >&6 else # Double quotes because CPP needs to be expanded for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp" do ac_preproc_ok=false for ac_c_preproc_warn_flag in '' yes do # Use a header file that comes with gcc, so configuring glibc # with a fresh cross-compiler works. # Prefer to if __STDC__ is defined, since # exists even on freestanding compilers. # On the NeXT, cc -E runs the code through the compiler's parser, # not just through cpp. "Syntax error" is here to catch this case. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #ifdef __STDC__ # include #else # include #endif Syntax error _ACEOF if ac_fn_c_try_cpp "$LINENO"; then : else # Broken: fails on valid input. continue fi rm -f conftest.err conftest.i conftest.$ac_ext # OK, works on sane cases. Now check whether nonexistent headers # can be detected and how. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if ac_fn_c_try_cpp "$LINENO"; then : # Broken: success on invalid input. continue else # Passes both tests. ac_preproc_ok=: break fi rm -f conftest.err conftest.i conftest.$ac_ext done # Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. rm -f conftest.i conftest.err conftest.$ac_ext if $ac_preproc_ok; then : break fi done ac_cv_prog_CPP=$CPP fi CPP=$ac_cv_prog_CPP else ac_cv_prog_CPP=$CPP fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5 $as_echo "$CPP" >&6; } ac_preproc_ok=false for ac_c_preproc_warn_flag in '' yes do # Use a header file that comes with gcc, so configuring glibc # with a fresh cross-compiler works. # Prefer to if __STDC__ is defined, since # exists even on freestanding compilers. # On the NeXT, cc -E runs the code through the compiler's parser, # not just through cpp. "Syntax error" is here to catch this case. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #ifdef __STDC__ # include #else # include #endif Syntax error _ACEOF if ac_fn_c_try_cpp "$LINENO"; then : else # Broken: fails on valid input. continue fi rm -f conftest.err conftest.i conftest.$ac_ext # OK, works on sane cases. Now check whether nonexistent headers # can be detected and how. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if ac_fn_c_try_cpp "$LINENO"; then : # Broken: success on invalid input. continue else # Passes both tests. ac_preproc_ok=: break fi rm -f conftest.err conftest.i conftest.$ac_ext done # Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. rm -f conftest.i conftest.err conftest.$ac_ext if $ac_preproc_ok; then : else { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "C preprocessor \"$CPP\" fails sanity check See \`config.log' for more details" "$LINENO" 5 ; } fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu { $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5 $as_echo_n "checking for grep that handles long lines and -e... " >&6; } if test "${ac_cv_path_GREP+set}" = set; then : $as_echo_n "(cached) " >&6 else if test -z "$GREP"; then ac_path_GREP_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_prog in grep ggrep; do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext" { test -f "$ac_path_GREP" && $as_test_x "$ac_path_GREP"; } || continue # Check for GNU ac_path_GREP and select it if it is found. # Check for GNU $ac_path_GREP case `"$ac_path_GREP" --version 2>&1` in *GNU*) ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;; *) ac_count=0 $as_echo_n 0123456789 >"conftest.in" while : do cat "conftest.in" "conftest.in" >"conftest.tmp" mv "conftest.tmp" "conftest.in" cp "conftest.in" "conftest.nl" $as_echo 'GREP' >> "conftest.nl" "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break as_fn_arith $ac_count + 1 && ac_count=$as_val if test $ac_count -gt ${ac_path_GREP_max-0}; then # Best one so far, save it but keep looking for a better one ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_max=$ac_count fi # 10*(2^10) chars as input seems more than enough test $ac_count -gt 10 && break done rm -f conftest.in conftest.tmp conftest.nl conftest.out;; esac $ac_path_GREP_found && break 3 done done done IFS=$as_save_IFS if test -z "$ac_cv_path_GREP"; then as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 fi else ac_cv_path_GREP=$GREP fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5 $as_echo "$ac_cv_path_GREP" >&6; } GREP="$ac_cv_path_GREP" { $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5 $as_echo_n "checking for egrep... " >&6; } if test "${ac_cv_path_EGREP+set}" = set; then : $as_echo_n "(cached) " >&6 else if echo a | $GREP -E '(a|b)' >/dev/null 2>&1 then ac_cv_path_EGREP="$GREP -E" else if test -z "$EGREP"; then ac_path_EGREP_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_prog in egrep; do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext" { test -f "$ac_path_EGREP" && $as_test_x "$ac_path_EGREP"; } || continue # Check for GNU ac_path_EGREP and select it if it is found. # Check for GNU $ac_path_EGREP case `"$ac_path_EGREP" --version 2>&1` in *GNU*) ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;; *) ac_count=0 $as_echo_n 0123456789 >"conftest.in" while : do cat "conftest.in" "conftest.in" >"conftest.tmp" mv "conftest.tmp" "conftest.in" cp "conftest.in" "conftest.nl" $as_echo 'EGREP' >> "conftest.nl" "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break as_fn_arith $ac_count + 1 && ac_count=$as_val if test $ac_count -gt ${ac_path_EGREP_max-0}; then # Best one so far, save it but keep looking for a better one ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_max=$ac_count fi # 10*(2^10) chars as input seems more than enough test $ac_count -gt 10 && break done rm -f conftest.in conftest.tmp conftest.nl conftest.out;; esac $ac_path_EGREP_found && break 3 done done done IFS=$as_save_IFS if test -z "$ac_cv_path_EGREP"; then as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 fi else ac_cv_path_EGREP=$EGREP fi fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5 $as_echo "$ac_cv_path_EGREP" >&6; } EGREP="$ac_cv_path_EGREP" { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5 $as_echo_n "checking for ANSI C header files... " >&6; } if test "${ac_cv_header_stdc+set}" = set; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include #include int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_header_stdc=yes else ac_cv_header_stdc=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext if test $ac_cv_header_stdc = yes; then # SunOS 4.x string.h does not declare mem*, contrary to ANSI. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | $EGREP "memchr" >/dev/null 2>&1; then : else ac_cv_header_stdc=no fi rm -f conftest* fi if test $ac_cv_header_stdc = yes; then # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | $EGREP "free" >/dev/null 2>&1; then : else ac_cv_header_stdc=no fi rm -f conftest* fi if test $ac_cv_header_stdc = yes; then # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. if test "$cross_compiling" = yes; then : : else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #if ((' ' & 0x0FF) == 0x020) # define ISLOWER(c) ('a' <= (c) && (c) <= 'z') # define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) #else # define ISLOWER(c) \ (('a' <= (c) && (c) <= 'i') \ || ('j' <= (c) && (c) <= 'r') \ || ('s' <= (c) && (c) <= 'z')) # define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c)) #endif #define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) int main () { int i; for (i = 0; i < 256; i++) if (XOR (islower (i), ISLOWER (i)) || toupper (i) != TOUPPER (i)) return 2; return 0; } _ACEOF if ac_fn_c_try_run "$LINENO"; then : else ac_cv_header_stdc=no fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ conftest.$ac_objext conftest.beam conftest.$ac_ext fi fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5 $as_echo "$ac_cv_header_stdc" >&6; } if test $ac_cv_header_stdc = yes; then $as_echo "#define STDC_HEADERS 1" >>confdefs.h fi # On IRIX 5.3, sys/types and inttypes.h are conflicting. for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \ inttypes.h stdint.h unistd.h do : as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default " if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : cat >>confdefs.h <<_ACEOF #define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 _ACEOF fi done ac_fn_c_check_header_mongrel "$LINENO" "minix/config.h" "ac_cv_header_minix_config_h" "$ac_includes_default" if test "x$ac_cv_header_minix_config_h" = x""yes; then : MINIX=yes else MINIX= fi if test "$MINIX" = yes; then $as_echo "#define _POSIX_SOURCE 1" >>confdefs.h $as_echo "#define _POSIX_1_SOURCE 2" >>confdefs.h $as_echo "#define _MINIX 1" >>confdefs.h fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether it is safe to define __EXTENSIONS__" >&5 $as_echo_n "checking whether it is safe to define __EXTENSIONS__... " >&6; } if test "${ac_cv_safe_to_define___extensions__+set}" = set; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ # define __EXTENSIONS__ 1 $ac_includes_default int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_safe_to_define___extensions__=yes else ac_cv_safe_to_define___extensions__=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_safe_to_define___extensions__" >&5 $as_echo "$ac_cv_safe_to_define___extensions__" >&6; } test $ac_cv_safe_to_define___extensions__ = yes && $as_echo "#define __EXTENSIONS__ 1" >>confdefs.h $as_echo "#define _ALL_SOURCE 1" >>confdefs.h $as_echo "#define _GNU_SOURCE 1" >>confdefs.h $as_echo "#define _POSIX_PTHREAD_SEMANTICS 1" >>confdefs.h $as_echo "#define _TANDEM_SOURCE 1" >>confdefs.h # Check whether --with-datadir was given. if test "${with_datadir+set}" = set; then : withval=$with_datadir; if test "x$withval" != "xno" ; then $as_echo "#define HAVE_DATA_DIR /**/" >>confdefs.h cat >>confdefs.h <<_ACEOF #define DATA_DIR "$withval" _ACEOF MAKE_DATA_DIR=$withval fi fi # Check whether --with-helpdir was given. if test "${with_helpdir+set}" = set; then : withval=$with_helpdir; if test "x$withval" != "xno" ; then $as_echo "#define HAVE_HELP_DIR /**/" >>confdefs.h cat >>confdefs.h <<_ACEOF #define HELP_DIR "$withval" _ACEOF MAKE_HELP_DIR=$withval fi fi # Check whether --with-staticdatadir was given. if test "${with_staticdatadir+set}" = set; then : withval=$with_staticdatadir; if test "x$withval" != "xno" ; then $as_echo "#define HAVE_STATICDATA_DIR /**/" >>confdefs.h cat >>confdefs.h <<_ACEOF #define STATICDATA_DIR "$withval" _ACEOF MAKE_STATICDATA_DIR=$withval fi fi # Check whether --with-ssldir was given. if test "${with_ssldir+set}" = set; then : withval=$with_ssldir; if test "x$withval" != "xno" ; then ssl_dir="$withval" fi fi cat >>confdefs.h <<_ACEOF #define SSL_DIR "$ssl_dir" _ACEOF # Check whether --with-spooldir was given. if test "${with_spooldir+set}" = set; then : withval=$with_spooldir; if test "x$withval" != "xno" ; then $as_echo "#define HAVE_SPOOL_DIR /**/" >>confdefs.h cat >>confdefs.h <<_ACEOF #define SPOOL_DIR "$withval" _ACEOF MAKE_SPOOL_DIR=$withval fi fi # Check whether --with-sysconfdir was given. if test "${with_sysconfdir+set}" = set; then : withval=$with_sysconfdir; if test "x$withval" != "xno" ; then $as_echo "#define HAVE_ETC_DIR /**/" >>confdefs.h cat >>confdefs.h <<_ACEOF #define ETC_DIR "$withval" _ACEOF MAKE_ETC_DIR=$withval fi fi # Check whether --with-autosysconfdir was given. if test "${with_autosysconfdir+set}" = set; then : withval=$with_autosysconfdir; if test "x$withval" != "xno" ; then $as_echo "#define HAVE_AUTO_ETC_DIR /**/" >>confdefs.h cat >>confdefs.h <<_ACEOF #define AUTO_ETC_DIR "$withval" _ACEOF MAKE_AUTO_ETC_DIR=$withval fi fi # Check whether --with-utility-bindir was given. if test "${with_utility_bindir+set}" = set; then : withval=$with_utility_bindir; if test "x$withval" != "xno" ; then $as_echo "#define HAVE_UTILBIN_DIR /**/" >>confdefs.h cat >>confdefs.h <<_ACEOF #define UTILBIN_DIR "$withval" _ACEOF MAKE_UTILBIN_DIR=$withval fi fi # Check whether --with-rundir was given. if test "${with_rundir+set}" = set; then : withval=$with_rundir; if test "x$withval" != "xno" ; then $as_echo "#define HAVE_RUN_DIR /**/" >>confdefs.h cat >>confdefs.h <<_ACEOF #define RUN_DIR "$withval" _ACEOF MAKE_RUN_DIR=$withval fi fi cat >>confdefs.h <<_ACEOF #define EGD_POOL "/var/run/egd-pool" _ACEOF # Check whether --with-egdpool was given. if test "${with_egdpool+set}" = set; then : withval=$with_egdpool; if test "x$withval" != "xno" ; then cat >>confdefs.h <<_ACEOF #define EGD_POOL "$withval" _ACEOF fi fi # Check whether --with-docdir was given. if test "${with_docdir+set}" = set; then : withval=$with_docdir; if test "x$withval" != "xno" ; then MAKE_DOC_DIR=$withval fi fi # Check whether --with-localedir was given. if test "${with_localedir+set}" = set; then : withval=$with_localedir; if test "x$withval" != "xno" ; then localedir=$withval fi fi cat >>confdefs.h <<_ACEOF #define LOCALEDIR "$localedir" _ACEOF LOCALEDIR=$localedir saved_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $SERVER_LIBS" ac_fn_c_check_header_mongrel "$LINENO" "zlib.h" "ac_cv_header_zlib_h" "$ac_includes_default" if test "x$ac_cv_header_zlib_h" = x""yes; then : { $as_echo "$as_me:${as_lineno-$LINENO}: checking for zlibVersion in -lz" >&5 $as_echo_n "checking for zlibVersion in -lz... " >&6; } if test "${ac_cv_lib_z_zlibVersion+set}" = set; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lz $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char zlibVersion (); int main () { return zlibVersion (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_z_zlibVersion=yes else ac_cv_lib_z_zlibVersion=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_z_zlibVersion" >&5 $as_echo "$ac_cv_lib_z_zlibVersion" >&6; } if test "x$ac_cv_lib_z_zlibVersion" = x""yes; then : LIBS="-lz $LIBS $SERVER_LIBS" else as_fn_error $? "zlib was not found or is not usable. Please install zlib." "$LINENO" 5 fi else as_fn_error $? "zlib.h was not found or is not usable. Please install zlib." "$LINENO" 5 fi CFLAGS="$saved_CFLAGS" # Check whether --enable-iconv was given. if test "${enable_iconv+set}" = set; then : enableval=$enable_iconv; ok_iconv=no else ok_iconv=yes fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking Checking to see if your system supports iconv" >&5 $as_echo_n "checking Checking to see if your system supports iconv... " >&6; } if test "$cross_compiling" = yes; then : { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot run test program while cross compiling See \`config.log' for more details" "$LINENO" 5 ; } else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include main() { iconv_t ic = (iconv_t)(-1) ; ic = iconv_open("UTF-8", "us-ascii"); iconv_close(ic); exit(0); } _ACEOF if ac_fn_c_try_run "$LINENO"; then : ok_iconv=yes { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } else ok_iconv=no { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ conftest.$ac_objext conftest.beam conftest.$ac_ext fi if test "$ok_iconv" = no; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking Checking for an external libiconv" >&5 $as_echo_n "checking Checking for an external libiconv... " >&6; } OLD_LDFLAGS="$LDFLAGS" LDFLAGS="$LDFLAGS -liconv" if test "$cross_compiling" = yes; then : { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot run test program while cross compiling See \`config.log' for more details" "$LINENO" 5 ; } else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include main() { iconv_t ic = (iconv_t)(-1) ; ic = iconv_open("UTF-8", "us-ascii"); iconv_close(ic); } _ACEOF if ac_fn_c_try_run "$LINENO"; then : ok_iconv=yes { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } else ok_iconv=no LDFLAGS="$OLD_LDFLAGS" { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ conftest.$ac_objext conftest.beam conftest.$ac_ext fi fi if test "$ok_iconv" != "no"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: Citadel will be built with character set conversion." >&5 $as_echo "Citadel will be built with character set conversion." >&6; } $as_echo "#define HAVE_ICONV /**/" >>confdefs.h else { $as_echo "$as_me:${as_lineno-$LINENO}: result: Citadel will be built without character set conversion." >&5 $as_echo "Citadel will be built without character set conversion." >&6; } fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for libintl_bindtextdomain in -lintl" >&5 $as_echo_n "checking for libintl_bindtextdomain in -lintl... " >&6; } if test "${ac_cv_lib_intl_libintl_bindtextdomain+set}" = set; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lintl $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char libintl_bindtextdomain (); int main () { return libintl_bindtextdomain (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_intl_libintl_bindtextdomain=yes else ac_cv_lib_intl_libintl_bindtextdomain=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_intl_libintl_bindtextdomain" >&5 $as_echo "$ac_cv_lib_intl_libintl_bindtextdomain" >&6; } if test "x$ac_cv_lib_intl_libintl_bindtextdomain" = x""yes; then : LDFLAGS="$LDFLAGS -lintl" fi # Check whether --enable-pie was given. if test "${enable_pie+set}" = set; then : enableval=$enable_pie; fi # Check whether --with-pam was given. if test "${with_pam+set}" = set; then : withval=$with_pam; fi # Check whether --with-kthread was given. if test "${with_kthread+set}" = set; then : withval=$with_kthread; fi # Check whether --with-db was given. if test "${with_db+set}" = set; then : withval=$with_db; fi # Check whether --with-ssl was given. if test "${with_ssl+set}" = set; then : withval=$with_ssl; if test "x$withval" != "xno" ; then tryssldir=$withval fi fi # Check whether --with-with_ldap was given. if test "${with_with_ldap+set}" = set; then : withval=$with_with_ldap; fi # Check whether --with-with_gc was given. if test "${with_with_gc+set}" = set; then : withval=$with_with_gc; fi if test "x$with_db" != xno -a "x$with_db" != xyes -a "$with_db"; then db_dir="$with_db" with_db=yes else test -f /usr/local/lib/libdb.a -o -f /usr/local/lib/libdb.so \ -o -f /usr/local/lib/libdb4.a -o -f /usr/local/lib/libdb4.so \ && db_dir=/usr/local test -d /usr/local/BerkeleyDB.4.1 && db_dir=/usr/local/BerkeleyDB.4.1 test -d /usr/local/BerkeleyDB.4.2 && db_dir=/usr/local/BerkeleyDB.4.2 test -d /usr/local/BerkeleyDB.4.3 && db_dir=/usr/local/BerkeleyDB.4.3 test -d /usr/local/BerkeleyDB.4.4 && db_dir=/usr/local/BerkeleyDB.4.4 test -d /usr/local/BerkeleyDB.4.5 && db_dir=/usr/local/BerkeleyDB.4.5 fi ac_aux_dir= for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do if test -f "$ac_dir/install-sh"; then ac_aux_dir=$ac_dir ac_install_sh="$ac_aux_dir/install-sh -c" break elif test -f "$ac_dir/install.sh"; then ac_aux_dir=$ac_dir ac_install_sh="$ac_aux_dir/install.sh -c" break elif test -f "$ac_dir/shtool"; then ac_aux_dir=$ac_dir ac_install_sh="$ac_aux_dir/shtool install -c" break fi done if test -z "$ac_aux_dir"; then as_fn_error $? "cannot find install-sh, install.sh, or shtool in \"$srcdir\" \"$srcdir/..\" \"$srcdir/../..\"" "$LINENO" 5 fi # These three variables are undocumented and unsupported, # and are intended to be withdrawn in a future Autoconf release. # They can cause serious problems if a builder's source tree is in a directory # whose full name contains unusual characters. ac_config_guess="$SHELL $ac_aux_dir/config.guess" # Please don't use this var. ac_config_sub="$SHELL $ac_aux_dir/config.sub" # Please don't use this var. ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var. # Make sure we can run config.sub. $SHELL "$ac_aux_dir/config.sub" sun4 >/dev/null 2>&1 || as_fn_error $? "cannot run $SHELL $ac_aux_dir/config.sub" "$LINENO" 5 { $as_echo "$as_me:${as_lineno-$LINENO}: checking build system type" >&5 $as_echo_n "checking build system type... " >&6; } if test "${ac_cv_build+set}" = set; then : $as_echo_n "(cached) " >&6 else ac_build_alias=$build_alias test "x$ac_build_alias" = x && ac_build_alias=`$SHELL "$ac_aux_dir/config.guess"` test "x$ac_build_alias" = x && as_fn_error $? "cannot guess build type; you must specify one" "$LINENO" 5 ac_cv_build=`$SHELL "$ac_aux_dir/config.sub" $ac_build_alias` || as_fn_error $? "$SHELL $ac_aux_dir/config.sub $ac_build_alias failed" "$LINENO" 5 fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_build" >&5 $as_echo "$ac_cv_build" >&6; } case $ac_cv_build in *-*-*) ;; *) as_fn_error $? "invalid value of canonical build" "$LINENO" 5 ;; esac build=$ac_cv_build ac_save_IFS=$IFS; IFS='-' set x $ac_cv_build shift build_cpu=$1 build_vendor=$2 shift; shift # Remember, the first character of IFS is used to create $*, # except with old shells: build_os=$* IFS=$ac_save_IFS case $build_os in *\ *) build_os=`echo "$build_os" | sed 's/ /-/g'`;; esac { $as_echo "$as_me:${as_lineno-$LINENO}: checking host system type" >&5 $as_echo_n "checking host system type... " >&6; } if test "${ac_cv_host+set}" = set; then : $as_echo_n "(cached) " >&6 else if test "x$host_alias" = x; then ac_cv_host=$ac_cv_build else ac_cv_host=`$SHELL "$ac_aux_dir/config.sub" $host_alias` || as_fn_error $? "$SHELL $ac_aux_dir/config.sub $host_alias failed" "$LINENO" 5 fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_host" >&5 $as_echo "$ac_cv_host" >&6; } case $ac_cv_host in *-*-*) ;; *) as_fn_error $? "invalid value of canonical host" "$LINENO" 5 ;; esac host=$ac_cv_host ac_save_IFS=$IFS; IFS='-' set x $ac_cv_host shift host_cpu=$1 host_vendor=$2 shift; shift # Remember, the first character of IFS is used to create $*, # except with old shells: host_os=$* IFS=$ac_save_IFS case $host_os in *\ *) host_os=`echo "$host_os" | sed 's/ /-/g'`;; esac PTHREAD_DEFS=-D_REENTRANT { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to compile with POSIX threads" >&5 $as_echo_n "checking how to compile with POSIX threads... " >&6; } case "$host" in *-*-bsdi123*) test -z "$CC" -a -x /usr/bin/shlicc2 && CC=shlicc2 $as_echo "#define HAVE_NONREENTRANT_NETDB /**/" >>confdefs.h { $as_echo "$as_me:${as_lineno-$LINENO}: result: Old BSDI" >&5 $as_echo "Old BSDI" >&6; } ;; *-*-bsdi*) $as_echo "#define HAVE_NONREENTRANT_NETDB /**/" >>confdefs.h { $as_echo "$as_me:${as_lineno-$LINENO}: result: BSD/OS" >&5 $as_echo "BSD/OS" >&6; } ;; *-*-darwin*) $as_echo "#define HAVE_DARWIN /**/" >>confdefs.h { $as_echo "$as_me:${as_lineno-$LINENO}: result: Mac OS X" >&5 $as_echo "Mac OS X" >&6; } ;; alpha*-dec-osf*) test -z "$CC" && CC=cc LIBS="-lpthread -lexc $LIBS" check_pthread=no { $as_echo "$as_me:${as_lineno-$LINENO}: result: Tru64 or Digital UNIX" >&5 $as_echo "Tru64 or Digital UNIX" >&6; } ;; *-*-freebsd*) if test "$with_kthread" = yes; then LIBS="-kthread $LIBS" else LIBS="-pthread $LIBS" fi check_pthread=no PTHREAD_DEFS=-D_THREAD_SAFE { $as_echo "$as_me:${as_lineno-$LINENO}: result: FreeBSD" >&5 $as_echo "FreeBSD" >&6; } ;; *-*-openbsd*) LIBS="-pthread $LIBS" check_pthread=no PTHREAD_DEFS=-pthread { $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenBSD" >&5 $as_echo "OpenBSD" >&6; } ;; *-*-linux*) PTHREAD_DEFS="-D_REENTRANT -pthread" { $as_echo "$as_me:${as_lineno-$LINENO}: result: Linux" >&5 $as_echo "Linux" >&6; } ;; *-*-solaris*) PTHREAD_DEFS="-D_REENTRANT -D_PTHREADS" { $as_echo "$as_me:${as_lineno-$LINENO}: result: Solaris" >&5 $as_echo "Solaris" >&6; } ;; *-*-cygwin*) SERVER_LDFLAGS="-Wl,-subsystem,windows" { $as_echo "$as_me:${as_lineno-$LINENO}: result: Cygwin" >&5 $as_echo "Cygwin" >&6; } ;; *) { $as_echo "$as_me:${as_lineno-$LINENO}: result: default" >&5 $as_echo "default" >&6; } ;; esac ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. set dummy ${ac_tool_prefix}gcc; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if test "${ac_cv_prog_CC+set}" = set; then : $as_echo_n "(cached) " >&6 else if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_prog_CC="${ac_tool_prefix}gcc" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 $as_echo "$CC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi if test -z "$ac_cv_prog_CC"; then ac_ct_CC=$CC # Extract the first word of "gcc", so it can be a program name with args. set dummy gcc; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if test "${ac_cv_prog_ac_ct_CC+set}" = set; then : $as_echo_n "(cached) " >&6 else if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_prog_ac_ct_CC="gcc" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 $as_echo "$ac_ct_CC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi else CC="$ac_cv_prog_CC" fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. set dummy ${ac_tool_prefix}cc; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if test "${ac_cv_prog_CC+set}" = set; then : $as_echo_n "(cached) " >&6 else if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_prog_CC="${ac_tool_prefix}cc" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 $as_echo "$CC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi fi if test -z "$CC"; then # Extract the first word of "cc", so it can be a program name with args. set dummy cc; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if test "${ac_cv_prog_CC+set}" = set; then : $as_echo_n "(cached) " >&6 else if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else ac_prog_rejected=no as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then ac_prog_rejected=yes continue fi ac_cv_prog_CC="cc" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS if test $ac_prog_rejected = yes; then # We found a bogon in the path, so make sure we never use it. set dummy $ac_cv_prog_CC shift if test $# != 0; then # We chose a different compiler from the bogus one. # However, it has the same basename, so the bogon will be chosen # first if we set CC to just the basename; use the full file name. shift ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@" fi fi fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 $as_echo "$CC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then for ac_prog in cl.exe do # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. set dummy $ac_tool_prefix$ac_prog; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if test "${ac_cv_prog_CC+set}" = set; then : $as_echo_n "(cached) " >&6 else if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_prog_CC="$ac_tool_prefix$ac_prog" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 $as_echo "$CC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi test -n "$CC" && break done fi if test -z "$CC"; then ac_ct_CC=$CC for ac_prog in cl.exe do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if test "${ac_cv_prog_ac_ct_CC+set}" = set; then : $as_echo_n "(cached) " >&6 else if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_prog_ac_ct_CC="$ac_prog" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 $as_echo "$ac_ct_CC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi test -n "$ac_ct_CC" && break done if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 $as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi fi fi test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "no acceptable C compiler found in \$PATH See \`config.log' for more details" "$LINENO" 5 ; } # Provide some information about the compiler. $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 set X $ac_compile ac_compiler=$2 for ac_option in --version -v -V -qversion; do { { ac_try="$ac_compiler $ac_option >&5" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" $as_echo "$ac_try_echo"; } >&5 (eval "$ac_compiler $ac_option >&5") 2>conftest.err ac_status=$? if test -s conftest.err; then sed '10a\ ... rest of stderr output deleted ... 10q' conftest.err >conftest.er1 cat conftest.er1 >&5 fi rm -f conftest.er1 conftest.err $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } done { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5 $as_echo_n "checking whether we are using the GNU C compiler... " >&6; } if test "${ac_cv_c_compiler_gnu+set}" = set; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { #ifndef __GNUC__ choke me #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_compiler_gnu=yes else ac_compiler_gnu=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext ac_cv_c_compiler_gnu=$ac_compiler_gnu fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 $as_echo "$ac_cv_c_compiler_gnu" >&6; } if test $ac_compiler_gnu = yes; then GCC=yes else GCC= fi ac_test_CFLAGS=${CFLAGS+set} ac_save_CFLAGS=$CFLAGS { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 $as_echo_n "checking whether $CC accepts -g... " >&6; } if test "${ac_cv_prog_cc_g+set}" = set; then : $as_echo_n "(cached) " >&6 else ac_save_c_werror_flag=$ac_c_werror_flag ac_c_werror_flag=yes ac_cv_prog_cc_g=no CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_prog_cc_g=yes else CFLAGS="" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : else ac_c_werror_flag=$ac_save_c_werror_flag CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_prog_cc_g=yes fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext ac_c_werror_flag=$ac_save_c_werror_flag fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 $as_echo "$ac_cv_prog_cc_g" >&6; } if test "$ac_test_CFLAGS" = set; then CFLAGS=$ac_save_CFLAGS elif test $ac_cv_prog_cc_g = yes; then if test "$GCC" = yes; then CFLAGS="-g -O2" else CFLAGS="-g" fi else if test "$GCC" = yes; then CFLAGS="-O2" else CFLAGS= fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5 $as_echo_n "checking for $CC option to accept ISO C89... " >&6; } if test "${ac_cv_prog_cc_c89+set}" = set; then : $as_echo_n "(cached) " >&6 else ac_cv_prog_cc_c89=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include #include /* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ struct buf { int x; }; FILE * (*rcsopen) (struct buf *, struct stat *, int); static char *e (p, i) char **p; int i; { return p[i]; } static char *f (char * (*g) (char **, int), char **p, ...) { char *s; va_list v; va_start (v,p); s = g (p, va_arg (v,int)); va_end (v); return s; } /* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has function prototypes and stuff, but not '\xHH' hex character constants. These don't provoke an error unfortunately, instead are silently treated as 'x'. The following induces an error, until -std is added to get proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an array size at least. It's necessary to write '\x00'==0 to get something that's true only with -std. */ int osf4_cc_array ['\x00' == 0 ? 1 : -1]; /* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters inside strings and character constants. */ #define FOO(x) 'x' int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1]; int test (int i, double x); struct s1 {int (*f) (int a);}; struct s2 {int (*f) (double a);}; int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int); int argc; char **argv; int main () { return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]; ; return 0; } _ACEOF for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \ -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO"; then : ac_cv_prog_cc_c89=$ac_arg fi rm -f core conftest.err conftest.$ac_objext test "x$ac_cv_prog_cc_c89" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC fi # AC_CACHE_VAL case "x$ac_cv_prog_cc_c89" in x) { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 $as_echo "none needed" >&6; } ;; xno) { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 $as_echo "unsupported" >&6; } ;; *) CC="$CC $ac_cv_prog_cc_c89" { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 $as_echo "$ac_cv_prog_cc_c89" >&6; } ;; esac if test "x$ac_cv_prog_cc_c89" != xno; then : fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu if test "$GCC" = yes; then if test "$CC" = icc; then CFLAGS="$CFLAGS -w1" else case "$host" in *-*-solaris*|alpha*-dec-osf*) CFLAGS="$CFLAGS -Wall -Wcast-qual -Wcast-align -Wno-char-subscripts $PTHREAD_DEFS" ;; *) CFLAGS="$CFLAGS -Wall -Wcast-qual -Wcast-align -Wstrict-prototypes -Wno-strict-aliasing $PTHREAD_DEFS" ;; esac fi fi if test "x$enable_pie" = xyes; then save_CFLAGS="$CFLAGS" save_LDFLAGS="$LDFLAGS" CFLAGS="$CFLAGS -fpie" LDFLAGS="$LDFLAGS -pie -fpie" { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler accepts -pie -fpie" >&5 $as_echo_n "checking whether compiler accepts -pie -fpie... " >&6; } if test "${ac_cv_pie_fpie+set}" = set; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_pie_fpie=yes else ac_cv_pie_fpie=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_pie_fpie" >&5 $as_echo "$ac_cv_pie_fpie" >&6; } if test $ac_cv_pie_fpie = no; then CFLAGS="$save_CFLAGS" LDFLAGS="$save_LDFLAGS" fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to create dependancy checks" >&5 $as_echo_n "checking how to create dependancy checks... " >&6; } if test -n "`$CC -V 2>&1 |grep Sun`"; then DEPEND_FLAG=-xM; else DEPEND_FLAG=-M fi # Find a good install program. We prefer a C program (faster), # so one script is as good as another. But avoid the broken or # incompatible versions: # SysV /etc/install, /usr/sbin/install # SunOS /usr/etc/install # IRIX /sbin/install # AIX /bin/install # AmigaOS /C/install, which installs bootblocks on floppy discs # AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag # AFS /usr/afsws/bin/install, which mishandles nonexistent args # SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" # OS/2's system install, which has a completely different semantic # ./install, which can be erroneously created by make from ./install.sh. # Reject install programs that cannot install multiple files. { $as_echo "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5 $as_echo_n "checking for a BSD-compatible install... " >&6; } if test -z "$INSTALL"; then if test "${ac_cv_path_install+set}" = set; then : $as_echo_n "(cached) " >&6 else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. # Account for people who put trailing slashes in PATH elements. case $as_dir/ in #(( ./ | .// | /[cC]/* | \ /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \ ?:[\\/]os2[\\/]install[\\/]* | ?:[\\/]OS2[\\/]INSTALL[\\/]* | \ /usr/ucb/* ) ;; *) # OSF1 and SCO ODT 3.0 have their own names for install. # Don't use installbsd from OSF since it installs stuff as root # by default. for ac_prog in ginstall scoinst install; do for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_prog$ac_exec_ext" && $as_test_x "$as_dir/$ac_prog$ac_exec_ext"; }; then if test $ac_prog = install && grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # AIX install. It has an incompatible calling convention. : elif test $ac_prog = install && grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # program-specific install script used by HP pwplus--don't use. : else rm -rf conftest.one conftest.two conftest.dir echo one > conftest.one echo two > conftest.two mkdir conftest.dir if "$as_dir/$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir" && test -s conftest.one && test -s conftest.two && test -s conftest.dir/conftest.one && test -s conftest.dir/conftest.two then ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c" break 3 fi fi fi done done ;; esac done IFS=$as_save_IFS rm -rf conftest.one conftest.two conftest.dir fi if test "${ac_cv_path_install+set}" = set; then INSTALL=$ac_cv_path_install else # As a last resort, use the slow shell script. Don't cache a # value for INSTALL within a source directory, because that will # break other packages using the cache if that directory is # removed, or if the value is a relative name. INSTALL=$ac_install_sh fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5 $as_echo "$INSTALL" >&6; } # Use test -z because SunOS4 sh mishandles braces in ${var-val}. # It thinks the first close brace ends the variable substitution. test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}' test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' for ac_prog in 'bison -y' byacc do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if test "${ac_cv_prog_YACC+set}" = set; then : $as_echo_n "(cached) " >&6 else if test -n "$YACC"; then ac_cv_prog_YACC="$YACC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_prog_YACC="$ac_prog" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi YACC=$ac_cv_prog_YACC if test -n "$YACC"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $YACC" >&5 $as_echo "$YACC" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi test -n "$YACC" && break done test -n "$YACC" || YACC="yacc" # Extract the first word of "diff", so it can be a program name with args. set dummy diff; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if test "${ac_cv_path_DIFF+set}" = set; then : $as_echo_n "(cached) " >&6 else case $DIFF in [\\/]* | ?:[\\/]*) ac_cv_path_DIFF="$DIFF" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_path_DIFF="$as_dir/$ac_word$ac_exec_ext" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi DIFF=$ac_cv_path_DIFF if test -n "$DIFF"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DIFF" >&5 $as_echo "$DIFF" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi # Extract the first word of "patch", so it can be a program name with args. set dummy patch; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if test "${ac_cv_path_PATCH+set}" = set; then : $as_echo_n "(cached) " >&6 else case $PATCH in [\\/]* | ?:[\\/]*) ac_cv_path_PATCH="$PATCH" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_path_PATCH="$as_dir/$ac_word$ac_exec_ext" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi PATCH=$ac_cv_path_PATCH if test -n "$PATCH"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PATCH" >&5 $as_echo "$PATCH" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi missing_dir=`cd $ac_aux_dir && pwd` # expand $ac_aux_dir to an absolute path am_aux_dir=`cd $ac_aux_dir && pwd` if test x"${MISSING+set}" != xset; then case $am_aux_dir in *\ * | *\ *) MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;; *) MISSING="\${SHELL} $am_aux_dir/missing" ;; esac fi # Use eval to expand $SHELL if eval "$MISSING --run true"; then am_missing_run="$MISSING --run " else am_missing_run= { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: \`missing' script is too old or missing" >&5 $as_echo "$as_me: WARNING: \`missing' script is too old or missing" >&2;} fi AUTOCONF=${AUTOCONF-"${am_missing_run}autoconf"} ACLOCAL=${ACLOCAL-"${am_missing_run}aclocal"} # The cast to long int works around a bug in the HP C Compiler # version HP92453-01 B.11.11.23709.GP, which incorrectly rejects # declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'. # This bug is HP SR number 8606223364. { $as_echo "$as_me:${as_lineno-$LINENO}: checking size of char" >&5 $as_echo_n "checking size of char... " >&6; } if test "${ac_cv_sizeof_char+set}" = set; then : $as_echo_n "(cached) " >&6 else if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (char))" "ac_cv_sizeof_char" "$ac_includes_default"; then : else if test "$ac_cv_type_char" = yes; then { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error 77 "cannot compute sizeof (char) See \`config.log' for more details" "$LINENO" 5 ; } else ac_cv_sizeof_char=0 fi fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_char" >&5 $as_echo "$ac_cv_sizeof_char" >&6; } cat >>confdefs.h <<_ACEOF #define SIZEOF_CHAR $ac_cv_sizeof_char _ACEOF # The cast to long int works around a bug in the HP C Compiler # version HP92453-01 B.11.11.23709.GP, which incorrectly rejects # declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'. # This bug is HP SR number 8606223364. { $as_echo "$as_me:${as_lineno-$LINENO}: checking size of short" >&5 $as_echo_n "checking size of short... " >&6; } if test "${ac_cv_sizeof_short+set}" = set; then : $as_echo_n "(cached) " >&6 else if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (short))" "ac_cv_sizeof_short" "$ac_includes_default"; then : else if test "$ac_cv_type_short" = yes; then { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error 77 "cannot compute sizeof (short) See \`config.log' for more details" "$LINENO" 5 ; } else ac_cv_sizeof_short=0 fi fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_short" >&5 $as_echo "$ac_cv_sizeof_short" >&6; } cat >>confdefs.h <<_ACEOF #define SIZEOF_SHORT $ac_cv_sizeof_short _ACEOF # The cast to long int works around a bug in the HP C Compiler # version HP92453-01 B.11.11.23709.GP, which incorrectly rejects # declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'. # This bug is HP SR number 8606223364. { $as_echo "$as_me:${as_lineno-$LINENO}: checking size of int" >&5 $as_echo_n "checking size of int... " >&6; } if test "${ac_cv_sizeof_int+set}" = set; then : $as_echo_n "(cached) " >&6 else if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (int))" "ac_cv_sizeof_int" "$ac_includes_default"; then : else if test "$ac_cv_type_int" = yes; then { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error 77 "cannot compute sizeof (int) See \`config.log' for more details" "$LINENO" 5 ; } else ac_cv_sizeof_int=0 fi fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_int" >&5 $as_echo "$ac_cv_sizeof_int" >&6; } cat >>confdefs.h <<_ACEOF #define SIZEOF_INT $ac_cv_sizeof_int _ACEOF # The cast to long int works around a bug in the HP C Compiler # version HP92453-01 B.11.11.23709.GP, which incorrectly rejects # declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'. # This bug is HP SR number 8606223364. { $as_echo "$as_me:${as_lineno-$LINENO}: checking size of long" >&5 $as_echo_n "checking size of long... " >&6; } if test "${ac_cv_sizeof_long+set}" = set; then : $as_echo_n "(cached) " >&6 else if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (long))" "ac_cv_sizeof_long" "$ac_includes_default"; then : else if test "$ac_cv_type_long" = yes; then { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error 77 "cannot compute sizeof (long) See \`config.log' for more details" "$LINENO" 5 ; } else ac_cv_sizeof_long=0 fi fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_long" >&5 $as_echo "$ac_cv_sizeof_long" >&6; } cat >>confdefs.h <<_ACEOF #define SIZEOF_LONG $ac_cv_sizeof_long _ACEOF # The cast to long int works around a bug in the HP C Compiler # version HP92453-01 B.11.11.23709.GP, which incorrectly rejects # declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'. # This bug is HP SR number 8606223364. { $as_echo "$as_me:${as_lineno-$LINENO}: checking size of size_t" >&5 $as_echo_n "checking size of size_t... " >&6; } if test "${ac_cv_sizeof_size_t+set}" = set; then : $as_echo_n "(cached) " >&6 else if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (size_t))" "ac_cv_sizeof_size_t" "$ac_includes_default"; then : else if test "$ac_cv_type_size_t" = yes; then { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error 77 "cannot compute sizeof (size_t) See \`config.log' for more details" "$LINENO" 5 ; } else ac_cv_sizeof_size_t=0 fi fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_size_t" >&5 $as_echo "$ac_cv_sizeof_size_t" >&6; } cat >>confdefs.h <<_ACEOF #define SIZEOF_SIZE_T $ac_cv_sizeof_size_t _ACEOF # The cast to long int works around a bug in the HP C Compiler # version HP92453-01 B.11.11.23709.GP, which incorrectly rejects # declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'. # This bug is HP SR number 8606223364. { $as_echo "$as_me:${as_lineno-$LINENO}: checking size of loff_t" >&5 $as_echo_n "checking size of loff_t... " >&6; } if test "${ac_cv_sizeof_loff_t+set}" = set; then : $as_echo_n "(cached) " >&6 else if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (loff_t))" "ac_cv_sizeof_loff_t" "$ac_includes_default"; then : else if test "$ac_cv_type_loff_t" = yes; then { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error 77 "cannot compute sizeof (loff_t) See \`config.log' for more details" "$LINENO" 5 ; } else ac_cv_sizeof_loff_t=0 fi fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_loff_t" >&5 $as_echo "$ac_cv_sizeof_loff_t" >&6; } cat >>confdefs.h <<_ACEOF #define SIZEOF_LOFF_T $ac_cv_sizeof_loff_t _ACEOF for ac_func in crypt gethostbyname connect flock getpwnam_r getpwuid_r getloadavg do : as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" if eval test \"x\$"$as_ac_var"\" = x"yes"; then : cat >>confdefs.h <<_ACEOF #define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 _ACEOF fi done for ac_func in strftime_l uselocale gettext do : as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" if eval test \"x\$"$as_ac_var"\" = x"yes"; then : cat >>confdefs.h <<_ACEOF #define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 _ACEOF fi done if test "$ok_nls" != "no"; then # Extract the first word of "xgettext", so it can be a program name with args. set dummy xgettext; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if test "${ac_cv_prog_ok_xgettext+set}" = set; then : $as_echo_n "(cached) " >&6 else if test -n "$ok_xgettext"; then ac_cv_prog_ok_xgettext="$ok_xgettext" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_prog_ok_xgettext="yes" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_prog_ok_xgettext" && ac_cv_prog_ok_xgettext="no" fi fi ok_xgettext=$ac_cv_prog_ok_xgettext if test -n "$ok_xgettext"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ok_xgettext" >&5 $as_echo "$ok_xgettext" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi ok_nls=$ok_xgettext fi if test "$ok_nls" != "no"; then # Extract the first word of "msgmerge", so it can be a program name with args. set dummy msgmerge; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if test "${ac_cv_prog_ok_msgmerge+set}" = set; then : $as_echo_n "(cached) " >&6 else if test -n "$ok_msgmerge"; then ac_cv_prog_ok_msgmerge="$ok_msgmerge" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_prog_ok_msgmerge="yes" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_prog_ok_msgmerge" && ac_cv_prog_ok_msgmerge="no" fi fi ok_msgmerge=$ac_cv_prog_ok_msgmerge if test -n "$ok_msgmerge"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ok_msgmerge" >&5 $as_echo "$ok_msgmerge" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi ok_nls=$ok_msgmerge fi if test "$ok_nls" != "no"; then # Extract the first word of "msgfmt", so it can be a program name with args. set dummy msgfmt; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 $as_echo_n "checking for $ac_word... " >&6; } if test "${ac_cv_prog_ok_msgfmt+set}" = set; then : $as_echo_n "(cached) " >&6 else if test -n "$ok_msgfmt"; then ac_cv_prog_ok_msgfmt="$ok_msgfmt" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_prog_ok_msgfmt="yes" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_prog_ok_msgfmt" && ac_cv_prog_ok_msgfmt="no" fi fi ok_msgfmt=$ac_cv_prog_ok_msgfmt if test -n "$ok_msgfmt"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ok_msgfmt" >&5 $as_echo "$ok_msgfmt" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } fi ok_nls=$ok_msgfmt fi if test "$ok_nls" != "no"; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: citadel will be built with national language support." >&5 $as_echo "citadel will be built with national language support." >&6; } $as_echo "#define ENABLE_NLS /**/" >>confdefs.h PROG_SUBDIRS="$PROG_SUBDIRS po/citadel-setup" else { $as_echo "$as_me:${as_lineno-$LINENO}: result: citadel will be built without national language support." >&5 $as_echo "citadel will be built without national language support." >&6; } fi # Check whether --with-backtrace was given. if test "${with_backtrace+set}" = set; then : withval=$with_backtrace; if test "x$withval" != "xno" ; then CFLAGS="$CFLAGS -rdynamic " LDFLAGS="$LDFLAGS -rdynamic " SERVER_LDFLAGS="$SERVER_LDFLAGS -rdynamic " for ac_func in backtrace do : ac_fn_c_check_func "$LINENO" "backtrace" "ac_cv_func_backtrace" if test "x$ac_cv_func_backtrace" = x""yes; then : cat >>confdefs.h <<_ACEOF #define HAVE_BACKTRACE 1 _ACEOF fi done fi fi # Check whether --with-gprof was given. if test "${with_gprof+set}" = set; then : withval=$with_gprof; if test "x$withval" != "xno" ; then CFLAGS="$CFLAGS -pg " LDFLAGS="$LDFLAGS -pg " SERVER_LDFLAGS="$SERVER_LDFLAGS -pg " fi fi if test "$ac_cv_func_gethostbyname" = no; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking for gethostbyname in -lnsl" >&5 $as_echo_n "checking for gethostbyname in -lnsl... " >&6; } if test "${ac_cv_lib_nsl_gethostbyname+set}" = set; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lnsl $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char gethostbyname (); int main () { return gethostbyname (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_nsl_gethostbyname=yes else ac_cv_lib_nsl_gethostbyname=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nsl_gethostbyname" >&5 $as_echo "$ac_cv_lib_nsl_gethostbyname" >&6; } if test "x$ac_cv_lib_nsl_gethostbyname" = x""yes; then : cat >>confdefs.h <<_ACEOF #define HAVE_LIBNSL 1 _ACEOF LIBS="-lnsl $LIBS" fi fi if test "$ac_cv_func_connect" = no; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking for connect in -lsocket" >&5 $as_echo_n "checking for connect in -lsocket... " >&6; } if test "${ac_cv_lib_socket_connect+set}" = set; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lsocket $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char connect (); int main () { return connect (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_socket_connect=yes else ac_cv_lib_socket_connect=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_socket_connect" >&5 $as_echo "$ac_cv_lib_socket_connect" >&6; } if test "x$ac_cv_lib_socket_connect" = x""yes; then : cat >>confdefs.h <<_ACEOF #define HAVE_LIBSOCKET 1 _ACEOF LIBS="-lsocket $LIBS" fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for sched_yield in -lrt" >&5 $as_echo_n "checking for sched_yield in -lrt... " >&6; } if test "${ac_cv_lib_rt_sched_yield+set}" = set; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lrt $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char sched_yield (); int main () { return sched_yield (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_rt_sched_yield=yes else ac_cv_lib_rt_sched_yield=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_rt_sched_yield" >&5 $as_echo "$ac_cv_lib_rt_sched_yield" >&6; } if test "x$ac_cv_lib_rt_sched_yield" = x""yes; then : cat >>confdefs.h <<_ACEOF #define HAVE_LIBRT 1 _ACEOF LIBS="-lrt $LIBS" fi save_LIBS=$LIBS { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_start in -lpam" >&5 $as_echo_n "checking for pam_start in -lpam... " >&6; } if test "${ac_cv_lib_pam_pam_start+set}" = set; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lpam $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char pam_start (); int main () { return pam_start (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_pam_pam_start=yes else ac_cv_lib_pam_pam_start=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pam_pam_start" >&5 $as_echo "$ac_cv_lib_pam_pam_start" >&6; } if test "x$ac_cv_lib_pam_pam_start" = x""yes; then : chkpwd_LIBS="-lpam $chkpwd_LIBS" LIBS="-lpam $LIBS" fi for ac_func in pam_start do : ac_fn_c_check_func "$LINENO" "pam_start" "ac_cv_func_pam_start" if test "x$ac_cv_func_pam_start" = x""yes; then : cat >>confdefs.h <<_ACEOF #define HAVE_PAM_START 1 _ACEOF fi done test "$enable_chkpwd" != no && LIBS=$save_LIBS if test "$ac_cv_func_pam_start" = no -o "$with_pam" != yes; then save_LIBS=$LIBS { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing getspnam" >&5 $as_echo_n "checking for library containing getspnam... " >&6; } if test "${ac_cv_search_getspnam+set}" = set; then : $as_echo_n "(cached) " >&6 else ac_func_search_save_LIBS=$LIBS cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char getspnam (); int main () { return getspnam (); ; return 0; } _ACEOF for ac_lib in '' shadow; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi if ac_fn_c_try_link "$LINENO"; then : ac_cv_search_getspnam=$ac_res fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext if test "${ac_cv_search_getspnam+set}" = set; then : break fi done if test "${ac_cv_search_getspnam+set}" = set; then : else ac_cv_search_getspnam=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_getspnam" >&5 $as_echo "$ac_cv_search_getspnam" >&6; } ac_res=$ac_cv_search_getspnam if test "$ac_res" != no; then : test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" if test "$ac_cv_search_getspnam" = -lshadow; then chkpwd_LIBS="-lshadow $chkpwd_LIBS" fi fi test "$enable_chkpwd" != no && LIBS=$save_LIBS if test "$ac_cv_func_crypt" = no; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking for crypt in -lcrypt" >&5 $as_echo_n "checking for crypt in -lcrypt... " >&6; } if test "${ac_cv_lib_crypt_crypt+set}" = set; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lcrypt $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char crypt (); int main () { return crypt (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_crypt_crypt=yes else ac_cv_lib_crypt_crypt=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_crypt_crypt" >&5 $as_echo "$ac_cv_lib_crypt_crypt" >&6; } if test "x$ac_cv_lib_crypt_crypt" = x""yes; then : chkpwd_LIBS="-lcrypt $chkpwd_LIBS" test "$enable_chkpwd" = no && \ LIBS="-lcrypt $LIBS" fi fi fi for ac_func in vw_printw wcolor_set resizeterm wresize do : as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" if eval test \"x\$"$as_ac_var"\" = x"yes"; then : cat >>confdefs.h <<_ACEOF #define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 _ACEOF fi done if test "$check_pthread" != no; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pthread_create in -lpthread" >&5 $as_echo_n "checking for pthread_create in -lpthread... " >&6; } if test "${ac_cv_lib_pthread_pthread_create+set}" = set; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lpthread $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char pthread_create (); int main () { return pthread_create (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_pthread_pthread_create=yes else ac_cv_lib_pthread_pthread_create=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pthread_pthread_create" >&5 $as_echo "$ac_cv_lib_pthread_pthread_create" >&6; } if test "x$ac_cv_lib_pthread_pthread_create" = x""yes; then : cat >>confdefs.h <<_ACEOF #define HAVE_LIBPTHREAD 1 _ACEOF LIBS="-lpthread $LIBS" fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pthread_create in -lpthreads" >&5 $as_echo_n "checking for pthread_create in -lpthreads... " >&6; } if test "${ac_cv_lib_pthreads_pthread_create+set}" = set; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lpthreads $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char pthread_create (); int main () { return pthread_create (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_pthreads_pthread_create=yes else ac_cv_lib_pthreads_pthread_create=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pthreads_pthread_create" >&5 $as_echo "$ac_cv_lib_pthreads_pthread_create" >&6; } if test "x$ac_cv_lib_pthreads_pthread_create" = x""yes; then : cat >>confdefs.h <<_ACEOF #define HAVE_LIBPTHREADS 1 _ACEOF LIBS="-lpthreads $LIBS" fi fi test -d /usr/kerberos/include && CPPFLAGS="$CPPFLAGS -I/usr/kerberos/include" ac_fn_c_check_header_mongrel "$LINENO" "libical/ical.h" "ac_cv_header_libical_ical_h" "$ac_includes_default" if test "x$ac_cv_header_libical_ical_h" = x""yes; then : { $as_echo "$as_me:${as_lineno-$LINENO}: checking for icaltimezone_set_tzid_prefix in -lical" >&5 $as_echo_n "checking for icaltimezone_set_tzid_prefix in -lical... " >&6; } if test "${ac_cv_lib_ical_icaltimezone_set_tzid_prefix+set}" = set; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lical $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char icaltimezone_set_tzid_prefix (); int main () { return icaltimezone_set_tzid_prefix (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_ical_icaltimezone_set_tzid_prefix=yes else ac_cv_lib_ical_icaltimezone_set_tzid_prefix=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ical_icaltimezone_set_tzid_prefix" >&5 $as_echo "$ac_cv_lib_ical_icaltimezone_set_tzid_prefix" >&6; } if test "x$ac_cv_lib_ical_icaltimezone_set_tzid_prefix" = x""yes; then : SERVER_LIBS="-lical $SERVER_LIBS" else as_fn_error $? "libical was not found and is required. More info: http://www.citadel.org/doku.php/installation:start" "$LINENO" 5 fi else as_fn_error $? "libical/ical.h was not found and is required. More info: http://www.citadel.org/doku.php/installation:start" "$LINENO" 5 fi ac_fn_c_check_header_mongrel "$LINENO" "sieve2.h" "ac_cv_header_sieve2_h" "$ac_includes_default" if test "x$ac_cv_header_sieve2_h" = x""yes; then : { $as_echo "$as_me:${as_lineno-$LINENO}: checking for sieve2_license in -lsieve" >&5 $as_echo_n "checking for sieve2_license in -lsieve... " >&6; } if test "${ac_cv_lib_sieve_sieve2_license+set}" = set; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lsieve $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char sieve2_license (); int main () { return sieve2_license (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_sieve_sieve2_license=yes else ac_cv_lib_sieve_sieve2_license=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_sieve_sieve2_license" >&5 $as_echo "$ac_cv_lib_sieve_sieve2_license" >&6; } if test "x$ac_cv_lib_sieve_sieve2_license" = x""yes; then : SERVER_LIBS="-lsieve $SERVER_LIBS" else as_fn_error $? "libsieve was not found and is required. More info: http://www.citadel.org/doku.php/installation:start" "$LINENO" 5 fi else as_fn_error $? "sieve2.h was not found and is required. More info: http://www.citadel.org/doku.php/installation:start" "$LINENO" 5 fi saved_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $SERVER_LIBS" ac_fn_c_check_header_mongrel "$LINENO" "libcitadel.h" "ac_cv_header_libcitadel_h" "$ac_includes_default" if test "x$ac_cv_header_libcitadel_h" = x""yes; then : { $as_echo "$as_me:${as_lineno-$LINENO}: checking for libcitadel_version_string in -lcitadel" >&5 $as_echo_n "checking for libcitadel_version_string in -lcitadel... " >&6; } if test "${ac_cv_lib_citadel_libcitadel_version_string+set}" = set; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lcitadel $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char libcitadel_version_string (); int main () { return libcitadel_version_string (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_citadel_libcitadel_version_string=yes else ac_cv_lib_citadel_libcitadel_version_string=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_citadel_libcitadel_version_string" >&5 $as_echo "$ac_cv_lib_citadel_libcitadel_version_string" >&6; } if test "x$ac_cv_lib_citadel_libcitadel_version_string" = x""yes; then : LIBS="-lcitadel $LIBS $SERVER_LIBS" chkpwd_LIBS="-lcitadel $chkpwd_LIBS" else as_fn_error $? "libcitadel was not found or is not usable. Please install libcitadel." "$LINENO" 5 fi else as_fn_error $? "libcitadel.h was not found or is not usable. Please install libcitadel." "$LINENO" 5 fi CFLAGS="$saved_CFLAGS" { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ares_parse_mx_reply in -lcares" >&5 $as_echo_n "checking for ares_parse_mx_reply in -lcares... " >&6; } if test "${ac_cv_lib_cares_ares_parse_mx_reply+set}" = set; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lcares $SOCKET_LIBS $NSL_LIBS $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char ares_parse_mx_reply (); int main () { return ares_parse_mx_reply (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_cares_ares_parse_mx_reply=yes else ac_cv_lib_cares_ares_parse_mx_reply=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_cares_ares_parse_mx_reply" >&5 $as_echo "$ac_cv_lib_cares_ares_parse_mx_reply" >&6; } if test "x$ac_cv_lib_cares_ares_parse_mx_reply" = x""yes; then : C_ARES_LIBS=-lcares $as_echo "#define HAVE_C_ARES 1" >>confdefs.h have_good_c_ares=yes fi saved_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $SERVER_LIBS" ac_fn_c_check_header_mongrel "$LINENO" "ares.h" "ac_cv_header_ares_h" "$ac_includes_default" if test "x$ac_cv_header_ares_h" = x""yes; then : { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ares_parse_mx_reply in -lcares" >&5 $as_echo_n "checking for ares_parse_mx_reply in -lcares... " >&6; } if test "${ac_cv_lib_cares_ares_parse_mx_reply+set}" = set; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lcares $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char ares_parse_mx_reply (); int main () { return ares_parse_mx_reply (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_cares_ares_parse_mx_reply=yes else ac_cv_lib_cares_ares_parse_mx_reply=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_cares_ares_parse_mx_reply" >&5 $as_echo "$ac_cv_lib_cares_ares_parse_mx_reply" >&6; } if test "x$ac_cv_lib_cares_ares_parse_mx_reply" = x""yes; then : LIBS="-lcares $LIBS $SERVER_LIBS" else as_fn_error $? "libc-ares was not found or is not usable. Please install libc-ares." "$LINENO" 5 fi else as_fn_error $? "ares.h was not found or is not usable. Please install libc-ares." "$LINENO" 5 fi CFLAGS="$saved_CFLAGS" saved_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $SERVER_LIBS" ac_fn_c_check_header_mongrel "$LINENO" "ev.h" "ac_cv_header_ev_h" "$ac_includes_default" if test "x$ac_cv_header_ev_h" = x""yes; then : cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main () { ev_cleanup abort_by_shutdown; struct ev_loop *event_base; ev_cleanup_start(event_base, &abort_by_shutdown); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : LIBS="-lev -lm $LIBS $SERVER_LIBS" else as_fn_error $? "libev was not found or is not usable. Please install libev." "$LINENO" 5 fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext else as_fn_error $? "ev.h was not found or is not usable. Please install libev." "$LINENO" 5 fi CFLAGS="$saved_CFLAGS" # The big search for OpenSSL if test "$with_ssl" != "no"; then saved_LIBS="$LIBS" saved_LDFLAGS="$LDFLAGS" saved_CFLAGS="$CFLAGS" if test "x$prefix" != "xNONE"; then tryssldir="$tryssldir $prefix" fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for OpenSSL" >&5 $as_echo_n "checking for OpenSSL... " >&6; } if test "${ac_cv_openssldir+set}" = set; then : $as_echo_n "(cached) " >&6 else for ssldir in $tryssldir "" /usr /usr/local/openssl /usr/lib/openssl /usr/local/ssl /usr/lib/ssl /usr/local /usr/pkg /opt /opt/openssl ; do CFLAGS="$saved_CFLAGS" LDFLAGS="$saved_LDFLAGS" LIBS="$saved_LIBS -lssl -lcrypto" # Skip directories if they don't exist if test ! -z "$ssldir" -a ! -d "$ssldir" ; then continue; fi if test ! -z "$ssldir" -a "x$ssldir" != "x/usr"; then # Try to use $ssldir/lib if it exists, otherwise # $ssldir if test -d "$ssldir/lib" ; then LDFLAGS="-L$ssldir/lib $saved_LDFLAGS" if test ! -z "$need_dash_r" ; then LDFLAGS="-R$ssldir/lib $LDFLAGS" fi else LDFLAGS="-L$ssldir $saved_LDFLAGS" if test ! -z "$need_dash_r" ; then LDFLAGS="-R$ssldir $LDFLAGS" fi fi # Try to use $ssldir/include if it exists, otherwise # $ssldir if test -d "$ssldir/include" ; then CFLAGS="-I$ssldir/include $saved_CFLAGS" else CFLAGS="-I$ssldir $saved_CFLAGS" fi fi # Basic test to check for compatible version and correct linking # *does not* test for RSA - that comes later. if test "$cross_compiling" = yes; then : { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 $as_echo "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot run test program while cross compiling See \`config.log' for more details" "$LINENO" 5 ; } else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main(void) { char a[2048]; memset(a, 0, sizeof(a)); RAND_add(a, sizeof(a), sizeof(a)); return(RAND_status() <= 0); } _ACEOF if ac_fn_c_try_run "$LINENO"; then : found_crypto=1 break; fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ conftest.$ac_objext conftest.beam conftest.$ac_ext fi if test ! -z "$found_crypto" ; then break; fi done if test -z "$ssldir" ; then ssldir="(system)" fi if test ! -z "$found_crypto" ; then ac_cv_openssldir=$ssldir else ac_cv_openssldir="no" fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_openssldir" >&5 $as_echo "$ac_cv_openssldir" >&6; } LIBS="$saved_LIBS" LDFLAGS="$saved_LDFLAGS" CFLAGS="$saved_CFLAGS" if test "x$ac_cv_openssldir" != "xno" ; then $as_echo "#define HAVE_OPENSSL /**/" >>confdefs.h LIBS="-lssl -lcrypto $LIBS" ssldir=$ac_cv_openssldir if test ! -z "$ssldir" -a "x$ssldir" != "x/usr" -a "x$ssldir" != "x(system)"; then # Try to use $ssldir/lib if it exists, otherwise # $ssldir if test -d "$ssldir/lib" ; then LDFLAGS="-L$ssldir/lib $saved_LDFLAGS" if test ! -z "$need_dash_r" ; then LDFLAGS="-R$ssldir/lib $LDFLAGS" fi else LDFLAGS="-L$ssldir $saved_LDFLAGS" if test ! -z "$need_dash_r" ; then LDFLAGS="-R$ssldir $LDFLAGS" fi fi # Try to use $ssldir/include if it exists, otherwise # $ssldir if test -d "$ssldir/include" ; then CFLAGS="-I$ssldir/include $saved_CFLAGS" else CFLAGS="-I$ssldir $saved_CFLAGS" fi fi fi fi if test "x$with_db" != xno; then test "$db_dir" && LDFLAGS="$LDFLAGS -L$db_dir/lib" dblib="" if test -d "$db_dir/include/db4"; then CPPFLAGS="$CPPFLAGS -I$db_dir/include/db4" dblib="db4" elif test "$db_dir"; then CPPFLAGS="$CPPFLAGS -I$db_dir/include" elif test -d /usr/include/db4; then CPPFLAGS="$CPPFLAGS -I/usr/include/db4" dblib="db4" fi for lib in db db-4.1 db-4 db4 do as_ac_tr_db=`$as_echo "ac_cv_db_lib_${lib}" | $as_tr_sh` bogo_saved_LIBS="$LIBS" LIBS="$LIBS -l$lib" { $as_echo "$as_me:${as_lineno-$LINENO}: checking for db_create in -l${lib}" >&5 $as_echo_n "checking for db_create in -l${lib}... " >&6; } if eval "test \"\${$as_ac_tr_db+set}\"" = set; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main () { int foo=db_create((void *)0, (void *) 0, 0 ) ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : eval "$as_ac_tr_db=yes" else eval "$as_ac_tr_db=no" fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext fi eval ac_res=\$$as_ac_tr_db { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 $as_echo "$ac_res" >&6; } if test `eval 'as_val=${'$as_ac_tr_db'};$as_echo "$as_val"'` = yes; then : DATABASE=database.c LIBS="$bogo_saved_LIBS" SERVER_LIBS="$SERVER_LIBS -l$lib" db=yes else LIBS="$bogo_saved_LIBS" db=no fi test "$db" = "yes" && break done if test "$db" = "no"; then as_fn_error $? "Can not locate a suitable Berkeley DB library. Use --with-db=PATH to specify the path" "$LINENO" 5 fi fi if test "x$with_ldap" != xno ; then for ac_header in ldap.h do : ac_fn_c_check_header_mongrel "$LINENO" "ldap.h" "ac_cv_header_ldap_h" "$ac_includes_default" if test "x$ac_cv_header_ldap_h" = x""yes; then : cat >>confdefs.h <<_ACEOF #define HAVE_LDAP_H 1 _ACEOF { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ldap_initialize in -lldap" >&5 $as_echo_n "checking for ldap_initialize in -lldap... " >&6; } if test "${ac_cv_lib_ldap_ldap_initialize+set}" = set; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lldap $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char ldap_initialize (); int main () { return ldap_initialize (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_ldap_ldap_initialize=yes else ac_cv_lib_ldap_ldap_initialize=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ldap_ldap_initialize" >&5 $as_echo "$ac_cv_lib_ldap_ldap_initialize" >&6; } if test "x$ac_cv_lib_ldap_ldap_initialize" = x""yes; then : ok_ldap=yes fi fi done fi if test "x$ok_ldap" = xyes ; then SERVER_LIBS="-lldap $SERVER_LIBS" $as_echo "#define HAVE_LDAP /**/" >>confdefs.h fi ac_fn_c_check_header_mongrel "$LINENO" "expat.h" "ac_cv_header_expat_h" "$ac_includes_default" if test "x$ac_cv_header_expat_h" = x""yes; then : { $as_echo "$as_me:${as_lineno-$LINENO}: checking for XML_ParserCreateNS in -lexpat" >&5 $as_echo_n "checking for XML_ParserCreateNS in -lexpat... " >&6; } if test "${ac_cv_lib_expat_XML_ParserCreateNS+set}" = set; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lexpat $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char XML_ParserCreateNS (); int main () { return XML_ParserCreateNS (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_expat_XML_ParserCreateNS=yes else ac_cv_lib_expat_XML_ParserCreateNS=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_expat_XML_ParserCreateNS" >&5 $as_echo "$ac_cv_lib_expat_XML_ParserCreateNS" >&6; } if test "x$ac_cv_lib_expat_XML_ParserCreateNS" = x""yes; then : SERVER_LIBS="-lexpat $SERVER_LIBS" else as_fn_error $? "The Expat XML parser was not found and is required. More info: http://www.citadel.org/doku.php/installation:start" "$LINENO" 5 fi else as_fn_error $? "expat.h was not found and is required. More info: http://www.citadel.org/doku.php/installation:start" "$LINENO" 5 fi ac_fn_c_check_header_mongrel "$LINENO" "curl/curl.h" "ac_cv_header_curl_curl_h" "$ac_includes_default" if test "x$ac_cv_header_curl_curl_h" = x""yes; then : { $as_echo "$as_me:${as_lineno-$LINENO}: checking for curl_version in -lcurl" >&5 $as_echo_n "checking for curl_version in -lcurl... " >&6; } if test "${ac_cv_lib_curl_curl_version+set}" = set; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lcurl $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char curl_version (); int main () { return curl_version (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_curl_curl_version=yes else ac_cv_lib_curl_curl_version=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_curl_curl_version" >&5 $as_echo "$ac_cv_lib_curl_curl_version" >&6; } if test "x$ac_cv_lib_curl_curl_version" = x""yes; then : SERVER_LIBS="-lcurl $SERVER_LIBS" else as_fn_error $? "libcurl was not found and is required. More info: http://www.citadel.org/doku.php/installation:start" "$LINENO" 5 fi else as_fn_error $? "curl/curl.h was not found and is required. More info: http://www.citadel.org/doku.php/installation:start" "$LINENO" 5 fi ac_header_dirent=no for ac_hdr in dirent.h sys/ndir.h sys/dir.h ndir.h; do as_ac_Header=`$as_echo "ac_cv_header_dirent_$ac_hdr" | $as_tr_sh` { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_hdr that defines DIR" >&5 $as_echo_n "checking for $ac_hdr that defines DIR... " >&6; } if eval "test \"\${$as_ac_Header+set}\"" = set; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include <$ac_hdr> int main () { if ((DIR *) 0) return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : eval "$as_ac_Header=yes" else eval "$as_ac_Header=no" fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi eval ac_res=\$$as_ac_Header { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 $as_echo "$ac_res" >&6; } if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : cat >>confdefs.h <<_ACEOF #define `$as_echo "HAVE_$ac_hdr" | $as_tr_cpp` 1 _ACEOF ac_header_dirent=$ac_hdr; break fi done # Two versions of opendir et al. are in -ldir and -lx on SCO Xenix. if test $ac_header_dirent = dirent.h; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing opendir" >&5 $as_echo_n "checking for library containing opendir... " >&6; } if test "${ac_cv_search_opendir+set}" = set; then : $as_echo_n "(cached) " >&6 else ac_func_search_save_LIBS=$LIBS cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char opendir (); int main () { return opendir (); ; return 0; } _ACEOF for ac_lib in '' dir; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi if ac_fn_c_try_link "$LINENO"; then : ac_cv_search_opendir=$ac_res fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext if test "${ac_cv_search_opendir+set}" = set; then : break fi done if test "${ac_cv_search_opendir+set}" = set; then : else ac_cv_search_opendir=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_opendir" >&5 $as_echo "$ac_cv_search_opendir" >&6; } ac_res=$ac_cv_search_opendir if test "$ac_res" != no; then : test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" fi else { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing opendir" >&5 $as_echo_n "checking for library containing opendir... " >&6; } if test "${ac_cv_search_opendir+set}" = set; then : $as_echo_n "(cached) " >&6 else ac_func_search_save_LIBS=$LIBS cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char opendir (); int main () { return opendir (); ; return 0; } _ACEOF for ac_lib in '' x; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi if ac_fn_c_try_link "$LINENO"; then : ac_cv_search_opendir=$ac_res fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext if test "${ac_cv_search_opendir+set}" = set; then : break fi done if test "${ac_cv_search_opendir+set}" = set; then : else ac_cv_search_opendir=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_opendir" >&5 $as_echo "$ac_cv_search_opendir" >&6; } ac_res=$ac_cv_search_opendir if test "$ac_res" != no; then : test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5 $as_echo_n "checking for ANSI C header files... " >&6; } if test "${ac_cv_header_stdc+set}" = set; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include #include int main () { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_header_stdc=yes else ac_cv_header_stdc=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext if test $ac_cv_header_stdc = yes; then # SunOS 4.x string.h does not declare mem*, contrary to ANSI. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | $EGREP "memchr" >/dev/null 2>&1; then : else ac_cv_header_stdc=no fi rm -f conftest* fi if test $ac_cv_header_stdc = yes; then # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | $EGREP "free" >/dev/null 2>&1; then : else ac_cv_header_stdc=no fi rm -f conftest* fi if test $ac_cv_header_stdc = yes; then # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. if test "$cross_compiling" = yes; then : : else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #if ((' ' & 0x0FF) == 0x020) # define ISLOWER(c) ('a' <= (c) && (c) <= 'z') # define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) #else # define ISLOWER(c) \ (('a' <= (c) && (c) <= 'i') \ || ('j' <= (c) && (c) <= 'r') \ || ('s' <= (c) && (c) <= 'z')) # define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c)) #endif #define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) int main () { int i; for (i = 0; i < 256; i++) if (XOR (islower (i), ISLOWER (i)) || toupper (i) != TOUPPER (i)) return 2; return 0; } _ACEOF if ac_fn_c_try_run "$LINENO"; then : else ac_cv_header_stdc=no fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ conftest.$ac_objext conftest.beam conftest.$ac_ext fi fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5 $as_echo "$ac_cv_header_stdc" >&6; } if test $ac_cv_header_stdc = yes; then $as_echo "#define STDC_HEADERS 1" >>confdefs.h fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for sys/wait.h that is POSIX.1 compatible" >&5 $as_echo_n "checking for sys/wait.h that is POSIX.1 compatible... " >&6; } if test "${ac_cv_header_sys_wait_h+set}" = set; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #ifndef WEXITSTATUS # define WEXITSTATUS(stat_val) ((unsigned int) (stat_val) >> 8) #endif #ifndef WIFEXITED # define WIFEXITED(stat_val) (((stat_val) & 255) == 0) #endif int main () { int s; wait (&s); s = WIFEXITED (s) ? WEXITSTATUS (s) : 1; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_header_sys_wait_h=yes else ac_cv_header_sys_wait_h=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_sys_wait_h" >&5 $as_echo "$ac_cv_header_sys_wait_h" >&6; } if test $ac_cv_header_sys_wait_h = yes; then $as_echo "#define HAVE_SYS_WAIT_H 1" >>confdefs.h fi for ac_header in dl.h fcntl.h limits.h malloc.h termios.h sys/ioctl.h sys/select.h sys/stat.h sys/time.h sys/prctl.h syslog.h unistd.h utmp.h utmpx.h paths.h db.h db4/db.h pthread.h netinet/in.h arpa/nameser.h arpa/nameser_compat.h syscall.h sys/syscall.h do : as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : cat >>confdefs.h <<_ACEOF #define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 _ACEOF fi done ac_fn_c_check_header_compile "$LINENO" "resolv.h" "ac_cv_header_resolv_h" "#ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_NETINET_IN_H #include #endif #ifdef HAVE_ARPA_NAMESER_H #include #endif " if test "x$ac_cv_header_resolv_h" = x""yes; then : $as_echo "#define HAVE_RESOLV_H /**/" >>confdefs.h fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for an ANSI C-conforming const" >&5 $as_echo_n "checking for an ANSI C-conforming const... " >&6; } if test "${ac_cv_c_const+set}" = set; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main () { /* FIXME: Include the comments suggested by Paul. */ #ifndef __cplusplus /* Ultrix mips cc rejects this. */ typedef int charset[2]; const charset cs; /* SunOS 4.1.1 cc rejects this. */ char const *const *pcpcc; char **ppc; /* NEC SVR4.0.2 mips cc rejects this. */ struct point {int x, y;}; static struct point const zero = {0,0}; /* AIX XL C 1.02.0.0 rejects this. It does not let you subtract one const X* pointer from another in an arm of an if-expression whose if-part is not a constant expression */ const char *g = "string"; pcpcc = &g + (g ? g-g : 0); /* HPUX 7.0 cc rejects these. */ ++pcpcc; ppc = (char**) pcpcc; pcpcc = (char const *const *) ppc; { /* SCO 3.2v4 cc rejects this. */ char *t; char const *s = 0 ? (char *) 0 : (char const *) 0; *t++ = 0; if (s) return 0; } { /* Someone thinks the Sun supposedly-ANSI compiler will reject this. */ int x[] = {25, 17}; const int *foo = &x[0]; ++foo; } { /* Sun SC1.0 ANSI compiler rejects this -- but not the above. */ typedef const int *iptr; iptr p = 0; ++p; } { /* AIX XL C 1.02.0.0 rejects this saying "k.c", line 2.27: 1506-025 (S) Operand must be a modifiable lvalue. */ struct s { int j; const int *ap[3]; }; struct s *b; b->j = 5; } { /* ULTRIX-32 V3.1 (Rev 9) vcc rejects this */ const int foo = 10; if (!foo) return 0; } return !cs[0] && !zero.x; #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_c_const=yes else ac_cv_c_const=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_const" >&5 $as_echo "$ac_cv_c_const" >&6; } if test $ac_cv_c_const = no; then $as_echo "#define const /**/" >>confdefs.h fi ac_fn_c_check_type "$LINENO" "pid_t" "ac_cv_type_pid_t" "$ac_includes_default" if test "x$ac_cv_type_pid_t" = x""yes; then : else cat >>confdefs.h <<_ACEOF #define pid_t int _ACEOF fi ac_fn_c_check_type "$LINENO" "size_t" "ac_cv_type_size_t" "$ac_includes_default" if test "x$ac_cv_type_size_t" = x""yes; then : else cat >>confdefs.h <<_ACEOF #define size_t unsigned int _ACEOF fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether time.h and sys/time.h may both be included" >&5 $as_echo_n "checking whether time.h and sys/time.h may both be included... " >&6; } if test "${ac_cv_header_time+set}" = set; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include int main () { if ((struct tm *) 0) return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_header_time=yes else ac_cv_header_time=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_time" >&5 $as_echo "$ac_cv_header_time" >&6; } if test $ac_cv_header_time = yes; then $as_echo "#define TIME_WITH_SYS_TIME 1" >>confdefs.h fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether struct tm is in sys/time.h or time.h" >&5 $as_echo_n "checking whether struct tm is in sys/time.h or time.h... " >&6; } if test "${ac_cv_struct_tm+set}" = set; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main () { struct tm tm; int *p = &tm.tm_sec; return !p; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_struct_tm=time.h else ac_cv_struct_tm=sys/time.h fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_struct_tm" >&5 $as_echo "$ac_cv_struct_tm" >&6; } if test $ac_cv_struct_tm = sys/time.h; then $as_echo "#define TM_IN_SYS_TIME 1" >>confdefs.h fi ac_fn_c_check_member "$LINENO" "struct tm" "tm_gmtoff" "ac_cv_member_struct_tm_tm_gmtoff" "#include #include <$ac_cv_struct_tm> " if test "x$ac_cv_member_struct_tm_tm_gmtoff" = x""yes; then : cat >>confdefs.h <<_ACEOF #define HAVE_STRUCT_TM_TM_GMTOFF 1 _ACEOF fi if test "$ac_cv_member_struct_tm_tm_gmtoff" != yes; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking for timezone" >&5 $as_echo_n "checking for timezone... " >&6; } if test "${ac_cv_var_timezone+set}" = set; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main () { printf("%ld", (long)timezone); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_var_timezone=yes else ac_cv_var_timezone=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_var_timezone" >&5 $as_echo "$ac_cv_var_timezone" >&6; } if test $ac_cv_var_timezone = yes; then $as_echo "#define HAVE_TIMEZONE 1" >>confdefs.h fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ut_type in struct utmp" >&5 $as_echo_n "checking for ut_type in struct utmp... " >&6; } if test "${ac_cv_struct_ut_type+set}" = set; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main () { struct utmp ut; ut.ut_type; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_struct_ut_type=yes else ac_cv_struct_ut_type=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_struct_ut_type" >&5 $as_echo "$ac_cv_struct_ut_type" >&6; } if test $ac_cv_struct_ut_type = yes; then $as_echo "#define HAVE_UT_TYPE /**/" >>confdefs.h fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for call semantics from getpwuid_r" >&5 $as_echo_n "checking for call semantics from getpwuid_r... " >&6; } if test "${ac_cv_call_getpwuid_r+set}" = set; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main () { struct passwd pw, *pwp; char pwbuf[64]; uid_t uid; getpwuid_r(uid, &pw, pwbuf, sizeof(pwbuf), &pwp); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_call_getpwuid_r=yes else ac_cv_call_getpwuid_r=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_call_getpwuid_r" >&5 $as_echo "$ac_cv_call_getpwuid_r" >&6; } if test $ac_cv_call_getpwuid_r = no; then $as_echo "#define SOLARIS_GETPWUID /**/" >>confdefs.h $as_echo "#define F_UID_T \"%ld\"" >>confdefs.h $as_echo "#define F_PID_T \"%ld\"" >>confdefs.h $as_echo "#define F_XPID_T \"%lx\"" >>confdefs.h else $as_echo "#define F_UID_T \"%d\"" >>confdefs.h $as_echo "#define F_PID_T \"%d\"" >>confdefs.h $as_echo "#define F_XPID_T \"%x\"" >>confdefs.h fi case "`uname -a`" in OpenBSD*) echo "we don't need to check for resolv on openbsd" ;; FreeBSD*) echo "we don't need to check for resolv on freeBSD" ;; *) test -f /usr/local/lib/libresolv.a && LDFLAGS="$LDFLAGS -L/usr/local/lib" { $as_echo "$as_me:${as_lineno-$LINENO}: checking for res_query in -lresolv" >&5 $as_echo_n "checking for res_query in -lresolv... " >&6; } if test "${ac_cv_lib_resolv_res_query+set}" = set; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS LIBS="-lresolv $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char res_query (); int main () { return res_query (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_lib_resolv_res_query=yes else ac_cv_lib_resolv_res_query=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_resolv_res_query" >&5 $as_echo "$ac_cv_lib_resolv_res_query" >&6; } if test "x$ac_cv_lib_resolv_res_query" = x""yes; then : RESOLV="$RESOLV -lresolv" else { $as_echo "$as_me:${as_lineno-$LINENO}: checking for res_query in -lresolv (with resolv.h if present)" >&5 $as_echo_n "checking for res_query in -lresolv (with resolv.h if present)... " >&6; } saved_libs="$LIBS" LIBS="-lresolv $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #ifdef HAVE_RESOLV_H #include #endif int main () { res_query(0,0,0,0,0) ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } have_res_query=yes else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } as_fn_error $? "libresolv was not found. Citadel requires the resolver library." "$LINENO" 5 fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext fi ;; esac { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ut_host in struct utmp" >&5 $as_echo_n "checking for ut_host in struct utmp... " >&6; } if test "${ac_cv_struct_ut_host+set}" = set; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main () { struct utmp ut; ut.ut_host; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_struct_ut_host=yes else ac_cv_struct_ut_host=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_struct_ut_host" >&5 $as_echo "$ac_cv_struct_ut_host" >&6; } if test $ac_cv_struct_ut_host = yes; then $as_echo "#define HAVE_UT_HOST /**/" >>confdefs.h fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether getpgrp requires zero arguments" >&5 $as_echo_n "checking whether getpgrp requires zero arguments... " >&6; } if test "${ac_cv_func_getpgrp_void+set}" = set; then : $as_echo_n "(cached) " >&6 else # Use it with a single arg. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_includes_default int main () { getpgrp (0); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_func_getpgrp_void=no else ac_cv_func_getpgrp_void=yes fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_func_getpgrp_void" >&5 $as_echo "$ac_cv_func_getpgrp_void" >&6; } if test $ac_cv_func_getpgrp_void = yes; then $as_echo "#define GETPGRP_VOID 1" >>confdefs.h fi if test $ac_cv_c_compiler_gnu = yes; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC needs -traditional" >&5 $as_echo_n "checking whether $CC needs -traditional... " >&6; } if test "${ac_cv_prog_gcc_traditional+set}" = set; then : $as_echo_n "(cached) " >&6 else ac_pattern="Autoconf.*'x'" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include Autoconf TIOCGETP _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | $EGREP "$ac_pattern" >/dev/null 2>&1; then : ac_cv_prog_gcc_traditional=yes else ac_cv_prog_gcc_traditional=no fi rm -f conftest* if test $ac_cv_prog_gcc_traditional = no; then cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include Autoconf TCGETA _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | $EGREP "$ac_pattern" >/dev/null 2>&1; then : ac_cv_prog_gcc_traditional=yes fi rm -f conftest* fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_gcc_traditional" >&5 $as_echo "$ac_cv_prog_gcc_traditional" >&6; } if test $ac_cv_prog_gcc_traditional = yes; then CC="$CC -traditional" fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking return type of signal handlers" >&5 $as_echo_n "checking return type of signal handlers... " >&6; } if test "${ac_cv_type_signal+set}" = set; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main () { return *(signal (0, 0)) (0) == 1; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : ac_cv_type_signal=int else ac_cv_type_signal=void fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_type_signal" >&5 $as_echo "$ac_cv_type_signal" >&6; } cat >>confdefs.h <<_ACEOF #define RETSIGTYPE $ac_cv_type_signal _ACEOF for ac_func in vprintf do : ac_fn_c_check_func "$LINENO" "vprintf" "ac_cv_func_vprintf" if test "x$ac_cv_func_vprintf" = x""yes; then : cat >>confdefs.h <<_ACEOF #define HAVE_VPRINTF 1 _ACEOF ac_fn_c_check_func "$LINENO" "_doprnt" "ac_cv_func__doprnt" if test "x$ac_cv_func__doprnt" = x""yes; then : $as_echo "#define HAVE_DOPRNT 1" >>confdefs.h fi fi done for ac_func in getspnam getutxline mkdir mkfifo mktime rmdir select socket strerror strcasecmp strncasecmp do : as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" if eval test \"x\$"$as_ac_var"\" = x"yes"; then : cat >>confdefs.h <<_ACEOF #define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 _ACEOF fi done { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pthread_cancel" >&5 $as_echo_n "checking for pthread_cancel... " >&6; } if test "${ac_cv_func_pthread_cancel+set}" = set; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main () { pthread_t thread; /* The GNU C library defines this for functions which it implements to always fail with ENOSYS. Some functions are actually named something starting with __ and the normal name is an alias. */ #if defined (__stub_pthread_cancel) || defined (__stub___pthread_cancel) choke me #else pthread_cancel(thread); #endif ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_func_pthread_cancel=yes else ac_cv_func_pthread_cancel=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_func_pthread_cancel" >&5 $as_echo "$ac_cv_func_pthread_cancel" >&6; } if test "$ac_cv_func_pthread_cancel" = yes; then $as_echo "#define HAVE_PTHREAD_CANCEL /**/" >>confdefs.h fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pthread_create" >&5 $as_echo_n "checking for pthread_create... " >&6; } if test "${ac_cv_func_pthread_create+set}" = set; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main () { /* The GNU C library defines this for functions which it implements to always fail with ENOSYS. Some functions are actually named something starting with __ and the normal name is an alias. */ #if defined (__stub_pthread_cancel) || defined (__stub___pthread_cancel) choke me #else pthread_create(NULL, NULL, NULL, NULL); #endif ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO"; then : ac_cv_func_pthread_create=yes else ac_cv_func_pthread_create=no fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_func_pthread_create" >&5 $as_echo "$ac_cv_func_pthread_create" >&6; } if test "$ac_cv_func_pthread_create" = yes; then test "$DATABASE" && TARGETS="server utils" fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking the weather" >&5 $as_echo_n "checking the weather... " >&6; } if test "${ac_cv_weather+set}" = set; then : $as_echo_n "(cached) " >&6 else sleep 1 echo $ECHO_N "opening your window... $ECHO_C" >&6 sleep 2 month=`date | cut -f 2 -d ' '` case $month in Dec | Jan | Feb) ac_cv_weather="it's cold!" ;; Mar | Apr) ac_cv_weather="it's wet!" ;; Jul | Aug) ac_cv_weather="it's hot!" ;; Oct | Nov) ac_cv_weather="it's cool" ;; May | Jun | Sep | *) ac_cv_weather="it's fine" ;; esac fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_weather" >&5 $as_echo "$ac_cv_weather" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: checking under the bed" >&5 $as_echo_n "checking under the bed... " >&6; } if test "${ac_cv_under_the_bed+set}" = set; then : $as_echo_n "(cached) " >&6 else number=`date | cut -c 19` case $number in 7) ac_cv_under_the_bed="lunatics and monsters found" ;; *) ac_cv_under_the_bed="dust bunnies found" ;; esac fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_under_the_bed" >&5 $as_echo "$ac_cv_under_the_bed" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: checking for struct ucred" >&5 $as_echo_n "checking for struct ucred... " >&6; } if test "${mpd_cv_have_struct_ucred+set}" = set; then : $as_echo_n "(cached) " >&6 else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main () { struct ucred cred; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : mpd_cv_have_struct_ucred=yes else mpd_cv_have_struct_ucred=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext if test x$mpd_cv_have_struct_ucred = xno; then # glibc 2.8 forces _GNU_SOURCE on us cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #define _GNU_SOURCE #include int main () { struct ucred cred; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : mpd_cv_have_struct_ucred=yes else mpd_cv_have_struct_ucred=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext if test x$mpd_cv_have_struct_ucred = xyes; then # :( MPD_CFLAGS="$MPD_CFLAGS -D_GNU_SOURCE" fi fi fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $mpd_cv_have_struct_ucred" >&5 $as_echo "$mpd_cv_have_struct_ucred" >&6; } if test x$mpd_cv_have_struct_ucred = xyes; then $as_echo "#define HAVE_STRUCT_UCRED 1" >>confdefs.h fi ac_config_files="$ac_config_files Makefile" ac_config_files="$ac_config_files database_cleanup.sh po/citadel-setup/Makefile" cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure # tests run on this system so they can be shared between configure # scripts and configure runs, see configure's option --config-cache. # It is not useful on other systems. If it contains results you don't # want to keep, you may remove or edit it. # # config.status only pays attention to the cache file if you give it # the --recheck option to rerun configure. # # `ac_cv_env_foo' variables (set or unset) will be overridden when # loading this file, other *unset* `ac_cv_foo' will be assigned the # following values. _ACEOF # The following way of writing the cache mishandles newlines in values, # but we know of no workaround that is simple, portable, and efficient. # So, we kill variables containing newlines. # Ultrix sh set writes to stderr and can't be redirected directly, # and sets the high bit in the cache file unless we assign to the vars. ( for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space=' '; set) 2>&1` in #( *${as_nl}ac_space=\ *) # `set' does not quote correctly, so add quotes: double-quote # substitution turns \\\\ into \\, and sed turns \\ into \. sed -n \ "s/'/'\\\\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" ;; #( *) # `set' quotes correctly as required by POSIX, so do not add quotes. sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) | sed ' /^ac_cv_env_/b end t clear :clear s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/ t end s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ :end' >>confcache if diff "$cache_file" confcache >/dev/null 2>&1; then :; else if test -w "$cache_file"; then test "x$cache_file" != "x/dev/null" && { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 $as_echo "$as_me: updating cache $cache_file" >&6;} cat confcache >$cache_file else { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 $as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} fi fi rm -f confcache test "x$prefix" = xNONE && prefix=$ac_default_prefix # Let make expand exec_prefix. test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' DEFS=-DHAVE_CONFIG_H ac_libobjs= ac_ltlibobjs= U= for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue # 1. Remove the extension, and $U if already installed. ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' ac_i=`$as_echo "$ac_i" | sed "$ac_script"` # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR # will be set to the directory where LIBOBJS objects are built. as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' done LIBOBJS=$ac_libobjs LTLIBOBJS=$ac_ltlibobjs : ${CONFIG_STATUS=./config.status} ac_write_fail=0 ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files $CONFIG_STATUS" { $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 $as_echo "$as_me: creating $CONFIG_STATUS" >&6;} as_write_fail=0 cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 #! $SHELL # Generated by $as_me. # Run this file to recreate the current configuration. # Compiler output produced by configure, useful for debugging # configure, is in config.log if it exists. debug=false ac_cs_recheck=false ac_cs_silent=false SHELL=\${CONFIG_SHELL-$SHELL} export SHELL _ASEOF cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi as_nl=' ' export as_nl # Printing a long string crashes Solaris 7 /usr/bin/printf. as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo # Prefer a ksh shell builtin over an external printf program on Solaris, # but without wasting forks for bash or zsh. if test -z "$BASH_VERSION$ZSH_VERSION" \ && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='print -r --' as_echo_n='print -rn --' elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then as_echo='printf %s\n' as_echo_n='printf %s' else if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' as_echo_n='/usr/ucb/echo -n' else as_echo_body='eval expr "X$1" : "X\\(.*\\)"' as_echo_n_body='eval arg=$1; case $arg in #( *"$as_nl"*) expr "X$arg" : "X\\(.*\\)$as_nl"; arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; esac; expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" ' export as_echo_n_body as_echo_n='sh -c $as_echo_n_body as_echo' fi export as_echo_body as_echo='sh -c $as_echo_body as_echo' fi # The user is always right. if test "${PATH_SEPARATOR+set}" != set; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # IFS # We need space, tab and new line, in precisely that order. Quoting is # there to prevent editors from complaining about space-tab. # (If _AS_PATH_WALK were called with IFS unset, it would disable word # splitting by setting IFS to empty value.) IFS=" "" $as_nl" # Find who we are. Look in the path if we contain no directory separator. case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # Unset variables that we do not need and which cause bugs (e.g. in # pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" # suppresses any "Segmentation fault" message there. '((' could # trigger a bug in pdksh 5.2.14. for as_var in BASH_ENV ENV MAIL MAILPATH do eval test x\${$as_var+set} = xset \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done PS1='$ ' PS2='> ' PS4='+ ' # NLS nuisances. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # CDPATH. (unset CDPATH) >/dev/null 2>&1 && unset CDPATH # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi $as_echo "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : eval 'as_fn_append () { eval $1+=\$2 }' else as_fn_append () { eval $1=\$$1\$2 } fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : eval 'as_fn_arith () { as_val=$(( $* )) }' else as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } fi # as_fn_arith if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || $as_echo X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. xy) ECHO_C='\c';; *) echo `echo ksh88 bug on AIX 6.1` > /dev/null ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. # In both cases, we have to default to `cp -p'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -p' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -p' fi else as_ln_s='cp -p' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi if test -x / >/dev/null 2>&1; then as_test_x='test -x' else if ls -dL / >/dev/null 2>&1; then as_ls_L_option=L else as_ls_L_option= fi as_test_x=' eval sh -c '\'' if test -d "$1"; then test -d "$1/."; else case $1 in #( -*)set "./$1";; esac; case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in #(( ???[sx]*):;;*)false;;esac;fi '\'' sh ' fi as_executable_p=$as_test_x # Sed expression to map a string onto a valid CPP name. as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" # Sed expression to map a string onto a valid variable name. as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" exec 6>&1 ## ----------------------------------- ## ## Main body of $CONFIG_STATUS script. ## ## ----------------------------------- ## _ASEOF test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Save the log message, to keep $0 and so on meaningful, and to # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" This file was extended by Citadel $as_me 9.01, which was generated by GNU Autoconf 2.67. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS CONFIG_LINKS = $CONFIG_LINKS CONFIG_COMMANDS = $CONFIG_COMMANDS $ $0 $@ on `(hostname || uname -n) 2>/dev/null | sed 1q` " _ACEOF case $ac_config_files in *" "*) set x $ac_config_files; shift; ac_config_files=$*;; esac case $ac_config_headers in *" "*) set x $ac_config_headers; shift; ac_config_headers=$*;; esac cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 # Files that config.status was made for. config_files="$ac_config_files" config_headers="$ac_config_headers" _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 ac_cs_usage="\ \`$as_me' instantiates files and other configuration actions from templates according to the current configuration. Unless the files and actions are specified as TAGs, all are instantiated by default. Usage: $0 [OPTION]... [TAG]... -h, --help print this help, then exit -V, --version print version number and configuration settings, then exit --config print configuration, then exit -q, --quiet, --silent do not print progress messages -d, --debug don't remove temporary files --recheck update $as_me by reconfiguring in the same conditions --file=FILE[:TEMPLATE] instantiate the configuration file FILE --header=FILE[:TEMPLATE] instantiate the configuration header FILE Configuration files: $config_files Configuration headers: $config_headers Report bugs to ." _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ Citadel config.status 9.01 configured by $0, generated by GNU Autoconf 2.67, with options \\"\$ac_cs_config\\" Copyright (C) 2010 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." ac_pwd='$ac_pwd' srcdir='$srcdir' INSTALL='$INSTALL' test -n "\$AWK" || AWK=awk _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # The default lists apply if the user does not specify any file. ac_need_defaults=: while test $# != 0 do case $1 in --*=?*) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` ac_shift=: ;; --*=) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg= ac_shift=: ;; *) ac_option=$1 ac_optarg=$2 ac_shift=shift ;; esac case $ac_option in # Handling of the options. -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) ac_cs_recheck=: ;; --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) $as_echo "$ac_cs_version"; exit ;; --config | --confi | --conf | --con | --co | --c ) $as_echo "$ac_cs_config"; exit ;; --debug | --debu | --deb | --de | --d | -d ) debug=: ;; --file | --fil | --fi | --f ) $ac_shift case $ac_optarg in *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; '') as_fn_error $? "missing file argument" ;; esac as_fn_append CONFIG_FILES " '$ac_optarg'" ac_need_defaults=false;; --header | --heade | --head | --hea ) $ac_shift case $ac_optarg in *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; esac as_fn_append CONFIG_HEADERS " '$ac_optarg'" ac_need_defaults=false;; --he | --h) # Conflict between --help and --header as_fn_error $? "ambiguous option: \`$1' Try \`$0 --help' for more information.";; --help | --hel | -h ) $as_echo "$ac_cs_usage"; exit ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil | --si | --s) ac_cs_silent=: ;; # This is an error. -*) as_fn_error $? "unrecognized option: \`$1' Try \`$0 --help' for more information." ;; *) as_fn_append ac_config_targets " $1" ac_need_defaults=false ;; esac shift done ac_configure_extra_args= if $ac_cs_silent; then exec 6>/dev/null ac_configure_extra_args="$ac_configure_extra_args --silent" fi _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 if \$ac_cs_recheck; then set X '$SHELL' '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion shift \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 CONFIG_SHELL='$SHELL' export CONFIG_SHELL exec "\$@" fi _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 exec 5>>config.log { echo sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX ## Running $as_me. ## _ASBOX $as_echo "$ac_log" } >&5 _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Handling of arguments. for ac_config_target in $ac_config_targets do case $ac_config_target in "sysdep.h") CONFIG_HEADERS="$CONFIG_HEADERS sysdep.h" ;; "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; "database_cleanup.sh") CONFIG_FILES="$CONFIG_FILES database_cleanup.sh" ;; "po/citadel-setup/Makefile") CONFIG_FILES="$CONFIG_FILES po/citadel-setup/Makefile" ;; *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5 ;; esac done # If the user did not use the arguments to specify the items to instantiate, # then the envvar interface is used. Set only those that are not. # We use the long form for the default assignment because of an extremely # bizarre bug on SunOS 4.1.3. if $ac_need_defaults; then test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files test "${CONFIG_HEADERS+set}" = set || CONFIG_HEADERS=$config_headers fi # Have a temporary directory for convenience. Make it in the build tree # simply because there is no reason against having it here, and in addition, # creating and moving files from /tmp can sometimes cause problems. # Hook for its removal unless debugging. # Note that there is a small window in which the directory will not be cleaned: # after its creation but before its name has been assigned to `$tmp'. $debug || { tmp= trap 'exit_status=$? { test -z "$tmp" || test ! -d "$tmp" || rm -fr "$tmp"; } && exit $exit_status ' 0 trap 'as_fn_exit 1' 1 2 13 15 } # Create a (secure) tmp directory for tmp files. { tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" } || { tmp=./conf$$-$RANDOM (umask 077 && mkdir "$tmp") } || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 # Set up the scripts for CONFIG_FILES section. # No need to generate them if there are no CONFIG_FILES. # This happens for instance with `./config.status config.h'. if test -n "$CONFIG_FILES"; then ac_cr=`echo X | tr X '\015'` # On cygwin, bash can eat \r inside `` if the user requested igncr. # But we know of no other shell where ac_cr would be empty at this # point, so we can use a bashism as a fallback. if test "x$ac_cr" = x; then eval ac_cr=\$\'\\r\' fi ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then ac_cs_awk_cr='\\r' else ac_cs_awk_cr=$ac_cr fi echo 'BEGIN {' >"$tmp/subs1.awk" && _ACEOF { echo "cat >conf$$subs.awk <<_ACEOF" && echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && echo "_ACEOF" } >conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` ac_delim='%!_!# ' for ac_last_try in false false false false false :; do . ./conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` if test $ac_delim_n = $ac_delim_num; then break elif $ac_last_try; then as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 else ac_delim="$ac_delim!$ac_delim _$ac_delim!! " fi done rm -f conf$$subs.sh cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 cat >>"\$tmp/subs1.awk" <<\\_ACAWK && _ACEOF sed -n ' h s/^/S["/; s/!.*/"]=/ p g s/^[^!]*!// :repl t repl s/'"$ac_delim"'$// t delim :nl h s/\(.\{148\}\)..*/\1/ t more1 s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ p n b repl :more1 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t nl :delim h s/\(.\{148\}\)..*/\1/ t more2 s/["\\]/\\&/g; s/^/"/; s/$/"/ p b :more2 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t delim ' >$CONFIG_STATUS || ac_write_fail=1 rm -f conf$$subs.awk cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 _ACAWK cat >>"\$tmp/subs1.awk" <<_ACAWK && for (key in S) S_is_set[key] = 1 FS = "" } { line = $ 0 nfields = split(line, field, "@") substed = 0 len = length(field[1]) for (i = 2; i < nfields; i++) { key = field[i] keylen = length(key) if (S_is_set[key]) { value = S[key] line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) len += length(value) + length(field[++i]) substed = 1 } else len += 1 + keylen } print line } _ACAWK _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" else cat fi < "$tmp/subs1.awk" > "$tmp/subs.awk" \ || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 _ACEOF # VPATH may cause trouble with some makes, so we remove sole $(srcdir), # ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and # trailing colons and then remove the whole line if VPATH becomes empty # (actually we leave an empty line to preserve line numbers). if test "x$srcdir" = x.; then ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ h s/// s/^/:/ s/[ ]*$/:/ s/:\$(srcdir):/:/g s/:\${srcdir}:/:/g s/:@srcdir@:/:/g s/^:*// s/:*$// x s/\(=[ ]*\).*/\1/ G s/\n// s/^[^=]*=[ ]*$// }' fi cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 fi # test -n "$CONFIG_FILES" # Set up the scripts for CONFIG_HEADERS section. # No need to generate them if there are no CONFIG_HEADERS. # This happens for instance with `./config.status Makefile'. if test -n "$CONFIG_HEADERS"; then cat >"$tmp/defines.awk" <<\_ACAWK || BEGIN { _ACEOF # Transform confdefs.h into an awk script `defines.awk', embedded as # here-document in config.status, that substitutes the proper values into # config.h.in to produce config.h. # Create a delimiter string that does not exist in confdefs.h, to ease # handling of long lines. ac_delim='%!_!# ' for ac_last_try in false false :; do ac_t=`sed -n "/$ac_delim/p" confdefs.h` if test -z "$ac_t"; then break elif $ac_last_try; then as_fn_error $? "could not make $CONFIG_HEADERS" "$LINENO" 5 else ac_delim="$ac_delim!$ac_delim _$ac_delim!! " fi done # For the awk script, D is an array of macro values keyed by name, # likewise P contains macro parameters if any. Preserve backslash # newline sequences. ac_word_re=[_$as_cr_Letters][_$as_cr_alnum]* sed -n ' s/.\{148\}/&'"$ac_delim"'/g t rset :rset s/^[ ]*#[ ]*define[ ][ ]*/ / t def d :def s/\\$// t bsnl s/["\\]/\\&/g s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ D["\1"]=" \3"/p s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2"/p d :bsnl s/["\\]/\\&/g s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ D["\1"]=" \3\\\\\\n"\\/p t cont s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2\\\\\\n"\\/p t cont d :cont n s/.\{148\}/&'"$ac_delim"'/g t clear :clear s/\\$// t bsnlc s/["\\]/\\&/g; s/^/"/; s/$/"/p d :bsnlc s/["\\]/\\&/g; s/^/"/; s/$/\\\\\\n"\\/p b cont ' >$CONFIG_STATUS || ac_write_fail=1 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 for (key in D) D_is_set[key] = 1 FS = "" } /^[\t ]*#[\t ]*(define|undef)[\t ]+$ac_word_re([\t (]|\$)/ { line = \$ 0 split(line, arg, " ") if (arg[1] == "#") { defundef = arg[2] mac1 = arg[3] } else { defundef = substr(arg[1], 2) mac1 = arg[2] } split(mac1, mac2, "(") #) macro = mac2[1] prefix = substr(line, 1, index(line, defundef) - 1) if (D_is_set[macro]) { # Preserve the white space surrounding the "#". print prefix "define", macro P[macro] D[macro] next } else { # Replace #undef with comments. This is necessary, for example, # in the case of _POSIX_SOURCE, which is predefined and required # on some systems where configure will not decide to define it. if (defundef == "undef") { print "/*", prefix defundef, macro, "*/" next } } } { print } _ACAWK _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 as_fn_error $? "could not setup config headers machinery" "$LINENO" 5 fi # test -n "$CONFIG_HEADERS" eval set X " :F $CONFIG_FILES :H $CONFIG_HEADERS " shift for ac_tag do case $ac_tag in :[FHLC]) ac_mode=$ac_tag; continue;; esac case $ac_mode$ac_tag in :[FHL]*:*);; :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5 ;; :[FH]-) ac_tag=-:-;; :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; esac ac_save_IFS=$IFS IFS=: set x $ac_tag IFS=$ac_save_IFS shift ac_file=$1 shift case $ac_mode in :L) ac_source=$1;; :[FH]) ac_file_inputs= for ac_f do case $ac_f in -) ac_f="$tmp/stdin";; *) # Look for the file first in the build tree, then in the source tree # (if the path is not absolute). The absolute path cannot be DOS-style, # because $ac_f cannot contain `:'. test -f "$ac_f" || case $ac_f in [\\/$]*) false;; *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; esac || as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5 ;; esac case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac as_fn_append ac_file_inputs " '$ac_f'" done # Let's still pretend it is `configure' which instantiates (i.e., don't # use $as_me), people would be surprised to read: # /* config.h. Generated by config.status. */ configure_input='Generated from '` $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' `' by configure.' if test x"$ac_file" != x-; then configure_input="$ac_file. $configure_input" { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 $as_echo "$as_me: creating $ac_file" >&6;} fi # Neutralize special characters interpreted by sed in replacement strings. case $configure_input in #( *\&* | *\|* | *\\* ) ac_sed_conf_input=`$as_echo "$configure_input" | sed 's/[\\\\&|]/\\\\&/g'`;; #( *) ac_sed_conf_input=$configure_input;; esac case $ac_tag in *:-:* | *:-) cat >"$tmp/stdin" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; esac ;; esac ac_dir=`$as_dirname -- "$ac_file" || $as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$ac_file" : 'X\(//\)[^/]' \| \ X"$ac_file" : 'X\(//\)$' \| \ X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || $as_echo X"$ac_file" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` as_dir="$ac_dir"; as_fn_mkdir_p ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix case $ac_mode in :F) # # CONFIG_FILE # case $INSTALL in [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;; *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;; esac _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # If the template does not know about datarootdir, expand it. # FIXME: This hack should be removed a few years after 2.60. ac_datarootdir_hack=; ac_datarootdir_seen= ac_sed_dataroot=' /datarootdir/ { p q } /@datadir@/p /@docdir@/p /@infodir@/p /@localedir@/p /@mandir@/p' case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in *datarootdir*) ac_datarootdir_seen=yes;; *@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 $as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_datarootdir_hack=' s&@datadir@&$datadir&g s&@docdir@&$docdir&g s&@infodir@&$infodir&g s&@localedir@&$localedir&g s&@mandir@&$mandir&g s&\\\${datarootdir}&$datarootdir&g' ;; esac _ACEOF # Neutralize VPATH when `$srcdir' = `.'. # Shell code in configure.ac might set extrasub. # FIXME: do we really want to maintain this feature? cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_sed_extra="$ac_vpsub $extrasub _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 :t /@[a-zA-Z_][a-zA-Z_0-9]*@/!b s|@configure_input@|$ac_sed_conf_input|;t t s&@top_builddir@&$ac_top_builddir_sub&;t t s&@top_build_prefix@&$ac_top_build_prefix&;t t s&@srcdir@&$ac_srcdir&;t t s&@abs_srcdir@&$ac_abs_srcdir&;t t s&@top_srcdir@&$ac_top_srcdir&;t t s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t s&@builddir@&$ac_builddir&;t t s&@abs_builddir@&$ac_abs_builddir&;t t s&@abs_top_builddir@&$ac_abs_top_builddir&;t t s&@INSTALL@&$ac_INSTALL&;t t $ac_datarootdir_hack " eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$tmp/subs.awk" >$tmp/out \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && { ac_out=`sed -n '/\${datarootdir}/p' "$tmp/out"`; test -n "$ac_out"; } && { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' "$tmp/out"`; test -z "$ac_out"; } && { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&5 $as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&2;} rm -f "$tmp/stdin" case $ac_file in -) cat "$tmp/out" && rm -f "$tmp/out";; *) rm -f "$ac_file" && mv "$tmp/out" "$ac_file";; esac \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; :H) # # CONFIG_HEADER # if test x"$ac_file" != x-; then { $as_echo "/* $configure_input */" \ && eval '$AWK -f "$tmp/defines.awk"' "$ac_file_inputs" } >"$tmp/config.h" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 if diff "$ac_file" "$tmp/config.h" >/dev/null 2>&1; then { $as_echo "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5 $as_echo "$as_me: $ac_file is unchanged" >&6;} else rm -f "$ac_file" mv "$tmp/config.h" "$ac_file" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 fi else $as_echo "/* $configure_input */" \ && eval '$AWK -f "$tmp/defines.awk"' "$ac_file_inputs" \ || as_fn_error $? "could not create -" "$LINENO" 5 fi ;; esac done # for ac_tag as_fn_exit 0 _ACEOF ac_clean_files=$ac_clean_files_save test $ac_write_fail = 0 || as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 # configure is writing to config.log, and then calls config.status. # config.status does its own redirection, appending to config.log. # Unfortunately, on DOS this fails, as config.log is still kept open # by configure, so config.status won't be able to write to it; its # output is simply discarded. So we exec the FD to /dev/null, # effectively closing config.log, so it can be properly (re)opened and # appended to by config.status. When coming back to configure, we # need to make the FD available again. if test "$no_create" != yes; then ac_cs_success=: ac_config_status_args= test "$silent" = yes && ac_config_status_args="$ac_config_status_args --quiet" exec 5>/dev/null $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false exec 5>>config.log # Use ||, not &&, to avoid exiting from the if with $? = 1, which # would make configure fail if this is the last instruction. $ac_cs_success || as_fn_exit 1 fi if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} fi if test -z "$DATABASE"; then { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: No database driver was found. Please install Berkeley DB." >&5 $as_echo "$as_me: WARNING: No database driver was found. Please install Berkeley DB." >&2;} fi abs_srcdir="`cd $srcdir && pwd`" abs_builddir="`pwd`" if test "$abs_srcdir" != "$abs_builddir"; then ln -sf $abs_srcdir/include $abs_builddir ln -sf $abs_srcdir/Make_sources $abs_builddir ln -sf $abs_srcdir/Make_modules $abs_builddir for i in $abs_srcdir/*.h ; do if test "$abs_srcdir/sysdep.h" != "$i"; then ln -sf $i $abs_builddir fi done for d in `/bin/ls $abs_srcdir/modules/`; do (mkdir -p $abs_builddir/modules/$d) done if test -d "$abs_srcdir/user_modules/"; then for d in `/bin/ls $abs_srcdir/user_modules/`; do (mkdir -p $abs_builddir/user_modules/$d) done fi mkdir -p $abs_builddir/utils mkdir -p $abs_builddir/utillib fi if test -n "$srcdir"; then export srcdir=. fi echo ------------------------------------------------------------------------ echo 'LDAP support: ' $ok_ldap echo 'Character set conversion support:' $ok_iconv echo 'Boehm-Demers-Weiser support: ' $ok_gc echo echo 'Note: if you are not using Linux, make sure you are using GNU make' echo '(gmake) to compile Citadel.' echo citadel-9.01/buildpackages0000755000000000000000000000617412507024051014276 0ustar rootroot#!/bin/bash # find out the package version from conf if test -f Makefile; then make distclean fi ./bootstrap export `grep PACKAGE_VERSION= configure |sed -e "s;';;g" -e "s;PACKAGE;CITADEL;"` PACKAGE_VERSION=`cat packageversion` DATE=`date '+%a, %d %b %Y %H:%I:00 %z'` ACTUAL_DIR=`pwd` rm -rf debian/citadel-client debian/citadel-common debian/citadel-doc debian/citadel-mta debian/citadel-server debian/citadel-suite debian/tmp if echo "$ACTUAL_DIR" |grep -q "$CITADEL_VERSION"; then echo "directory ($ACTUAL_DIR) naming scheme seems right. nothing done." else done=false if test -L "$ACTUAL_DIR"; then SYMLINK_=`pwd` SYMLINK=`ls -l $SYMLINK_|sed "s;.*-> ;;"` if ls -l $SYMLINK_|grep -q "$CITADEL_VERSION"; then done=true fi else SYMLINK=`pwd|sed "s;.*/;;"` fi if test "$done" = "false"; then cd .. ln -sf citadel "citadel-$CITADEL_VERSION" cd "citadel-$CITADEL_VERSION" else cd "../citadel-$CITADEL_VERSION" fi fi case $1 in debian) if grep -q "($CITADEL_VERSION" debian/changelog; then echo rebuilding package. else echo "Upstream Version higher than local." fi if test "$2" == "src"; then cd .. rm -rf tmp mkdir tmp cp -rL citadel-$CITADEL_VERSION tmp cd tmp/citadel-$CITADEL_VERSION rm -rf `find -name .svn ` svn*tmp config.log config.status find -type f -exec chmod a-x {} \; chmod a+x configure *.sh *.sh.in debian/rules debian/*inst* debian/rules mkinstalldirs cd .. tar -chzf citadel_${CITADEL_VERSION}.orig.tar.gz citadel-${CITADEL_VERSION}/ --exclude "debian/*" pwd cd citadel-${CITADEL_VERSION}; debuild -S -sa -kw.goesgens@outgesourced.org else fakeroot dpkg-buildpackage fi ;; csw) if !test -d ~/pkgs/; then mkdir ~/pkgs fi echo " PKG=CSWcitadel NAME=citadel - The groupware server for Web 2.0 VERSION=${PACKAGE_VERSION} CATEGORY=application VENDOR=http://www.citadel.org/ packaged for CSW by Wilfried Goesgens HOTLINE=https://uncensored.citadel.org/ Room citadel support EMAIL=citadel@outgesourced.org " >~/pkgs/citadel export LDFLAGS='-L/opt/csw/lib -L /usr/local/lib' export CFLAGS='-I/opt/csw/include -I/usr/local/include -DDISABLE_CURSES' ./configure \ --with-db=/opt/csw/bdb44 \ --with-ical=/usr/local/ \ --with-prefix=/opt/csw/ \ --with-datadir=/opt/csw/var/lib/citadel \ --with-sysconfdir=/opt/csw/etc/citadel \ --with-ssldir=/opt/csw/etc/ssl/citadel/ \ --with-spooldir=/opt/csw/var/spool/citadel \ --with-rundir=/opt/csw/var/run/citadel \ --with-docdir=/opt/csw/share/doc/citadel-doc/ \ --with-pam \ --with-zlib \ --with-ldap \ --with-libsieve gmake citserver aidepost msgform citmail userlist sendcommand base64 whobbs citadel gmake DESTDIR=$ACTUAL_DIR/cswstage install-new ;; sourcedist) cd ..; tar \ --exclude ".gitignore" \ --exclude "*.lo" \ --exclude "*.o" \ --exclude "*.d" \ --exclude "autom4te.cache/*" \ --exclude "debian/*" \ --exclude "sysdep.h" \ \ -chvzf citadel-$CITADEL_VERSION.tar.gz citadel-$CITADEL_VERSION/ ;; *) echo "Not yet implemented. we have: debian " ;; esac